Skip to content

Commit f73dd0f

Browse files
committed
LevelUp - Add presence and application bonus features for message and voice XP
1 parent 1fe47c4 commit f73dd0f

File tree

5 files changed

+207
-14
lines changed

5 files changed

+207
-14
lines changed

levelup/commands/admin.py

Lines changed: 165 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -955,7 +955,7 @@ async def msg_chan_bonus(
955955
956956
This bonus applies to message xp
957957
958-
Set both min and max to 0 to remove the role bonus
958+
Set both min and max to 0 to remove the channel bonus
959959
"""
960960
if min_xp > max_xp:
961961
return await ctx.send(_("Min XP value cannot be greater than Max XP value"))
@@ -1016,9 +1016,9 @@ async def msg_role_bonus(
10161016
10171017
Set both min and max to 0 to remove the role bonus
10181018
"""
1019-
conf = self.db.get_conf(ctx.guild)
10201019
if min_xp > max_xp:
10211020
return await ctx.send(_("Min XP value cannot be greater than Max XP value"))
1021+
conf = self.db.get_conf(ctx.guild)
10221022
if role.id in conf.rolebonus.msg:
10231023
if min_xp == 0 and max_xp == 0:
10241024
del conf.rolebonus.msg[role.id]
@@ -1027,6 +1027,9 @@ async def msg_role_bonus(
10271027
conf.rolebonus.msg[role.id] = [min_xp, max_xp]
10281028
self.save()
10291029
return await ctx.send(_("Role bonus has been updated"))
1030+
1031+
if min_xp == 0 and max_xp == 0:
1032+
return await ctx.send(_("XP range cannot be 0"))
10301033
conf.rolebonus.msg[role.id] = [min_xp, max_xp]
10311034
self.save()
10321035
await ctx.send(_("Role bonus has been set"))
@@ -1212,9 +1215,9 @@ async def voice_role_bonus(
12121215
12131216
Set both min and max to 0 to remove the role bonus
12141217
"""
1215-
conf = self.db.get_conf(ctx.guild)
12161218
if min_xp > max_xp:
12171219
return await ctx.send(_("Min XP value cannot be greater than Max XP value"))
1220+
conf = self.db.get_conf(ctx.guild)
12181221
if role.id in conf.rolebonus.voice:
12191222
if min_xp == 0 and max_xp == 0:
12201223
del conf.rolebonus.voice[role.id]
@@ -1223,6 +1226,7 @@ async def voice_role_bonus(
12231226
conf.rolebonus.voice[role.id] = [min_xp, max_xp]
12241227
self.save()
12251228
return await ctx.send(_("Role bonus has been updated"))
1229+
12261230
if min_xp == 0 and max_xp == 0:
12271231
return await ctx.send(_("XP range cannot be 0"))
12281232
conf.rolebonus.voice[role.id] = [min_xp, max_xp]
@@ -1407,19 +1411,61 @@ async def voice_app_bonus(
14071411

14081412
application_name = application_name.upper() # Normalize application name
14091413

1410-
if application_name in conf.appbonus:
1414+
if application_name in conf.appbonus.voice:
1415+
if min_xp == 0 and max_xp == 0:
1416+
del conf.appbonus.voice[application_name]
1417+
self.save()
1418+
return await ctx.send(_("Application bonus for {} has been removed").format(application_name))
1419+
conf.appbonus.voice[application_name] = [min_xp, max_xp]
1420+
self.save()
1421+
return await ctx.send(_("Application bonus for {} has been updated").format(application_name))
1422+
1423+
if min_xp == 0 and max_xp == 0:
1424+
return await ctx.send(_("XP range cannot be 0"))
1425+
1426+
conf.appbonus.voice[application_name] = [min_xp, max_xp]
1427+
self.save()
1428+
await ctx.send(_("Application bonus for {} has been set").format(application_name))
1429+
1430+
@message_group.command(name="appbonus")
1431+
async def msg_app_bonus(
1432+
self,
1433+
ctx: commands.Context,
1434+
application_name: str,
1435+
min_xp: commands.positive_int,
1436+
max_xp: commands.positive_int,
1437+
):
1438+
"""
1439+
Add a range of bonus XP to users running a specific application/game
1440+
1441+
This bonus applies to message xp
1442+
1443+
Set both min and max to 0 to remove the application bonus
1444+
1445+
**Examples:**
1446+
• `[p]levelset messages appbonus VALORANT 5 10` - Users playing VALORANT get 5-10 bonus XP per message
1447+
• `[p]levelset messages appbonus "Visual Studio Code" 2 4` - Use quotes for names with spaces
1448+
• `[p]levelset messages appbonus VALORANT 0 0` - Remove the bonus for VALORANT
1449+
"""
1450+
conf = self.db.get_conf(ctx.guild)
1451+
if min_xp > max_xp:
1452+
return await ctx.send(_("Min XP value cannot be greater than Max XP value"))
1453+
1454+
application_name = application_name.upper() # Normalize application name
1455+
1456+
if application_name in conf.appbonus.msg:
14111457
if min_xp == 0 and max_xp == 0:
1412-
del conf.appbonus[application_name]
1458+
del conf.appbonus.msg[application_name]
14131459
self.save()
14141460
return await ctx.send(_("Application bonus for {} has been removed").format(application_name))
1415-
conf.appbonus[application_name] = [min_xp, max_xp]
1461+
conf.appbonus.msg[application_name] = [min_xp, max_xp]
14161462
self.save()
14171463
return await ctx.send(_("Application bonus for {} has been updated").format(application_name))
14181464

14191465
if min_xp == 0 and max_xp == 0:
14201466
return await ctx.send(_("XP range cannot be 0"))
14211467

1422-
conf.appbonus[application_name] = [min_xp, max_xp]
1468+
conf.appbonus.msg[application_name] = [min_xp, max_xp]
14231469
self.save()
14241470
await ctx.send(_("Application bonus for {} has been set").format(application_name))
14251471

@@ -1460,3 +1506,115 @@ async def set_default_background(self, ctx: commands.Context, *, background: str
14601506
"Warning: I couldn't find a background with that name. Make sure it exists in the backgrounds folder."
14611507
)
14621508
)
1509+
1510+
@levelset.group(name="presencebonus", aliases=["statusbonus"])
1511+
async def presence_bonus_group(self, ctx: commands.Context):
1512+
"""Presence status bonus settings"""
1513+
pass
1514+
1515+
@presence_bonus_group.command(name="message", aliases=["msg"])
1516+
async def presence_msg_bonus(
1517+
self,
1518+
ctx: commands.Context,
1519+
status: t.Literal["online", "idle", "dnd", "offline"],
1520+
min_xp: commands.positive_int,
1521+
max_xp: commands.positive_int,
1522+
):
1523+
"""
1524+
Add a range of bonus XP to users with a specific presence status
1525+
1526+
This bonus applies to message XP
1527+
1528+
Set both min and max to 0 to remove the status bonus
1529+
1530+
**Examples:**
1531+
• `[p]levelset presencebonus message online 2 5` - Users with online status get 2-5 bonus XP per message
1532+
• `[p]levelset presencebonus message idle 1 3` - Users with idle status get 1-3 bonus XP per message
1533+
• `[p]levelset presencebonus message dnd 0 0` - Remove the bonus for dnd status
1534+
"""
1535+
conf = self.db.get_conf(ctx.guild)
1536+
if min_xp > max_xp:
1537+
return await ctx.send(_("Min XP value cannot be greater than Max XP value"))
1538+
1539+
if status in conf.presencebonus.msg:
1540+
if min_xp == 0 and max_xp == 0:
1541+
del conf.presencebonus.msg[status]
1542+
self.save()
1543+
return await ctx.send(_("Presence bonus for {} status has been removed for messages").format(status))
1544+
conf.presencebonus.msg[status] = [min_xp, max_xp]
1545+
self.save()
1546+
return await ctx.send(_("Presence bonus for {} status has been updated for messages").format(status))
1547+
1548+
if min_xp == 0 and max_xp == 0:
1549+
return await ctx.send(_("XP range cannot be 0"))
1550+
1551+
conf.presencebonus.msg[status] = [min_xp, max_xp]
1552+
self.save()
1553+
await ctx.send(_("Presence bonus for {} status has been set for messages").format(status))
1554+
1555+
@presence_bonus_group.command(name="voice")
1556+
async def presence_voice_bonus(
1557+
self,
1558+
ctx: commands.Context,
1559+
status: t.Literal["online", "idle", "dnd", "offline"],
1560+
min_xp: commands.positive_int,
1561+
max_xp: commands.positive_int,
1562+
):
1563+
"""
1564+
Add a range of bonus XP to users with a specific presence status
1565+
1566+
This bonus applies to voice time XP
1567+
1568+
Set both min and max to 0 to remove the status bonus
1569+
1570+
**Examples:**
1571+
• `[p]levelset presencebonus voice online 2 5` - Users with online status get 2-5 bonus XP per minute in voice
1572+
• `[p]levelset presencebonus voice idle 1 3` - Users with idle status get 1-3 bonus XP per minute in voice
1573+
• `[p]levelset presencebonus voice dnd 0 0` - Remove the bonus for dnd status
1574+
"""
1575+
conf = self.db.get_conf(ctx.guild)
1576+
if min_xp > max_xp:
1577+
return await ctx.send(_("Min XP value cannot be greater than Max XP value"))
1578+
1579+
if status in conf.presencebonus.voice:
1580+
if min_xp == 0 and max_xp == 0:
1581+
del conf.presencebonus.voice[status]
1582+
self.save()
1583+
return await ctx.send(_("Presence bonus for {} status has been removed for voice").format(status))
1584+
conf.presencebonus.voice[status] = [min_xp, max_xp]
1585+
self.save()
1586+
return await ctx.send(_("Presence bonus for {} status has been updated for voice").format(status))
1587+
1588+
if min_xp == 0 and max_xp == 0:
1589+
return await ctx.send(_("XP range cannot be 0"))
1590+
1591+
conf.presencebonus.voice[status] = [min_xp, max_xp]
1592+
self.save()
1593+
await ctx.send(_("Presence bonus for {} status has been set for voice").format(status))
1594+
1595+
@presence_bonus_group.command(name="view")
1596+
async def view_presence_bonuses(self, ctx: commands.Context):
1597+
"""View all presence status bonuses"""
1598+
conf = self.db.get_conf(ctx.guild)
1599+
1600+
if not conf.presencebonus.msg and not conf.presencebonus.voice:
1601+
return await ctx.send(_("No presence bonuses have been set."))
1602+
1603+
embed = discord.Embed(
1604+
title=_("Presence Status Bonuses"),
1605+
color=await self.bot.get_embed_color(ctx),
1606+
)
1607+
1608+
if conf.presencebonus.msg:
1609+
msg_bonuses = "\n".join(
1610+
_("• {}: `{}`").format(status, xp_range) for status, xp_range in conf.presencebonus.msg.items()
1611+
)
1612+
embed.add_field(name=_("Message XP Bonuses"), value=msg_bonuses, inline=False)
1613+
1614+
if conf.presencebonus.voice:
1615+
voice_bonuses = "\n".join(
1616+
_("• {}: `{}`").format(status, xp_range) for status, xp_range in conf.presencebonus.voice.items()
1617+
)
1618+
embed.add_field(name=_("Voice XP Bonuses"), value=voice_bonuses, inline=False)
1619+
1620+
await ctx.send(embed=embed)

levelup/common/models.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,16 @@ class ChannelBonus(Base):
219219
voice: t.Dict[int, t.List[int]] = {} # Channel_ID: [Min, Max]
220220

221221

222+
class PresenceBonus(Base):
223+
msg: t.Dict[str, t.List[int]] = {} # online: [Min, Max], dnd: [Min, Max], idle: [Min, Max], ect...
224+
voice: t.Dict[str, t.List[int]] = {} # online: [Min, Max], dnd: [Min, Max], idle: [Min, Max], ect...
225+
226+
227+
class AppBonus(Base):
228+
msg: t.Dict[str, t.List[int]] = {} # Application_Name: [Min, Max]
229+
voice: t.Dict[str, t.List[int]] = {} # Application_Name: [Min, Max]
230+
231+
222232
class Algorithm(Base):
223233
base: int = 100 # Base denominator for level algorithm, higher takes longer to level
224234
exp: float = 2.0 # Exponent for level algorithm, higher is a more exponential/steeper curve
@@ -291,9 +301,10 @@ class GuildSettings(Base):
291301

292302
# Bonuses
293303
streambonus: t.List[int] = [] # Bonus voice XP for streaming in voice Example: [2, 5]
294-
appbonus: t.Dict[str, t.List[int]] = {} # Application_Name: [Min, Max] - Bonus for using specific applications
304+
appbonus: AppBonus = AppBonus() # Application bonuses for voice and messages
295305
rolebonus: RoleBonus = RoleBonus()
296306
channelbonus: ChannelBonus = ChannelBonus()
307+
presencebonus: PresenceBonus = PresenceBonus() # Bonus for presence status (online, dnd, idle, offline)
297308

298309
# Allowed
299310
allowedchannels: t.List[int] = [] # Only channels that gain XP if not empty
@@ -419,9 +430,6 @@ def run_migrations(settings: t.Dict[str, t.Any]) -> DB:
419430
"base": conf.get("base", 100),
420431
"exp": conf.get("exp", 2.0),
421432
}
422-
423-
migrated += 1
424-
425433
log.warning(f"Migrated {migrated} guilds to new schema")
426434
db: DB = DB.load(data)
427435
return db

levelup/listeners/messages.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,23 @@ async def on_message(self, message: discord.Message):
134134
for role_id, (bonus_min, bonus_max) in conf.rolebonus.msg.items():
135135
if role_id in role_ids:
136136
xp_to_add += random.randint(bonus_min, bonus_max)
137+
138+
# Add presence bonus if applicable
139+
presence_status = str(message.author.status).lower() # 'online', 'idle', 'dnd', 'offline'
140+
if presence_status in conf.presencebonus.msg:
141+
bonus_min, bonus_max = conf.presencebonus.msg[presence_status]
142+
xp_to_add += random.randint(bonus_min, bonus_max)
143+
log.debug(f"Adding {presence_status} presence bonus to {message.author.name} in {message.guild.name}")
144+
145+
# Add application bonus if the user is using a specific application
146+
if hasattr(message.author, "activity") and message.author.activity:
147+
activity_name = getattr(message.author.activity, "name", "").upper()
148+
if activity_name and activity_name in conf.appbonus.msg:
149+
app_bonus_min, app_bonus_max = conf.appbonus.msg[activity_name]
150+
app_bonus = random.randint(app_bonus_min, app_bonus_max)
151+
xp_to_add += app_bonus
152+
log.debug(f"Adding {app_bonus} application bonus XP to {message.author.name} for using {activity_name}")
153+
137154
# Add the xp to the role groups
138155
for role_id in role_ids:
139156
if role_id in conf.role_groups:

levelup/listeners/voice.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -195,14 +195,24 @@ async def _on_voice_state_update(
195195
# Add application bonus if the user was using a specific application
196196
if hasattr(member, "activity") and member.activity:
197197
activity_name = getattr(member.activity, "name", "").upper()
198-
if activity_name and activity_name in conf.appbonus:
199-
app_bonus_min, app_bonus_max = conf.appbonus[activity_name]
198+
if activity_name and activity_name in conf.appbonus.voice:
199+
app_bonus_min, app_bonus_max = conf.appbonus.voice[activity_name]
200200
app_bonus = random.randint(app_bonus_min, app_bonus_max) * (effective_time / 60)
201201
xp_to_add += app_bonus
202202
log.debug(
203203
f"Adding {round(app_bonus, 2)} application bonus XP to {member.name} for using {activity_name}"
204204
)
205205

206+
# Add presence bonus if applicable
207+
presence_status = str(member.status).lower() # 'online', 'idle', 'dnd', 'offline'
208+
if presence_status in conf.presencebonus.voice:
209+
bonus_min, bonus_max = conf.presencebonus.voice[presence_status]
210+
presence_bonus = random.randint(bonus_min, bonus_max) * (effective_time / 60)
211+
xp_to_add += presence_bonus
212+
log.debug(
213+
f"Adding {round(presence_bonus, 2)} presence bonus XP to {member.name} for {presence_status} status"
214+
)
215+
206216
# Add the exp to the user
207217
if xp_to_add:
208218
log.debug(f"Adding {round(xp_to_add, 2)} Voice XP to {member.name} in {member.guild}")

levelup/main.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ class LevelUp(
7777
"""
7878

7979
__author__ = "[vertyco](https://github.com/vertyco/vrt-cogs)"
80-
__version__ = "4.5.0"
80+
__version__ = "4.6.0"
8181
__contributors__ = [
8282
"[aikaterna](https://github.com/aikaterna/aikaterna-cogs)",
8383
"[AAA3A](https://github.com/AAA3A-AAA3A/AAA3A-cogs)",

0 commit comments

Comments
 (0)