From 8e0105355ca20e79bc581ef9890e0df90764689b Mon Sep 17 00:00:00 2001 From: bobloy Date: Thu, 1 Oct 2020 14:33:24 -0400 Subject: [PATCH 01/13] fix ww_stop bug when no game is running --- werewolf/werewolf.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/werewolf/werewolf.py b/werewolf/werewolf.py index bd68a6f..8ea5783 100644 --- a/werewolf/werewolf.py +++ b/werewolf/werewolf.py @@ -285,7 +285,8 @@ class Werewolf(Cog): game = await self._get_game(ctx) game.game_over = True - game.current_action.cancel() + if game.current_action: + game.current_action.cancel() await ctx.maybe_send_embed("Game has been stopped") @commands.guild_only() From 9ac89aa369b4f7f30b37ae3b7b74f276e935c89a Mon Sep 17 00:00:00 2001 From: ASSASSIN0831 Date: Wed, 2 Dec 2020 22:09:52 -0500 Subject: [PATCH 02/13] The big update Changes: [FIX]Fixed issued where toggling an infochannel off does not delete the channel [UPDATE] Default counter is now total server members instead of just human users [NEW] Can now toggle off the default counter [NEW] Added a shortcut for infochannelset as icset [NEW] Infochannels are now sorted into a seperate category [NEW ]Added New Counters: Total members(Users+Bots) Roles(Total roles in server) Channels(Total channels in server. Not including infochannels and categorys) Offline Role(members with a specified role) [NEW] Can now customize channel names including the category name --- infochannel/infochannel.py | 675 ++++++++++++++++++++++++++++++++++--- 1 file changed, 631 insertions(+), 44 deletions(-) diff --git a/infochannel/infochannel.py b/infochannel/infochannel.py index b8d36a3..d1a5f4c 100644 --- a/infochannel/infochannel.py +++ b/infochannel/infochannel.py @@ -30,12 +30,33 @@ class InfoChannel(Cog): ) default_guild = { + "category_id": None, "channel_id": None, + "humanchannel_id": None, "botchannel_id": None, + "roleschannel_id": None, + "channels_channel_id": None, "onlinechannel_id": None, + "offlinechannel_id": None, + "role_ids":{}, "member_count": True, + "human_count": False, "bot_count": False, + "roles_count": False, + "channels_count": False, "online_count": False, + "offline_count": False, + "channel_names":{ + "category_name": "Server Stats", + "members_channel": "Total Members: {count}", + "humans_channel": "Humans: {count}", + "bots_channel": "Bots: {count}", + "roles_channel": "Total Roles: {count}", + "channels_channel": "Total Channels: {count}", + "online_channel": "Online: {count}", + "offline_channel": "Offline:{count}", + "role_channel": "{role}: {count}" + } } self.config.register_guild(**default_guild) @@ -61,15 +82,16 @@ class InfoChannel(Cog): ) guild: discord.Guild = ctx.guild - channel_id = await self.config.guild(guild).channel_id() - channel = None - if channel_id is not None: - channel: Union[discord.VoiceChannel, None] = guild.get_channel(channel_id) + category_id = await self.config.guild(guild).category_id() + category = None - if channel_id is not None and channel is None: - await ctx.send("Info channel has been deleted, recreate it?") - elif channel_id is None: - await ctx.send("Enable info channel on this server?") + if category_id is not None: + category: Union[discord.CategoryChannel, None] = guild.get_channel(category_id) + + if category_id is not None and category is None: + await ctx.send("Info category has been deleted, recreate it?") + elif category_id is None: + await ctx.send("Enable info channels on this server?") else: await ctx.send("Do you wish to delete current info channels?") @@ -79,11 +101,11 @@ class InfoChannel(Cog): await ctx.send("Cancelled") return - if channel is None: + if category is None: try: await self.make_infochannel(guild) except discord.Forbidden: - await ctx.send("Failure: Missing permission to create voice channel") + await ctx.send("Failure: Missing permission to create neccessary channels") return else: await self.delete_all_infochannels(guild) @@ -91,7 +113,7 @@ class InfoChannel(Cog): if not await ctx.tick(): await ctx.send("Done!") - @commands.group() + @commands.group(aliases=['icset']) @checks.admin() async def infochannelset(self, ctx: commands.Context): """ @@ -99,7 +121,41 @@ class InfoChannel(Cog): """ if not ctx.invoked_subcommand: pass + + @infochannelset.command(name="membercount") + async def _infochannelset_membercount(self, ctx: commands.Context, enabled: bool = None): + """ + Toggle an infochannel that shows the amount of total members in the server + """ + guild = ctx.guild + if enabled is None: + enabled = not await self.config.guild(guild).member_count() + await self.config.guild(guild).member_count.set(enabled) + await self.make_infochannel(ctx.guild) + + if enabled: + await ctx.send("InfoChannel for member count has been enabled.") + else: + await ctx.send("InfoChannel for member count has been disabled.") + + @infochannelset.command(name="humancount") + async def _infochannelset_humancount(self, ctx: commands.Context, enabled: bool = None): + """ + Toggle an infochannel that shows the amount of human users in the server + """ + guild = ctx.guild + if enabled is None: + enabled = not await self.config.guild(guild).human_count() + + await self.config.guild(guild).human_count.set(enabled) + await self.make_infochannel(ctx.guild) + + if enabled: + await ctx.send("InfoChannel for human user count has been enabled.") + else: + await ctx.send("InfoChannel for human user count has been disabled.") + @infochannelset.command(name="botcount") async def _infochannelset_botcount(self, ctx: commands.Context, enabled: bool = None): """ @@ -117,6 +173,40 @@ class InfoChannel(Cog): else: await ctx.send("InfoChannel for bot count has been disabled.") + @infochannelset.command(name="rolescount") + async def _infochannelset_rolescount(self, ctx: commands.Context, enabled: bool = None): + """ + Toggle an infochannel that shows the amount of roles in the server + """ + guild = ctx.guild + if enabled is None: + enabled = not await self.config.guild(guild).roles_count() + + await self.config.guild(guild).roles_count.set(enabled) + await self.make_infochannel(ctx.guild) + + if enabled: + await ctx.send("InfoChannel for roles count has been enabled.") + else: + await ctx.send("InfoChannel for roles count has been disabled.") + + @infochannelset.command(name="channelscount") + async def _infochannelset_channelscount(self, ctx: commands.Context, enabled: bool = None): + """ + Toggle an infochannel that shows the amount of channels in the server + """ + guild = ctx.guild + if enabled is None: + enabled = not await self.config.guild(guild).channels_count() + + await self.config.guild(guild).channels_count.set(enabled) + await self.make_infochannel(ctx.guild) + + if enabled: + await ctx.send("InfoChannel for channels count has been enabled.") + else: + await ctx.send("InfoChannel for channels count has been disabled.") + @infochannelset.command(name="onlinecount") async def _infochannelset_onlinecount(self, ctx: commands.Context, enabled: bool = None): """ @@ -134,76 +224,477 @@ class InfoChannel(Cog): else: await ctx.send("InfoChannel for online user count has been disabled.") - async def make_infochannel(self, guild: discord.Guild): + @infochannelset.command(name="offlinecount") + async def _infochannelset_offlinecount(self, ctx: commands.Context, enabled: bool = None): + """ + Toggle an infochannel that shows the amount of offline users in the server + """ + guild = ctx.guild + if enabled is None: + enabled = not await self.config.guild(guild).offline_count() + + await self.config.guild(guild).offline_count.set(enabled) + await self.make_infochannel(ctx.guild) + + if enabled: + await ctx.send("InfoChannel for offline user count has been enabled.") + else: + await ctx.send("InfoChannel for offline user count has been disabled.") + + @infochannelset.command(name="rolecount") + async def _infochannelset_rolecount(self, ctx: commands.Context, role: discord.Role, enabled: bool = None): + """ + Toggle an infochannel that shows the amount of users in the server with the specified role + """ + guild = ctx.guild + role_data = await self.config.guild(guild).role_ids.all() + + if str(role.id) in role_data.keys(): + enabled = False + else: + enabled = True + + await self.make_infochannel(ctx.guild, role) + + if enabled: + await ctx.send(f"InfoChannel for {role.name} count has been enabled.") + else: + await ctx.send(f"InfoChannel for {role.name} count has been disabled.") + + #delete later + @infochannelset.command(name="cleardata") + async def _infochannelset_cleardata(self, ctx: commands.Context): + """ + Clears the the servers data in case of corruption + """ + guild = ctx.guild + await self.config.guild(guild).clear() + await ctx.send("The data for this server is cleared.") + + @infochannelset.group(name='name') + async def channelname(self, ctx: commands.Context): + """ + Change the name of the infochannels + """ + if not ctx.invoked_subcommand: + pass + + @channelname.command(name='category') + async def _channelname_Category(self, ctx: commands.Context, *, text): + """ + Change the name of the infochannel's category. + """ + guild = ctx.message.guild + category_id = await self.config.guild(guild).category_id() + category: discord.CategoryChannel = guild.get_channel(category_id) + await category.edit(name = text) + await self.config.guild(guild).channel_names.category_name.set(text) + if not await ctx.tick(): + await ctx.send("Done!") + + @channelname.command(name='members') + async def _channelname_Members(self, ctx: commands.Context, *, text=None): + """ + Change the name of the total members infochannel. + + {count} can be used to display number of total members in the server. + Leave blank to set back to default + Default is set to: + Total Members: {count} + + Example Formats: + Total Members: {count} + {count} Members + """ + guild = ctx.message.guild + if text: + await self.config.guild(guild).channel_names.members_channel.set(text) + else: + await self.config.guild(guild).channel_names.members_channel.clear() + + await self.update_infochannel(guild) + if not await ctx.tick(): + await ctx.send("Done!") + + @channelname.command(name='humans') + async def _channelname_Humans(self, ctx: commands.Context, *, text=None): + """ + Change the name of the human users infochannel. + + {count} can be used to display number of users in the server. + Leave blank to set back to default + Default is set to: + Humans: {count} + + Example Formats: + Users: {count} + {count} Users + """ + guild = ctx.message.guild + if text: + await self.config.guild(guild).channel_names.humans_channel.set(text) + else: + await self.config.guild(guild).channel_names.humans_channel.clear() + + await self.update_infochannel(guild) + if not await ctx.tick(): + await ctx.send("Done!") + + @channelname.command(name='bots') + async def _channelname_Bots(self, ctx: commands.Context, *, text=None): + """ + Change the name of the bots infochannel. + + {count} can be used to display number of bots in the server. + Leave blank to set back to default + Default is set to: + Bots: {count} + + Example Formats: + Total Bots: {count} + {count} Robots + """ + guild = ctx.message.guild + if text: + await self.config.guild(guild).channel_names.bots_channel.set(text) + else: + await self.config.guild(guild).channel_names.bots_channel.clear() + + await self.update_infochannel(guild) + if not await ctx.tick(): + await ctx.send("Done!") + + @channelname.command(name='roles') + async def _channelname_Roles(self, ctx: commands.Context, *, text=None): + """ + Change the name of the roles infochannel. + + Do NOT confuse with the role command that counts number of members with a specified role + + {count} can be used to display number of roles in the server. + Leave blank to set back to default + Default is set to: + Total Roles: {count} + + Example Formats: + Total Roles: {count} + {count} Roles + """ + guild = ctx.message.guild + if text: + await self.config.guild(guild).channel_names.roles_channel.set(text) + else: + await self.config.guild(guild).channel_names.roles_channel.clear() + + await self.update_infochannel(guild) + if not await ctx.tick(): + await ctx.send("Done!") + + @channelname.command(name='channels') + async def _channelname_Channels(self, ctx: commands.Context, *, text=None): + """ + Change the name of the channels infochannel. + + {count} can be used to display number of channels in the server. + This does not count the infochannels + Leave blank to set back to default + Default is set to: + Total Channels: {count} + + Example Formats: + Total Channels: {count} + {count} Channels + """ + guild = ctx.message.guild + if text: + await self.config.guild(guild).channel_names.channels_channel.set(text) + else: + await self.config.guild(guild).channel_names.channels_channel.clear() + + await self.update_infochannel(guild) + if not await ctx.tick(): + await ctx.send("Done!") + + @channelname.command(name='online') + async def _channelname_Online(self, ctx: commands.Context, *, text=None): + """ + Change the name of the online infochannel. + + {count} can be used to display number online members in the server. + Leave blank to set back to default + Default is set to: + Online: {count} + + Example Formats: + Total Online: {count} + {count} Online Members + """ + guild = ctx.message.guild + if text: + await self.config.guild(guild).channel_names.online_channel.set(text) + else: + await self.config.guild(guild).channel_names.online_channel.clear() + + await self.update_infochannel(guild) + if not await ctx.tick(): + await ctx.send("Done!") + + @channelname.command(name='offline') + async def _channelname_Offline(self, ctx: commands.Context, *, text=None): + """ + Change the name of the offline infochannel. + + {count} can be used to display number offline members in the server. + Leave blank to set back to default + Default is set to: + Offline: {count} + + Example Formats: + Total Offline: {count} + {count} Offline Members + """ + guild = ctx.message.guild + if text: + await self.config.guild(guild).channel_names.offline_channel.set(text) + else: + await self.config.guild(guild).channel_names.offline_channel.clear() + + await self.update_infochannel(guild) + if not await ctx.tick(): + await ctx.send("Done!") + + @channelname.command(name='role') + async def _channelname_Role(self, ctx: commands.Context, *, text=None): + """ + Change the name of the infochannel for specific roles. + + All role infochannels follow this format. + Do NOT confuse with the roles command that counts number of roles in the server + + {count} can be used to display number members with the given role. + {role} can be used for the roles name + Leave blank to set back to default + Default is set to: + {role}: {count} + + Example Formats: + {role}: {count} + {count} with {role} role + """ + guild = ctx.message.guild + if text: + await self.config.guild(guild).channel_names.role_channel.set(text) + else: + await self.config.guild(guild).channel_names.role_channel.clear() + + await self.update_infochannel(guild) + if not await ctx.tick(): + await ctx.send("Done!") + + async def make_infochannel(self, guild: discord.Guild, role: discord.Role = None): + membercount = await self.config.guild(guild).member_count() + humancount = await self.config.guild(guild).human_count() botcount = await self.config.guild(guild).bot_count() + rolescount = await self.config.guild(guild).roles_count() + channelscount = await self.config.guild(guild).channels_count() onlinecount = await self.config.guild(guild).online_count() + offlinecount = await self.config.guild(guild).offline_count() overwrites = { guild.default_role: discord.PermissionOverwrite(connect=False), guild.me: discord.PermissionOverwrite(manage_channels=True, connect=True), } - # Remove the old info channel first + # Check for and create the category + category_id = await self.config.guild(guild).category_id() + if category_id is not None: + category: discord.CategoryChannel = guild.get_channel(category_id) + if category is None: + await self.config.guild(guild).category_id.set(None) + category_id = None + + if category_id is None: + category: discord.CategoryChannel = await guild.create_category( + "Server Stats", reason="InfoChannel Category make" + ) + await self.config.guild(guild).category_id.set(category.id) + await category.edit(position = 0) + category_id = category.id + + category: discord.CategoryChannel = guild.get_channel(category_id) + + # Remove the old members channel first channel_id = await self.config.guild(guild).channel_id() - if channel_id is not None: + if category_id is not None: channel: discord.VoiceChannel = guild.get_channel(channel_id) if channel: await channel.delete(reason="InfoChannel delete") - - # Then create the new one - channel = await guild.create_voice_channel( - "Total Humans:", reason="InfoChannel make", overwrites=overwrites - ) - await self.config.guild(guild).channel_id.set(channel.id) - + if membercount: + # Then create the new one + channel = await category.create_voice_channel( + "Total Members:", reason="InfoChannel make", overwrites=overwrites + ) + await self.config.guild(guild).channel_id.set(channel.id) + + # Remove the old human channel first + humanchannel_id = await self.config.guild(guild).humanchannel_id() + if category_id is not None: + humanchannel: discord.VoiceChannel = guild.get_channel(humanchannel_id) + if humanchannel: + await humanchannel.delete(reason="InfoChannel delete") + if humancount: + # Then create the new one + humanchannel = await category.create_voice_channel( + "Humans:", reason="InfoChannel humancount", overwrites=overwrites + ) + await self.config.guild(guild).humanchannel_id.set(humanchannel.id) + + + # Remove the old bot channel first + botchannel_id = await self.config.guild(guild).botchannel_id() + if category_id is not None: + botchannel: discord.VoiceChannel = guild.get_channel(botchannel_id) + if botchannel: + await botchannel.delete(reason="InfoChannel delete") if botcount: - # Remove the old bot channel first - botchannel_id = await self.config.guild(guild).botchannel_id() - if channel_id is not None: - botchannel: discord.VoiceChannel = guild.get_channel(botchannel_id) - if botchannel: - await botchannel.delete(reason="InfoChannel delete") - # Then create the new one - botchannel = await guild.create_voice_channel( + botchannel = await category.create_voice_channel( "Bots:", reason="InfoChannel botcount", overwrites=overwrites ) await self.config.guild(guild).botchannel_id.set(botchannel.id) - if onlinecount: - # Remove the old online channel first - onlinechannel_id = await self.config.guild(guild).onlinechannel_id() - if channel_id is not None: - onlinechannel: discord.VoiceChannel = guild.get_channel(onlinechannel_id) - if onlinechannel: - await onlinechannel.delete(reason="InfoChannel delete") + + # Remove the old roles channel first + roleschannel_id = await self.config.guild(guild).roleschannel_id() + if category_id is not None: + roleschannel: discord.VoiceChannel = guild.get_channel(roleschannel_id) + if roleschannel: + await roleschannel.delete(reason="InfoChannel delete") + + if rolescount: + # Then create the new one + roleschannel = await category.create_voice_channel( + "Total Roles:", reason="InfoChannel rolescount", overwrites=overwrites + ) + await self.config.guild(guild).roleschannel_id.set(roleschannel.id) + + + # Remove the old channels channel first + channels_channel_id = await self.config.guild(guild).channels_channel_id() + if category_id is not None: + channels_channel: discord.VoiceChannel = guild.get_channel(channels_channel_id) + if channels_channel: + await channels_channel.delete(reason="InfoChannel delete") + if channelscount: + # Then create the new one + channels_channel = await category.create_voice_channel( + "Total Channels:", reason="InfoChannel botcount", overwrites=overwrites + ) + await self.config.guild(guild).channels_channel_id.set(channels_channel.id) + + # Remove the old online channel first + onlinechannel_id = await self.config.guild(guild).onlinechannel_id() + if channel_id is not None: + onlinechannel: discord.VoiceChannel = guild.get_channel(onlinechannel_id) + if onlinechannel: + await onlinechannel.delete(reason="InfoChannel delete") + if onlinecount: # Then create the new one - onlinechannel = await guild.create_voice_channel( + onlinechannel = await category.create_voice_channel( "Online:", reason="InfoChannel onlinecount", overwrites=overwrites ) await self.config.guild(guild).onlinechannel_id.set(onlinechannel.id) + + # Remove the old offline channel first + offlinechannel_id = await self.config.guild(guild).offlinechannel_id() + if channel_id is not None: + offlinechannel: discord.VoiceChannel = guild.get_channel(offlinechannel_id) + if offlinechannel: + await offlinechannel.delete(reason="InfoChannel delete") + if offlinecount: + # Then create the new one + offlinechannel = await category.create_voice_channel( + "Offline:", reason="InfoChannel offlinecount", overwrites=overwrites + ) + await self.config.guild(guild).offlinechannel_id.set(offlinechannel.id) + + async with self.config.guild(guild).role_ids() as role_data: + #Remove the old role channels first + for role_id in role_data.keys(): + role_channel_id = role_data[role_id] + if role_channel_id is not None: + rolechannel: discord.VoiceChannel = guild.get_channel(role_channel_id) + if rolechannel: + await rolechannel.delete(reason="InfoChannel delete") + + #The actual toggle for a role counter + if role: + if str(role.id) in role_data.keys(): + role_data.pop(str(role.id)) #if the role is there, then remove it + else: + role_data[role.id] = None #No channel created yet but we want one to be made + if role_data: + # Then create the new ones + for role_id in role_data.keys(): + rolechannel = await category.create_voice_channel( + str(role_id)+":", reason="InfoChannel rolecount", overwrites=overwrites + ) + role_data[role_id] = rolechannel.id await self.update_infochannel(guild) async def delete_all_infochannels(self, guild: discord.Guild): guild_data = await self.config.guild(guild).all() + role_data = guild_data["role_ids"] + category_id = guild_data["category_id"] + humanchannel_id = guild_data["humanchannel_id"] botchannel_id = guild_data["botchannel_id"] + roleschannel_id = guild_data["roleschannel_id"] + channels_channel_id = guild_data["channels_channel_id"] onlinechannel_id = guild_data["onlinechannel_id"] + offlinechannel_id = guild_data["offlinechannel_id"] + category: discord.CategoryChannel = guild.get_channel(category_id) + humanchannel: discord.VoiceChannel = guild.get_channel(humanchannel_id) botchannel: discord.VoiceChannel = guild.get_channel(botchannel_id) + roleschannel: discord.VoiceChannel = guild.get_channel(roleschannel_id) + channels_channel: discord.VoiceChannel = guild.get_channel(channels_channel_id) onlinechannel: discord.VoiceChannel = guild.get_channel(onlinechannel_id) + offlinechannel: discord.VoiceChannel = guild.get_channel(offlinechannel_id) channel_id = guild_data["channel_id"] channel: discord.VoiceChannel = guild.get_channel(channel_id) await channel.delete(reason="InfoChannel delete") + if humanchannel_id is not None: + await humanchannel.delete(reason="InfoChannel delete") if botchannel_id is not None: await botchannel.delete(reason="InfoChannel delete") + if roleschannel_id is not None: + await roleschannel.delete(reason="InfoChannel delete") + if channels_channel is not None: + await channels_channel.delete(reason="InfoChannel delete") if onlinechannel_id is not None: await onlinechannel.delete(reason="InfoChannel delete") - + if offlinechannel_id is not None: + await offlinechannel.delete(reason="InfoChannel delete") + if category_id is not None: + await category.delete(reason="InfoChannel delete") + async with self.config.guild(guild).role_ids() as role_data: + if role_data: + for role_channel_id in role_data.values(): + rolechannel: discord.VoiceChannel = guild.get_channel(role_channel_id) + if rolechannel: + await rolechannel.delete(reason="InfoChannel delete") + await self.config.guild(guild).clear() async def update_infochannel(self, guild: discord.Guild): guild_data = await self.config.guild(guild).all() + humancount = guild_data["human_count"] botcount = guild_data["bot_count"] + rolescount = guild_data["roles_count"] + channelscount = guild_data["channels_count"] onlinecount = guild_data["online_count"] + offlinecount = guild_data["offline_count"] + + category = guild.get_channel(guild_data["category_id"]) # Gets count of bots # bots = lambda x: x.bot @@ -212,40 +703,88 @@ class InfoChannel(Cog): bot_num = len([m for m in guild.members if m.bot]) # bot_msg = f"Bots: {num}" - # Gets count of online users + #Gets count of roles in the server + roles_num = len(guild.roles)-1 + # roles_msg = f"Total Roles: {num}" + + #Gets count of channels in the server + # - - + channels_num = len(guild.channels) - len(category.voice_channels) - len(guild.categories) + # channels_msg = f"Total Channels: {num}" + + # Gets all counts of members members = guild.member_count + # member_msg = f"Total Members: {num}" offline = len(list(filter(lambda m: m.status is discord.Status.offline, guild.members))) + # offline_msg = f"Offline: {num}" online_num = members - offline # online_msg = f"Online: {num}" # Gets count of actual users total = lambda x: not x.bot human_num = len([m for m in guild.members if total(m)]) - # human_msg = f"Total Humans: {num}" + # human_msg = f"Users: {num}" channel_id = guild_data["channel_id"] if channel_id is None: return False botchannel_id = guild_data["botchannel_id"] + roleschannel_id = guild_data["roleschannel_id"] + channels_channel_id = guild_data["channels_channel_id"] onlinechannel_id = guild_data["onlinechannel_id"] + offlinechannel_id = guild_data["offlinechannel_id"] + humanchannel_id = guild_data["humanchannel_id"] channel_id = guild_data["channel_id"] channel: discord.VoiceChannel = guild.get_channel(channel_id) + humanchannel: discord.VoiceChannel = guild.get_channel(humanchannel_id) botchannel: discord.VoiceChannel = guild.get_channel(botchannel_id) + roleschannel: discord.VoiceChannel = guild.get_channel(roleschannel_id) + channels_channel: discord.VoiceChannel = guild.get_channel(channels_channel_id) onlinechannel: discord.VoiceChannel = guild.get_channel(onlinechannel_id) + offlinechannel: discord.VoiceChannel = guild.get_channel(offlinechannel_id) - if guild_data["member_count"]: - name = f"{channel.name.split(':')[0]}: {human_num}" + channel_names = await self.config.guild(guild).channel_names.all() + if guild_data["member_count"]: + name = channel_names["members_channel"].format(count = members) await channel.edit(reason="InfoChannel update", name=name) + if humancount: + name = channel_names["humans_channel"].format(count = human_num) + await humanchannel.edit(reason="InfoChannel update", name=name) + if botcount: - name = f"{botchannel.name.split(':')[0]}: {bot_num}" + name = channel_names["bots_channel"].format(count = bot_num) await botchannel.edit(reason="InfoChannel update", name=name) + + if rolescount: + name = channel_names["roles_channel"].format(count = roles_num) + await roleschannel.edit(reason="InfoChannel update", name=name) + + if channelscount: + name = channel_names["channels_channel"].format(count = channels_num) + await channels_channel.edit(reason="InfoChannel update", name=name) if onlinecount: - name = f"{onlinechannel.name.split(':')[0]}: {online_num}" + name = channel_names["online_channel"].format(count = online_num) await onlinechannel.edit(reason="InfoChannel update", name=name) + + if offlinecount: + name = channel_names["offline_channel"].format(count = offline) + await offlinechannel.edit(reason="InfoChannel update", name=name) + + async with self.config.guild(guild).role_ids() as role_data: + if role_data: + for role_id, role_channel_id in role_data.items(): + rolechannel: discord.VoiceChannel = guild.get_channel(role_channel_id) + role: discord.Role = guild.get_role(int(role_id)) + + role_num = len(role.members) + + name = channel_names["role_channel"].format(count = role_num, role = role.name) + await rolechannel.edit(reason="InfoChannel update", name=name) + async def update_infochannel_with_cooldown(self, guild): """My attempt at preventing rate limits, lets see how it goes""" @@ -291,3 +830,51 @@ class InfoChannel(Cog): if onlinecount: if before.status != after.status: await self.update_infochannel_with_cooldown(after.guild) + role_data = await self.config.guild(after.guild).role_ids.all() + if role_data: + b = set(before.roles) + a = set(after.roles) + if b != a: + await self.update_infochannel_with_cooldown(after.guild) + + @Cog.listener() + async def on_guild_channel_create(self, channel: discord.abc.GuildChannel): + if await self.bot.cog_disabled_in_guild(self, channel.guild): + return + channelscount = await self.config.guild(channel.guild).channels_count() + if channelscount: + await self.update_infochannel_with_cooldown(channel.guild) + + @Cog.listener() + async def on_guild_channel_delete(self, channel: discord.abc.GuildChannel): + if await self.bot.cog_disabled_in_guild(self, channel.guild): + return + channelscount = await self.config.guild(channel.guild).channels_count() + if channelscount: + await self.update_infochannel_with_cooldown(channel.guild) + + @Cog.listener() + async def on_guild_role_create(self, role): + if await self.bot.cog_disabled_in_guild(self, role.guild): + return + + rolescount = await self.config.guild(role.guild).roles_count() + if rolescount: + await self.update_infochannel_with_cooldown(role.guild) + + @Cog.listener() + async def on_guild_role_delete(self, role): + if await self.bot.cog_disabled_in_guild(self, role.guild): + return + + rolescount = await self.config.guild(role.guild).roles_count() + if rolescount: + await self.update_infochannel_with_cooldown(role.guild) + + #delete specific role counter if the role is deleted + async with self.config.guild(role.guild).role_ids() as role_data: + if str(role.id) in role_data.keys(): + role_channel_id = role_data[str(role.id)] + rolechannel: discord.VoiceChannel = role.guild.get_channel(role_channel_id) + await rolechannel.delete(reason="InfoChannel delete") + del role_data[str(role.id)] From bce07f069fc7084ecb51fb06e84db31e28231939 Mon Sep 17 00:00:00 2001 From: ASSASSIN0831 Date: Wed, 2 Dec 2020 22:14:22 -0500 Subject: [PATCH 03/13] Update infochannel.py --- infochannel/infochannel.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/infochannel/infochannel.py b/infochannel/infochannel.py index d1a5f4c..512cf2d 100644 --- a/infochannel/infochannel.py +++ b/infochannel/infochannel.py @@ -261,16 +261,6 @@ class InfoChannel(Cog): else: await ctx.send(f"InfoChannel for {role.name} count has been disabled.") - #delete later - @infochannelset.command(name="cleardata") - async def _infochannelset_cleardata(self, ctx: commands.Context): - """ - Clears the the servers data in case of corruption - """ - guild = ctx.guild - await self.config.guild(guild).clear() - await ctx.send("The data for this server is cleared.") - @infochannelset.group(name='name') async def channelname(self, ctx: commands.Context): """ From 69e2e5acb3d1c34d19e4a9426db9e5faefccaefa Mon Sep 17 00:00:00 2001 From: ASSASSIN0831 Date: Thu, 3 Dec 2020 13:53:17 -0500 Subject: [PATCH 04/13] Black formatting --- infochannel/infochannel.py | 120 ++++++++++++++++++------------------- 1 file changed, 59 insertions(+), 61 deletions(-) diff --git a/infochannel/infochannel.py b/infochannel/infochannel.py index 512cf2d..f021c56 100644 --- a/infochannel/infochannel.py +++ b/infochannel/infochannel.py @@ -38,7 +38,7 @@ class InfoChannel(Cog): "channels_channel_id": None, "onlinechannel_id": None, "offlinechannel_id": None, - "role_ids":{}, + "role_ids": {}, "member_count": True, "human_count": False, "bot_count": False, @@ -46,7 +46,7 @@ class InfoChannel(Cog): "channels_count": False, "online_count": False, "offline_count": False, - "channel_names":{ + "channel_names": { "category_name": "Server Stats", "members_channel": "Total Members: {count}", "humans_channel": "Humans: {count}", @@ -55,8 +55,8 @@ class InfoChannel(Cog): "channels_channel": "Total Channels: {count}", "online_channel": "Online: {count}", "offline_channel": "Offline:{count}", - "role_channel": "{role}: {count}" - } + "role_channel": "{role}: {count}", + }, } self.config.register_guild(**default_guild) @@ -113,7 +113,7 @@ class InfoChannel(Cog): if not await ctx.tick(): await ctx.send("Done!") - @commands.group(aliases=['icset']) + @commands.group(aliases=["icset"]) @checks.admin() async def infochannelset(self, ctx: commands.Context): """ @@ -121,7 +121,7 @@ class InfoChannel(Cog): """ if not ctx.invoked_subcommand: pass - + @infochannelset.command(name="membercount") async def _infochannelset_membercount(self, ctx: commands.Context, enabled: bool = None): """ @@ -155,7 +155,7 @@ class InfoChannel(Cog): await ctx.send("InfoChannel for human user count has been enabled.") else: await ctx.send("InfoChannel for human user count has been disabled.") - + @infochannelset.command(name="botcount") async def _infochannelset_botcount(self, ctx: commands.Context, enabled: bool = None): """ @@ -242,7 +242,9 @@ class InfoChannel(Cog): await ctx.send("InfoChannel for offline user count has been disabled.") @infochannelset.command(name="rolecount") - async def _infochannelset_rolecount(self, ctx: commands.Context, role: discord.Role, enabled: bool = None): + async def _infochannelset_rolecount( + self, ctx: commands.Context, role: discord.Role, enabled: bool = None + ): """ Toggle an infochannel that shows the amount of users in the server with the specified role """ @@ -261,15 +263,15 @@ class InfoChannel(Cog): else: await ctx.send(f"InfoChannel for {role.name} count has been disabled.") - @infochannelset.group(name='name') + @infochannelset.group(name="name") async def channelname(self, ctx: commands.Context): """ Change the name of the infochannels """ if not ctx.invoked_subcommand: pass - - @channelname.command(name='category') + + @channelname.command(name="category") async def _channelname_Category(self, ctx: commands.Context, *, text): """ Change the name of the infochannel's category. @@ -277,12 +279,12 @@ class InfoChannel(Cog): guild = ctx.message.guild category_id = await self.config.guild(guild).category_id() category: discord.CategoryChannel = guild.get_channel(category_id) - await category.edit(name = text) + await category.edit(name=text) await self.config.guild(guild).channel_names.category_name.set(text) if not await ctx.tick(): await ctx.send("Done!") - @channelname.command(name='members') + @channelname.command(name="members") async def _channelname_Members(self, ctx: commands.Context, *, text=None): """ Change the name of the total members infochannel. @@ -306,7 +308,7 @@ class InfoChannel(Cog): if not await ctx.tick(): await ctx.send("Done!") - @channelname.command(name='humans') + @channelname.command(name="humans") async def _channelname_Humans(self, ctx: commands.Context, *, text=None): """ Change the name of the human users infochannel. @@ -329,8 +331,8 @@ class InfoChannel(Cog): await self.update_infochannel(guild) if not await ctx.tick(): await ctx.send("Done!") - - @channelname.command(name='bots') + + @channelname.command(name="bots") async def _channelname_Bots(self, ctx: commands.Context, *, text=None): """ Change the name of the bots infochannel. @@ -354,7 +356,7 @@ class InfoChannel(Cog): if not await ctx.tick(): await ctx.send("Done!") - @channelname.command(name='roles') + @channelname.command(name="roles") async def _channelname_Roles(self, ctx: commands.Context, *, text=None): """ Change the name of the roles infochannel. @@ -379,8 +381,8 @@ class InfoChannel(Cog): await self.update_infochannel(guild) if not await ctx.tick(): await ctx.send("Done!") - - @channelname.command(name='channels') + + @channelname.command(name="channels") async def _channelname_Channels(self, ctx: commands.Context, *, text=None): """ Change the name of the channels infochannel. @@ -405,7 +407,7 @@ class InfoChannel(Cog): if not await ctx.tick(): await ctx.send("Done!") - @channelname.command(name='online') + @channelname.command(name="online") async def _channelname_Online(self, ctx: commands.Context, *, text=None): """ Change the name of the online infochannel. @@ -429,7 +431,7 @@ class InfoChannel(Cog): if not await ctx.tick(): await ctx.send("Done!") - @channelname.command(name='offline') + @channelname.command(name="offline") async def _channelname_Offline(self, ctx: commands.Context, *, text=None): """ Change the name of the offline infochannel. @@ -453,11 +455,11 @@ class InfoChannel(Cog): if not await ctx.tick(): await ctx.send("Done!") - @channelname.command(name='role') + @channelname.command(name="role") async def _channelname_Role(self, ctx: commands.Context, *, text=None): """ Change the name of the infochannel for specific roles. - + All role infochannels follow this format. Do NOT confuse with the roles command that counts number of roles in the server @@ -501,17 +503,17 @@ class InfoChannel(Cog): if category is None: await self.config.guild(guild).category_id.set(None) category_id = None - + if category_id is None: category: discord.CategoryChannel = await guild.create_category( "Server Stats", reason="InfoChannel Category make" ) await self.config.guild(guild).category_id.set(category.id) - await category.edit(position = 0) + await category.edit(position=0) category_id = category.id - + category: discord.CategoryChannel = guild.get_channel(category_id) - + # Remove the old members channel first channel_id = await self.config.guild(guild).channel_id() if category_id is not None: @@ -538,7 +540,6 @@ class InfoChannel(Cog): ) await self.config.guild(guild).humanchannel_id.set(humanchannel.id) - # Remove the old bot channel first botchannel_id = await self.config.guild(guild).botchannel_id() if category_id is not None: @@ -552,7 +553,6 @@ class InfoChannel(Cog): ) await self.config.guild(guild).botchannel_id.set(botchannel.id) - # Remove the old roles channel first roleschannel_id = await self.config.guild(guild).roleschannel_id() if category_id is not None: @@ -567,7 +567,6 @@ class InfoChannel(Cog): ) await self.config.guild(guild).roleschannel_id.set(roleschannel.id) - # Remove the old channels channel first channels_channel_id = await self.config.guild(guild).channels_channel_id() if category_id is not None: @@ -580,7 +579,7 @@ class InfoChannel(Cog): "Total Channels:", reason="InfoChannel botcount", overwrites=overwrites ) await self.config.guild(guild).channels_channel_id.set(channels_channel.id) - + # Remove the old online channel first onlinechannel_id = await self.config.guild(guild).onlinechannel_id() if channel_id is not None: @@ -593,7 +592,7 @@ class InfoChannel(Cog): "Online:", reason="InfoChannel onlinecount", overwrites=overwrites ) await self.config.guild(guild).onlinechannel_id.set(onlinechannel.id) - + # Remove the old offline channel first offlinechannel_id = await self.config.guild(guild).offlinechannel_id() if channel_id is not None: @@ -608,7 +607,7 @@ class InfoChannel(Cog): await self.config.guild(guild).offlinechannel_id.set(offlinechannel.id) async with self.config.guild(guild).role_ids() as role_data: - #Remove the old role channels first + # Remove the old role channels first for role_id in role_data.keys(): role_channel_id = role_data[role_id] if role_channel_id is not None: @@ -616,17 +615,17 @@ class InfoChannel(Cog): if rolechannel: await rolechannel.delete(reason="InfoChannel delete") - #The actual toggle for a role counter + # The actual toggle for a role counter if role: if str(role.id) in role_data.keys(): - role_data.pop(str(role.id)) #if the role is there, then remove it + role_data.pop(str(role.id)) # if the role is there, then remove it else: - role_data[role.id] = None #No channel created yet but we want one to be made + role_data[role.id] = None # No channel created yet but we want one to be made if role_data: # Then create the new ones for role_id in role_data.keys(): rolechannel = await category.create_voice_channel( - str(role_id)+":", reason="InfoChannel rolecount", overwrites=overwrites + str(role_id) + ":", reason="InfoChannel rolecount", overwrites=overwrites ) role_data[role_id] = rolechannel.id @@ -672,7 +671,7 @@ class InfoChannel(Cog): rolechannel: discord.VoiceChannel = guild.get_channel(role_channel_id) if rolechannel: await rolechannel.delete(reason="InfoChannel delete") - + await self.config.guild(guild).clear() async def update_infochannel(self, guild: discord.Guild): @@ -693,12 +692,12 @@ class InfoChannel(Cog): bot_num = len([m for m in guild.members if m.bot]) # bot_msg = f"Bots: {num}" - #Gets count of roles in the server - roles_num = len(guild.roles)-1 + # Gets count of roles in the server + roles_num = len(guild.roles) - 1 # roles_msg = f"Total Roles: {num}" - #Gets count of channels in the server - # - - + # Gets count of channels in the server + # - - channels_num = len(guild.channels) - len(category.voice_channels) - len(guild.categories) # channels_msg = f"Total Channels: {num}" @@ -737,31 +736,31 @@ class InfoChannel(Cog): channel_names = await self.config.guild(guild).channel_names.all() if guild_data["member_count"]: - name = channel_names["members_channel"].format(count = members) + name = channel_names["members_channel"].format(count=members) await channel.edit(reason="InfoChannel update", name=name) if humancount: - name = channel_names["humans_channel"].format(count = human_num) + name = channel_names["humans_channel"].format(count=human_num) await humanchannel.edit(reason="InfoChannel update", name=name) if botcount: - name = channel_names["bots_channel"].format(count = bot_num) + name = channel_names["bots_channel"].format(count=bot_num) await botchannel.edit(reason="InfoChannel update", name=name) - + if rolescount: - name = channel_names["roles_channel"].format(count = roles_num) + name = channel_names["roles_channel"].format(count=roles_num) await roleschannel.edit(reason="InfoChannel update", name=name) - + if channelscount: - name = channel_names["channels_channel"].format(count = channels_num) + name = channel_names["channels_channel"].format(count=channels_num) await channels_channel.edit(reason="InfoChannel update", name=name) if onlinecount: - name = channel_names["online_channel"].format(count = online_num) + name = channel_names["online_channel"].format(count=online_num) await onlinechannel.edit(reason="InfoChannel update", name=name) - + if offlinecount: - name = channel_names["offline_channel"].format(count = offline) + name = channel_names["offline_channel"].format(count=offline) await offlinechannel.edit(reason="InfoChannel update", name=name) async with self.config.guild(guild).role_ids() as role_data: @@ -772,10 +771,9 @@ class InfoChannel(Cog): role_num = len(role.members) - name = channel_names["role_channel"].format(count = role_num, role = role.name) + name = channel_names["role_channel"].format(count=role_num, role=role.name) await rolechannel.edit(reason="InfoChannel update", name=name) - async def update_infochannel_with_cooldown(self, guild): """My attempt at preventing rate limits, lets see how it goes""" if self._critical_section_wooah_: @@ -842,26 +840,26 @@ class InfoChannel(Cog): channelscount = await self.config.guild(channel.guild).channels_count() if channelscount: await self.update_infochannel_with_cooldown(channel.guild) - - @Cog.listener() + + @Cog.listener() async def on_guild_role_create(self, role): if await self.bot.cog_disabled_in_guild(self, role.guild): return - + rolescount = await self.config.guild(role.guild).roles_count() if rolescount: await self.update_infochannel_with_cooldown(role.guild) - @Cog.listener() + @Cog.listener() async def on_guild_role_delete(self, role): if await self.bot.cog_disabled_in_guild(self, role.guild): return - + rolescount = await self.config.guild(role.guild).roles_count() if rolescount: await self.update_infochannel_with_cooldown(role.guild) - - #delete specific role counter if the role is deleted + + # delete specific role counter if the role is deleted async with self.config.guild(role.guild).role_ids() as role_data: if str(role.id) in role_data.keys(): role_channel_id = role_data[str(role.id)] From b566b58e1aad9a6637d8b7d12f6be81f1ec9937f Mon Sep 17 00:00:00 2001 From: bobloy Date: Tue, 8 Dec 2020 10:53:24 -0500 Subject: [PATCH 05/13] Infochannel rewrite --- infochannel/__init__.py | 8 +- infochannel/infochannel.py | 1171 +++++++++++++++--------------------- 2 files changed, 494 insertions(+), 685 deletions(-) diff --git a/infochannel/__init__.py b/infochannel/__init__.py index 514cd5f..1c4d081 100644 --- a/infochannel/__init__.py +++ b/infochannel/__init__.py @@ -1,5 +1,9 @@ +import asyncio + from .infochannel import InfoChannel -def setup(bot): - bot.add_cog(InfoChannel(bot)) +async def setup(bot): + ic_cog = InfoChannel(bot) + bot.add_cog(ic_cog) + await ic_cog.initialize() diff --git a/infochannel/infochannel.py b/infochannel/infochannel.py index f021c56..1b2bce4 100644 --- a/infochannel/infochannel.py +++ b/infochannel/infochannel.py @@ -1,25 +1,50 @@ import asyncio -from typing import Union +import logging +from collections import defaultdict +from typing import Dict, Optional, Union import discord from redbot.core import Config, checks, commands from redbot.core.bot import Red from redbot.core.commands import Cog -# Cog: Any = getattr(commands, "Cog", object) -# listener = getattr(commands.Cog, "listener", None) # Trusty + Sinbad -# if listener is None: -# def listener(name=None): -# return lambda x: x - -RATE_LIMIT_DELAY = 60 * 10 # If you're willing to risk rate limiting, you can decrease the delay +# 10 minutes. Rate limit is 2 per 10, so 1 per 6 is safe. +RATE_LIMIT_DELAY = 60 * 6 # If you're willing to risk rate limiting, you can decrease the delay + +log = logging.getLogger("red.fox_v3.infochannel") + + +async def get_channel_counts(category, guild): + # Gets count of bots + bot_num = len([m for m in guild.members if m.bot]) + # Gets count of roles in the server + roles_num = len(guild.roles) - 1 + # Gets count of channels in the server + # - - + channels_num = len(guild.channels) - len(category.voice_channels) - len(guild.categories) + # Gets all counts of members + members = guild.member_count + offline_num = len(list(filter(lambda m: m.status is discord.Status.offline, guild.members))) + online_num = members - offline_num + # Gets count of actual users + human_num = members - bot_num + return { + "members": members, + "humans": human_num, + "bots": bot_num, + "roles": roles_num, + "channels": channels_num, + "online": online_num, + "offline": offline_num, + } class InfoChannel(Cog): """ Create a channel with updating server info - Less important information about the cog + This relies on editing channels, which is a strictly rate-limited activity. + As such, updates will not be frequent. Currently capped at 1 per 5 minutes per server. """ def __init__(self, bot: Red): @@ -29,44 +54,55 @@ class InfoChannel(Cog): self, identifier=731101021116710497110110101108, force_registration=True ) + # self. so I can get the keys from this later + self.default_channel_names = { + "members": "Members: {count}", + "humans": "Humans: {count}", + "bots": "Bots: {count}", + "roles": "Roles: {count}", + "channels": "Channels: {count}", + "online": "Online: {count}", + "offline": "Offline: {count}", + } + + default_channel_ids = {k: None for k in self.default_channel_names.keys()} + # Only members is enabled by default + default_enabled_counts = {k: k == "members" for k in self.default_channel_names.keys()} + default_guild = { "category_id": None, - "channel_id": None, - "humanchannel_id": None, - "botchannel_id": None, - "roleschannel_id": None, - "channels_channel_id": None, - "onlinechannel_id": None, - "offlinechannel_id": None, - "role_ids": {}, - "member_count": True, - "human_count": False, - "bot_count": False, - "roles_count": False, - "channels_count": False, - "online_count": False, - "offline_count": False, - "channel_names": { - "category_name": "Server Stats", - "members_channel": "Total Members: {count}", - "humans_channel": "Humans: {count}", - "bots_channel": "Bots: {count}", - "roles_channel": "Total Roles: {count}", - "channels_channel": "Total Channels: {count}", - "online_channel": "Online: {count}", - "offline_channel": "Offline:{count}", - "role_channel": "{role}: {count}", - }, + "channel_ids": default_channel_ids, + "enabled_channels": default_enabled_counts, + "channel_names": self.default_channel_names, } self.config.register_guild(**default_guild) + self.default_role = {"enabled": False, "channel_id": None, "name": "{role}: {count}"} + + self.config.register_role(**self.default_role) + self._critical_section_wooah_ = 0 + self.channel_data = defaultdict(dict) + + self.edit_queue = defaultdict(lambda: defaultdict(lambda: asyncio.Queue(maxsize=2))) + + self._rate_limited_edits: Dict[int, Dict[str, Optional[asyncio.Task]]] = defaultdict( + lambda: defaultdict(lambda: None) + ) + async def red_delete_data_for_user(self, **kwargs): """Nothing to delete""" return + async def initialize(self): + for guild in self.bot.guilds: + await self.update_infochannel(guild) + + def cog_unload(self): + self.stop_all_queues() + @commands.command() @checks.admin() async def infochannel(self, ctx: commands.Context): @@ -89,29 +125,33 @@ class InfoChannel(Cog): category: Union[discord.CategoryChannel, None] = guild.get_channel(category_id) if category_id is not None and category is None: - await ctx.send("Info category has been deleted, recreate it?") + await ctx.maybe_send_embed("Info category has been deleted, recreate it?") elif category_id is None: - await ctx.send("Enable info channels on this server?") + await ctx.maybe_send_embed("Enable info channels on this server?") else: - await ctx.send("Do you wish to delete current info channels?") + await ctx.maybe_send_embed("Do you wish to delete current info channels?") msg = await self.bot.wait_for("message", check=check) if msg.content.upper() in ["N", "NO"]: - await ctx.send("Cancelled") + await ctx.maybe_send_embed("Cancelled") return if category is None: try: await self.make_infochannel(guild) except discord.Forbidden: - await ctx.send("Failure: Missing permission to create neccessary channels") + await ctx.maybe_send_embed( + "Failure: Missing permission to create necessary channels" + ) return else: await self.delete_all_infochannels(guild) + ctx.message = msg + if not await ctx.tick(): - await ctx.send("Done!") + await ctx.maybe_send_embed("Done!") @commands.group(aliases=["icset"]) @checks.admin() @@ -122,388 +162,189 @@ class InfoChannel(Cog): if not ctx.invoked_subcommand: pass - @infochannelset.command(name="membercount") - async def _infochannelset_membercount(self, ctx: commands.Context, enabled: bool = None): - """ - Toggle an infochannel that shows the amount of total members in the server - """ - guild = ctx.guild - if enabled is None: - enabled = not await self.config.guild(guild).member_count() - - await self.config.guild(guild).member_count.set(enabled) - await self.make_infochannel(ctx.guild) - - if enabled: - await ctx.send("InfoChannel for member count has been enabled.") - else: - await ctx.send("InfoChannel for member count has been disabled.") - - @infochannelset.command(name="humancount") - async def _infochannelset_humancount(self, ctx: commands.Context, enabled: bool = None): - """ - Toggle an infochannel that shows the amount of human users in the server - """ - guild = ctx.guild - if enabled is None: - enabled = not await self.config.guild(guild).human_count() - - await self.config.guild(guild).human_count.set(enabled) - await self.make_infochannel(ctx.guild) - - if enabled: - await ctx.send("InfoChannel for human user count has been enabled.") - else: - await ctx.send("InfoChannel for human user count has been disabled.") - - @infochannelset.command(name="botcount") - async def _infochannelset_botcount(self, ctx: commands.Context, enabled: bool = None): - """ - Toggle an infochannel that shows the amount of bots in the server - """ - guild = ctx.guild - if enabled is None: - enabled = not await self.config.guild(guild).bot_count() - - await self.config.guild(guild).bot_count.set(enabled) - await self.make_infochannel(ctx.guild) - - if enabled: - await ctx.send("InfoChannel for bot count has been enabled.") - else: - await ctx.send("InfoChannel for bot count has been disabled.") - - @infochannelset.command(name="rolescount") - async def _infochannelset_rolescount(self, ctx: commands.Context, enabled: bool = None): - """ - Toggle an infochannel that shows the amount of roles in the server - """ - guild = ctx.guild - if enabled is None: - enabled = not await self.config.guild(guild).roles_count() - - await self.config.guild(guild).roles_count.set(enabled) - await self.make_infochannel(ctx.guild) - - if enabled: - await ctx.send("InfoChannel for roles count has been enabled.") - else: - await ctx.send("InfoChannel for roles count has been disabled.") - - @infochannelset.command(name="channelscount") - async def _infochannelset_channelscount(self, ctx: commands.Context, enabled: bool = None): - """ - Toggle an infochannel that shows the amount of channels in the server - """ - guild = ctx.guild - if enabled is None: - enabled = not await self.config.guild(guild).channels_count() - - await self.config.guild(guild).channels_count.set(enabled) - await self.make_infochannel(ctx.guild) - - if enabled: - await ctx.send("InfoChannel for channels count has been enabled.") - else: - await ctx.send("InfoChannel for channels count has been disabled.") + @infochannelset.command(name="togglechannel") + async def _infochannelset_togglechannel( + self, ctx: commands.Context, channel_type: str, enabled: Optional[bool] = None + ): + """Toggles the infochannel for the specified channel type. - @infochannelset.command(name="onlinecount") - async def _infochannelset_onlinecount(self, ctx: commands.Context, enabled: bool = None): - """ - Toggle an infochannel that shows the amount of online users in the server + Valid Types are: + - `members`: Total members on the server + - `humans`: Total members that aren't bots + - `bots`: Total bots + - `roles`: Total number of roles + - `channels`: Total number of channels excluding infochannels, + - `online`: Total online members, + - `offline`: Total offline members, """ guild = ctx.guild - if enabled is None: - enabled = not await self.config.guild(guild).online_count() - - await self.config.guild(guild).online_count.set(enabled) - await self.make_infochannel(ctx.guild) - - if enabled: - await ctx.send("InfoChannel for online user count has been enabled.") - else: - await ctx.send("InfoChannel for online user count has been disabled.") + if channel_type not in self.default_channel_names.keys(): + await ctx.maybe_send_embed("Invalid channel type provided.") + return - @infochannelset.command(name="offlinecount") - async def _infochannelset_offlinecount(self, ctx: commands.Context, enabled: bool = None): - """ - Toggle an infochannel that shows the amount of offline users in the server - """ - guild = ctx.guild if enabled is None: - enabled = not await self.config.guild(guild).offline_count() + enabled = not await self.config.guild(guild).enabled_channels.get_raw(channel_type) - await self.config.guild(guild).offline_count.set(enabled) - await self.make_infochannel(ctx.guild) + await self.config.guild(guild).enabled_channels.set_raw(channel_type, value=enabled) + await self.make_infochannel(ctx.guild, channel_type=channel_type) if enabled: - await ctx.send("InfoChannel for offline user count has been enabled.") + await ctx.maybe_send_embed(f"InfoChannel `{channel_type}` has been enabled.") else: - await ctx.send("InfoChannel for offline user count has been disabled.") + await ctx.maybe_send_embed(f"InfoChannel `{channel_type}` has been disabled.") - @infochannelset.command(name="rolecount") + @infochannelset.command(name="togglerole") async def _infochannelset_rolecount( self, ctx: commands.Context, role: discord.Role, enabled: bool = None ): - """ - Toggle an infochannel that shows the amount of users in the server with the specified role - """ - guild = ctx.guild - role_data = await self.config.guild(guild).role_ids.all() + """Toggle an infochannel that shows the count of users with the specified role""" + if enabled is None: + enabled = not await self.config.role(role).enabled() - if str(role.id) in role_data.keys(): - enabled = False - else: - enabled = True + await self.config.role(role).enabled.set(enabled) - await self.make_infochannel(ctx.guild, role) + await self.make_infochannel(ctx.guild, channel_role=role) if enabled: - await ctx.send(f"InfoChannel for {role.name} count has been enabled.") + await ctx.maybe_send_embed(f"InfoChannel for {role.name} count has been enabled.") else: - await ctx.send(f"InfoChannel for {role.name} count has been disabled.") + await ctx.maybe_send_embed(f"InfoChannel for {role.name} count has been disabled.") - @infochannelset.group(name="name") - async def channelname(self, ctx: commands.Context): - """ - Change the name of the infochannels + @infochannelset.command(name="name") + async def _infochannelset_name(self, ctx: commands.Context, channel_type: str, *, text=None): """ - if not ctx.invoked_subcommand: - pass + Change the name of the infochannel for the specified channel type. - @channelname.command(name="category") - async def _channelname_Category(self, ctx: commands.Context, *, text): - """ - Change the name of the infochannel's category. - """ - guild = ctx.message.guild - category_id = await self.config.guild(guild).category_id() - category: discord.CategoryChannel = guild.get_channel(category_id) - await category.edit(name=text) - await self.config.guild(guild).channel_names.category_name.set(text) - if not await ctx.tick(): - await ctx.send("Done!") - - @channelname.command(name="members") - async def _channelname_Members(self, ctx: commands.Context, *, text=None): - """ - Change the name of the total members infochannel. - - {count} can be used to display number of total members in the server. - Leave blank to set back to default - Default is set to: - Total Members: {count} - - Example Formats: - Total Members: {count} - {count} Members - """ - guild = ctx.message.guild - if text: - await self.config.guild(guild).channel_names.members_channel.set(text) - else: - await self.config.guild(guild).channel_names.members_channel.clear() - - await self.update_infochannel(guild) - if not await ctx.tick(): - await ctx.send("Done!") - - @channelname.command(name="humans") - async def _channelname_Humans(self, ctx: commands.Context, *, text=None): - """ - Change the name of the human users infochannel. - - {count} can be used to display number of users in the server. - Leave blank to set back to default - Default is set to: - Humans: {count} + {count} must be used to display number of total members in the server. + Leave blank to set back to default. - Example Formats: - Users: {count} - {count} Users - """ - guild = ctx.message.guild - if text: - await self.config.guild(guild).channel_names.humans_channel.set(text) - else: - await self.config.guild(guild).channel_names.humans_channel.clear() - - await self.update_infochannel(guild) - if not await ctx.tick(): - await ctx.send("Done!") - - @channelname.command(name="bots") - async def _channelname_Bots(self, ctx: commands.Context, *, text=None): - """ - Change the name of the bots infochannel. + Examples: + - `[p]infochannelset name members Cool Cats: {count}` + - `[p]infochannelset name bots {count} Robot Overlords` - {count} can be used to display number of bots in the server. - Leave blank to set back to default - Default is set to: - Bots: {count} - - Example Formats: - Total Bots: {count} - {count} Robots - """ - guild = ctx.message.guild - if text: - await self.config.guild(guild).channel_names.bots_channel.set(text) - else: - await self.config.guild(guild).channel_names.bots_channel.clear() - - await self.update_infochannel(guild) - if not await ctx.tick(): - await ctx.send("Done!") + Valid Types are: + - `members`: Total members on the server + - `humans`: Total members that aren't bots + - `bots`: Total bots + - `roles`: Total number of roles + - `channels`: Total number of channels excluding infochannels + - `online`: Total online members + - `offline`: Total offline members - @channelname.command(name="roles") - async def _channelname_Roles(self, ctx: commands.Context, *, text=None): + Warning: This command counts against the channel update rate limit and may be queued. """ - Change the name of the roles infochannel. - - Do NOT confuse with the role command that counts number of members with a specified role - - {count} can be used to display number of roles in the server. - Leave blank to set back to default - Default is set to: - Total Roles: {count} + guild = ctx.guild + if channel_type not in self.default_channel_names.keys(): + await ctx.maybe_send_embed("Invalid channel type provided.") + return - Example Formats: - Total Roles: {count} - {count} Roles - """ - guild = ctx.message.guild - if text: - await self.config.guild(guild).channel_names.roles_channel.set(text) - else: - await self.config.guild(guild).channel_names.roles_channel.clear() + if text is None: + text = self.default_channel_names.get(channel_type) + elif "{count}" not in text: + await ctx.maybe_send_embed( + "Improperly formatted. Make sure to use `{count}` in your channel name" + ) + return + elif len(text) > 93: + await ctx.maybe_send_embed("Name is too long, max length is 93.") + return - await self.update_infochannel(guild) + await self.config.guild(guild).channel_names.set_raw(channel_type, value=text) + await self.update_infochannel(guild, channel_type=channel_type) if not await ctx.tick(): - await ctx.send("Done!") + await ctx.maybe_send_embed("Done!") - @channelname.command(name="channels") - async def _channelname_Channels(self, ctx: commands.Context, *, text=None): - """ - Change the name of the channels infochannel. - - {count} can be used to display number of channels in the server. - This does not count the infochannels - Leave blank to set back to default - Default is set to: - Total Channels: {count} - - Example Formats: - Total Channels: {count} - {count} Channels + @infochannelset.command(name="rolename") + async def _infochannelset_rolename( + self, ctx: commands.Context, role: discord.Role, *, text=None + ): """ - guild = ctx.message.guild - if text: - await self.config.guild(guild).channel_names.channels_channel.set(text) - else: - await self.config.guild(guild).channel_names.channels_channel.clear() + Change the name of the infochannel for specific roles. - await self.update_infochannel(guild) - if not await ctx.tick(): - await ctx.send("Done!") + {count} must be used to display number members with the given role. + {role} can be used for the roles name. + Leave blank to set back to default. - @channelname.command(name="online") - async def _channelname_Online(self, ctx: commands.Context, *, text=None): - """ - Change the name of the online infochannel. + Default is set to: `{role}: {count}` - {count} can be used to display number online members in the server. - Leave blank to set back to default - Default is set to: - Online: {count} + Examples: + - `[p]infochannelset rolename @Patrons {role}: {count}` + - `[p]infochannelset rolename Elite {count} members with {role} role` + - `[p]infochannelset rolename "Space Role" Total boosters: {count}` - Example Formats: - Total Online: {count} - {count} Online Members + Warning: This command counts against the channel update rate limit and may be queued. """ guild = ctx.message.guild - if text: - await self.config.guild(guild).channel_names.online_channel.set(text) - else: - await self.config.guild(guild).channel_names.online_channel.clear() - - await self.update_infochannel(guild) - if not await ctx.tick(): - await ctx.send("Done!") - - @channelname.command(name="offline") - async def _channelname_Offline(self, ctx: commands.Context, *, text=None): - """ - Change the name of the offline infochannel. - - {count} can be used to display number offline members in the server. - Leave blank to set back to default - Default is set to: - Offline: {count} - - Example Formats: - Total Offline: {count} - {count} Offline Members - """ - guild = ctx.message.guild - if text: - await self.config.guild(guild).channel_names.offline_channel.set(text) - else: - await self.config.guild(guild).channel_names.offline_channel.clear() + if text is None: + text = self.default_role["name"] + elif "{count}" not in text: + await ctx.maybe_send_embed( + "Improperly formatted. Make sure to use `{count}` in your channel name" + ) + return - await self.update_infochannel(guild) + await self.config.role(role).name.set(text) + await self.update_infochannel(guild, channel_role=role) if not await ctx.tick(): - await ctx.send("Done!") + await ctx.maybe_send_embed("Done!") - @channelname.command(name="role") - async def _channelname_Role(self, ctx: commands.Context, *, text=None): - """ - Change the name of the infochannel for specific roles. + async def create_individual_channel( + self, guild, category: discord.CategoryChannel, overwrites, channel_type, count + ): + # Delete the channel if it exists + channel_id = await self.config.guild(guild).channel_ids.get_raw(channel_type) + if channel_id is not None: + channel: discord.VoiceChannel = guild.get_channel(channel_id) + if channel: + self.stop_queue(guild.id, channel_type) + await channel.delete(reason="InfoChannel delete") - All role infochannels follow this format. - Do NOT confuse with the roles command that counts number of roles in the server + # Only make the channel if it's enabled + if await self.config.guild(guild).enabled_channels.get_raw(channel_type): + name = await self.config.guild(guild).channel_names.get_raw(channel_type) + name = name.format(count=count) + channel = await category.create_voice_channel( + name, reason="InfoChannel make", overwrites=overwrites + ) + await self.config.guild(guild).channel_ids.set_raw(channel_type, value=channel.id) + return channel + return None - {count} can be used to display number members with the given role. - {role} can be used for the roles name - Leave blank to set back to default - Default is set to: - {role}: {count} + async def create_role_channel( + self, guild, category: discord.CategoryChannel, overwrites, role: discord.Role + ): + # Delete the channel if it exists + channel_id = await self.config.role(role).channel_id() + if channel_id is not None: + channel: discord.VoiceChannel = guild.get_channel(channel_id) + if channel: + self.stop_queue(guild.id, role.id) + await channel.delete(reason="InfoChannel delete") - Example Formats: - {role}: {count} - {count} with {role} role - """ - guild = ctx.message.guild - if text: - await self.config.guild(guild).channel_names.role_channel.set(text) - else: - await self.config.guild(guild).channel_names.role_channel.clear() + # Only make the channel if it's enabled + if await self.config.role(role).enabled(): + count = len(role.members) + name = await self.config.role(role).name() + name = name.format(role=role.name, count=count) + channel = await category.create_voice_channel( + name, reason="InfoChannel make", overwrites=overwrites + ) + await self.config.role(role).channel_id.set(channel.id) + return channel + return None - await self.update_infochannel(guild) - if not await ctx.tick(): - await ctx.send("Done!") - - async def make_infochannel(self, guild: discord.Guild, role: discord.Role = None): - membercount = await self.config.guild(guild).member_count() - humancount = await self.config.guild(guild).human_count() - botcount = await self.config.guild(guild).bot_count() - rolescount = await self.config.guild(guild).roles_count() - channelscount = await self.config.guild(guild).channels_count() - onlinecount = await self.config.guild(guild).online_count() - offlinecount = await self.config.guild(guild).offline_count() + async def make_infochannel(self, guild: discord.Guild, channel_type=None, channel_role=None): overwrites = { guild.default_role: discord.PermissionOverwrite(connect=False), guild.me: discord.PermissionOverwrite(manage_channels=True, connect=True), } - # Check for and create the category + # Check for and create the Infochannel category category_id = await self.config.guild(guild).category_id() if category_id is not None: category: discord.CategoryChannel = guild.get_channel(category_id) - if category is None: - await self.config.guild(guild).category_id.set(None) + if category is None: # Category id is invalid, probably deleted. category_id = None - if category_id is None: category: discord.CategoryChannel = await guild.create_category( "Server Stats", reason="InfoChannel Category make" @@ -514,355 +355,319 @@ class InfoChannel(Cog): category: discord.CategoryChannel = guild.get_channel(category_id) - # Remove the old members channel first - channel_id = await self.config.guild(guild).channel_id() - if category_id is not None: - channel: discord.VoiceChannel = guild.get_channel(channel_id) - if channel: - await channel.delete(reason="InfoChannel delete") - if membercount: - # Then create the new one - channel = await category.create_voice_channel( - "Total Members:", reason="InfoChannel make", overwrites=overwrites - ) - await self.config.guild(guild).channel_id.set(channel.id) + channel_data = await get_channel_counts(category, guild) - # Remove the old human channel first - humanchannel_id = await self.config.guild(guild).humanchannel_id() - if category_id is not None: - humanchannel: discord.VoiceChannel = guild.get_channel(humanchannel_id) - if humanchannel: - await humanchannel.delete(reason="InfoChannel delete") - if humancount: - # Then create the new one - humanchannel = await category.create_voice_channel( - "Humans:", reason="InfoChannel humancount", overwrites=overwrites + # Only update a single channel + if channel_type is not None: + await self.create_individual_channel( + guild, category, overwrites, channel_type, channel_data[channel_type] ) - await self.config.guild(guild).humanchannel_id.set(humanchannel.id) + return + if channel_role is not None: + await self.create_role_channel(guild, category, overwrites, channel_role) + return - # Remove the old bot channel first - botchannel_id = await self.config.guild(guild).botchannel_id() - if category_id is not None: - botchannel: discord.VoiceChannel = guild.get_channel(botchannel_id) - if botchannel: - await botchannel.delete(reason="InfoChannel delete") - if botcount: - # Then create the new one - botchannel = await category.create_voice_channel( - "Bots:", reason="InfoChannel botcount", overwrites=overwrites + # Update all channels + for channel_type in self.default_channel_names.keys(): + await self.create_individual_channel( + guild, category, overwrites, channel_type, channel_data[channel_type] ) - await self.config.guild(guild).botchannel_id.set(botchannel.id) - # Remove the old roles channel first - roleschannel_id = await self.config.guild(guild).roleschannel_id() - if category_id is not None: - roleschannel: discord.VoiceChannel = guild.get_channel(roleschannel_id) - if roleschannel: - await roleschannel.delete(reason="InfoChannel delete") - - if rolescount: - # Then create the new one - roleschannel = await category.create_voice_channel( - "Total Roles:", reason="InfoChannel rolescount", overwrites=overwrites - ) - await self.config.guild(guild).roleschannel_id.set(roleschannel.id) + for role in guild.roles: + await self.create_role_channel(guild, category, overwrites, role) + + # await self.update_infochannel(guild) - # Remove the old channels channel first - channels_channel_id = await self.config.guild(guild).channels_channel_id() + async def delete_all_infochannels(self, guild: discord.Guild): + self.stop_guild_queues(guild.id) # Stop processing edits + + # Delete regular channels + for channel_type in self.default_channel_names.keys(): + channel_id = await self.config.guild(guild).channel_ids.get_raw(channel_type) + if channel_id is not None: + channel = guild.get_channel(channel_id) + if channel is not None: + await channel.delete(reason="InfoChannel delete") + await self.config.guild(guild).channel_ids.clear_raw(channel_type) + + # Delete role channels + for role in guild.roles: + channel_id = await self.config.role(role).channel_id() + if channel_id is not None: + channel = guild.get_channel(channel_id) + if channel is not None: + await channel.delete(reason="InfoChannel delete") + await self.config.role(role).channel_id.clear() + + # Delete the category last + category_id = await self.config.guild(guild).category_id() if category_id is not None: - channels_channel: discord.VoiceChannel = guild.get_channel(channels_channel_id) - if channels_channel: - await channels_channel.delete(reason="InfoChannel delete") - if channelscount: - # Then create the new one - channels_channel = await category.create_voice_channel( - "Total Channels:", reason="InfoChannel botcount", overwrites=overwrites - ) - await self.config.guild(guild).channels_channel_id.set(channels_channel.id) + category = guild.get_channel(category_id) + if category is not None: + await category.delete(reason="InfoChannel delete") - # Remove the old online channel first - onlinechannel_id = await self.config.guild(guild).onlinechannel_id() - if channel_id is not None: - onlinechannel: discord.VoiceChannel = guild.get_channel(onlinechannel_id) - if onlinechannel: - await onlinechannel.delete(reason="InfoChannel delete") - if onlinecount: - # Then create the new one - onlinechannel = await category.create_voice_channel( - "Online:", reason="InfoChannel onlinecount", overwrites=overwrites + async def add_to_queue(self, guild, channel, identifier, count, formatted_name): + self.channel_data[guild.id][identifier] = (count, formatted_name, channel.id) + if not self.edit_queue[guild.id][identifier].full(): + try: + self.edit_queue[guild.id][identifier].put_nowait(identifier) + except asyncio.QueueFull: + pass # If queue is full, disregard + + if self._rate_limited_edits[guild.id][identifier] is None: + await self.start_queue(guild.id, identifier) + + async def update_individual_channel(self, guild, channel_type, count, guild_data): + name = guild_data["channel_names"][channel_type] + name = name.format(count=count) + channel = guild.get_channel(guild_data["channel_ids"][channel_type]) + if channel is None: + return # abort + await self.add_to_queue(guild, channel, channel_type, count, name) + + async def update_role_channel(self, guild, role: discord.Role, role_data): + if not role_data["enabled"]: + return # Not enabled + count = len(role.members) + name = role_data["name"] + name = name.format(role=role.name, count=count) + channel = guild.get_channel(role_data["channel_id"]) + if channel is None: + return # abort + await self.add_to_queue(guild, channel, role.id, count, name) + + async def update_infochannel(self, guild: discord.Guild, channel_type=None, channel_role=None): + if channel_type is None and channel_role is None: + return await self.trigger_updates_for( + guild, + members=True, + humans=True, + bots=True, + roles=True, + channels=True, + online=True, + offline=True, + extra_roles=set(guild.roles), ) - await self.config.guild(guild).onlinechannel_id.set(onlinechannel.id) - # Remove the old offline channel first - offlinechannel_id = await self.config.guild(guild).offlinechannel_id() - if channel_id is not None: - offlinechannel: discord.VoiceChannel = guild.get_channel(offlinechannel_id) - if offlinechannel: - await offlinechannel.delete(reason="InfoChannel delete") - if offlinecount: - # Then create the new one - offlinechannel = await category.create_voice_channel( - "Offline:", reason="InfoChannel offlinecount", overwrites=overwrites - ) - await self.config.guild(guild).offlinechannel_id.set(offlinechannel.id) - - async with self.config.guild(guild).role_ids() as role_data: - # Remove the old role channels first - for role_id in role_data.keys(): - role_channel_id = role_data[role_id] - if role_channel_id is not None: - rolechannel: discord.VoiceChannel = guild.get_channel(role_channel_id) - if rolechannel: - await rolechannel.delete(reason="InfoChannel delete") - - # The actual toggle for a role counter - if role: - if str(role.id) in role_data.keys(): - role_data.pop(str(role.id)) # if the role is there, then remove it - else: - role_data[role.id] = None # No channel created yet but we want one to be made - if role_data: - # Then create the new ones - for role_id in role_data.keys(): - rolechannel = await category.create_voice_channel( - str(role_id) + ":", reason="InfoChannel rolecount", overwrites=overwrites - ) - role_data[role_id] = rolechannel.id + if channel_type is not None: + return await self.trigger_updates_for(guild, **{channel_type: True}) + + return await self.trigger_updates_for(guild, extra_roles=set(channel_role)) + + # category = guild.get_channel(await self.config.guild(guild).category_id()) + # + # if category is None: + # return # Nothing to update, must be off + # + # channel_counts = await get_channel_counts(category, guild) + # + # # Update individual channel + # if channel_type is not None: + # await self.update_individual_channel(guild, channel_type, channel_counts[channel_type]) + # return + # if channel_role is not None: + # await self.update_role_channel(guild, channel_role) + # return + # + # # Update all channels + # enabled_channels = await self.config.guild(guild).enabled_channels.all() + # for channel_type, enabled in enabled_channels.items(): + # if not enabled: + # continue + # await self.update_individual_channel(guild, channel_type, channel_counts[channel_type]) + # + # all_roles = await self.config.all_roles() + # guild_role_ids = set(role.id for role in guild.roles) + # for role_id, role_data in all_roles.values(): + # if int(role_id) not in guild_role_ids: + # continue + # role: discord.Role = guild.get_role(int(role_id)) + # await self.update_role_channel(guild, role) + + # def _start_timer(self, guild_id): + # self._stop_timer(guild_id) + # self._rate_limited_edits[guild_id] = asyncio.create_task( + # self.sleep_then_wakup(guild_id) + # ) + # + # async def sleep_then_wakup(self, guild_id, wait_seconds): + # await asyncio.sleep(wait_seconds) + # asyncio.create_task(self.wakeup(guild_id)) + # + # def _stop_timer(self, guild_id=None): + # if guild_id is None: + # for guild_id in self._timeout.keys(): + # if self._timeout[guild_id]: + # self._timeout[guild_id].cancel() + # self._timeout[guild_id] = None + # # del self._timeout + # else: + # if self._timeout[guild_id]: + # self._timeout[guild_id].cancel() + # self._timeout[guild_id] = None + # # del self._timeout[guild_id] + # + # async def wakeup(self, guild_id): + # self._stop_timer(guild_id) + # wait_seconds = await self._process_queue(guild_id) + # self._start_timer(guild_id, wait_seconds) + + async def start_queue(self, guild_id, identifier): + self._rate_limited_edits[guild_id][identifier] = asyncio.create_task( + self._process_queue(guild_id, identifier) + ) - await self.update_infochannel(guild) + def stop_queue(self, guild_id, identifier): + if self._rate_limited_edits[guild_id][identifier] is not None: + self._rate_limited_edits[guild_id][identifier].cancel() - async def delete_all_infochannels(self, guild: discord.Guild): - guild_data = await self.config.guild(guild).all() - role_data = guild_data["role_ids"] - category_id = guild_data["category_id"] - humanchannel_id = guild_data["humanchannel_id"] - botchannel_id = guild_data["botchannel_id"] - roleschannel_id = guild_data["roleschannel_id"] - channels_channel_id = guild_data["channels_channel_id"] - onlinechannel_id = guild_data["onlinechannel_id"] - offlinechannel_id = guild_data["offlinechannel_id"] - category: discord.CategoryChannel = guild.get_channel(category_id) - humanchannel: discord.VoiceChannel = guild.get_channel(humanchannel_id) - botchannel: discord.VoiceChannel = guild.get_channel(botchannel_id) - roleschannel: discord.VoiceChannel = guild.get_channel(roleschannel_id) - channels_channel: discord.VoiceChannel = guild.get_channel(channels_channel_id) - onlinechannel: discord.VoiceChannel = guild.get_channel(onlinechannel_id) - offlinechannel: discord.VoiceChannel = guild.get_channel(offlinechannel_id) - channel_id = guild_data["channel_id"] - channel: discord.VoiceChannel = guild.get_channel(channel_id) - await channel.delete(reason="InfoChannel delete") - if humanchannel_id is not None: - await humanchannel.delete(reason="InfoChannel delete") - if botchannel_id is not None: - await botchannel.delete(reason="InfoChannel delete") - if roleschannel_id is not None: - await roleschannel.delete(reason="InfoChannel delete") - if channels_channel is not None: - await channels_channel.delete(reason="InfoChannel delete") - if onlinechannel_id is not None: - await onlinechannel.delete(reason="InfoChannel delete") - if offlinechannel_id is not None: - await offlinechannel.delete(reason="InfoChannel delete") - if category_id is not None: - await category.delete(reason="InfoChannel delete") - async with self.config.guild(guild).role_ids() as role_data: - if role_data: - for role_channel_id in role_data.values(): - rolechannel: discord.VoiceChannel = guild.get_channel(role_channel_id) - if rolechannel: - await rolechannel.delete(reason="InfoChannel delete") + def stop_guild_queues(self, guild_id): + for identifier in self._rate_limited_edits[guild_id].keys(): + self.stop_queue(guild_id, identifier) + + def stop_all_queues(self): + for guild_id in self._rate_limited_edits.keys(): + self.stop_guild_queues(guild_id) - await self.config.guild(guild).clear() + async def _process_queue(self, guild_id, identifier): + while True: + identifier = await self.edit_queue[guild_id][identifier].get() # Waits forever - async def update_infochannel(self, guild: discord.Guild): + count, formatted_name, channel_id = self.channel_data[guild_id][identifier] + channel: discord.VoiceChannel = self.bot.get_channel(channel_id) + + if channel.name == formatted_name: + continue # Nothing to process + + log.debug(f"Processing guild_id: {guild_id} - identifier: {identifier}") + + try: + await channel.edit(reason="InfoChannel update", name=formatted_name) + except (discord.Forbidden, discord.HTTPException): + pass # Don't bother figuring it out + except discord.InvalidArgument: + log.exception(f"Invalid formatted infochannel: {formatted_name}") + else: + await asyncio.sleep(RATE_LIMIT_DELAY) # Wait a reasonable amount of time + + # async def update_infochannel_with_cooldown(self, guild): + # """My attempt at preventing rate limits, lets see how it goes""" + # if self._critical_section_wooah_: + # if self._critical_section_wooah_ == 2: + # # print("Already pending, skipping") + # return # Another one is already pending, don't queue more than one + # # print("Queuing another update") + # self._critical_section_wooah_ = 2 + # + # while self._critical_section_wooah_: + # await asyncio.sleep( + # RATE_LIMIT_DELAY // 4 + # ) # Max delay ends up as 1.25 * RATE_LIMIT_DELAY + # + # # print("Issuing queued update") + # return await self.update_infochannel_with_cooldown(guild) + # + # # print("Entering critical") + # self._critical_section_wooah_ = 1 + # await self.update_infochannel(guild) + # await asyncio.sleep(RATE_LIMIT_DELAY) + # self._critical_section_wooah_ = 0 + # # print("Exiting critical") + + # async def trigger_updates_for_all(self, guild_id): + # guild = self.bot.get_guild(guild_id) + # if guild is None: + # return None + # + # if await self.bot.cog_disabled_in_guild(self, guild): + # # Stop this background process if cog is disabled + # return None + # + # await self.update_infochannel(guild) # Refills the queue with updates + # return 30 + + async def trigger_updates_for(self, guild, **kwargs): + extra_roles: Optional[set] = kwargs.pop("extra_roles", False) guild_data = await self.config.guild(guild).all() - humancount = guild_data["human_count"] - botcount = guild_data["bot_count"] - rolescount = guild_data["roles_count"] - channelscount = guild_data["channels_count"] - onlinecount = guild_data["online_count"] - offlinecount = guild_data["offline_count"] - - category = guild.get_channel(guild_data["category_id"]) - - # Gets count of bots - # bots = lambda x: x.bot - # def bots(x): return x.bot - - bot_num = len([m for m in guild.members if m.bot]) - # bot_msg = f"Bots: {num}" - - # Gets count of roles in the server - roles_num = len(guild.roles) - 1 - # roles_msg = f"Total Roles: {num}" - - # Gets count of channels in the server - # - - - channels_num = len(guild.channels) - len(category.voice_channels) - len(guild.categories) - # channels_msg = f"Total Channels: {num}" - - # Gets all counts of members - members = guild.member_count - # member_msg = f"Total Members: {num}" - offline = len(list(filter(lambda m: m.status is discord.Status.offline, guild.members))) - # offline_msg = f"Offline: {num}" - online_num = members - offline - # online_msg = f"Online: {num}" - - # Gets count of actual users - total = lambda x: not x.bot - human_num = len([m for m in guild.members if total(m)]) - # human_msg = f"Users: {num}" - - channel_id = guild_data["channel_id"] - if channel_id is None: - return False - - botchannel_id = guild_data["botchannel_id"] - roleschannel_id = guild_data["roleschannel_id"] - channels_channel_id = guild_data["channels_channel_id"] - onlinechannel_id = guild_data["onlinechannel_id"] - offlinechannel_id = guild_data["offlinechannel_id"] - humanchannel_id = guild_data["humanchannel_id"] - channel_id = guild_data["channel_id"] - channel: discord.VoiceChannel = guild.get_channel(channel_id) - humanchannel: discord.VoiceChannel = guild.get_channel(humanchannel_id) - botchannel: discord.VoiceChannel = guild.get_channel(botchannel_id) - roleschannel: discord.VoiceChannel = guild.get_channel(roleschannel_id) - channels_channel: discord.VoiceChannel = guild.get_channel(channels_channel_id) - onlinechannel: discord.VoiceChannel = guild.get_channel(onlinechannel_id) - offlinechannel: discord.VoiceChannel = guild.get_channel(offlinechannel_id) - - channel_names = await self.config.guild(guild).channel_names.all() - - if guild_data["member_count"]: - name = channel_names["members_channel"].format(count=members) - await channel.edit(reason="InfoChannel update", name=name) - - if humancount: - name = channel_names["humans_channel"].format(count=human_num) - await humanchannel.edit(reason="InfoChannel update", name=name) - - if botcount: - name = channel_names["bots_channel"].format(count=bot_num) - await botchannel.edit(reason="InfoChannel update", name=name) - - if rolescount: - name = channel_names["roles_channel"].format(count=roles_num) - await roleschannel.edit(reason="InfoChannel update", name=name) - - if channelscount: - name = channel_names["channels_channel"].format(count=channels_num) - await channels_channel.edit(reason="InfoChannel update", name=name) - - if onlinecount: - name = channel_names["online_channel"].format(count=online_num) - await onlinechannel.edit(reason="InfoChannel update", name=name) - - if offlinecount: - name = channel_names["offline_channel"].format(count=offline) - await offlinechannel.edit(reason="InfoChannel update", name=name) - - async with self.config.guild(guild).role_ids() as role_data: - if role_data: - for role_id, role_channel_id in role_data.items(): - rolechannel: discord.VoiceChannel = guild.get_channel(role_channel_id) - role: discord.Role = guild.get_role(int(role_id)) - - role_num = len(role.members) - - name = channel_names["role_channel"].format(count=role_num, role=role.name) - await rolechannel.edit(reason="InfoChannel update", name=name) - - async def update_infochannel_with_cooldown(self, guild): - """My attempt at preventing rate limits, lets see how it goes""" - if self._critical_section_wooah_: - if self._critical_section_wooah_ == 2: - # print("Already pending, skipping") - return # Another one is already pending, don't queue more than one - # print("Queuing another update") - self._critical_section_wooah_ = 2 - - while self._critical_section_wooah_: - await asyncio.sleep( - RATE_LIMIT_DELAY // 4 - ) # Max delay ends up as 1.25 * RATE_LIMIT_DELAY - - # print("Issuing queued update") - return await self.update_infochannel_with_cooldown(guild) - - # print("Entering critical") - self._critical_section_wooah_ = 1 - await self.update_infochannel(guild) - await asyncio.sleep(RATE_LIMIT_DELAY) - self._critical_section_wooah_ = 0 - # print("Exiting critical") - @Cog.listener() - async def on_member_join(self, member: discord.Member): - if await self.bot.cog_disabled_in_guild(self, member.guild): - return - await self.update_infochannel_with_cooldown(member.guild) + to_update = ( + kwargs.keys() & guild_data["enabled_channels"].keys() + ) # Value in kwargs doesn't matter - @Cog.listener() - async def on_member_remove(self, member: discord.Member): + log.debug(f"{to_update=}") + + if to_update or extra_roles: + category = guild.get_channel(guild_data["category_id"]) + if category is None: + return # Nothing to update, must be off + + channel_data = await get_channel_counts(category, guild) + if to_update: + for channel_type in to_update: + await self.update_individual_channel( + guild, channel_type, channel_data[channel_type], guild_data + ) + if extra_roles: + role_data = await self.config.all_roles() + for channel_role in extra_roles: + if channel_role.id in role_data: + await self.update_role_channel( + guild, channel_role, role_data[channel_role.id] + ) + + @Cog.listener(name="on_member_join") + @Cog.listener(name="on_member_remove") + async def on_member_join_remove(self, member: discord.Member): if await self.bot.cog_disabled_in_guild(self, member.guild): return - await self.update_infochannel_with_cooldown(member.guild) + + if member.bot: + await self.trigger_updates_for( + member.guild, members=True, bots=True, online=True, offline=True + ) + else: + await self.trigger_updates_for( + member.guild, members=True, humans=True, online=True, offline=True + ) @Cog.listener() async def on_member_update(self, before: discord.Member, after: discord.Member): if await self.bot.cog_disabled_in_guild(self, after.guild): return - onlinecount = await self.config.guild(after.guild).online_count() - if onlinecount: - if before.status != after.status: - await self.update_infochannel_with_cooldown(after.guild) - role_data = await self.config.guild(after.guild).role_ids.all() - if role_data: - b = set(before.roles) - a = set(after.roles) - if b != a: - await self.update_infochannel_with_cooldown(after.guild) - @Cog.listener() - async def on_guild_channel_create(self, channel: discord.abc.GuildChannel): - if await self.bot.cog_disabled_in_guild(self, channel.guild): - return - channelscount = await self.config.guild(channel.guild).channels_count() - if channelscount: - await self.update_infochannel_with_cooldown(channel.guild) + if before.status != after.status: + return await self.trigger_updates_for(after.guild, online=True, offline=True) - @Cog.listener() - async def on_guild_channel_delete(self, channel: discord.abc.GuildChannel): + # XOR + c = set(after.roles) ^ set(before.roles) + + if c: + await self.trigger_updates_for(after.guild, extra_roles=c) + + @Cog.listener("on_guild_channel_create") + @Cog.listener("on_guild_channel_delete") + async def on_guild_channel_create_delete(self, channel: discord.TextChannel): if await self.bot.cog_disabled_in_guild(self, channel.guild): return - channelscount = await self.config.guild(channel.guild).channels_count() - if channelscount: - await self.update_infochannel_with_cooldown(channel.guild) + await self.trigger_updates_for(channel.guild, channels=True) @Cog.listener() async def on_guild_role_create(self, role): if await self.bot.cog_disabled_in_guild(self, role.guild): return - - rolescount = await self.config.guild(role.guild).roles_count() - if rolescount: - await self.update_infochannel_with_cooldown(role.guild) + await self.trigger_updates_for(role.guild, roles=True) @Cog.listener() async def on_guild_role_delete(self, role): if await self.bot.cog_disabled_in_guild(self, role.guild): return + await self.trigger_updates_for(role.guild, roles=True) - rolescount = await self.config.guild(role.guild).roles_count() - if rolescount: - await self.update_infochannel_with_cooldown(role.guild) - - # delete specific role counter if the role is deleted - async with self.config.guild(role.guild).role_ids() as role_data: - if str(role.id) in role_data.keys(): - role_channel_id = role_data[str(role.id)] - rolechannel: discord.VoiceChannel = role.guild.get_channel(role_channel_id) + role_channel_id = await self.config.role(role).channel_id() + if role_channel_id is not None: + rolechannel: discord.VoiceChannel = role.guild.get_channel(role_channel_id) + if rolechannel: await rolechannel.delete(reason="InfoChannel delete") - del role_data[str(role.id)] + + await self.config.role(role).clear() From c7820ec40c5ae6454c8b06de1a9ca6b3e75276f8 Mon Sep 17 00:00:00 2001 From: bobloy Date: Tue, 8 Dec 2020 10:54:59 -0500 Subject: [PATCH 06/13] No asyncio here --- infochannel/__init__.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/infochannel/__init__.py b/infochannel/__init__.py index 1c4d081..bbff901 100644 --- a/infochannel/__init__.py +++ b/infochannel/__init__.py @@ -1,5 +1,3 @@ -import asyncio - from .infochannel import InfoChannel From bf3c292fee9bc3275bc7382d6e002d211b8cfd2c Mon Sep 17 00:00:00 2001 From: bobloy Date: Tue, 8 Dec 2020 10:57:36 -0500 Subject: [PATCH 07/13] Black formatting --- fifo/redconfigjobstore.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fifo/redconfigjobstore.py b/fifo/redconfigjobstore.py index 126cfc0..51b3cdc 100644 --- a/fifo/redconfigjobstore.py +++ b/fifo/redconfigjobstore.py @@ -8,6 +8,7 @@ from apscheduler.jobstores.memory import MemoryJobStore from apscheduler.schedulers.asyncio import run_in_event_loop from apscheduler.util import datetime_to_utc_timestamp from redbot.core import Config + # TODO: use get_lock on config maybe from redbot.core.bot import Red from redbot.core.utils import AsyncIter @@ -26,7 +27,6 @@ class RedConfigJobStore(MemoryJobStore): self.pickle_protocol = pickle.HIGHEST_PROTOCOL self._eventloop = self.bot.loop # Used for @run_in_event_loop - @run_in_event_loop def start(self, scheduler, alias): super().start(scheduler, alias) From fc8e465c33cf14db3e51205b06d4db03eefbb18b Mon Sep 17 00:00:00 2001 From: bobloy Date: Tue, 8 Dec 2020 11:08:20 -0500 Subject: [PATCH 08/13] Remove excess comments --- infochannel/infochannel.py | 94 -------------------------------------- 1 file changed, 94 deletions(-) diff --git a/infochannel/infochannel.py b/infochannel/infochannel.py index 1b2bce4..9c9192c 100644 --- a/infochannel/infochannel.py +++ b/infochannel/infochannel.py @@ -455,64 +455,6 @@ class InfoChannel(Cog): return await self.trigger_updates_for(guild, extra_roles=set(channel_role)) - # category = guild.get_channel(await self.config.guild(guild).category_id()) - # - # if category is None: - # return # Nothing to update, must be off - # - # channel_counts = await get_channel_counts(category, guild) - # - # # Update individual channel - # if channel_type is not None: - # await self.update_individual_channel(guild, channel_type, channel_counts[channel_type]) - # return - # if channel_role is not None: - # await self.update_role_channel(guild, channel_role) - # return - # - # # Update all channels - # enabled_channels = await self.config.guild(guild).enabled_channels.all() - # for channel_type, enabled in enabled_channels.items(): - # if not enabled: - # continue - # await self.update_individual_channel(guild, channel_type, channel_counts[channel_type]) - # - # all_roles = await self.config.all_roles() - # guild_role_ids = set(role.id for role in guild.roles) - # for role_id, role_data in all_roles.values(): - # if int(role_id) not in guild_role_ids: - # continue - # role: discord.Role = guild.get_role(int(role_id)) - # await self.update_role_channel(guild, role) - - # def _start_timer(self, guild_id): - # self._stop_timer(guild_id) - # self._rate_limited_edits[guild_id] = asyncio.create_task( - # self.sleep_then_wakup(guild_id) - # ) - # - # async def sleep_then_wakup(self, guild_id, wait_seconds): - # await asyncio.sleep(wait_seconds) - # asyncio.create_task(self.wakeup(guild_id)) - # - # def _stop_timer(self, guild_id=None): - # if guild_id is None: - # for guild_id in self._timeout.keys(): - # if self._timeout[guild_id]: - # self._timeout[guild_id].cancel() - # self._timeout[guild_id] = None - # # del self._timeout - # else: - # if self._timeout[guild_id]: - # self._timeout[guild_id].cancel() - # self._timeout[guild_id] = None - # # del self._timeout[guild_id] - # - # async def wakeup(self, guild_id): - # self._stop_timer(guild_id) - # wait_seconds = await self._process_queue(guild_id) - # self._start_timer(guild_id, wait_seconds) - async def start_queue(self, guild_id, identifier): self._rate_limited_edits[guild_id][identifier] = asyncio.create_task( self._process_queue(guild_id, identifier) @@ -551,42 +493,6 @@ class InfoChannel(Cog): else: await asyncio.sleep(RATE_LIMIT_DELAY) # Wait a reasonable amount of time - # async def update_infochannel_with_cooldown(self, guild): - # """My attempt at preventing rate limits, lets see how it goes""" - # if self._critical_section_wooah_: - # if self._critical_section_wooah_ == 2: - # # print("Already pending, skipping") - # return # Another one is already pending, don't queue more than one - # # print("Queuing another update") - # self._critical_section_wooah_ = 2 - # - # while self._critical_section_wooah_: - # await asyncio.sleep( - # RATE_LIMIT_DELAY // 4 - # ) # Max delay ends up as 1.25 * RATE_LIMIT_DELAY - # - # # print("Issuing queued update") - # return await self.update_infochannel_with_cooldown(guild) - # - # # print("Entering critical") - # self._critical_section_wooah_ = 1 - # await self.update_infochannel(guild) - # await asyncio.sleep(RATE_LIMIT_DELAY) - # self._critical_section_wooah_ = 0 - # # print("Exiting critical") - - # async def trigger_updates_for_all(self, guild_id): - # guild = self.bot.get_guild(guild_id) - # if guild is None: - # return None - # - # if await self.bot.cog_disabled_in_guild(self, guild): - # # Stop this background process if cog is disabled - # return None - # - # await self.update_infochannel(guild) # Refills the queue with updates - # return 30 - async def trigger_updates_for(self, guild, **kwargs): extra_roles: Optional[set] = kwargs.pop("extra_roles", False) guild_data = await self.config.guild(guild).all() From 36826a44e7122216468b0e3941dc123329edede6 Mon Sep 17 00:00:00 2001 From: bobloy Date: Tue, 8 Dec 2020 11:17:50 -0500 Subject: [PATCH 09/13] Mostly line separators --- werewolf/werewolf.py | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/werewolf/werewolf.py b/werewolf/werewolf.py index f648569..d6be0b3 100644 --- a/werewolf/werewolf.py +++ b/werewolf/werewolf.py @@ -15,19 +15,11 @@ from werewolf.builder import ( role_from_id, role_from_name, ) -from werewolf.game import Game +from werewolf.game import Game, anyone_has_role log = logging.getLogger("red.fox_v3.werewolf") -async def anyone_has_role( - member_list: List[discord.Member], role: discord.Role -) -> Union[None, discord.Member]: - return await AsyncIter(member_list).find( - lambda m: AsyncIter(m.roles).find(lambda r: r.id == role.id) - ) - - class Werewolf(Cog): """ Base to host werewolf on a guild @@ -263,6 +255,7 @@ class Werewolf(Cog): game = await self._get_game(ctx) if not game: await ctx.maybe_send_embed("No game running, cannot start") + return if not await game.setup(ctx): pass # ToDo something? From 5892bed5b937054c4805977c0fb302545a0bb0ca Mon Sep 17 00:00:00 2001 From: bobloy Date: Tue, 8 Dec 2020 11:20:21 -0500 Subject: [PATCH 10/13] Didn't do werewolf label right --- .github/labeler.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/labeler.yml b/.github/labeler.yml index dd944c8..ecc1116 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -59,4 +59,4 @@ 'cog: unicode': - unicode/* 'cog: werewolf': - - werewolf \ No newline at end of file + - werewolf/* \ No newline at end of file From f3dab0f0c67a31b6118841ac0a019e81898c9ff4 Mon Sep 17 00:00:00 2001 From: bobloy Date: Fri, 18 Dec 2020 17:57:48 -0500 Subject: [PATCH 11/13] Fix construction of set --- infochannel/infochannel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infochannel/infochannel.py b/infochannel/infochannel.py index 9c9192c..33e2b10 100644 --- a/infochannel/infochannel.py +++ b/infochannel/infochannel.py @@ -453,7 +453,7 @@ class InfoChannel(Cog): if channel_type is not None: return await self.trigger_updates_for(guild, **{channel_type: True}) - return await self.trigger_updates_for(guild, extra_roles=set(channel_role)) + return await self.trigger_updates_for(guild, extra_roles={channel_role}) async def start_queue(self, guild_id, identifier): self._rate_limited_edits[guild_id][identifier] = asyncio.create_task( From b2c8268c9be00e31b9b8366dff17bb1d23339fdc Mon Sep 17 00:00:00 2001 From: bobloy Date: Tue, 22 Dec 2020 13:55:16 -0500 Subject: [PATCH 12/13] Update labeler --- .github/workflows/labeler.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index 65e6640..82a4441 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -6,7 +6,7 @@ # https://github.com/actions/labeler name: Labeler -on: [pull_request] +on: [pull_request_target] jobs: label: From d13fd39cfcaad02d9f30b080bd26c1895fa427db Mon Sep 17 00:00:00 2001 From: bobloy Date: Tue, 22 Dec 2020 13:56:28 -0500 Subject: [PATCH 13/13] Require python-dateutil --- fifo/info.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fifo/info.json b/fifo/info.json index eb2a576..a690a92 100644 --- a/fifo/info.json +++ b/fifo/info.json @@ -10,7 +10,8 @@ "end_user_data_statement": "This cog does not store any End User Data", "requirements": [ "apscheduler", - "pytz" + "pytz", + "python-dateutil" ], "tags": [ "bobloy",