diff --git a/bot/bot.py b/bot/bot.py index 74073fa..8b7d7e2 100644 --- a/bot/bot.py +++ b/bot/bot.py @@ -13,10 +13,9 @@ class Ranger(Bot): def __init__(self, **options): - intents = discord.Intents.default() - intents.guilds = True - intents.members = True - intents.presences = True + intents = discord.Intents( + guilds=True, members=True, presences=True, messages=True + ) super().__init__(intents=intents, **options) self.persistent_views_added = False self.config: ConfigDict = dict() diff --git a/bot/cogs/forum_analysis.py b/bot/cogs/forum_analysis.py new file mode 100644 index 0000000..e229c1d --- /dev/null +++ b/bot/cogs/forum_analysis.py @@ -0,0 +1,121 @@ +import json +import asyncio +from itertools import zip_longest + + +import discord +from discord.ext import tasks, commands +from loguru import logger + +from .cog_base import CogBase +from ..utils.helper import project_base_path, MAIN_GUILD_ID + +from ..bot import Ranger + + +class ForumAnalyser(CogBase): + def __init__(self, bot_: Ranger): + self.bot = bot_ + self.guild = None + self.forum_channel: discord.ForumChannel | None = None + self.target_channel: discord.TextChannel | None = None + self.threads = [] + + @commands.slash_command( + name="sort_data", + description="Update the data", + guild_ids=[MAIN_GUILD_ID], + guild_only=True, + ) + @discord.default_permissions( + administrator=True, + ) + async def sort_data(self, ctx: discord.ApplicationContext): + if self.guild is None: + self.guild = ctx.guild + self.forum_channel = self.guild.get_channel(1006491137441276005) + self.target_channel = self.guild.get_channel(1152930428714483813) + + if ctx.channel != self.target_channel: + return await ctx.respond(f"Can only be used in <#{self.target_channel}>") + + interaction = await ctx.respond('Generating data...') + asyncio.create_task(self.generate_data(interaction)) + return + + async def generate_data(self, interaction: discord.Interaction): + self.threads = self.forum_channel.threads + async for thread in self.forum_channel.archived_threads(limit=None): + self.threads.append(thread) + await interaction.edit_original_response(content=f"Got {len(self.threads)} threads") + + latest_thread = self.threads[0] + if latest_thread.is_pinned(): + latest_thread = self.threads[1] + + stats = [ + f"> Stats :", + f"> Total Threads : {len(self.threads)}", + f"> Latest Thread : {latest_thread.mention}", + f"> First Thread : {self.threads[-1].mention}", + ] + await self.target_channel.send("\n".join(stats)) + # filter for "bug report : " + bug_report_msg = await self.target_channel.send(content=f"Generating bug report stats...") + threads_processed = 0 + TOP_X_LIMIT = 10 + bugs = [ + thread for thread in self.threads if is_valid_bug_thread(thread) + ] + bug_thread: discord.Thread + reaction_counts = [] + for bug_thread in bugs: + starter = bug_thread.starting_message + if starter is None: + msg_list = (await bug_thread.history(limit=1, oldest_first=True).flatten()) + if len(msg_list): + starter = msg_list[0] + else: + logger.warning(f"Thread {bug_thread.name}:{bug_thread.id} has no starting message") + continue + if len(starter.reactions): + # add thread if it has good activity + if (reaction_count := starter.reactions[0].count) > 5 or bug_thread.message_count > 30: + + reaction_counts.append((reaction_count, bug_thread)) + else: + logger.trace(f"Thread {bug_thread.name}:{bug_thread.id} has no reactions") + threads_processed += 1 + if threads_processed % 10 == 0: + await bug_report_msg.edit( + content=f"Generating bug report stats...Processed {threads_processed}/{len(bugs)} threads" + ) + reaction_counts.sort(key=lambda x: x[0], reverse=True) + stats = [ + f"> # Top threads for bugs :", + "**(filtered by reaction > 5 OR messages > 30)**", + *[ + f"> {thread.mention} : {count} upvotes and {thread.message_count} messages" + for count, thread in reaction_counts + ], + ] + await bug_report_msg.edit(content="\n".join(stats)) + + +def is_valid_bug_thread(thread: discord.Thread): + BUG_TAG_ID = 1006563163925397640 + PATCHED_TAG_ID = 1009125563295871117 + PATCHED_EMOJI_ID = None # todo: get emoji id + if thread.locked or has_tag(thread, PATCHED_TAG_ID) or thread.is_pinned(): + return False + if has_tag(thread, BUG_TAG_ID): + return True + return False + + +def has_tag(thread: discord.Thread, tag_id: int): + return tag_id in [tag.id for tag in thread.applied_tags] + + +def setup(bot: Ranger): + bot.add_cog(ForumAnalyser(bot)) diff --git a/bot/dashboard/dashboard.py b/bot/dashboard/dashboard.py index 43c4e92..845fde0 100644 --- a/bot/dashboard/dashboard.py +++ b/bot/dashboard/dashboard.py @@ -96,9 +96,10 @@ def ensure_defaults(): templates = Jinja2Templates(directory=dashboard_root / "templates") from bot.bot import Ranger + from ..utils.helper import MAIN_GUILD_ID if os.getenv("DEBUG_SERVER", "").lower() != "true": - bot = Ranger(debug_guilds=[int(os.getenv("GUILD_ID"))]) + bot = Ranger(debug_guilds=[int(os.getenv("GUILD_ID")), MAIN_GUILD_ID]) @app.on_event("startup") async def startup(): diff --git a/bot/utils/helper.py b/bot/utils/helper.py index fe4a3f1..b0f6add 100644 --- a/bot/utils/helper.py +++ b/bot/utils/helper.py @@ -5,6 +5,8 @@ configs_path = project_base_path / "configs" configs_base = project_base_path / "configs_base" +MAIN_GUILD_ID = 870246147455877181 + def random_emoji() -> str: return choice(