diff --git a/kdb-bot/src/bot_core/abc/client_utils_service_abc.py b/kdb-bot/src/bot_core/abc/client_utils_service_abc.py index ca6405ef..531e21c3 100644 --- a/kdb-bot/src/bot_core/abc/client_utils_service_abc.py +++ b/kdb-bot/src/bot_core/abc/client_utils_service_abc.py @@ -4,6 +4,8 @@ from typing import Callable from cpl_query.extension import List from discord.ext.commands import Context +from bot_data.model.user import User + class ClientUtilsServiceABC(ABC): @@ -33,3 +35,6 @@ class ClientUtilsServiceABC(ABC): @abstractmethod def get_auto_complete_list(self, _l: List, current: str, select: Callable = None) -> List: pass + + @abstractmethod + def get_ontime_for_user(self, user: User) -> float: pass diff --git a/kdb-bot/src/bot_core/service/client_utils_service.py b/kdb-bot/src/bot_core/service/client_utils_service.py index bf2aaef1..48d87d84 100644 --- a/kdb-bot/src/bot_core/service/client_utils_service.py +++ b/kdb-bot/src/bot_core/service/client_utils_service.py @@ -17,6 +17,8 @@ from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum from bot_core.configuration.feature_flags_settings import FeatureFlagsSettings from bot_data.abc.client_repository_abc import ClientRepositoryABC from bot_data.abc.server_repository_abc import ServerRepositoryABC +from bot_data.abc.user_joined_voice_channel_abc import UserJoinedVoiceChannelRepositoryABC +from bot_data.model.user import User class ClientUtilsService(ClientUtilsServiceABC): @@ -28,6 +30,7 @@ class ClientUtilsService(ClientUtilsServiceABC): bot: DiscordBotServiceABC, servers: ServerRepositoryABC, clients: ClientRepositoryABC, + user_joined_vc: UserJoinedVoiceChannelRepositoryABC, message_service: MessageServiceABC, db: DatabaseContextABC, t: TranslatePipe, @@ -39,6 +42,7 @@ class ClientUtilsService(ClientUtilsServiceABC): self._bot = bot self._servers = servers self._clients = clients + self._user_joined_voice_channel = user_joined_vc self._message_service = message_service self._db = db self._t = t @@ -80,7 +84,11 @@ class ClientUtilsService(ClientUtilsServiceABC): async def check_if_bot_is_ready_yet_and_respond(self, ctx: Context) -> bool: result = await self.check_if_bot_is_ready_yet() if not result: - await self._message_service.send_ctx_msg(ctx, self._t.transform('common.errors.bot_not_ready_yet'), without_tracking=True) + await self._message_service.send_ctx_msg( + ctx, + self._t.transform('common.errors.bot_not_ready_yet'), + without_tracking=True + ) return result @@ -106,3 +114,8 @@ class ClientUtilsService(ClientUtilsServiceABC): _l = _l.where(lambda x: x.name in sl) return _l.take(25) + + def get_ontime_for_user(self, user: User) -> float: + return self._user_joined_voice_channel.get_user_joined_voice_channels_by_user_id(user.user_id) \ + .where(lambda x: x.leaved_on is not None and x.joined_on is not None) \ + .sum(lambda join: round((join.leaved_on - join.joined_on).total_seconds() / 3600, 2)) diff --git a/kdb-bot/src/bot_data/service/user_joined_voice_channel_service.py b/kdb-bot/src/bot_data/service/user_joined_voice_channel_service.py index 919b2ee5..4d99065a 100644 --- a/kdb-bot/src/bot_data/service/user_joined_voice_channel_service.py +++ b/kdb-bot/src/bot_data/service/user_joined_voice_channel_service.py @@ -94,7 +94,7 @@ class UserJoinedVoiceChannelRepositoryService(UserJoinedVoiceChannelRepositoryAB ) def find_active_user_joined_voice_channels_by_user_id(self, user_id: int) -> List[Optional[UserJoinedVoiceChannel]]: - self._logger.trace(__name__, f'Send SQL command: {UserJoinedVoiceChannel.get_select_by_user_id_string(user_id)}') + self._logger.trace(__name__, f'Send SQL command: {UserJoinedVoiceChannel.get_select_active_by_user_id_string(user_id)}') result = List(UserJoinedVoiceChannel) db_results = self._context.select(UserJoinedVoiceChannel.get_select_active_by_user_id_string(user_id)) diff --git a/kdb-bot/src/modules/base/command/user_group.py b/kdb-bot/src/modules/base/command/user_group.py index 5725e5b6..b0f41230 100644 --- a/kdb-bot/src/modules/base/command/user_group.py +++ b/kdb-bot/src/modules/base/command/user_group.py @@ -9,7 +9,6 @@ from cpl_translation import TranslatePipe from discord import app_commands from discord.ext import commands from discord.ext.commands import Context -from mysql.connector.errors import DatabaseError from bot_core.abc.client_utils_service_abc import ClientUtilsServiceABC from bot_core.abc.message_service_abc import MessageServiceABC @@ -98,10 +97,6 @@ class UserGroup(DiscordCommandABC): color=int('ef9d0d', 16) ) - ontime = self._user_joined_voice_channel.get_user_joined_voice_channels_by_user_id(user.user_id)\ - .where(lambda x: x.leaved_on is not None and x.joined_on is not None)\ - .sum(lambda join: round((join.leaved_on - join.joined_on).total_seconds() / 3600, 2)) - embed.add_field(name=self._t.transform('modules.base.user.atr.id'), value=member.id) embed.add_field(name=self._t.transform('modules.base.user.atr.name'), value=member.name) embed.add_field(name=self._t.transform('modules.base.user.atr.discord_join'), @@ -109,7 +104,8 @@ class UserGroup(DiscordCommandABC): embed.add_field(name=self._t.transform('modules.base.user.atr.last_join'), value=self._date.transform(member.joined_at), inline=False) embed.add_field(name=self._t.transform('modules.base.user.atr.xp'), value=str(user.xp)) - embed.add_field(name=self._t.transform('modules.base.user.atr.ontime'), value=str(ontime)) + embed.add_field(name=self._t.transform('modules.base.user.atr.ontime'), + value=str(self._client_utils.get_ontime_for_user(user))) roles = '' for role in member.roles: @@ -162,14 +158,13 @@ class UserGroup(DiscordCommandABC): value = str(user.xp) elif atr == 'ontime': - value = str(round( - self._user_joined_voice_channel.get_user_joined_voice_channels_by_user_id(user.user_id) - .sum(lambda join: (join.leaved_on - join.joined_on).total_seconds() / 3600), - 2 - )) + value = str(self._client_utils.get_ontime_for_user(user)) else: - await self._message_service.send_interaction_msg(ctx.interaction, self._t.transform('modules.base.user.error.atr_not_found').format(atr)) + await self._message_service.send_interaction_msg( + ctx.interaction, + self._t.transform('modules.base.user.error.atr_not_found').format(atr) + ) return await self._message_service.send_interaction_msg( @@ -196,14 +191,19 @@ class UserGroup(DiscordCommandABC): if atr == 'xp': if not value.isnumeric(): - await self._message_service.send_interaction_msg(ctx.interaction, self._t.transform('modules.base.user.set.error.value_type_not_numeric')) + await self._message_service.send_interaction_msg( + ctx.interaction, self._t.transform('modules.base.user.set.error.value_type_not_numeric') + ) return try: user.xp = int(value) except TypeError as te: self._logger.error(__name__, f'String value couldn\'t be converted to int', te) - await self._message_service.send_interaction_msg(ctx.interaction, self._t.transform('modules.base.user.set.error.type_error')) + await self._message_service.send_interaction_msg( + ctx.interaction, + self._t.transform('modules.base.user.set.error.type_error') + ) return else: self._users.update_user(user) @@ -211,10 +211,16 @@ class UserGroup(DiscordCommandABC): await self._level.check_level(member) else: - await self._message_service.send_interaction_msg(ctx.interaction, self._t.transform('modules.base.user.error.atr_not_found').format(atr)) + await self._message_service.send_interaction_msg( + ctx.interaction, + self._t.transform('modules.base.user.error.atr_not_found').format(atr) + ) return - await self._message_service.send_interaction_msg(ctx.interaction, self._t.transform(f'modules.base.user.set.{atr.lower()}').format(member.mention, value)) + await self._message_service.send_interaction_msg( + ctx.interaction, + self._t.transform(f'modules.base.user.set.{atr.lower()}').format(member.mention, value) + ) @set.autocomplete('atr') async def set_autocomplete(self, interaction: discord.Interaction, current: str) -> List[app_commands.Choice[str]]: @@ -245,7 +251,10 @@ class UserGroup(DiscordCommandABC): self._db.save_changes() else: - await self._message_service.send_interaction_msg(ctx.interaction, self._t.transform('modules.base.user.error.atr_not_found').format(atr)) + await self._message_service.send_interaction_msg( + ctx.interaction, + self._t.transform('modules.base.user.error.atr_not_found').format(atr) + ) return await self._message_service.send_interaction_msg( @@ -254,5 +263,7 @@ class UserGroup(DiscordCommandABC): ) @remove.autocomplete('atr') - async def remove_autocomplete(self, interaction: discord.Interaction, current: str) -> List[app_commands.Choice[str]]: + async def remove_autocomplete(self, interaction: discord.Interaction, current: str) -> List[ + app_commands.Choice[str] + ]: return [app_commands.Choice(name=value, value=key) for key, value in self._atr_list] diff --git a/kdb-bot/src/modules/base/events/base_on_voice_state_update_event.py b/kdb-bot/src/modules/base/events/base_on_voice_state_update_event.py index 83794725..c9fcd41b 100644 --- a/kdb-bot/src/modules/base/events/base_on_voice_state_update_event.py +++ b/kdb-bot/src/modules/base/events/base_on_voice_state_update_event.py @@ -7,6 +7,7 @@ from cpl_core.database.context import DatabaseContextABC from cpl_core.logging import LoggerABC from cpl_discord.events import OnVoiceStateUpdateABC +from bot_core.abc.client_utils_service_abc import ClientUtilsServiceABC from bot_core.helper.event_checks import EventChecks from bot_data.abc.known_user_repository_abc import KnownUserRepositoryABC from bot_data.abc.server_repository_abc import ServerRepositoryABC @@ -32,6 +33,7 @@ class BaseOnVoiceStateUpdateEvent(OnVoiceStateUpdateABC): users: UserRepositoryABC, user_joins: UserJoinedServerRepositoryABC, user_joins_vc: UserJoinedVoiceChannelRepositoryABC, + client_utils: ClientUtilsServiceABC, db: DatabaseContextABC, ): OnVoiceStateUpdateABC.__init__(self) @@ -43,6 +45,7 @@ class BaseOnVoiceStateUpdateEvent(OnVoiceStateUpdateABC): self._users = users self._user_joins = user_joins self._user_joins_vc = user_joins_vc + self._client_utils = client_utils self._db = db self._logger.info(__name__, f'Module {type(self)} loaded') @@ -72,7 +75,7 @@ class BaseOnVoiceStateUpdateEvent(OnVoiceStateUpdateABC): join.leaved_on = datetime.now() # ontime as hours - ontime = round((join.leaved_on - join.joined_on).total_seconds() / 3600, 2) + ontime = self._client_utils.get_ontime_for_user(user) old_xp = user.xp user.xp += round(ontime * settings.xp_per_ontime_hour) @@ -80,12 +83,18 @@ class BaseOnVoiceStateUpdateEvent(OnVoiceStateUpdateABC): self._users.update_user(user) self._db.save_changes() - self._logger.debug(__name__, f'User {user} leaved_on {join.leaved_on}. Ontime: {ontime}h | xp: from {old_xp} to {user.xp}') + self._logger.debug(__name__, + f'User {user} leaved_on {join.leaved_on}. Ontime: {ontime}h | xp: from {old_xp} to {user.xp}') except Exception as e: self._logger.error(__name__, f'Ontime validation failed', e) @EventChecks.check_is_ready() - async def on_voice_state_update(self, member: discord.Member, before: discord.VoiceState, after: discord.VoiceState): + async def on_voice_state_update( + self, + member: discord.Member, + before: discord.VoiceState, + after: discord.VoiceState + ): self._logger.debug(__name__, f'Module {type(self)} started') self._logger.trace(__name__, f'Detected on_voice_state_update {member.id} from {before} to {after}') settings: BaseServerSettings = self._base_helper.get_config(member.guild.id) diff --git a/kdb-bot/src/modules/level/service/level_service.py b/kdb-bot/src/modules/level/service/level_service.py index e49d6f31..7bd7f2d6 100644 --- a/kdb-bot/src/modules/level/service/level_service.py +++ b/kdb-bot/src/modules/level/service/level_service.py @@ -42,8 +42,15 @@ class LevelService: self._t = t def get_level(self, user: User) -> Level: - levels = self._levels.get_levels_by_server_id(user.server.server_id).order_by(lambda l: l.min_xp) - return levels.where(lambda l: user.xp >= l.min_xp).last() + levels_by_server = self._levels.get_levels_by_server_id(user.server.server_id) + levels = levels_by_server \ + .order_by(lambda l: l.min_xp) \ + .where(lambda l: user.xp >= l.min_xp) + + if levels.count() == 0: + return levels_by_server.order_by(lambda l: l.min_xp).last() + + return levels.last() async def set_level(self, user: User): level_names = self._levels.get_levels_by_server_id(user.server.server_id).select(lambda l: l.name)