1
Fork 0
mirror of https://github.com/wlinator/luminara.git synced 2024-10-02 22:23:13 +00:00
Lumi/handlers/xp_handler.py

264 lines
8.4 KiB
Python
Raw Normal View History

2024-04-05 12:06:44 +00:00
import asyncio
2024-07-07 14:31:51 +00:00
import contextlib
2023-07-14 15:51:26 +00:00
import random
2023-06-29 11:21:17 +00:00
import time
2024-07-07 14:31:51 +00:00
from typing import Optional
2024-03-20 17:15:10 +00:00
import discord
2024-07-07 14:31:51 +00:00
from discord.ext import commands
from discord.ext.commands import TextChannelConverter
2023-06-29 11:21:17 +00:00
2024-07-17 12:01:12 +00:00
from Client import LumiBot
2024-03-20 17:15:10 +00:00
from lib import formatter
2024-08-14 11:36:12 +00:00
from lib.constants import CONST
2024-07-08 19:50:07 +00:00
from services.blacklist_service import BlacklistUserService
2024-07-17 12:01:12 +00:00
from services.config_service import GuildConfig
from services.xp_service import XpRewardService, XpService
2023-06-29 11:21:17 +00:00
2023-07-12 19:37:21 +00:00
2024-03-20 17:15:10 +00:00
class XPHandler:
2024-07-07 14:31:51 +00:00
def __init__(self, client: LumiBot, message: discord.Message) -> None:
"""
2024-07-07 14:36:48 +00:00
Initializes the XPHandler with the given client and message.
2023-07-02 12:20:16 +00:00
2024-07-07 14:31:51 +00:00
Args:
2024-07-07 14:36:48 +00:00
client (LumiBot): The bot client.
2024-07-07 14:31:51 +00:00
message (discord.Message): The message object.
"""
self.client = client
self.message: discord.Message = message
self.author: discord.Member | discord.User = message.author
self.guild: discord.Guild | None = message.guild
self.xp_conf: XpService = XpService(
2024-07-17 11:47:26 +00:00
self.author.id,
self.guild.id if self.guild else 0,
2024-07-07 14:31:51 +00:00
)
self.guild_conf: Optional[GuildConfig] = None
2023-06-29 11:21:17 +00:00
def process(self) -> bool:
2024-07-07 14:31:51 +00:00
"""
Processes the XP gain and level up for the user.
Returns:
bool: True if the user leveled up, False otherwise.
"""
_xp: XpService = self.xp_conf
_now: float = time.time()
leveled_up: bool = False
2023-07-15 15:33:58 +00:00
if _xp.cooldown_time and _now < _xp.cooldown_time:
return False
2023-07-14 15:51:26 +00:00
2024-07-07 14:36:48 +00:00
# Award the amount of XP specified in .env
_xp.xp += _xp.xp_gain
2023-07-14 15:51:26 +00:00
2024-07-07 14:36:48 +00:00
# Check if total XP now exceeds the XP required to level up
if _xp.xp >= XpService.xp_needed_for_next_level(_xp.level):
_xp.level += 1
_xp.xp = 0
leveled_up = True
2023-07-14 15:51:26 +00:00
_xp.cooldown_time = _now + _xp.new_cooldown
_xp.push()
return leveled_up
2023-07-14 15:51:26 +00:00
async def notify(self) -> None:
2024-07-07 14:31:51 +00:00
"""
Notifies the user and the guild about the level up.
"""
if self.guild is None:
return
_xp: XpService = self.xp_conf
_gd: GuildConfig = GuildConfig(self.guild.id)
2023-07-14 15:51:26 +00:00
2024-07-07 14:31:51 +00:00
level_message: Optional[str] = None # Initialize level_message
if isinstance(self.author, discord.Member):
level_message = await self.get_level_message(_gd, _xp, self.author)
2023-06-29 11:21:17 +00:00
if level_message:
2024-07-07 14:31:51 +00:00
level_channel: Optional[discord.TextChannel] = await self.get_level_channel(
2024-07-17 11:47:26 +00:00
self.message,
_gd,
2024-07-07 14:31:51 +00:00
)
2023-07-12 14:49:18 +00:00
if level_channel:
await level_channel.send(content=level_message)
else:
await self.message.reply(content=level_message)
async def reward(self) -> None:
2024-07-07 14:31:51 +00:00
"""
Rewards the user with a role for leveling up.
"""
if self.guild is None:
return
_xp: XpService = self.xp_conf
_rew: XpRewardService = XpRewardService(self.guild.id)
if role_id := _rew.get_role(_xp.level):
reason: str = "Automated Level Reward"
if role := self.guild.get_role(role_id):
with contextlib.suppress(
2024-07-17 11:47:26 +00:00
discord.Forbidden,
discord.NotFound,
discord.HTTPException,
2024-07-07 14:31:51 +00:00
):
if isinstance(self.author, discord.Member):
await self.author.add_roles(role, reason=reason)
previous, replace = _rew.should_replace_previous_reward(_xp.level)
if replace and isinstance(self.author, discord.Member):
if role := self.guild.get_role(previous or role_id):
with contextlib.suppress(
2024-07-17 11:47:26 +00:00
discord.Forbidden,
discord.NotFound,
discord.HTTPException,
2024-07-07 14:31:51 +00:00
):
await self.author.remove_roles(role, reason=reason)
2023-06-29 11:21:17 +00:00
2024-07-07 14:31:51 +00:00
async def get_level_channel(
2024-07-17 11:47:26 +00:00
self,
message: discord.Message,
guild_config: GuildConfig,
2024-07-07 14:31:51 +00:00
) -> Optional[discord.TextChannel]:
"""
Retrieves the level up notification channel for the guild.
Args:
message (discord.Message): The message object.
guild_config (GuildConfig): The guild configuration.
Returns:
2024-07-07 14:36:48 +00:00
Optional[discord.TextChannel]: The level up notification channel, or None if not found.
2024-07-07 14:31:51 +00:00
"""
if guild_config.level_channel_id and message.guild:
context = await self.client.get_context(message)
2023-06-29 11:21:17 +00:00
with contextlib.suppress(commands.BadArgument, commands.CommandError):
return await TextChannelConverter().convert(
2024-07-17 11:47:26 +00:00
context,
str(guild_config.level_channel_id),
2024-07-07 14:31:51 +00:00
)
2024-03-20 17:15:10 +00:00
return None
@staticmethod
2024-07-07 14:31:51 +00:00
async def get_level_message(
2024-07-17 11:47:26 +00:00
guild_config: GuildConfig,
level_config: XpService,
author: discord.Member,
2024-07-07 14:31:51 +00:00
) -> Optional[str]:
"""
Retrieves the level up message for the user.
Args:
guild_config (GuildConfig): The guild configuration.
level_config (XpService): The XP service configuration.
author (discord.Member): The user who leveled up.
Returns:
Optional[str]: The level up message, or None if not found.
"""
2024-03-20 17:15:10 +00:00
match guild_config.level_message_type:
case 0:
level_message = None
case 1:
level_message = XPHandler.messages_whimsical(level_config.level, author)
2024-03-20 17:15:10 +00:00
case 2:
if not guild_config.level_message:
2024-07-07 14:31:51 +00:00
level_message = XPHandler.level_message_generic(
2024-07-17 11:47:26 +00:00
level_config.level,
author,
2024-07-07 14:31:51 +00:00
)
2024-03-20 17:15:10 +00:00
else:
2024-07-07 14:31:51 +00:00
level_message = formatter.template(
2024-07-17 11:47:26 +00:00
guild_config.level_message,
author.name,
level_config.level,
2024-07-07 14:31:51 +00:00
)
2024-03-20 17:15:10 +00:00
case _:
2024-07-07 14:31:51 +00:00
raise ValueError("Invalid level message type")
2024-03-20 17:15:10 +00:00
return level_message
2023-06-29 11:21:17 +00:00
2024-03-20 17:15:10 +00:00
@staticmethod
2024-07-07 14:31:51 +00:00
def level_message_generic(level: int, author: discord.Member) -> str:
"""
Generates a generic level up message.
Args:
level (int): The new level of the user.
author (discord.Member): The user who leveled up.
Returns:
str: The generic level up message.
"""
2024-08-14 11:36:12 +00:00
return CONST.STRINGS["level_up"].format(author.name, level)
2023-06-29 11:21:17 +00:00
@staticmethod
2024-07-07 14:31:51 +00:00
def messages_whimsical(level: int, author: discord.Member) -> str:
2024-03-20 17:15:10 +00:00
"""
2024-07-07 14:31:51 +00:00
Generates a whimsical level up message.
Args:
level (int): The new level of the user.
author (discord.Member): The user who leveled up.
2024-03-20 17:15:10 +00:00
2024-07-07 14:31:51 +00:00
Returns:
str: The whimsical level up message.
"""
level_range: Optional[str] = None
2024-08-14 11:36:12 +00:00
for key in CONST.LEVEL_MESSAGES.keys():
2024-07-07 14:31:51 +00:00
start, end = map(int, key.split("-"))
2024-03-20 17:15:10 +00:00
if start <= level <= end:
level_range = key
break
if level_range is None:
2024-07-07 14:36:48 +00:00
# Generic fallback
2024-03-20 17:15:10 +00:00
return XPHandler.level_message_generic(level, author)
2024-08-14 11:36:12 +00:00
message_list = CONST.LEVEL_MESSAGES[level_range]
2024-03-20 17:15:10 +00:00
random_message = random.choice(message_list)
2024-08-14 11:36:12 +00:00
start_string = CONST.STRINGS["level_up_prefix"].format(author.name)
2024-03-20 17:15:10 +00:00
return start_string + random_message.format(level)
2024-04-05 12:05:36 +00:00
2024-07-07 14:31:51 +00:00
class XpListener(commands.Cog):
def __init__(self, client: LumiBot) -> None:
"""
Initializes the XpListener with the given client.
2024-04-05 12:05:36 +00:00
2024-07-07 14:31:51 +00:00
Args:
2024-07-07 14:36:48 +00:00
client (LumiBot): The bot client.
2024-07-07 14:31:51 +00:00
"""
self.client: LumiBot = client
2024-04-05 12:05:36 +00:00
2024-07-07 14:31:51 +00:00
@commands.Cog.listener("on_message")
async def xp_listener(self, message: discord.Message) -> None:
"""
Listens for messages and processes XP gain and level up.
Args:
message (discord.Message): The message object.
"""
2024-07-08 19:50:07 +00:00
if BlacklistUserService.is_user_blacklisted(message.author.id):
return
2024-07-07 14:31:51 +00:00
if message.author.bot or message.guild is None:
return
2024-04-05 12:05:36 +00:00
2024-07-07 14:31:51 +00:00
_xp: XPHandler = XPHandler(self.client, message)
if _xp.process():
await asyncio.gather(
_xp.notify(),
_xp.reward(),
)
2024-04-05 12:05:36 +00:00
2024-07-07 14:31:51 +00:00
def setup(client: LumiBot) -> None:
2024-04-05 12:05:36 +00:00
client.add_cog(XpListener(client))