From 9db9e300361f6e4d9b33949a66ba1f1155f174ce Mon Sep 17 00:00:00 2001 From: HadesArmy Date: Mon, 9 Oct 2023 21:07:19 +0100 Subject: [PATCH 1/5] feat: insight cog --- koala/cogs/__init__.py | 3 +- koala/cogs/insights/__init__.py | 2 + koala/cogs/insights/cog.py | 79 +++++++++++++++++++++++++ koala/cogs/insights/log.py | 3 + koalabot.py | 4 +- tests/cogs/insights/__init__.py | 0 tests/cogs/insights/test_cog.py | 102 ++++++++++++++++++++++++++++++++ 7 files changed, 190 insertions(+), 3 deletions(-) create mode 100644 koala/cogs/insights/__init__.py create mode 100644 koala/cogs/insights/cog.py create mode 100644 koala/cogs/insights/log.py create mode 100644 tests/cogs/insights/__init__.py create mode 100644 tests/cogs/insights/test_cog.py diff --git a/koala/cogs/__init__.py b/koala/cogs/__init__.py index c82ceaae..8f47bb9d 100644 --- a/koala/cogs/__init__.py +++ b/koala/cogs/__init__.py @@ -1,9 +1,10 @@ from .announce import Announce from .base import BaseCog from .colour_role import ColourRole +from .insights import Insights from .intro_cog import IntroCog from .react_for_role import ReactForRole from .text_filter import TextFilter from .twitch_alert import TwitchAlert from .verification import Verification -from .voting import Voting +from .voting import Voting \ No newline at end of file diff --git a/koala/cogs/insights/__init__.py b/koala/cogs/insights/__init__.py new file mode 100644 index 00000000..215c7ff4 --- /dev/null +++ b/koala/cogs/insights/__init__.py @@ -0,0 +1,2 @@ +#from . import utils, db, models - use this if necessary in future +from .cog import Insights, setup diff --git a/koala/cogs/insights/cog.py b/koala/cogs/insights/cog.py new file mode 100644 index 00000000..164ac25e --- /dev/null +++ b/koala/cogs/insights/cog.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python + +""" +Koala Bot Insights Cog Code +""" +# Futures + +# Built-in/Generic Imports + +# Libs +from discord.ext import commands + +# Own modules +import koalabot +from .log import logger + +# Constants + +# Variables + + +class Insights(commands.Cog, name="Insights"): + """ + A discord.py cog with commands to give insight into information about the servers the bot is in + """ + + def __init__(self, bot): + self.bot = bot + + @commands.command(name="insights") + @commands.check(koalabot.is_owner) + async def insights(self, ctx): + """ + Lists the number of servers the bot is in, and the total number of members across all of those servers + (includes double counting) + :param ctx: Context of the command + """ + + message = (f"Insights:\nThis bot is in a total of {len(self.bot.guilds)} servers.\nThere are a total " + f"of {sum([len(guild.members) for guild in self.bot.guilds])} members across these servers.") + + await ctx.send(message) + + @commands.command(name="servers") + @commands.check(koalabot.is_owner) + async def list_servers(self, ctx, filter_string=""): + """ + Lists all servers that the bot is in, optional parameter for specifying that the servers must contain + a specific string + :param ctx: Context of the command + :param filter_string: The string used to filter servers listed + """ + + if filter_string != "": + server_list = [guild.name for guild in self.bot.guilds if filter_string.lower() in guild.name.lower()] + else: + server_list = [guild.name for guild in self.bot.guilds] + + if len(server_list) > 0: + partial_message = server_list[0] + for guild in server_list[1:]: + guild_length = len(guild) + if len(partial_message) + guild_length + 2 > 2000: + await ctx.send(partial_message) + partial_message = guild + else: + partial_message += f", {guild}" + await ctx.send(partial_message) + else: + await ctx.send(f"No servers found containing the string \"{filter_string}\".") + + +async def setup(bot: koalabot) -> None: + """ + Loads this cog into the selected bot + :param bot: The client of the KoalaBot + """ + await bot.add_cog(Insights(bot)) + logger.info("Insights Cog is ready.") diff --git a/koala/cogs/insights/log.py b/koala/cogs/insights/log.py new file mode 100644 index 00000000..38a00197 --- /dev/null +++ b/koala/cogs/insights/log.py @@ -0,0 +1,3 @@ +from koala.log import get_logger + +logger = get_logger(__name__) diff --git a/koalabot.py b/koalabot.py index a9a8fa5a..fbb937ae 100644 --- a/koalabot.py +++ b/koalabot.py @@ -45,8 +45,8 @@ PERMISSION_ERROR_TEXT = "This guild does not have this extension enabled, go to http://koalabot.uk, " \ "or use `k!help enableExt` to enable it" KOALA_IMAGE_URL = "https://cdn.discordapp.com/attachments/737280260541907015/752024535985029240/discord1.png" -ENABLED_COGS = ["base", "announce", "colour_role", "intro_cog", "react_for_role", "text_filter", "twitch_alert", - "verification", "voting"] +ENABLED_COGS = ["base", "announce", "colour_role", "insights", "intro_cog", "react_for_role", "text_filter", + "twitch_alert", "verification", "voting"] # Variables intent = discord.Intents.default() diff --git a/tests/cogs/insights/__init__.py b/tests/cogs/insights/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/cogs/insights/test_cog.py b/tests/cogs/insights/test_cog.py new file mode 100644 index 00000000..4ae1f1ab --- /dev/null +++ b/tests/cogs/insights/test_cog.py @@ -0,0 +1,102 @@ +#!/usr/bin/env python +""" +Testing KoalaBot Insights Cog +""" +# Futures + +# Built-in/Generic Imports + +# Libs +import discord +import discord.ext.test as dpytest +import mock +import pytest +import pytest_asyncio + +# Own modules +import koalabot +from koala.cogs.insights import cog + +# Constants + +# Variables + + +@pytest.mark.asyncio +async def test_setup(bot): + with mock.patch.object(discord.ext.commands.bot.Bot, 'add_cog') as mock1: + await cog.setup(bot) + mock1.assert_called() + + +@pytest_asyncio.fixture +async def insights_cog(bot: discord.ext.commands.Bot): + """ setup any state specific to the execution of the given module.""" + insights_cog = cog.Insights(bot) + await bot.add_cog(insights_cog) + await dpytest.empty_queue() + dpytest.configure(bot) + return insights_cog + + +@pytest.mark.asyncio +@pytest.mark.parametrize("members,guilds", [(0, 0), (10, 10), (100, 100), (1000, 1000)]) +async def test_insights(bot, insights_cog, members, guilds): + for x in range(members): + await dpytest.member_join(0, name=f"TestUser{x}", discrim={x}) + + for x in range(guilds): + guild = dpytest.backend.make_guild(name=f"FakeGuild{x}") + dpytest.get_config().guilds.append(guild) + await dpytest.member_join(guild, dpytest.get_config().client.user) + + total_guilds = 1 + guilds + total_members = 2 + guilds + members + + await dpytest.message(koalabot.COMMAND_PREFIX + "insights") + + expected_message = (f"Insights:\nThis bot is in a total of {total_guilds} servers." + f"\nThere are a total of {total_members} members across these servers.") + + assert dpytest.verify().message().content(expected_message) + + +@pytest.mark.asyncio +@pytest.mark.parametrize("total_servers", [0, 50, 500, 1000]) +async def test_list_servers(bot, insights_cog, total_servers): + for x in range(total_servers): + guild = dpytest.backend.make_guild(name=f"{x}") + dpytest.get_config().guilds.append(guild) + await dpytest.member_join(guild, dpytest.get_config().client.user) + + await dpytest.message(koalabot.COMMAND_PREFIX + "servers") + + if total_servers > 0: + expected_partial_message = "Test Guild 0" + for x in range(total_servers): + int_length = len(str(x)) + if len(expected_partial_message) + int_length + 2 > 2000: + assert dpytest.verify().message().content(expected_partial_message) + expected_partial_message = str(x) + else: + expected_partial_message += f", {x}" + assert dpytest.verify().message().content(expected_partial_message) + else: + assert dpytest.verify().message().content("Test Guild 0") + + +@pytest.mark.asyncio +@pytest.mark.parametrize("filter_term, expected", [("", "Test Guild 0, this, is, a, list, of, servers"), + ("s", "Test Guild 0, this, is, list, servers"), + ("is", "this, is, list"), + ("hello", """No servers found containing the string "hello".""")]) +async def test_list_servers_with_filter(bot, insights_cog, filter_term, expected): + server_list_names = ["this", "is", "a", "list", "of", "servers"] + for x in server_list_names: + guild = dpytest.backend.make_guild(name=x) + dpytest.get_config().guilds.append(guild) + await dpytest.member_join(guild, dpytest.get_config().client.user) + + await dpytest.message(koalabot.COMMAND_PREFIX + "servers " + filter_term) + + assert dpytest.verify().message().content(expected) From 211195611d1f878662848dd4490622dd247b23f4 Mon Sep 17 00:00:00 2001 From: HadesArmy Date: Mon, 9 Oct 2023 21:24:22 +0100 Subject: [PATCH 2/5] chore: updated changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 739265e3..f166a3ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ administrators - Added optimisations for verified roles - Add backend API - Fix an issue where multiple emails can be passed to bypass domain-specific verification +### Insights +- Added new commands `insights` and `servers` under the insights cog ## [0.6.0] - 01-01-2023 - Upgraded to discord.py 2.1.0 From febf0a074acd91299251cb6747d1b36cdf2afa51 Mon Sep 17 00:00:00 2001 From: HadesArmy Date: Tue, 10 Oct 2023 16:09:55 +0100 Subject: [PATCH 3/5] chore: moved processing to core.py so cog.py only deals with I/O --- koala/cogs/insights/cog.py | 25 +++----------- koala/cogs/insights/core.py | 59 +++++++++++++++++++++++++++++++++ tests/cogs/insights/test_cog.py | 4 +-- 3 files changed, 65 insertions(+), 23 deletions(-) create mode 100644 koala/cogs/insights/core.py diff --git a/koala/cogs/insights/cog.py b/koala/cogs/insights/cog.py index 164ac25e..1bd32b55 100644 --- a/koala/cogs/insights/cog.py +++ b/koala/cogs/insights/cog.py @@ -13,6 +13,7 @@ # Own modules import koalabot from .log import logger +from .core import get_insights, get_servers # Constants @@ -36,10 +37,7 @@ async def insights(self, ctx): :param ctx: Context of the command """ - message = (f"Insights:\nThis bot is in a total of {len(self.bot.guilds)} servers.\nThere are a total " - f"of {sum([len(guild.members) for guild in self.bot.guilds])} members across these servers.") - - await ctx.send(message) + await ctx.send(get_insights(self.bot)) @commands.command(name="servers") @commands.check(koalabot.is_owner) @@ -51,23 +49,8 @@ async def list_servers(self, ctx, filter_string=""): :param filter_string: The string used to filter servers listed """ - if filter_string != "": - server_list = [guild.name for guild in self.bot.guilds if filter_string.lower() in guild.name.lower()] - else: - server_list = [guild.name for guild in self.bot.guilds] - - if len(server_list) > 0: - partial_message = server_list[0] - for guild in server_list[1:]: - guild_length = len(guild) - if len(partial_message) + guild_length + 2 > 2000: - await ctx.send(partial_message) - partial_message = guild - else: - partial_message += f", {guild}" - await ctx.send(partial_message) - else: - await ctx.send(f"No servers found containing the string \"{filter_string}\".") + for message in get_servers(self.bot, filter_string): + await ctx.send(message) async def setup(bot: koalabot) -> None: diff --git a/koala/cogs/insights/core.py b/koala/cogs/insights/core.py new file mode 100644 index 00000000..ceec0b91 --- /dev/null +++ b/koala/cogs/insights/core.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python + +""" +Koala Bot Insights Core Code +""" +# Futures + +# Built-in/Generic Imports + +# Libs + +# Own modules + +# Constants + +# Variables + + +def get_insights(bot): + """ + Processes the information concerning the number of servers and members, and the formatting for insights + :param bot: The bot for which information is being gathered + """ + + message = f"Insights:\nThis bot is in a total of {len(bot.guilds)} servers. \nThere are a total " +\ + f"of {sum([len(guild.members) for guild in bot.guilds])} members across these servers." + + return message + + +def get_servers(bot, filter_string): + """ + Processes a list of servers the bot is in, packaged into 2000 character messages, can also use a filter to select + only servers containing that string + :param bot: The bot for which information is being gathered + :param filter_string: A filter string which allows only servers containing that string to be selected + """ + + messages = [] + + if filter_string != "": + server_list = [guild.name for guild in bot.guilds if filter_string.lower() in guild.name.lower()] + else: + server_list = [guild.name for guild in bot.guilds] + + if len(server_list) > 0: + partial_message = server_list[0] + for guild in server_list[1:]: + guild_length = len(guild) + if len(partial_message) + guild_length + 2 > 2000: + messages.append(partial_message) + partial_message = guild + else: + partial_message += f", {guild}" + messages.append(partial_message) + else: + return [f"No servers found containing the string \"{filter_string}\"."] + + return messages diff --git a/tests/cogs/insights/test_cog.py b/tests/cogs/insights/test_cog.py index 4ae1f1ab..ab159873 100644 --- a/tests/cogs/insights/test_cog.py +++ b/tests/cogs/insights/test_cog.py @@ -55,8 +55,8 @@ async def test_insights(bot, insights_cog, members, guilds): await dpytest.message(koalabot.COMMAND_PREFIX + "insights") - expected_message = (f"Insights:\nThis bot is in a total of {total_guilds} servers." - f"\nThere are a total of {total_members} members across these servers.") + expected_message = f"Insights:\nThis bot is in a total of {total_guilds} servers." +\ + f"\nThere are a total of {total_members} members across these servers." assert dpytest.verify().message().content(expected_message) From 1e67fe2118790a481bffff17dd9ca331ac86d845 Mon Sep 17 00:00:00 2001 From: HadesArmy Date: Tue, 10 Oct 2023 16:16:16 +0100 Subject: [PATCH 4/5] fix: fixed typo in core.get_insights had missed out a space in a message to be sent --- koala/cogs/insights/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/koala/cogs/insights/core.py b/koala/cogs/insights/core.py index ceec0b91..f154f37b 100644 --- a/koala/cogs/insights/core.py +++ b/koala/cogs/insights/core.py @@ -22,7 +22,7 @@ def get_insights(bot): :param bot: The bot for which information is being gathered """ - message = f"Insights:\nThis bot is in a total of {len(bot.guilds)} servers. \nThere are a total " +\ + message = f"Insights:\nThis bot is in a total of {len(bot.guilds)} servers.\nThere are a total " +\ f"of {sum([len(guild.members) for guild in bot.guilds])} members across these servers." return message From d705df693dcb2af38a45b85ed69a0f32a523c045 Mon Sep 17 00:00:00 2001 From: HadesArmy Date: Wed, 11 Oct 2023 14:49:19 +0100 Subject: [PATCH 5/5] chore: moving code between cog.py and core.py --- koala/cogs/insights/cog.py | 22 +++++++++++++++++----- koala/cogs/insights/core.py | 20 ++------------------ 2 files changed, 19 insertions(+), 23 deletions(-) diff --git a/koala/cogs/insights/cog.py b/koala/cogs/insights/cog.py index 1bd32b55..9077e8de 100644 --- a/koala/cogs/insights/cog.py +++ b/koala/cogs/insights/cog.py @@ -43,15 +43,27 @@ async def insights(self, ctx): @commands.check(koalabot.is_owner) async def list_servers(self, ctx, filter_string=""): """ - Lists all servers that the bot is in, optional parameter for specifying that the servers must contain - a specific string + Lists all servers that the bot is in, packaged into 2000 character messages, optional parameter for specifying + that the servers must contain a specific string :param ctx: Context of the command :param filter_string: The string used to filter servers listed """ - for message in get_servers(self.bot, filter_string): - await ctx.send(message) - + server_list = get_servers(self.bot, filter_string) + + if len(server_list) > 0: + partial_message = server_list[0] + for guild in server_list[1:]: + guild_length = len(guild) + if len(partial_message) + guild_length + 2 > 2000: + await ctx.send(partial_message) + partial_message = guild + else: + partial_message += f", {guild}" + await ctx.send(partial_message) + else: + await ctx.send(f"No servers found containing the string \"{filter_string}\".") + async def setup(bot: koalabot) -> None: """ diff --git a/koala/cogs/insights/core.py b/koala/cogs/insights/core.py index f154f37b..f2311670 100644 --- a/koala/cogs/insights/core.py +++ b/koala/cogs/insights/core.py @@ -30,30 +30,14 @@ def get_insights(bot): def get_servers(bot, filter_string): """ - Processes a list of servers the bot is in, packaged into 2000 character messages, can also use a filter to select - only servers containing that string + Retrieves a list of servers that the bot is in, can also use a filter to select only servers containing that string :param bot: The bot for which information is being gathered :param filter_string: A filter string which allows only servers containing that string to be selected """ - messages = [] - if filter_string != "": server_list = [guild.name for guild in bot.guilds if filter_string.lower() in guild.name.lower()] else: server_list = [guild.name for guild in bot.guilds] - if len(server_list) > 0: - partial_message = server_list[0] - for guild in server_list[1:]: - guild_length = len(guild) - if len(partial_message) + guild_length + 2 > 2000: - messages.append(partial_message) - partial_message = guild - else: - partial_message += f", {guild}" - messages.append(partial_message) - else: - return [f"No servers found containing the string \"{filter_string}\"."] - - return messages + return server_list