diff --git a/src/gismo/appsettings.example.json b/src/gismo/appsettings.example.json index 6a0c857..c13db34 100644 --- a/src/gismo/appsettings.example.json +++ b/src/gismo/appsettings.example.json @@ -26,7 +26,8 @@ "GoodbyeMessage": "", "MaxVoiceStateHours": 0, "XpPerMessage": 0, - "XpPerOntimeHour": 0 + "XpPerOntimeHour": 0, + "AFKChannelIds": [] } ] } diff --git a/src/gismo_core/configuration/server_settings.py b/src/gismo_core/configuration/server_settings.py index 242217b..ae21cb0 100644 --- a/src/gismo_core/configuration/server_settings.py +++ b/src/gismo_core/configuration/server_settings.py @@ -18,6 +18,7 @@ class ServerSettings(ConfigurationModelABC): self._max_voice_state_hours: int = 0 self._xp_per_message: int = 0 self._xp_per_ontime_hour: int = 0 + self._afk_channel_ids: list[int] = [] @property def id(self) -> str: @@ -55,6 +56,10 @@ class ServerSettings(ConfigurationModelABC): def xp_per_ontime_hour(self) -> int: return self._xp_per_ontime_hour + @property + def afk_channel_ids(self) -> list[int]: + return self._afk_channel_ids + def from_dict(self, settings: dict): try: self._id = int(settings['Id']) @@ -66,6 +71,8 @@ class ServerSettings(ConfigurationModelABC): self._max_voice_state_hours = int(settings['MaxVoiceStateHours']) self._xp_per_message = int(settings['XpPerMessage']) self._xp_per_ontime_hour = int(settings['XpPerOntimeHour']) + for id in settings['AFKChannelIds']: + self._afk_channel_ids.append(int(id)) except Exception as e: Console.error(f'[ ERROR ] [ {__name__} ]: Reading error in settings') Console.error(f'[ EXCEPTION ] [ {__name__} ]: {e} -> {traceback.format_exc()}') diff --git a/src/gismo_data/model/user.py b/src/gismo_data/model/user.py index 5597c07..83f4b9f 100644 --- a/src/gismo_data/model/user.py +++ b/src/gismo_data/model/user.py @@ -86,12 +86,12 @@ class User(TableABC): UPDATE `Users` SET `XP` = {self._xp}, `LastModifiedAt` = '{self._modified_at}' - WHERE `Id` = {self._user_id}; + WHERE `UserId` = {self._user_id}; """) @property def delete_string(self) -> str: return str(f""" DELETE FROM `Users` - WHERE `Id` = {self._user_id}; + WHERE `UserId` = {self._user_id}; """) diff --git a/src/gismo_data/model/user_joined_voice_channel.py b/src/gismo_data/model/user_joined_voice_channel.py index 63604e0..83729a3 100644 --- a/src/gismo_data/model/user_joined_voice_channel.py +++ b/src/gismo_data/model/user_joined_voice_channel.py @@ -81,9 +81,10 @@ class UserJoinedVoiceChannel(TableABC): if self._leaved_on is not None: return str(f""" INSERT INTO `UserJoinedVoiceChannel` ( - `UserId`, `JoinedOn`, `LeavedOn`, `CreatedAt`, `LastModifiedAt` + `UserId`, `DiscordChannelId`, `JoinedOn`, `LeavedOn`, `CreatedAt`, `LastModifiedAt` ) VALUES ( {self._user.user_id}, + {self._dc_channel_id}, '{self._joined_on}', '{self._leaved_on}', '{self._created_at}', @@ -93,9 +94,10 @@ class UserJoinedVoiceChannel(TableABC): else: return str(f""" INSERT INTO `UserJoinedVoiceChannel` ( - `UserId`, `JoinedOn`, `CreatedAt`, `LastModifiedAt` + `UserId`, `DiscordChannelId`, `JoinedOn`, `CreatedAt`, `LastModifiedAt` ) VALUES ( {self._user.user_id}, + {self._dc_channel_id}, '{self._joined_on}', '{self._created_at}', '{self._modified_at}' diff --git a/src/modules/base/base.py b/src/modules/base/base.py index 5d233ef..fc4d79f 100644 --- a/src/modules/base/base.py +++ b/src/modules/base/base.py @@ -14,10 +14,12 @@ from gismo_data.abc.known_user_repository_abc import KnownUserRepositoryABC from gismo_data.abc.server_repository_abc import ServerRepositoryABC from gismo_data.abc.user_joined_server_repository_abc import \ UserJoinedServerRepositoryABC +from gismo_data.abc.user_joined_voice_channel_abc import UserJoinedVoiceChannelRepositoryABC from gismo_data.abc.user_repository_abc import UserRepositoryABC from gismo_data.model.known_user import KnownUser from gismo_data.model.user import User from gismo_data.model.user_joined_server import UserJoinedServer +from gismo_data.model.user_joined_voice_channel import UserJoinedVoiceChannel from modules_core.abc.events.on_member_join_abc import OnMemberJoinABC from modules_core.abc.events.on_member_remove_abc import OnMemberRemoveABC from modules_core.abc.events.on_message_abc import OnMessageABC @@ -37,6 +39,7 @@ class Base(ModuleABC, OnMemberJoinABC, OnMemberRemoveABC, OnMessageABC, OnVoiceS known_users: KnownUserRepositoryABC, users: UserRepositoryABC, user_joins: UserJoinedServerRepositoryABC, + user_joins_vc: UserJoinedVoiceChannelRepositoryABC, bot: BotServiceABC, db: DatabaseContextABC, messenger: MessageServiceABC @@ -48,6 +51,7 @@ class Base(ModuleABC, OnMemberJoinABC, OnMemberRemoveABC, OnMessageABC, OnVoiceS self._known_users = known_users self._users = users self._user_joins = user_joins + self._user_joins_vc = user_joins_vc self._bot = bot self._db = db self._messenger = messenger @@ -128,6 +132,70 @@ class Base(ModuleABC, OnMemberJoinABC, OnMemberRemoveABC, OnMessageABC, OnVoiceS except Exception as e: self._logger.error(__name__, f'Cannot get user {member.id}', e) + def _update_voice_state(self, joined: bool, dc_user_id: int, dc_channel_id: int, srv_id: int): + user: User = None + try: + user = self._users.get_user_by_discord_id_and_server_id(dc_user_id, srv_id) + except Exception as e: + self._logger.error(__name__, f'Cannot get user {dc_user_id}', e) + return + + if user is None: + self._logger.error(__name__, f'User not found {dc_user_id}') + return + + try: + if joined: + join = UserJoinedVoiceChannel(user, dc_channel_id, datetime.now()) + self._user_joins_vc.add_user_joined_voice_channel(join) + self._db.save_changes() + return + + server_st: ServerSettings = self._config.get_configuration(f'DSERVER_{user.server.discord_server_id}') + + join = self._user_joins_vc.get_active_user_joined_voice_channel_by_user_id(user.user_id) + join.leaved_on = datetime.now() + + # ontime as hours + ontime = (join.leaved_on - join.joined_on).total_seconds()/3600 + old_xp = user.xp + user.xp += round(ontime * server_st.xp_per_ontime_hour) + + self._user_joins_vc.update_user_joined_voice_channel(join) + 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}') + except Exception as e: + self._logger.error(__name__, f'Ontime validation failed', e) + + def _handle_message_for_xp(self, message: discord.Message): + dc_user_id = message.author.id + try: + server = self._servers.get_server_by_discord_id(message.guild.id) + except Exception as e: + self._logger.error(__name__, f'Cannot get server {message.guild.id}', e) + return + + user: User = None + try: + user = self._users.get_user_by_discord_id_and_server_id(dc_user_id, server.server_id) + except Exception as e: + self._logger.error(__name__, f'Cannot get user {dc_user_id}', e) + return + + if user is None: + self._logger.error(__name__, f'User not found {dc_user_id}') + return + + server_st: ServerSettings = self._config.get_configuration(f'DSERVER_{user.server.discord_server_id}') + old_xp = user.xp + user.xp += server_st._xp_per_message + self._users.update_user(user) + self._db.save_changes() + + self._logger.debug(__name__, f'User {user} sent message. xp: from {old_xp} to {user.xp}') + async def on_member_join(self, member: Union[discord.User, discord.Member]): self._logger.debug(__name__, f'Module {type(self)} started') self._check_for_known_user(member) @@ -143,7 +211,40 @@ class Base(ModuleABC, OnMemberJoinABC, OnMemberRemoveABC, OnMessageABC, OnVoiceS return self._apppend_received_message_count(message.guild.id) + if not message.author.bot: + self._handle_message_for_xp(message) + 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') - # todo: save ontime - # todo: add xp to user when he goes offline + self._logger.trace(__name__, f'Detected on_voice_state_update {member.id} from {before} to {after}') + u: discord.User = member + server_st: ServerSettings = self._config.get_configuration(f'DSERVER_{member.guild.id}') + server = self._servers.get_server_by_discord_id(member.guild.id) + + try: + # join + if before.channel is None and after.channel is not None and after.channel.id not in server_st.afk_channel_ids: + self._logger.trace(__name__, f'User {u.id} joined {after.channel}') + self._update_voice_state(True, member.id, after.channel.id, server.server_id) + + # leave + elif before.channel is not None and after.channel is None and before.channel.id not in server_st.afk_channel_ids: + self._logger.trace(__name__, f'User {u.id} left {before.channel}') + self._update_voice_state(False, member.id, before.channel.id, server.server_id) + + # channel to channel + elif before.channel is not None and after.channel is not None: + # joined + if before.channel.id in server_st.afk_channel_ids and after.channel.id not in server_st.afk_channel_ids: + self._logger.trace(__name__, f'User {u.id} joined {after.channel}') + self._update_voice_state(True, member.id, after.channel.id, server.server_id) + + # left + elif after.channel.id in server_st.afk_channel_ids and before.channel.id not in server_st.afk_channel_ids: + self._logger.trace(__name__, f'User {u.id} left {before.channel}') + self._update_voice_state(False, member.id, before.channel.id, server.server_id) + + else: + self._logger.trace(__name__, f'User {u.id} switched to {after.channel}') + except Exception as e: + self._logger.error(__name__, f'Cannot handle voice state for user {u.id}', e)