From ebb6b0503ae9aed41935f6a06a210bcd6b37bd95 Mon Sep 17 00:00:00 2001 From: osam7a Date: Mon, 31 Jul 2023 19:26:24 +0400 Subject: [PATCH 01/26] Adding main Classes to the /classes/__init__.py , and modified other filed accordingly --- micro/API.py | 2 +- micro/__main__.py | 9 +++++++-- micro/classes/__init__.py | 8 ++++++++ micro/cogs/commands.py | 13 +++++++++---- micro/cogs/events.py | 11 +++++++---- micro/cogs/issues.py | 3 +-- micro/cogs/moderation.py | 3 +-- micro/static/misc.py | 2 +- 8 files changed, 35 insertions(+), 16 deletions(-) diff --git a/micro/API.py b/micro/API.py index e5ba1b9..a8fefcb 100644 --- a/micro/API.py +++ b/micro/API.py @@ -3,7 +3,7 @@ from pydantic import BaseModel from datetime import datetime -from classes.colored_embed import CEmbed +from classes import CEmbed import os, aiohttp diff --git a/micro/__main__.py b/micro/__main__.py index ee21e78..5c7c601 100644 --- a/micro/__main__.py +++ b/micro/__main__.py @@ -4,8 +4,13 @@ import discord from discord.ext import commands -from classes.views import ModerationView, ResourcesView, RoleView, IssueListView -from classes.database import Database +from classes import ( + ModerationView, + ResourcesView, + RoleView, + IssueListView +) +from classes import Database from logger import logger from static.paths import COGS_DIR from API import app diff --git a/micro/classes/__init__.py b/micro/classes/__init__.py index e69de29..e92936d 100644 --- a/micro/classes/__init__.py +++ b/micro/classes/__init__.py @@ -0,0 +1,8 @@ +from .database import Database +from .badWords import BadWords +from .github import GitHub +from .QuestionQuery import QuestionQuery +from .resources import Resources +from .colored_embed import CEmbed + +from .views import * \ No newline at end of file diff --git a/micro/cogs/commands.py b/micro/cogs/commands.py index 51bd502..60edf66 100644 --- a/micro/cogs/commands.py +++ b/micro/cogs/commands.py @@ -4,12 +4,17 @@ from discord.utils import format_dt from discord.app_commands import Choice -from classes.resources import Resources -from classes.views import ResourcesView, RoleView -from classes.colored_embed import CEmbed -import static.constants as constants +from classes import ( + Resources, + ResourcesView, + RoleView, + CEmbed +) + from static.misc import get_current_guild +import static.constants as constants + class Commands(commands.Cog): def __init__(self, bot: commands.Bot): diff --git a/micro/cogs/events.py b/micro/cogs/events.py index 81880e2..ab4e6d4 100644 --- a/micro/cogs/events.py +++ b/micro/cogs/events.py @@ -2,10 +2,13 @@ from discord.utils import utcnow from discord.ext import commands -from classes.database import Database -from classes.github import GitHub -from classes.colored_embed import CEmbed -from classes.views import ModerationView +from classes import ( + Database, + GitHub, + CEmbed, + ModerationView +) + from static.misc import get_extra_channel, get_current_guild import static.constants as constants diff --git a/micro/cogs/issues.py b/micro/cogs/issues.py index 759cf80..4318960 100644 --- a/micro/cogs/issues.py +++ b/micro/cogs/issues.py @@ -2,8 +2,7 @@ from discord import app_commands from discord.ext import commands -from classes.github import GitHub -from classes.views import IssueListView, IssueModal +from classes import GitHub, IssueListView, IssueModal from static import constants from classes.colored_embed import CEmbed diff --git a/micro/cogs/moderation.py b/micro/cogs/moderation.py index cfd71cf..737bb11 100644 --- a/micro/cogs/moderation.py +++ b/micro/cogs/moderation.py @@ -2,8 +2,7 @@ from discord import app_commands from discord.ext import commands -from classes.database import Database -from classes.colored_embed import CEmbed +from classes import Database, CEmbed from static.misc import get_extra_channel import static.constants as constants diff --git a/micro/static/misc.py b/micro/static/misc.py index 7b7deab..65ebd9d 100644 --- a/micro/static/misc.py +++ b/micro/static/misc.py @@ -3,7 +3,7 @@ from discord.ext.commands import Context from static.constants import MODERATION_CHANNEL_NAME, LOG_CHANNEL_NAME -from classes.colored_embed import CEmbed +from classes import CEmbed def get_current_guild( From 3ef88b13220b1bdc3b40a89f900df81edd0d81b0 Mon Sep 17 00:00:00 2001 From: osam7a Date: Mon, 31 Jul 2023 19:26:56 +0400 Subject: [PATCH 02/26] Added pandas to requirements (QuestionQuery.py) --- requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 4091dbe..a7d9c62 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,4 +2,5 @@ python-dotenv discord.py fastapi[all] PyGithub -asyncpg \ No newline at end of file +asyncpg +pandas \ No newline at end of file From 17feb167cc5e80780d57090fec400c9a9614d2b0 Mon Sep 17 00:00:00 2001 From: osam7a Date: Mon, 31 Jul 2023 19:36:16 +0400 Subject: [PATCH 03/26] Nicer ping command because why not --- micro/cogs/commands.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/micro/cogs/commands.py b/micro/cogs/commands.py index 60edf66..00adbf4 100644 --- a/micro/cogs/commands.py +++ b/micro/cogs/commands.py @@ -1,4 +1,5 @@ import discord +import requests from discord import app_commands from discord.ext import commands from discord.utils import format_dt @@ -200,8 +201,9 @@ async def help(self, interaction: discord.Interaction): @app_commands.command(name="ping", description=constants.USER_COMMANDS["ping"]) async def ping(self, interaction: discord.Interaction): try: + api_latency = requests.get("http://localhost:8000").elapsed.total_seconds() / 1000 embed = CEmbed( - description=f"Pongin' with **{round(self.bot.latency* 1000)}ms**!" + description=f"# Pong!\n```diff\nBot latency: \n{'+' if self.bot.latency < 300 else '-'} {self.bot.latency * 1000:.0f}ms\nAPI Latecy: \n{'+' if api_latency < 300 else '-'} {api_latency * 1000:.0f}ms\n```" ) await interaction.response.send_message(embed=embed) From d11bddd74211d08cce5e050a6b9d221ca092ceb7 Mon Sep 17 00:00:00 2001 From: osam7a Date: Mon, 31 Jul 2023 19:43:57 +0400 Subject: [PATCH 04/26] Use channel IDs for channel identifications (Rather than names) --- micro/cogs/moderation.py | 2 +- micro/static/constants.py | 6 +++--- micro/static/misc.py | 12 ++++++------ 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/micro/cogs/moderation.py b/micro/cogs/moderation.py index 737bb11..dfc91b7 100644 --- a/micro/cogs/moderation.py +++ b/micro/cogs/moderation.py @@ -587,7 +587,7 @@ async def send_in_log_channel( log.add_field(name="Reason", value=reason if reason else "N/A", inline=False) try: - log_channel = get_extra_channel("log", obj=interaction) + log_channel = get_extra_channel("log", obj=interaction, bot=self.bot) except ValueError as e: fail_embed = CEmbed(description=f"Log channel not set up correctly") return await interaction.channel.send(embed=fail_embed) diff --git a/micro/static/constants.py b/micro/static/constants.py index 6f86854..27c62af 100644 --- a/micro/static/constants.py +++ b/micro/static/constants.py @@ -14,9 +14,9 @@ If you are still unsure then ask on <#947073932719190086> we will help guide you to a task that you will enjoy the most. Have fun! """ -MEMBERCOUNT_CHANNEL_NAME = "membercount" -LOG_CHANNEL_NAME = "micro-logs" -MODERATION_CHANNEL_NAME = "moderation" +MEMBERCOUNT_CHANNEL = 0 +LOG_CHANNEL = 0 +MODERATION_CHANNEL = 0 BOT_COLOR = 0x00AFB1 USER_COMMANDS = { diff --git a/micro/static/misc.py b/micro/static/misc.py index 65ebd9d..5d35eec 100644 --- a/micro/static/misc.py +++ b/micro/static/misc.py @@ -1,8 +1,8 @@ import discord from discord import Guild, Interaction, Message, TextChannel, Member, Embed -from discord.ext.commands import Context +from discord.ext.commands import Context, Bot -from static.constants import MODERATION_CHANNEL_NAME, LOG_CHANNEL_NAME +from static.constants import MODERATION_CHANNEL, LOG_CHANNEL from classes import CEmbed @@ -16,21 +16,21 @@ def get_current_guild( def get_extra_channel( - channel_type: str, obj: Interaction | Message | TextChannel | Member | Context + channel_type: str, obj: Interaction | Message | TextChannel | Member | Context, bot: Bot ) -> TextChannel: guild = get_current_guild(obj) if not guild: raise TypeError(f"The given '{repr(obj)}' cannot extract guild property") channels = { - "mod": MODERATION_CHANNEL_NAME, - "log": LOG_CHANNEL_NAME, + "mod": MODERATION_CHANNEL, + "log": LOG_CHANNEL, } req_channel = channels.get(channel_type) if not req_channel: raise ValueError(f"There is no requirement channel named '{channel_type}'") - channel_obj = discord.utils.get(guild.channels, name=req_channel) + channel_obj = bot.get_channel(req_channel) # Get channel by ID if not channel_obj: raise ValueError( f"There are no existing channels in the server named '{req_channel}'" From 90ad6ccf2e71792d08cb0f943b1ea1d68d82faaf Mon Sep 17 00:00:00 2001 From: osam7a Date: Mon, 31 Jul 2023 19:47:17 +0400 Subject: [PATCH 05/26] Renamed files: Always use snake case in Python, rather than camelCase or PascalCase --- micro/classes/__init__.py | 4 ++-- micro/classes/{badWords.py => bad_words.py} | 0 micro/classes/{QuestionQuery.py => question_query.py} | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename micro/classes/{badWords.py => bad_words.py} (100%) rename micro/classes/{QuestionQuery.py => question_query.py} (100%) diff --git a/micro/classes/__init__.py b/micro/classes/__init__.py index e92936d..72bedd5 100644 --- a/micro/classes/__init__.py +++ b/micro/classes/__init__.py @@ -1,7 +1,7 @@ from .database import Database -from .badWords import BadWords +from .bad_words import BadWords from .github import GitHub -from .QuestionQuery import QuestionQuery +from .question_query import QuestionQuery from .resources import Resources from .colored_embed import CEmbed diff --git a/micro/classes/badWords.py b/micro/classes/bad_words.py similarity index 100% rename from micro/classes/badWords.py rename to micro/classes/bad_words.py diff --git a/micro/classes/QuestionQuery.py b/micro/classes/question_query.py similarity index 100% rename from micro/classes/QuestionQuery.py rename to micro/classes/question_query.py From 6c9b60d9772ba37534d44e4162105d87c5a0d066 Mon Sep 17 00:00:00 2001 From: osam7a Date: Mon, 31 Jul 2023 19:49:45 +0400 Subject: [PATCH 06/26] More renaming: camelCase to snake_case --- micro/classes/bad_words.py | 2 +- micro/classes/question_query.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/micro/classes/bad_words.py b/micro/classes/bad_words.py index 35180ca..9d283ae 100644 --- a/micro/classes/bad_words.py +++ b/micro/classes/bad_words.py @@ -6,7 +6,7 @@ def __init__(self,logger): self.logger = logger self.insults = get_insult_list() - def isItInsult(self, msg_lst): + def is_it_insult(self, msg_lst): try: msg = ' '.join(msg_lst) diff --git a/micro/classes/question_query.py b/micro/classes/question_query.py index 1dc3dd2..169ac3f 100644 --- a/micro/classes/question_query.py +++ b/micro/classes/question_query.py @@ -20,9 +20,9 @@ def question(self, command): title, msg = self.data['Title']['who'], self.data['Answer']['who'] if command in self.data['Answer'].keys(): title, msg = self.data['Title'][command], self.data['Answer'][command] - return self._toEmbed(title, msg) + return self._toembed(title, msg) - def _toEmbed(self, title, msg): + def _toembed(self, title, msg): try: return embeds.Embed.from_dict( { From 6ef15e2d5b83c0115c0d06626c4d6a12386587bb Mon Sep 17 00:00:00 2001 From: osam7a Date: Mon, 31 Jul 2023 20:04:05 +0400 Subject: [PATCH 07/26] Moved views into a seperate folder (Just to organize things together) --- micro/__main__.py | 2 +- micro/classes/__init__.py | 4 +- micro/classes/views.py | 473 ------------------------------------ micro/cogs/commands.py | 8 +- micro/cogs/events.py | 4 +- micro/cogs/issues.py | 4 +- micro/views/__init__.py | 6 + micro/views/choose_label.py | 73 ++++++ micro/views/issue_list.py | 125 ++++++++++ micro/views/issue_modal.py | 52 ++++ micro/views/moderation.py | 63 +++++ micro/views/resources.py | 148 +++++++++++ micro/views/role.py | 29 +++ micro/views/views.py | 25 ++ 14 files changed, 530 insertions(+), 486 deletions(-) delete mode 100644 micro/classes/views.py create mode 100644 micro/views/__init__.py create mode 100644 micro/views/choose_label.py create mode 100644 micro/views/issue_list.py create mode 100644 micro/views/issue_modal.py create mode 100644 micro/views/moderation.py create mode 100644 micro/views/resources.py create mode 100644 micro/views/role.py create mode 100644 micro/views/views.py diff --git a/micro/__main__.py b/micro/__main__.py index 5c7c601..9eb1af9 100644 --- a/micro/__main__.py +++ b/micro/__main__.py @@ -4,7 +4,7 @@ import discord from discord.ext import commands -from classes import ( +from views import ( ModerationView, ResourcesView, RoleView, diff --git a/micro/classes/__init__.py b/micro/classes/__init__.py index 72bedd5..4adf70e 100644 --- a/micro/classes/__init__.py +++ b/micro/classes/__init__.py @@ -3,6 +3,4 @@ from .github import GitHub from .question_query import QuestionQuery from .resources import Resources -from .colored_embed import CEmbed - -from .views import * \ No newline at end of file +from .colored_embed import CEmbed \ No newline at end of file diff --git a/micro/classes/views.py b/micro/classes/views.py deleted file mode 100644 index c68ac40..0000000 --- a/micro/classes/views.py +++ /dev/null @@ -1,473 +0,0 @@ -import discord -from discord.ui import View, button, Button, select, Select, Modal, TextInput -from discord import Interaction, Embed, Message - -from classes.resources import Resources -from classes.github import GitHub -from classes.colored_embed import CEmbed -from static import constants - - -class ResourcesView(View): - def __init__(self, pages: list[Embed] = None, lang: str = None): - self.res = Resources() - - self.pages = pages - self.lang = lang - self.current_page = 0 - - self.res.reload_resource() - super().__init__(timeout=None) - - @button( - label="Previous Page", emoji="⏪", disabled=True, custom_id="button_previous" - ) - async def prev_page(self, interaction: Interaction, button: Button): - if not self.pages: - self.setup(interaction) - - req_user = interaction.message.interaction.user - if interaction.user != req_user: - return await interaction.response.send_message( - "Only the owner of the message can control the pagination menu!", - ephemeral=True, - ) - - if not self.current_page < 0: - self.prev_page.disabled = False - self.next_page.disabled = False - self.current_page -= 1 - - if self.current_page == 0: - self.prev_page.disabled = True - - current_page = self.pages[self.current_page] - await interaction.response.edit_message(embed=current_page, view=self) - - @button(label="Next Page", emoji="⏩", custom_id="button_forward") - async def next_page(self, interaction: Interaction, button: Button): - if not self.pages: - self.setup(interaction) - - if self.get_total_page(interaction) == 1: - self.next_page.disabled = True - await interaction.message.edit(view=self) - return await interaction.response.send_message( - "There is only one page", ephemeral=True - ) - - req_user = interaction.message.interaction.user - - if interaction.user != req_user: - return await interaction.response.send_message( - "Only the owner of the message can control the pagination menu!", - ephemeral=True, - ) - - if not self.current_page + 1 >= len(self.pages): - self.next_page.disabled = False - self.prev_page.disabled = False - self.current_page += 1 - - if self.current_page == len(self.pages) - 1: - self.next_page.disabled = True - - current_page = self.pages[self.current_page] - await interaction.response.edit_message(embed=current_page, view=self) - - @button( - label="Exit", - emoji="🗑️", - style=discord.ButtonStyle.danger, - custom_id="button_quit", - ) - async def quit(self, interaction: Interaction, button: Button): - req_user = interaction.message.interaction.user - if interaction.user != req_user: - return await interaction.response.send_message( - "Only the owner of the message can control the pagination menu!", - ephemeral=True, - ) - - try: - await interaction.message.delete() - except: - embed = CEmbed(description="Unable to delete the interaction") - await interaction.response.send_message(embed=embed, ephemeral=True) - - def get_paginated(self, interaction: Interaction) -> list[list]: - lang = self.get_lang(interaction) - data = self.res.get_details(lang) - paginated = self.res.prepare_pagination(data) - return paginated - - def create_pages(self, paginated: list[list], lang: str) -> list[Embed]: - pages = [] - for idx, page in enumerate(paginated, start=1): - embed = CEmbed(title=lang) - embed.set_footer(text=f"Page {idx} - {len(paginated)}") - - for d in page: - for category, contents in d.items(): - text = "" - if category == "Description": - text = contents - embed.add_field(name=category, value=text, inline=False) - else: - for content in contents: - title = content.split(" - ") - if len(title) > 1: - link = title[-1] - name = " - ".join(title[:-1]) - text += f"[{name}]({link})\n" - else: - link = content - text += f"{link}\n" - embed.add_field(name=category, value=text, inline=False) - - pages.append(embed) - - return pages - - def get_lang(self, interaction: Interaction) -> str: - lang = interaction.message.embeds[0].title - return lang - - def get_current_page(self, interaction: Interaction) -> int: - page_text = interaction.message.embeds[0].footer.text - current_page = page_text.split(" - ")[0].split(" ")[-1] - return int(current_page) - - def get_total_page(self, interaction: Interaction) -> int: - page_text = interaction.message.embeds[0].footer.text - total_page = page_text.split(" - ")[-1] - return int(total_page) - - def setup(self, interaction: Interaction): - lang = self.get_lang(interaction) - paginated = self.get_paginated(interaction) - self.pages = self.create_pages(paginated, lang) - current_page = self.get_current_page(interaction) - self.total_page = self.get_total_page(interaction) - self.current_page = current_page - 1 # Indexing - - -class ModerationView(View): - def __init__(self, embed_sent: Embed = None, profane_msg: Message = None): - self.embed_sent = embed_sent - self.profane_msg = profane_msg - - super().__init__(timeout=None) - - @button( - label="Allow message", - style=discord.ButtonStyle.green, - custom_id="accept_button", - ) - async def accept(self, interaction: Interaction, button: Button): - if not self.embed_sent and not self.profane_msg: - await self.setup(interaction) - await interaction.response.defer() - - temp_embed = CEmbed(description=f"The message has been marked as non profane.") - await self.embed_sent.edit(embed=temp_embed, delete_after=5, view=None) - - @button( - label="Deny message", style=discord.ButtonStyle.red, custom_id="deny_button" - ) - async def deny(self, interaction: Interaction, button: Button): - extra = "" - if not self.embed_sent and not self.profane_msg: - await self.setup(interaction) - - await interaction.response.defer() - try: - if self.profane_msg: - await self.profane_msg.delete() - - except discord.errors.NotFound: - extra = "already" - pass - - temp_embed = CEmbed( - description=f"The potentially profane message has been {extra if extra else ''} deleted." - ) - await self.embed_sent.edit(embed=temp_embed, delete_after=5, view=None) - - async def setup(self, interaction: Interaction): - self.embed_sent = interaction.message - embed = self.embed_sent.embeds[-1] - - link = embed.fields[-1].value.split("(")[-1][:-1] - profane_cid, profane_mid = int(link.split("/")[-2]), int(link.split("/")[-1]) - - channel = discord.utils.get(interaction.guild.channels, id=profane_cid) - try: - self.profane_msg = await channel.fetch_message(profane_mid) - except discord.errors.NotFound: - temp_embed = CEmbed( - description=f"The potentially profane message has already been deleted." - ) - await self.embed_sent.edit(embed=temp_embed, delete_after=5, view=None) - - -class RoleView(View): - def __init__(self): - super().__init__(timeout=None) - - @select( - options=[ - discord.SelectOption(label=label, emoji=emoji) - for label, emoji in constants.SELF_ROLES.items() - ], - max_values=11, - min_values=1, - placeholder="Select roles to apply", - custom_id="self_role", - ) - async def add_roles(self, interaction: Interaction, select: Select): - await interaction.response.defer(ephemeral=True) - roles = [ - discord.utils.get(interaction.guild.roles, name=role) - for role in select.values - ] - - await interaction.user.add_roles(*roles, reason="User requested") - await interaction.followup.send("Roles added succesfully", ephemeral=True) - - -class IssueListView(View): - def __init__(self, pages: list[Embed] = None): - self.gh = GitHub() - - self.pages = pages - self.current_page = 0 - - super().__init__(timeout=None) - - @button( - label="Previous Page", emoji="⏪", disabled=True, custom_id="previous_button" - ) - async def prev_page(self, interaction: Interaction, button: Button): - req_user = interaction.message.interaction.user - if not self.pages: - self.setup(interaction) - - if interaction.user != req_user: - return await interaction.response.send_message( - "Only the owner of the message can control the pagination menu!", - ephemeral=True, - ) - - if not self.current_page < 0: - self.prev_page.disabled = False - self.next_page.disabled = False - self.current_page -= 1 - - if self.current_page == 0: - self.prev_page.disabled = True - - current_page = self.pages[self.current_page] - await interaction.response.edit_message(embed=current_page, view=self) - - @button( - label="Next Page", - emoji="⏩", - custom_id="forward_button", - ) - async def next_page(self, interaction: Interaction, button: Button): - req_user = interaction.message.interaction.user - if not self.pages: - self.setup(interaction) - - if self.get_total_page(interaction) == 1: - self.next_page.disabled = True - await interaction.message.edit(view=self) - return await interaction.response.send_message( - "There is only one page", ephemeral=True - ) - - if interaction.user != req_user: - return await interaction.response.send_message( - "Only the owner of the message can control the pagination menu!", - ephemeral=True, - ) - - if not self.current_page + 1 >= len(self.pages): - self.next_page.disabled = False - self.prev_page.disabled = False - self.current_page += 1 - - if self.current_page == len(self.pages) - 1: - self.next_page.disabled = True - - current_page = self.pages[self.current_page] - await interaction.response.edit_message(embed=current_page, view=self) - - @button( - label="Exit", - emoji="🗑️", - style=discord.ButtonStyle.danger, - custom_id="quit_button", - ) - async def quit(self, interaction: Interaction, button: Button): - req_user = interaction.message.interaction.user - if interaction.user != req_user: - return await interaction.response.send_message( - "Only the owner of the message can control the pagination menu!", - ephemeral=True, - ) - - try: - await interaction.message.delete() - except: - embed = CEmbed(description="Unable to delete the interaction") - await interaction.response.send_message(embed=embed, ephemeral=True) - - def setup(self, interaction: Interaction): - issue_list = self.gh.list_issues(paginate=True) - self.pages = self.make_pages(issue_list) - self.current_page = self.get_current_page(interaction) - 1 # For indexing - self.total_pages = self.get_total_page(interaction) - - def make_pages(self, paginated: list[list]) -> list[Embed]: - pages = [] - for idx, page in enumerate(paginated, start=1): - embed = CEmbed(title="Issues") - embed.set_footer(text=f"Page {idx} - {len(paginated)}") - for issue in page: - embed.add_field( - name=f"{issue.number}. {issue.title}", - value=f"{issue.html_url}", - inline=False, - ) - - pages.append(embed) - - return pages - - def get_current_page(self, interaction: Interaction) -> int: - page_text = interaction.message.embeds[0].footer.text - current_page = page_text.split(" - ")[0].split(" ")[-1] - return int(current_page) - - def get_total_page(self, interaction: Interaction) -> int: - page_text = interaction.message.embeds[0].footer.text - total_page = page_text.split(" - ")[-1] - return int(total_page) - - -class ChooseLabelView(View): - def __init__( - self, - issue_title: str, - issue_description: str = "", - issue_lbls: list[str] = [], - edit_num: int = None, - ): - self.gh = GitHub() - labels = self.gh.get_all_labels() - self.edit_num = edit_num - - super().__init__(timeout=None) - - self.select = Select( - placeholder="Select your labels", - max_values=len(labels), - min_values=0, - custom_id="label_select", - ) - self.select.callback = self.callback - for lbl in ["None"] + labels: - self.select.add_option( - label=lbl, value=lbl, default=True if lbl in issue_lbls else False - ) - self.add_item(self.select) - - self.button = Button( - label=f"{'Update' if edit_num else 'Create'} Issue", - style=discord.ButtonStyle.green, - custom_id="crup_issue_btn", - ) - self.button.callback = lambda interaction: self.button_callback( - interaction, issue_title, issue_description - ) - self.add_item(self.button) - - async def callback(self, interaction: Interaction): - await interaction.response.defer() - - async def button_callback(self, interaction: Interaction, title: str, desc: str): - await interaction.response.defer() - - if "None" in self.select.values: - lbls = [] - else: - lbls = self.select.values - - if self.edit_num: - issue = self.gh.update_issue(self.edit_num, title, desc, lbls) - else: - issue = self.gh.create_issue(lbls, title, desc) - embed = CEmbed() - - if issue: - embed.title = "Issues" - embed.description = f"Issue #{issue.number} {'created' if not self.edit_num else 'updated'}\n[Jump to issue]({issue.html_url})" - else: - embed.description = ( - "Unable to process /issue, please check your details and try again" - ) - - self.button.disabled = True - self.select.disabled = True - - await interaction.edit_original_response(view=self) - await interaction.followup.send(embed=embed) - - -class IssueModal(Modal, title="Create/Update Issue"): - issue_title = TextInput( - label="Title of the issue", - placeholder="Module x not working when clicking on y", - ) - issue_description = TextInput( - label="Description of the issue", - placeholder="Type your description...", - style=discord.TextStyle.long, - required=False, - ) - issue_lbls = [] - - def __init__(self, title, update_num: int = None) -> None: - if update_num: - gh = GitHub() - - issue_title, issue_desc, self.issue_lbls = gh.get_details(update_num) - self.issue_title.default = issue_title - self.issue_description.default = issue_desc - - self.update_num = update_num - - super().__init__(title=title, timeout=None, custom_id="custom_id") - - async def on_submit(self, interaction: Interaction) -> None: - if self.issue_lbls: - await interaction.response.send_message( - view=ChooseLabelView( - self.issue_title.value, - self.issue_description.value, - self.issue_lbls, - edit_num=self.update_num, - ), - ephemeral=True, - ) - else: - await interaction.response.send_message( - view=ChooseLabelView( - self.issue_title.value, - self.issue_description.value, - edit_num=self.update_num, - ), - ephemeral=True, - ) diff --git a/micro/cogs/commands.py b/micro/cogs/commands.py index 00adbf4..e6cc424 100644 --- a/micro/cogs/commands.py +++ b/micro/cogs/commands.py @@ -5,12 +5,8 @@ from discord.utils import format_dt from discord.app_commands import Choice -from classes import ( - Resources, - ResourcesView, - RoleView, - CEmbed -) +from classes import Resources, CEmbed +from views import ResourcesView, RoleView from static.misc import get_current_guild diff --git a/micro/cogs/events.py b/micro/cogs/events.py index ab4e6d4..4adba47 100644 --- a/micro/cogs/events.py +++ b/micro/cogs/events.py @@ -5,9 +5,9 @@ from classes import ( Database, GitHub, - CEmbed, - ModerationView + CEmbed ) +from views import ModerationView from static.misc import get_extra_channel, get_current_guild import static.constants as constants diff --git a/micro/cogs/issues.py b/micro/cogs/issues.py index 4318960..75c06fb 100644 --- a/micro/cogs/issues.py +++ b/micro/cogs/issues.py @@ -2,7 +2,9 @@ from discord import app_commands from discord.ext import commands -from classes import GitHub, IssueListView, IssueModal +from classes import GitHub +from views import IssueListView, IssueModal + from static import constants from classes.colored_embed import CEmbed diff --git a/micro/views/__init__.py b/micro/views/__init__.py new file mode 100644 index 0000000..791f315 --- /dev/null +++ b/micro/views/__init__.py @@ -0,0 +1,6 @@ +from .issue_list import IssueListView +from .issue_modal import IssueModal +from .role import RoleView +from .resources import ResourcesView +from .moderation import ModerationView +from .choose_label import ChooseLabelView \ No newline at end of file diff --git a/micro/views/choose_label.py b/micro/views/choose_label.py new file mode 100644 index 0000000..09f3632 --- /dev/null +++ b/micro/views/choose_label.py @@ -0,0 +1,73 @@ +import discord +from discord.ui import View, button, Button, select, Select, Modal, TextInput +from discord import Interaction, Embed, Message + +from classes import GitHub, CEmbed + +class ChooseLabelView(View): + def __init__( + self, + issue_title: str, + issue_description: str = "", + issue_lbls: list[str] = [], + edit_num: int = None, + ): + self.gh = GitHub() + labels = self.gh.get_all_labels() + self.edit_num = edit_num + + super().__init__(timeout=None) + + self.select = Select( + placeholder="Select your labels", + max_values=len(labels), + min_values=0, + custom_id="label_select", + ) + self.select.callback = self.callback + for lbl in ["None"] + labels: + self.select.add_option( + label=lbl, value=lbl, default=True if lbl in issue_lbls else False + ) + self.add_item(self.select) + + self.button = Button( + label=f"{'Update' if edit_num else 'Create'} Issue", + style=discord.ButtonStyle.green, + custom_id="crup_issue_btn", + ) + self.button.callback = lambda interaction: self.button_callback( + interaction, issue_title, issue_description + ) + self.add_item(self.button) + + async def callback(self, interaction: Interaction): + await interaction.response.defer() + + async def button_callback(self, interaction: Interaction, title: str, desc: str): + await interaction.response.defer() + + if "None" in self.select.values: + lbls = [] + else: + lbls = self.select.values + + if self.edit_num: + issue = self.gh.update_issue(self.edit_num, title, desc, lbls) + else: + issue = self.gh.create_issue(lbls, title, desc) + embed = CEmbed() + + if issue: + embed.title = "Issues" + embed.description = f"Issue #{issue.number} {'created' if not self.edit_num else 'updated'}\n[Jump to issue]({issue.html_url})" + else: + embed.description = ( + "Unable to process /issue, please check your details and try again" + ) + + self.button.disabled = True + self.select.disabled = True + + await interaction.edit_original_response(view=self) + await interaction.followup.send(embed=embed) \ No newline at end of file diff --git a/micro/views/issue_list.py b/micro/views/issue_list.py new file mode 100644 index 0000000..1562e47 --- /dev/null +++ b/micro/views/issue_list.py @@ -0,0 +1,125 @@ +import discord +from discord.ui import View, button, Button, select, Select, Modal, TextInput +from discord import Interaction, Embed, Message + +from classes import GitHub, CEmbed + +class IssueListView(View): + def __init__(self, pages: list[Embed] = None): + self.gh = GitHub() + + self.pages = pages + self.current_page = 0 + + super().__init__(timeout=None) + + @button( + label="Previous Page", emoji="⏪", disabled=True, custom_id="previous_button" + ) + async def prev_page(self, interaction: Interaction, button: Button): + req_user = interaction.message.interaction.user + if not self.pages: + self.setup(interaction) + + if interaction.user != req_user: + return await interaction.response.send_message( + "Only the owner of the message can control the pagination menu!", + ephemeral=True, + ) + + if not self.current_page < 0: + self.prev_page.disabled = False + self.next_page.disabled = False + self.current_page -= 1 + + if self.current_page == 0: + self.prev_page.disabled = True + + current_page = self.pages[self.current_page] + await interaction.response.edit_message(embed=current_page, view=self) + + @button( + label="Next Page", + emoji="⏩", + custom_id="forward_button", + ) + async def next_page(self, interaction: Interaction, button: Button): + req_user = interaction.message.interaction.user + if not self.pages: + self.setup(interaction) + + if self.get_total_page(interaction) == 1: + self.next_page.disabled = True + await interaction.message.edit(view=self) + return await interaction.response.send_message( + "There is only one page", ephemeral=True + ) + + if interaction.user != req_user: + return await interaction.response.send_message( + "Only the owner of the message can control the pagination menu!", + ephemeral=True, + ) + + if not self.current_page + 1 >= len(self.pages): + self.next_page.disabled = False + self.prev_page.disabled = False + self.current_page += 1 + + if self.current_page == len(self.pages) - 1: + self.next_page.disabled = True + + current_page = self.pages[self.current_page] + await interaction.response.edit_message(embed=current_page, view=self) + + @button( + label="Exit", + emoji="🗑️", + style=discord.ButtonStyle.danger, + custom_id="quit_button", + ) + async def quit(self, interaction: Interaction, button: Button): + req_user = interaction.message.interaction.user + if interaction.user != req_user: + return await interaction.response.send_message( + "Only the owner of the message can control the pagination menu!", + ephemeral=True, + ) + + try: + await interaction.message.delete() + except: + embed = CEmbed(description="Unable to delete the interaction") + await interaction.response.send_message(embed=embed, ephemeral=True) + + def setup(self, interaction: Interaction): + issue_list = self.gh.list_issues(paginate=True) + self.pages = self.make_pages(issue_list) + self.current_page = self.get_current_page(interaction) - 1 # For indexing + self.total_pages = self.get_total_page(interaction) + + def make_pages(self, paginated: list[list]) -> list[Embed]: + pages = [] + for idx, page in enumerate(paginated, start=1): + embed = CEmbed(title="Issues") + embed.set_footer(text=f"Page {idx} - {len(paginated)}") + for issue in page: + embed.add_field( + name=f"{issue.number}. {issue.title}", + value=f"{issue.html_url}", + inline=False, + ) + + pages.append(embed) + + return pages + + def get_current_page(self, interaction: Interaction) -> int: + page_text = interaction.message.embeds[0].footer.text + current_page = page_text.split(" - ")[0].split(" ")[-1] + return int(current_page) + + def get_total_page(self, interaction: Interaction) -> int: + page_text = interaction.message.embeds[0].footer.text + total_page = page_text.split(" - ")[-1] + return int(total_page) \ No newline at end of file diff --git a/micro/views/issue_modal.py b/micro/views/issue_modal.py new file mode 100644 index 0000000..a1d7932 --- /dev/null +++ b/micro/views/issue_modal.py @@ -0,0 +1,52 @@ +import discord +from discord.ui import Modal, TextInput +from discord import Interaction + +from classes import GitHub +from .choose_label import ChooseLabelView + +class IssueModal(Modal, title="Create/Update Issue"): + issue_title = TextInput( + label="Title of the issue", + placeholder="Module x not working when clicking on y", + ) + issue_description = TextInput( + label="Description of the issue", + placeholder="Type your description...", + style=discord.TextStyle.long, + required=False, + ) + issue_lbls = [] + + def __init__(self, title, update_num: int = None) -> None: + if update_num: + gh = GitHub() + + issue_title, issue_desc, self.issue_lbls = gh.get_details(update_num) + self.issue_title.default = issue_title + self.issue_description.default = issue_desc + + self.update_num = update_num + + super().__init__(title=title, timeout=None, custom_id="custom_id") + + async def on_submit(self, interaction: Interaction) -> None: + if self.issue_lbls: + await interaction.response.send_message( + view=ChooseLabelView( + self.issue_title.value, + self.issue_description.value, + self.issue_lbls, + edit_num=self.update_num, + ), + ephemeral=True, + ) + else: + await interaction.response.send_message( + view=ChooseLabelView( + self.issue_title.value, + self.issue_description.value, + edit_num=self.update_num, + ), + ephemeral=True, + ) diff --git a/micro/views/moderation.py b/micro/views/moderation.py new file mode 100644 index 0000000..5641ac3 --- /dev/null +++ b/micro/views/moderation.py @@ -0,0 +1,63 @@ +import discord +from discord.ui import View, button, Button +from discord import Interaction, Embed, Message + +from classes import CEmbed + +class ModerationView(View): + def __init__(self, embed_sent: Embed = None, profane_msg: Message = None): + self.embed_sent = embed_sent + self.profane_msg = profane_msg + + super().__init__(timeout=None) + + @button( + label="Allow message", + style=discord.ButtonStyle.green, + custom_id="accept_button", + ) + async def accept(self, interaction: Interaction, button: Button): + if not self.embed_sent and not self.profane_msg: + await self.setup(interaction) + await interaction.response.defer() + + temp_embed = CEmbed(description="The message has been marked as non profane.") + await self.embed_sent.edit(embed=temp_embed, delete_after=5, view=None) + + @button( + label="Deny message", style=discord.ButtonStyle.red, custom_id="deny_button" + ) + async def deny(self, interaction: Interaction, button: Button): + extra = "" + if not self.embed_sent and not self.profane_msg: + await self.setup(interaction) + + await interaction.response.defer() + try: + if self.profane_msg: + await self.profane_msg.delete() + + except discord.errors.NotFound: + extra = "already" + pass + + temp_embed = CEmbed( + description=f"The potentially profane message has been {extra if extra else ''} deleted." + ) + await self.embed_sent.edit(embed=temp_embed, delete_after=5, view=None) + + async def setup(self, interaction: Interaction): + self.embed_sent = interaction.message + embed = self.embed_sent.embeds[-1] + + link = embed.fields[-1].value.split("(")[-1][:-1] + profane_cid, profane_mid = int(link.split("/")[-2]), int(link.split("/")[-1]) + + channel = discord.utils.get(interaction.guild.channels, id=profane_cid) + try: + self.profane_msg = await channel.fetch_message(profane_mid) + except discord.errors.NotFound: + temp_embed = CEmbed( + description="The potentially profane message has already been deleted." + ) + await self.embed_sent.edit(embed=temp_embed, delete_after=5, view=None) \ No newline at end of file diff --git a/micro/views/resources.py b/micro/views/resources.py new file mode 100644 index 0000000..3ed72d8 --- /dev/null +++ b/micro/views/resources.py @@ -0,0 +1,148 @@ +import discord +from discord.ui import View, button, Button +from discord import Interaction, Embed + +from classes import Resources, CEmbed + +class ResourcesView(View): + def __init__(self, pages: list[Embed] = None, lang: str = None): + self.res = Resources() + + self.pages = pages + self.lang = lang + self.current_page = 0 + + self.res.reload_resource() + super().__init__(timeout=None) + + @button( + label="Previous Page", emoji="⏪", disabled=True, custom_id="button_previous" + ) + async def prev_page(self, interaction: Interaction, button: Button): + if not self.pages: + self.setup(interaction) + + req_user = interaction.message.interaction.user + if interaction.user != req_user: + return await interaction.response.send_message( + "Only the owner of the message can control the pagination menu!", + ephemeral=True, + ) + + if not self.current_page < 0: + self.prev_page.disabled = False + self.next_page.disabled = False + self.current_page -= 1 + + if self.current_page == 0: + self.prev_page.disabled = True + + current_page = self.pages[self.current_page] + await interaction.response.edit_message(embed=current_page, view=self) + + @button(label="Next Page", emoji="⏩", custom_id="button_forward") + async def next_page(self, interaction: Interaction, button: Button): + if not self.pages: + self.setup(interaction) + + if self.get_total_page(interaction) == 1: + self.next_page.disabled = True + await interaction.message.edit(view=self) + return await interaction.response.send_message( + "There is only one page", ephemeral=True + ) + + req_user = interaction.message.interaction.user + + if interaction.user != req_user: + return await interaction.response.send_message( + "Only the owner of the message can control the pagination menu!", + ephemeral=True, + ) + + if not self.current_page + 1 >= len(self.pages): + self.next_page.disabled = False + self.prev_page.disabled = False + self.current_page += 1 + + if self.current_page == len(self.pages) - 1: + self.next_page.disabled = True + + current_page = self.pages[self.current_page] + await interaction.response.edit_message(embed=current_page, view=self) + + @button( + label="Exit", + emoji="🗑️", + style=discord.ButtonStyle.danger, + custom_id="button_quit", + ) + async def quit(self, interaction: Interaction, button: Button): + req_user = interaction.message.interaction.user + if interaction.user != req_user: + return await interaction.response.send_message( + "Only the owner of the message can control the pagination menu!", + ephemeral=True, + ) + + try: + await interaction.message.delete() + except: + embed = CEmbed(description="Unable to delete the interaction") + await interaction.response.send_message(embed=embed, ephemeral=True) + + def get_paginated(self, interaction: Interaction) -> list[list]: + lang = self.get_lang(interaction) + data = self.res.get_details(lang) + paginated = self.res.prepare_pagination(data) + return paginated + + def create_pages(self, paginated: list[list], lang: str) -> list[Embed]: + pages = [] + for idx, page in enumerate(paginated, start=1): + embed = CEmbed(title=lang) + embed.set_footer(text=f"Page {idx} - {len(paginated)}") + + for d in page: + for category, contents in d.items(): + text = "" + if category == "Description": + text = contents + embed.add_field(name=category, value=text, inline=False) + else: + for content in contents: + title = content.split(" - ") + if len(title) > 1: + link = title[-1] + name = " - ".join(title[:-1]) + text += f"[{name}]({link})\n" + else: + link = content + text += f"{link}\n" + embed.add_field(name=category, value=text, inline=False) + + pages.append(embed) + + return pages + + def get_lang(self, interaction: Interaction) -> str: + lang = interaction.message.embeds[0].title + return lang + + def get_current_page(self, interaction: Interaction) -> int: + page_text = interaction.message.embeds[0].footer.text + current_page = page_text.split(" - ")[0].split(" ")[-1] + return int(current_page) + + def get_total_page(self, interaction: Interaction) -> int: + page_text = interaction.message.embeds[0].footer.text + total_page = page_text.split(" - ")[-1] + return int(total_page) + + def setup(self, interaction: Interaction): + lang = self.get_lang(interaction) + paginated = self.get_paginated(interaction) + self.pages = self.create_pages(paginated, lang) + current_page = self.get_current_page(interaction) + self.total_page = self.get_total_page(interaction) + self.current_page = current_page - 1 # Indexing \ No newline at end of file diff --git a/micro/views/role.py b/micro/views/role.py new file mode 100644 index 0000000..c231e69 --- /dev/null +++ b/micro/views/role.py @@ -0,0 +1,29 @@ +import discord +from discord.ui import View, select, Select +from discord import Interaction + +from static import constants + +class RoleView(View): + def __init__(self): + super().__init__(timeout=None) + + @select( + options=[ + discord.SelectOption(label=label, emoji=emoji) + for label, emoji in constants.SELF_ROLES.items() + ], + max_values=11, + min_values=1, + placeholder="Select roles to apply", + custom_id="self_role", + ) + async def add_roles(self, interaction: Interaction, select: Select): + await interaction.response.defer(ephemeral=True) + roles = [ + discord.utils.get(interaction.guild.roles, name=role) + for role in select.values + ] + + await interaction.user.add_roles(*roles, reason="User requested") + await interaction.followup.send("Roles added succesfully", ephemeral=True) \ No newline at end of file diff --git a/micro/views/views.py b/micro/views/views.py new file mode 100644 index 0000000..8212259 --- /dev/null +++ b/micro/views/views.py @@ -0,0 +1,25 @@ +import discord +from discord.ui import View, button, Button, select, Select, Modal, TextInput +from discord import Interaction, Embed, Message + +from classes.resources import Resources +from classes.github import GitHub +from classes.colored_embed import CEmbed +from static import constants + + + + + + + + + + + + + + + + + From 932fae28110171147e348d709e8c4f52bc6294c2 Mon Sep 17 00:00:00 2001 From: osam7a Date: Mon, 31 Jul 2023 21:53:29 +0400 Subject: [PATCH 08/26] Rewrite help command --- micro/cogs/commands.py | 28 ++++++++++++++++++-------- micro/cogs/{issues.py => github.py} | 16 +++++---------- micro/cogs/{events.py => listeners.py} | 4 ++-- 3 files changed, 27 insertions(+), 21 deletions(-) rename micro/cogs/{issues.py => github.py} (96%) rename micro/cogs/{events.py => listeners.py} (99%) diff --git a/micro/cogs/commands.py b/micro/cogs/commands.py index e6cc424..513a35b 100644 --- a/micro/cogs/commands.py +++ b/micro/cogs/commands.py @@ -177,14 +177,26 @@ async def help(self, interaction: discord.Interaction): admin_role = discord.utils.get(interaction.guild.roles, name="Admin") mod_role = discord.utils.get(interaction.guild.roles, name="Moderator") - if admin_role in author.roles or mod_role in author.roles: - cmds = constants.ALL_COMMANDS - else: - cmds = constants.USER_COMMANDS - - embed = CEmbed(title="List of bot commands") - for cmd, desc in cmds.items(): - embed.add_field(name=f"/{cmd}", value=desc, inline=False) + cmds = [] + cogs = set() + + for cog in self.bot.cogs: + cogs.add(cog) + for cmd in self.bot.get_cog(cog).walk_app_commands(): + cmds.append((cog, cmd.name, cmd.description)) + + description = "" + for cog in cogs: + if cog == "Listeners": + continue + if (cog == "Admin" or cog == "Moderation") and (admin_role not in author.roles or mod_role not in author.roles): + continue + description += f"## {cog}\n" + for cmd in cmds: + if cmd[0] == cog: + description += f"`/{cmd[1]}` - {cmd[2]}\n" + description += "\n" + embed = CEmbed(title="_List of bot commands_", description=description) await interaction.response.send_message(embed=embed) diff --git a/micro/cogs/issues.py b/micro/cogs/github.py similarity index 96% rename from micro/cogs/issues.py rename to micro/cogs/github.py index 75c06fb..ada1612 100644 --- a/micro/cogs/issues.py +++ b/micro/cogs/github.py @@ -2,7 +2,7 @@ from discord import app_commands from discord.ext import commands -from classes import GitHub +from classes import GitHub as GitHubBackend from views import IssueListView, IssueModal from static import constants @@ -10,16 +10,10 @@ from dotenv import load_dotenv -@app_commands.guild_only() -@app_commands.default_permissions(manage_messages=True) -class GHIssues( - commands.GroupCog, - name="issue", - description="Command group for managing GitHub issues", -): +class GitHub(commands.Cog): def __init__(self, bot: commands.Bot): self.bot = bot - self.gh = GitHub() + self.gh = GitHubBackend() super().__init__() @@ -211,7 +205,7 @@ async def unable_to_connect(self): async def refresh_env(self): load_dotenv(override=True) - self.gh = GitHub() + self.gh = GitHubBackend() async def setup(bot: commands.Bot): # async - await bot.add_cog(GHIssues(bot)) + await bot.add_cog(GitHub(bot)) diff --git a/micro/cogs/events.py b/micro/cogs/listeners.py similarity index 99% rename from micro/cogs/events.py rename to micro/cogs/listeners.py index 4adba47..afa089a 100644 --- a/micro/cogs/events.py +++ b/micro/cogs/listeners.py @@ -13,7 +13,7 @@ import static.constants as constants -class Events(commands.Cog): +class Listeners(commands.Cog): def __init__(self, bot: commands.Bot): self.bot = bot @@ -281,4 +281,4 @@ async def on_member_unban(self, guild: discord.Guild, user: discord.Member): async def setup(bot): - await bot.add_cog(Events(bot)) # await this + await bot.add_cog(Listeners(bot)) # await this From bcbf78d2fa6c2616a3c6c43b78f45050906ef509 Mon Sep 17 00:00:00 2001 From: osam7a Date: Mon, 31 Jul 2023 22:07:55 +0400 Subject: [PATCH 09/26] Removed views.py (Mistake) --- micro/views/views.py | 25 ------------------------- 1 file changed, 25 deletions(-) delete mode 100644 micro/views/views.py diff --git a/micro/views/views.py b/micro/views/views.py deleted file mode 100644 index 8212259..0000000 --- a/micro/views/views.py +++ /dev/null @@ -1,25 +0,0 @@ -import discord -from discord.ui import View, button, Button, select, Select, Modal, TextInput -from discord import Interaction, Embed, Message - -from classes.resources import Resources -from classes.github import GitHub -from classes.colored_embed import CEmbed -from static import constants - - - - - - - - - - - - - - - - - From 512938ce90cea9412a8048f48625b346f8bc6dd6 Mon Sep 17 00:00:00 2001 From: Osama Alhennawi Date: Wed, 16 Aug 2023 18:39:44 +0400 Subject: [PATCH 10/26] Added simple channel for 'Upcoming Events' (NOT WORKING YET, WILL ADD TO THIS IN A BIT) --- micro/API.py | 6 ++++-- micro/__main__.py | 32 ++++++++++++++++++++++++++++++-- micro/static/constants.py | 2 ++ micro/tests/request_test.py | 2 +- 4 files changed, 37 insertions(+), 5 deletions(-) diff --git a/micro/API.py b/micro/API.py index a8fefcb..fdc3a43 100644 --- a/micro/API.py +++ b/micro/API.py @@ -22,7 +22,9 @@ class Event(BaseModel): def get_embed(event: Event): - date = datetime.strptime(event.date_time, "%Y-%m-%d") + date_time = event.date_time.split() + date = datetime.strptime(date_time[0], "%Y-%m-%d") + time = datetime.strptime(date_time[1], "%H:%M") return CEmbed.from_dict( { "title": event.title, @@ -40,7 +42,7 @@ def get_embed(event: Event): }, { "name": "Time", - "value": date.strftime("%H-%M"), + "value": time.strftime("%H:%M"), "inline": True }, { diff --git a/micro/__main__.py b/micro/__main__.py index 9eb1af9..5a38063 100644 --- a/micro/__main__.py +++ b/micro/__main__.py @@ -2,7 +2,8 @@ load_dotenv() import discord -from discord.ext import commands +import datetime +from discord.ext import commands, tasks from views import ( ModerationView, @@ -13,6 +14,7 @@ from classes import Database from logger import logger from static.paths import COGS_DIR +from static.constants import EVENTS_CHANNEL, NEXT_EVENT_CHANNEL from API import app from logging import Logger @@ -31,7 +33,7 @@ def __init__(self, logger: Logger, *args, **kwargs): # Setup logger self.logger = logger - + super().__init__(*args, activity=activity, intents=intents, **kwargs) async def setup_hook(self): @@ -62,6 +64,32 @@ async def create_pool(self, dsn): print(msg) return msg + + @tasks.loop(seconds=2) + async def update_next_event(self): + try: + events_channel = await self.fetch_channel(EVENTS_CHANNEL) + next_event_channel = await self.fetch_channel(NEXT_EVENT_CHANNEL) + except discord.errors.NotFound: + self.logger.error("Couldn't find the events channel, ignoring exception") + return + if not events_channel.last_message: + await next_event_channel.edit(name="No upcoming events...") + return + last_msg = events_channel.last_message + day = datetime.datetime.strptime(last_msg.embeds[0].fields[1].value, "%Y-%m-%d") # 2nd field is the date + time = datetime.datetime.strptime(last_msg.embeds[0].fields[2].value, "%H:%M") # 3rd field is the time + print(day) + print(time) + # if day and time are upcoming, set the channel name to the event. otherwise "No upcoming events" + if day > datetime.datetime.now() or (day == datetime.datetime.now() and time > datetime.datetime.now()): + await next_event_channel.edit(name="Next Event: " + last_msg.embeds[0].title) + else: + await next_event_channel.edit(name="No upcoming events...") + + + async def on_ready(self): + bot.update_next_event.start() # Init logger diff --git a/micro/static/constants.py b/micro/static/constants.py index 27c62af..bffcda9 100644 --- a/micro/static/constants.py +++ b/micro/static/constants.py @@ -17,6 +17,8 @@ MEMBERCOUNT_CHANNEL = 0 LOG_CHANNEL = 0 MODERATION_CHANNEL = 0 +EVENTS_CHANNEL = 1141375227771760700 # The channel where the event webhooks gets sent to +NEXT_EVENT_CHANNEL = 1141374906056052766 # Locked voice channel which just shows the next events BOT_COLOR = 0x00AFB1 USER_COMMANDS = { diff --git a/micro/tests/request_test.py b/micro/tests/request_test.py index c4562f1..3c7d55e 100644 --- a/micro/tests/request_test.py +++ b/micro/tests/request_test.py @@ -3,7 +3,7 @@ body = { "title" : "EKoders - Sales Force", "image": "https://codershq0.blob.core.windows.net/media/event/image/e-koders-sales-force_sIJIj6B.jpg", - "date_time": "2022-04-23T18:25:43.511Z", + "date_time": "2022-04-23 12:00", "duration": 3, "short_description": "Salesforce seminars series provided by EK koders(Emirates Airlines)", "event_link": "", From d3923fa329eb76ffae73863ef08edf0a1be4c7c4 Mon Sep 17 00:00:00 2001 From: Osama Alhennawi Date: Wed, 16 Aug 2023 18:54:56 +0400 Subject: [PATCH 11/26] Added Upcoming Events channel and task loop (Fixed and working). MIGHT NEED TO MAKE THE INTERVAL MORE THAN 5 MINUTES!! --- micro/__main__.py | 15 +++++++-------- micro/tests/request_test.py | 2 +- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/micro/__main__.py b/micro/__main__.py index 5a38063..971c008 100644 --- a/micro/__main__.py +++ b/micro/__main__.py @@ -65,7 +65,7 @@ async def create_pool(self, dsn): return msg - @tasks.loop(seconds=2) + @tasks.loop(seconds=60) async def update_next_event(self): try: events_channel = await self.fetch_channel(EVENTS_CHANNEL) @@ -77,13 +77,12 @@ async def update_next_event(self): await next_event_channel.edit(name="No upcoming events...") return last_msg = events_channel.last_message - day = datetime.datetime.strptime(last_msg.embeds[0].fields[1].value, "%Y-%m-%d") # 2nd field is the date - time = datetime.datetime.strptime(last_msg.embeds[0].fields[2].value, "%H:%M") # 3rd field is the time - print(day) - print(time) - # if day and time are upcoming, set the channel name to the event. otherwise "No upcoming events" - if day > datetime.datetime.now() or (day == datetime.datetime.now() and time > datetime.datetime.now()): - await next_event_channel.edit(name="Next Event: " + last_msg.embeds[0].title) + day_value = last_msg.embeds[0].fields[1].value + time_value = last_msg.embeds[0].fields[2].value + date = datetime.datetime.strptime(f"{day_value} {time_value}", "%Y-%m-%d %H:%M") + # if day and time are upcoming, set the channel name to the event. otherwise "No upcoming events" + if date > datetime.datetime.now(): + await next_event_channel.edit(name=last_msg.embeds[0].title) else: await next_event_channel.edit(name="No upcoming events...") diff --git a/micro/tests/request_test.py b/micro/tests/request_test.py index 3c7d55e..123e403 100644 --- a/micro/tests/request_test.py +++ b/micro/tests/request_test.py @@ -3,7 +3,7 @@ body = { "title" : "EKoders - Sales Force", "image": "https://codershq0.blob.core.windows.net/media/event/image/e-koders-sales-force_sIJIj6B.jpg", - "date_time": "2022-04-23 12:00", + "date_time": "2023-08-16 20:00", "duration": 3, "short_description": "Salesforce seminars series provided by EK koders(Emirates Airlines)", "event_link": "", From d50a4f496b7e9912ab43761874ee1dbed8ec595b Mon Sep 17 00:00:00 2001 From: Osama Alhennawi Date: Wed, 16 Aug 2023 19:08:43 +0400 Subject: [PATCH 12/26] Set tasks loop interval for 10 minutes --- micro/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/micro/__main__.py b/micro/__main__.py index 971c008..900b8a9 100644 --- a/micro/__main__.py +++ b/micro/__main__.py @@ -65,7 +65,7 @@ async def create_pool(self, dsn): return msg - @tasks.loop(seconds=60) + @tasks.loop(minutes=10) async def update_next_event(self): try: events_channel = await self.fetch_channel(EVENTS_CHANNEL) From 1ce86ba38ee696d0f055c8b308019fa430e9a489 Mon Sep 17 00:00:00 2001 From: osam7a Date: Wed, 16 Aug 2023 21:47:12 +0400 Subject: [PATCH 13/26] Saved all Channel objects as attributes for DiscordBot, and added Member Count channel logic --- micro/__main__.py | 35 ++++++++++++++++++++--------------- micro/static/constants.py | 2 +- 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/micro/__main__.py b/micro/__main__.py index 900b8a9..633e5e1 100644 --- a/micro/__main__.py +++ b/micro/__main__.py @@ -14,7 +14,7 @@ from classes import Database from logger import logger from static.paths import COGS_DIR -from static.constants import EVENTS_CHANNEL, NEXT_EVENT_CHANNEL +from static.constants import EVENTS_CHANNEL, NEXT_EVENT_CHANNEL, MEMBERCOUNT_CHANNEL from API import app from logging import Logger @@ -33,7 +33,9 @@ def __init__(self, logger: Logger, *args, **kwargs): # Setup logger self.logger = logger - + + # Setup channels + super().__init__(*args, activity=activity, intents=intents, **kwargs) async def setup_hook(self): @@ -66,29 +68,32 @@ async def create_pool(self, dsn): return msg @tasks.loop(minutes=10) - async def update_next_event(self): - try: - events_channel = await self.fetch_channel(EVENTS_CHANNEL) - next_event_channel = await self.fetch_channel(NEXT_EVENT_CHANNEL) - except discord.errors.NotFound: - self.logger.error("Couldn't find the events channel, ignoring exception") + async def update(self): + # Update upcoming event channel + if not self.events_channel.last_message: + await self.next_event_channel.edit(name="No upcoming events...") return - if not events_channel.last_message: - await next_event_channel.edit(name="No upcoming events...") - return - last_msg = events_channel.last_message + last_msg = self.events_channel.last_message day_value = last_msg.embeds[0].fields[1].value time_value = last_msg.embeds[0].fields[2].value date = datetime.datetime.strptime(f"{day_value} {time_value}", "%Y-%m-%d %H:%M") # if day and time are upcoming, set the channel name to the event. otherwise "No upcoming events" if date > datetime.datetime.now(): - await next_event_channel.edit(name=last_msg.embeds[0].title) + await self.next_event_channel.edit(name=last_msg.embeds[0].title) else: - await next_event_channel.edit(name="No upcoming events...") + await self.next_event_channel.edit(name="No upcoming events...") + + # Update member count channel + await self.membercount_ch.edit(name=f"Member Count: {len(self.users)}") async def on_ready(self): - bot.update_next_event.start() + self.membercount_ch = await self.fetch_channel(MEMBERCOUNT_CHANNEL) + self.events_channel = await self.fetch_channel(EVENTS_CHANNEL) + self.next_event_channel = await self.fetch_channel(NEXT_EVENT_CHANNEL) + await self.update() + self.update.start() + await self.membercount_ch.edit(name=f"Member Count: {len(self.users)}") # Init logger diff --git a/micro/static/constants.py b/micro/static/constants.py index bffcda9..8fcafc5 100644 --- a/micro/static/constants.py +++ b/micro/static/constants.py @@ -14,7 +14,7 @@ If you are still unsure then ask on <#947073932719190086> we will help guide you to a task that you will enjoy the most. Have fun! """ -MEMBERCOUNT_CHANNEL = 0 +MEMBERCOUNT_CHANNEL = 1141423539124175019 LOG_CHANNEL = 0 MODERATION_CHANNEL = 0 EVENTS_CHANNEL = 1141375227771760700 # The channel where the event webhooks gets sent to From 527a2a512570af1b4ec9b012dba627dc9a254398 Mon Sep 17 00:00:00 2001 From: osam7a Date: Wed, 16 Aug 2023 22:21:08 +0400 Subject: [PATCH 14/26] Changed "no events upcoming" to "no events yet" --- micro/__main__.py | 2 +- micro/tests/request_test.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/micro/__main__.py b/micro/__main__.py index 633e5e1..43762ec 100644 --- a/micro/__main__.py +++ b/micro/__main__.py @@ -71,7 +71,7 @@ async def create_pool(self, dsn): async def update(self): # Update upcoming event channel if not self.events_channel.last_message: - await self.next_event_channel.edit(name="No upcoming events...") + await self.next_event_channel.edit(name="No events yet...") return last_msg = self.events_channel.last_message day_value = last_msg.embeds[0].fields[1].value diff --git a/micro/tests/request_test.py b/micro/tests/request_test.py index 123e403..a726c21 100644 --- a/micro/tests/request_test.py +++ b/micro/tests/request_test.py @@ -3,7 +3,7 @@ body = { "title" : "EKoders - Sales Force", "image": "https://codershq0.blob.core.windows.net/media/event/image/e-koders-sales-force_sIJIj6B.jpg", - "date_time": "2023-08-16 20:00", + "date_time": "2023-08-17 20:00", "duration": 3, "short_description": "Salesforce seminars series provided by EK koders(Emirates Airlines)", "event_link": "", From 49f6c27f75882b92f79fedb5bee4fbdae1cf7591 Mon Sep 17 00:00:00 2001 From: osam7a Date: Wed, 16 Aug 2023 22:39:39 +0400 Subject: [PATCH 15/26] Added mentions field in API for mentions on top of the webhook --- micro/API.py | 5 ++++- micro/static/constants.py | 1 + micro/tests/request_test.py | 3 ++- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/micro/API.py b/micro/API.py index fdc3a43..90c6cac 100644 --- a/micro/API.py +++ b/micro/API.py @@ -4,6 +4,7 @@ from datetime import datetime from classes import CEmbed +from static.constants import EVENTS_ROLE_ID import os, aiohttp @@ -16,6 +17,7 @@ class Event(BaseModel): event_link: str event_location: str seats: int + mentions: list[int] app = FastAPI() @@ -75,5 +77,6 @@ async def publish_event(event: Event): WB_URL = os.getenv("WEBHOOK_URL") async with aiohttp.ClientSession() as session: + mentions = " ".join([f"<@&{id}>" for id in event.mentions]) webhook = Webhook.from_url(WB_URL, session=session) - await webhook.send(embed=get_embed(event)) + await webhook.send(content=f"<@&{EVENTS_ROLE_ID}>{mentions}", embed=get_embed(event)) diff --git a/micro/static/constants.py b/micro/static/constants.py index 8fcafc5..a288a76 100644 --- a/micro/static/constants.py +++ b/micro/static/constants.py @@ -19,6 +19,7 @@ MODERATION_CHANNEL = 0 EVENTS_CHANNEL = 1141375227771760700 # The channel where the event webhooks gets sent to NEXT_EVENT_CHANNEL = 1141374906056052766 # Locked voice channel which just shows the next events +EVENTS_ROLE_ID = 1141438382740873339 BOT_COLOR = 0x00AFB1 USER_COMMANDS = { diff --git a/micro/tests/request_test.py b/micro/tests/request_test.py index a726c21..347b2e5 100644 --- a/micro/tests/request_test.py +++ b/micro/tests/request_test.py @@ -8,6 +8,7 @@ "short_description": "Salesforce seminars series provided by EK koders(Emirates Airlines)", "event_link": "", "event_location" : "Coders HQ, Emirates Towers, Dubai", - "seats" : 15 + "seats" : 15, + "mentions": [1141438457772789762] } resp = requests.post('http://127.0.0.1:8000/bot/event',json=body) \ No newline at end of file From ccfa8e10634fd6f14230871baa6fad34c925a73d Mon Sep 17 00:00:00 2001 From: osam7a Date: Wed, 16 Aug 2023 22:59:09 +0400 Subject: [PATCH 16/26] Switched from tasks loop to using normal listeners on_message with detecting events channel ID for the upcoming event on_member_join for updating member count suggested by Delrius --- micro/__main__.py | 31 ++----------------------------- micro/cogs/listeners.py | 14 +++++++++++++- micro/tests/request_test.py | 2 +- 3 files changed, 16 insertions(+), 31 deletions(-) diff --git a/micro/__main__.py b/micro/__main__.py index 43762ec..eec871f 100644 --- a/micro/__main__.py +++ b/micro/__main__.py @@ -34,8 +34,6 @@ def __init__(self, logger: Logger, *args, **kwargs): # Setup logger self.logger = logger - # Setup channels - super().__init__(*args, activity=activity, intents=intents, **kwargs) async def setup_hook(self): @@ -66,35 +64,10 @@ async def create_pool(self, dsn): print(msg) return msg - - @tasks.loop(minutes=10) - async def update(self): - # Update upcoming event channel - if not self.events_channel.last_message: - await self.next_event_channel.edit(name="No events yet...") - return - last_msg = self.events_channel.last_message - day_value = last_msg.embeds[0].fields[1].value - time_value = last_msg.embeds[0].fields[2].value - date = datetime.datetime.strptime(f"{day_value} {time_value}", "%Y-%m-%d %H:%M") - # if day and time are upcoming, set the channel name to the event. otherwise "No upcoming events" - if date > datetime.datetime.now(): - await self.next_event_channel.edit(name=last_msg.embeds[0].title) - else: - await self.next_event_channel.edit(name="No upcoming events...") - - # Update member count channel - await self.membercount_ch.edit(name=f"Member Count: {len(self.users)}") - async def on_ready(self): - self.membercount_ch = await self.fetch_channel(MEMBERCOUNT_CHANNEL) - self.events_channel = await self.fetch_channel(EVENTS_CHANNEL) - self.next_event_channel = await self.fetch_channel(NEXT_EVENT_CHANNEL) - await self.update() - self.update.start() - await self.membercount_ch.edit(name=f"Member Count: {len(self.users)}") - + members_ch = self.get_channel(MEMBERCOUNT_CHANNEL) + await members_ch.edit(name=f"Member Count: {len(self.users)}") # Init logger logger = logger() diff --git a/micro/cogs/listeners.py b/micro/cogs/listeners.py index afa089a..840966c 100644 --- a/micro/cogs/listeners.py +++ b/micro/cogs/listeners.py @@ -1,4 +1,5 @@ import discord +import datetime from discord.utils import utcnow from discord.ext import commands @@ -77,7 +78,8 @@ async def on_member_join(self, member: discord.Member): self.bot.logger.info( f"New User: {member} ({member.id}) | Welcome Message sent, logged" ) - + members_ch = self.bot.get_channel(constants.MEMBERCOUNT_CHANNEL) + await members_ch.edit(name=f"Member Count: {guild.member_count}") except Exception as e: self.bot.logger.error( f"Error in running on_member_join event for {member}: {e}" @@ -200,6 +202,16 @@ async def on_raw_message_edit(self, payload: discord.RawMessageUpdateEvent): @commands.Cog.listener() async def on_message(self, message: discord.Message): try: + if message.channel.id == constants.EVENTS_CHANNEL: + if message.webhook_id: + embed = message.embeds[0] + next_event_channel = await self.bot.fetch_channel(constants.NEXT_EVENT_CHANNEL) + date = datetime.datetime.strptime(f"{embed.fields[1].value} {embed.fields[2].value}", "%Y-%m-%d %H:%M") + if date >= datetime.datetime.now(): + await next_event_channel.edit(name=embed.title) + else: + await next_event_channel.edit(name="No upcoming events") + return # ignore it if it's from the bot if message.author == self.bot.user: return diff --git a/micro/tests/request_test.py b/micro/tests/request_test.py index 347b2e5..0d9eb93 100644 --- a/micro/tests/request_test.py +++ b/micro/tests/request_test.py @@ -1,7 +1,7 @@ import requests body = { - "title" : "EKoders - Sales Force", + "title" : "Cool Event!!!", "image": "https://codershq0.blob.core.windows.net/media/event/image/e-koders-sales-force_sIJIj6B.jpg", "date_time": "2023-08-17 20:00", "duration": 3, From 6885e4fdb1b737239fb617b7c7b91ab8881645a5 Mon Sep 17 00:00:00 2001 From: osam7a Date: Sat, 19 Aug 2023 18:57:07 +0400 Subject: [PATCH 17/26] Removed unused import from misc.py --- micro/static/misc.py | 1 - 1 file changed, 1 deletion(-) diff --git a/micro/static/misc.py b/micro/static/misc.py index 5d35eec..afa0f7e 100644 --- a/micro/static/misc.py +++ b/micro/static/misc.py @@ -1,4 +1,3 @@ -import discord from discord import Guild, Interaction, Message, TextChannel, Member, Embed from discord.ext.commands import Context, Bot From 45bd9e1b27528af55183fa2dbc4aed7777d4b1da Mon Sep 17 00:00:00 2001 From: osam7a Date: Sat, 19 Aug 2023 18:57:45 +0400 Subject: [PATCH 18/26] Remove unused imports from issue_list view --- micro/views/issue_list.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/micro/views/issue_list.py b/micro/views/issue_list.py index 1562e47..d12c49e 100644 --- a/micro/views/issue_list.py +++ b/micro/views/issue_list.py @@ -1,6 +1,6 @@ import discord -from discord.ui import View, button, Button, select, Select, Modal, TextInput -from discord import Interaction, Embed, Message +from discord.ui import View, button, Button +from discord import Interaction, Embed from classes import GitHub, CEmbed From 0d12a9a681d0dd14cd9ac0827e1bcbbd1dc7aad9 Mon Sep 17 00:00:00 2001 From: osam7a Date: Sat, 19 Aug 2023 19:03:19 +0400 Subject: [PATCH 19/26] Basic paginator class --- micro/classes/__init__.py | 3 ++- micro/classes/pagination.py | 24 ++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 micro/classes/pagination.py diff --git a/micro/classes/__init__.py b/micro/classes/__init__.py index 4adf70e..a6a2a3d 100644 --- a/micro/classes/__init__.py +++ b/micro/classes/__init__.py @@ -3,4 +3,5 @@ from .github import GitHub from .question_query import QuestionQuery from .resources import Resources -from .colored_embed import CEmbed \ No newline at end of file +from .colored_embed import CEmbed +from .pagination import Paginator \ No newline at end of file diff --git a/micro/classes/pagination.py b/micro/classes/pagination.py new file mode 100644 index 0000000..b6fc71f --- /dev/null +++ b/micro/classes/pagination.py @@ -0,0 +1,24 @@ +class Paginator: + def __init__(self, items: list, page_size: int): + self.items = items + self.page_size = page_size + self.curr_page = 1 + self.curr_index = 0 + self.total_pages = len(self.items) // self.page_size + 1 + + def get_page(self): + return self.items[self.curr_index:self.curr_index+self.page_size] + + def next_page(self): + if self.curr_index + self.page_size < len(self.items): + self.curr_index += self.page_size + self.curr_page += 1 + return self.get_page() + return None + + def prev_page(self): + if self.curr_index - self.page_size >= 0: + self.curr_index -= self.page_size + self.curr_page -= 1 + return self.get_page() + return None \ No newline at end of file From a8142ac67e75a51f00f48134e7a5dedde44609b2 Mon Sep 17 00:00:00 2001 From: osam7a Date: Sat, 19 Aug 2023 19:12:00 +0400 Subject: [PATCH 20/26] Removed pagination in issue listing (Pagination is handled within the issue list command/view) --- micro/classes/github.py | 4 +--- micro/views/issue_list.py | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/micro/classes/github.py b/micro/classes/github.py index fe406fe..c90803c 100644 --- a/micro/classes/github.py +++ b/micro/classes/github.py @@ -78,14 +78,12 @@ def get_issue(self, num: int) -> Issue.Issue | None: return issue def list_issues( - self, paginate: bool + self ) -> PaginatedList.PaginatedList | list[list] | None: """Returns the list of issues of the repo, or the paginated list of issue""" if not self.repo: return issues = self.repo.get_issues() - if paginate: - return self.prepare_pagination(issues) return issues def create_issue( diff --git a/micro/views/issue_list.py b/micro/views/issue_list.py index d12c49e..22943f2 100644 --- a/micro/views/issue_list.py +++ b/micro/views/issue_list.py @@ -2,7 +2,7 @@ from discord.ui import View, button, Button from discord import Interaction, Embed -from classes import GitHub, CEmbed +from classes import GitHub, CEmbed, Paginator class IssueListView(View): def __init__(self, pages: list[Embed] = None): From c50a1c126d2ef6302776aa625b2e7f8634b3343a Mon Sep 17 00:00:00 2001 From: osam7a Date: Sat, 19 Aug 2023 19:24:49 +0400 Subject: [PATCH 21/26] Better issue-list view system (NOT TESTED!!) --- micro/classes/__init__.py | 2 +- micro/classes/{pagination.py => paginator.py} | 0 micro/cogs/github.py | 24 ++--- micro/views/issue_list.py | 91 +++++-------------- 4 files changed, 33 insertions(+), 84 deletions(-) rename micro/classes/{pagination.py => paginator.py} (100%) diff --git a/micro/classes/__init__.py b/micro/classes/__init__.py index a6a2a3d..d35289a 100644 --- a/micro/classes/__init__.py +++ b/micro/classes/__init__.py @@ -4,4 +4,4 @@ from .question_query import QuestionQuery from .resources import Resources from .colored_embed import CEmbed -from .pagination import Paginator \ No newline at end of file +from .paginator import Paginator \ No newline at end of file diff --git a/micro/classes/pagination.py b/micro/classes/paginator.py similarity index 100% rename from micro/classes/pagination.py rename to micro/classes/paginator.py diff --git a/micro/cogs/github.py b/micro/cogs/github.py index ada1612..d2c2c73 100644 --- a/micro/cogs/github.py +++ b/micro/cogs/github.py @@ -33,25 +33,19 @@ async def issue_list(self, interaction: discord.Interaction): ) return await interaction.followup.send(embed=empty_embed) - pages = [] + issues_str = [] - for idx, page in enumerate(issues, start=1): - embed = CEmbed(title="Issues") - embed.set_footer(text=f"Page {idx} - {len(issues)}") - for issue in page: - embed.add_field( - name=f"{issue.number}. {issue.title}", - value=f"{issue.html_url}", - inline=False, - ) + for issue in issues: + issues_str.append(f"## - [#{issue.number} {issue.title}]({issue.html_url})") - pages.append(embed) + view = IssueListView(issues=issues, page_size=5) - view = IssueListView(pages=pages) - - current_page = 0 + curr_page = view.paginator.get_page() + + embed = CEmbed(title="Issues", description=curr_page) + embed.set_footer(text=f"Page {view.paginator.curr_page + 1} - {view.paginator.total_pages}") - await interaction.followup.send(embed=pages[current_page], view=view) + await interaction.followup.send(embed=embed, view=view) self.bot.logger.info( f"Successfully executed /issue list command for {interaction.user}" ) diff --git a/micro/views/issue_list.py b/micro/views/issue_list.py index 22943f2..87cfc08 100644 --- a/micro/views/issue_list.py +++ b/micro/views/issue_list.py @@ -5,11 +5,10 @@ from classes import GitHub, CEmbed, Paginator class IssueListView(View): - def __init__(self, pages: list[Embed] = None): + def __init__(self, issues: list[str] = None, page_size: int = 5): self.gh = GitHub() - self.pages = pages - self.current_page = 0 + self.paginator = Paginator(issues, page_size) super().__init__(timeout=None) @@ -18,8 +17,6 @@ def __init__(self, pages: list[Embed] = None): ) async def prev_page(self, interaction: Interaction, button: Button): req_user = interaction.message.interaction.user - if not self.pages: - self.setup(interaction) if interaction.user != req_user: return await interaction.response.send_message( @@ -27,16 +24,15 @@ async def prev_page(self, interaction: Interaction, button: Button): ephemeral=True, ) - if not self.current_page < 0: - self.prev_page.disabled = False - self.next_page.disabled = False - self.current_page -= 1 - - if self.current_page == 0: - self.prev_page.disabled = True - - current_page = self.pages[self.current_page] - await interaction.response.edit_message(embed=current_page, view=self) + current_page = self.paginator.prev_page() + if current_page is None: + return + emb = CEmbed(title="Issues", description=current_page) + emb.set_footer(text=f"Page {self.paginator.current_page} - {self.paginator.total_pages}") + return await interaction.response.send_message( + embed=emb, + view=self + ) @button( label="Next Page", @@ -45,32 +41,23 @@ async def prev_page(self, interaction: Interaction, button: Button): ) async def next_page(self, interaction: Interaction, button: Button): req_user = interaction.message.interaction.user - if not self.pages: - self.setup(interaction) - - if self.get_total_page(interaction) == 1: - self.next_page.disabled = True - await interaction.message.edit(view=self) - return await interaction.response.send_message( - "There is only one page", ephemeral=True - ) if interaction.user != req_user: return await interaction.response.send_message( "Only the owner of the message can control the pagination menu!", ephemeral=True, ) - - if not self.current_page + 1 >= len(self.pages): - self.next_page.disabled = False - self.prev_page.disabled = False - self.current_page += 1 - - if self.current_page == len(self.pages) - 1: - self.next_page.disabled = True - - current_page = self.pages[self.current_page] - await interaction.response.edit_message(embed=current_page, view=self) + + current_page = self.paginator.next_page() + if current_page is None: + return + + emb = CEmbed(title="Issues", description=current_page) + emb.set_footer(text=f"Page {self.paginator.current_page} - {self.paginator.total_pages}") + return await interaction.response.send_message( + embed=emb, + view=self + ) @button( label="Exit", @@ -90,36 +77,4 @@ async def quit(self, interaction: Interaction, button: Button): await interaction.message.delete() except: embed = CEmbed(description="Unable to delete the interaction") - await interaction.response.send_message(embed=embed, ephemeral=True) - - def setup(self, interaction: Interaction): - issue_list = self.gh.list_issues(paginate=True) - self.pages = self.make_pages(issue_list) - self.current_page = self.get_current_page(interaction) - 1 # For indexing - self.total_pages = self.get_total_page(interaction) - - def make_pages(self, paginated: list[list]) -> list[Embed]: - pages = [] - for idx, page in enumerate(paginated, start=1): - embed = CEmbed(title="Issues") - embed.set_footer(text=f"Page {idx} - {len(paginated)}") - for issue in page: - embed.add_field( - name=f"{issue.number}. {issue.title}", - value=f"{issue.html_url}", - inline=False, - ) - - pages.append(embed) - - return pages - - def get_current_page(self, interaction: Interaction) -> int: - page_text = interaction.message.embeds[0].footer.text - current_page = page_text.split(" - ")[0].split(" ")[-1] - return int(current_page) - - def get_total_page(self, interaction: Interaction) -> int: - page_text = interaction.message.embeds[0].footer.text - total_page = page_text.split(" - ")[-1] - return int(total_page) \ No newline at end of file + await interaction.response.send_message(embed=embed, ephemeral=True) \ No newline at end of file From cb642541dc8e74316ada70becec0beda17be2ca9 Mon Sep 17 00:00:00 2001 From: osam7a Date: Sat, 19 Aug 2023 20:31:51 +0400 Subject: [PATCH 22/26] unused import in listeners --- micro/cogs/listeners.py | 1 - 1 file changed, 1 deletion(-) diff --git a/micro/cogs/listeners.py b/micro/cogs/listeners.py index 840966c..99179f9 100644 --- a/micro/cogs/listeners.py +++ b/micro/cogs/listeners.py @@ -8,7 +8,6 @@ GitHub, CEmbed ) -from views import ModerationView from static.misc import get_extra_channel, get_current_guild import static.constants as constants From feeeb15d5f3c56a764c64f9dab69058860fb596f Mon Sep 17 00:00:00 2001 From: osam7a Date: Sat, 19 Aug 2023 20:32:16 +0400 Subject: [PATCH 23/26] unused import in github class --- micro/classes/github.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/micro/classes/github.py b/micro/classes/github.py index c90803c..eace22b 100644 --- a/micro/classes/github.py +++ b/micro/classes/github.py @@ -1,4 +1,4 @@ -import os, requests, discord +import os, requests from typing import Iterable from math import ceil From 1b8026cd4519004e086e4c719235de60b174cbfd Mon Sep 17 00:00:00 2001 From: osam7a Date: Sat, 19 Aug 2023 23:20:48 +0400 Subject: [PATCH 24/26] fixes for the new paginator system, and made it persistent --- micro/classes/github.py | 6 +-- micro/cogs/github.py | 10 ++--- micro/views/issue_list.py | 85 +++++++++++++++++++++++++++------------ 3 files changed, 67 insertions(+), 34 deletions(-) diff --git a/micro/classes/github.py b/micro/classes/github.py index eace22b..b41f958 100644 --- a/micro/classes/github.py +++ b/micro/classes/github.py @@ -2,7 +2,7 @@ from typing import Iterable from math import ceil -from github import Github, Issue, PaginatedList +from github import Github, Issue from github.GithubException import UnknownObjectException, BadCredentialsException @@ -79,11 +79,11 @@ def get_issue(self, num: int) -> Issue.Issue | None: def list_issues( self - ) -> PaginatedList.PaginatedList | list[list] | None: + ) -> list[Issue.Issue] | None: """Returns the list of issues of the repo, or the paginated list of issue""" if not self.repo: return - issues = self.repo.get_issues() + issues = list(self.repo.get_issues()) return issues def create_issue( diff --git a/micro/cogs/github.py b/micro/cogs/github.py index d2c2c73..66f6e04 100644 --- a/micro/cogs/github.py +++ b/micro/cogs/github.py @@ -22,7 +22,7 @@ async def issue_list(self, interaction: discord.Interaction): await self.refresh_env() await interaction.response.defer() try: - issues = self.gh.list_issues(paginate=True) + issues = self.gh.list_issues() if issues is None: embed = await self.unable_to_connect() return await interaction.followup.send(embed=embed) @@ -36,14 +36,14 @@ async def issue_list(self, interaction: discord.Interaction): issues_str = [] for issue in issues: - issues_str.append(f"## - [#{issue.number} {issue.title}]({issue.html_url})") + issues_str.append(f"### - [#{issue.number}]({issue.html_url}) {issue.title}") - view = IssueListView(issues=issues, page_size=5) + view = IssueListView(issues=issues_str, page_size=5) curr_page = view.paginator.get_page() - embed = CEmbed(title="Issues", description=curr_page) - embed.set_footer(text=f"Page {view.paginator.curr_page + 1} - {view.paginator.total_pages}") + embed = CEmbed(title="Issues", description='\n'.join(curr_page)) + embed.set_footer(text=f"Page {view.paginator.curr_page} - {view.paginator.total_pages}") await interaction.followup.send(embed=embed, view=view) self.bot.logger.info( diff --git a/micro/views/issue_list.py b/micro/views/issue_list.py index 87cfc08..65dca92 100644 --- a/micro/views/issue_list.py +++ b/micro/views/issue_list.py @@ -1,14 +1,17 @@ import discord from discord.ui import View, button, Button -from discord import Interaction, Embed +from discord import Interaction from classes import GitHub, CEmbed, Paginator class IssueListView(View): def __init__(self, issues: list[str] = None, page_size: int = 5): self.gh = GitHub() + if issues is not None: + self.paginator = Paginator(issues, page_size) + else: self.paginator = None - self.paginator = Paginator(issues, page_size) + self.page_size = page_size super().__init__(timeout=None) @@ -18,22 +21,52 @@ def __init__(self, issues: list[str] = None, page_size: int = 5): async def prev_page(self, interaction: Interaction, button: Button): req_user = interaction.message.interaction.user + if self.paginator == None: + self.paginator = self.get_paginator(interaction) + if interaction.user != req_user: return await interaction.response.send_message( "Only the owner of the message can control the pagination menu!", ephemeral=True, ) + + if self.paginator.curr_page == 2: self.prev_page.disabled = True + if self.paginator.curr_page == self.paginator.total_pages: self.next_page.disabled = True current_page = self.paginator.prev_page() if current_page is None: return - emb = CEmbed(title="Issues", description=current_page) - emb.set_footer(text=f"Page {self.paginator.current_page} - {self.paginator.total_pages}") - return await interaction.response.send_message( + emb = CEmbed(title="Issues", description='\n'.join(current_page)) + emb.set_footer(text=f"Page {self.paginator.curr_page} - {self.paginator.total_pages}") + return await interaction.response.edit_message( embed=emb, view=self ) + @button( + label="Exit", + emoji="🗑️", + style=discord.ButtonStyle.danger, + custom_id="quit_button", + ) + async def quit(self, interaction: Interaction, button: Button): + req_user = interaction.message.interaction.user + + if self.paginator == None: + self.paginator = self.get_paginator(interaction) + + if interaction.user != req_user: + return await interaction.response.send_message( + "Only the owner of the message can control the pagination menu!", + ephemeral=True, + ) + + try: + await interaction.message.delete() + except: + embed = CEmbed(description="Unable to delete the interaction") + await interaction.response.send_message(embed=embed, ephemeral=True) + @button( label="Next Page", emoji="⏩", @@ -42,39 +75,39 @@ async def prev_page(self, interaction: Interaction, button: Button): async def next_page(self, interaction: Interaction, button: Button): req_user = interaction.message.interaction.user + if self.paginator == None: + self.paginator = self.get_paginator(interaction) + if interaction.user != req_user: return await interaction.response.send_message( "Only the owner of the message can control the pagination menu!", ephemeral=True, ) + if self.paginator.curr_page == 1: self.prev_page.disabled = False + if self.paginator.curr_page == self.paginator.total_pages - 1: self.next_page.disabled = True + current_page = self.paginator.next_page() if current_page is None: return - emb = CEmbed(title="Issues", description=current_page) - emb.set_footer(text=f"Page {self.paginator.current_page} - {self.paginator.total_pages}") - return await interaction.response.send_message( + emb = CEmbed(title="Issues", description='\n'.join(current_page)) + emb.set_footer(text=f"Page {self.paginator.curr_page} - {self.paginator.total_pages}") + return await interaction.response.edit_message( embed=emb, view=self ) - @button( - label="Exit", - emoji="🗑️", - style=discord.ButtonStyle.danger, - custom_id="quit_button", - ) - async def quit(self, interaction: Interaction, button: Button): - req_user = interaction.message.interaction.user - if interaction.user != req_user: - return await interaction.response.send_message( - "Only the owner of the message can control the pagination menu!", - ephemeral=True, - ) + def get_paginator(self, interaction): + issues = [] + for issue in self.gh.list_issues(): + issues.append(f"### - [#{issue.number}]({issue.html_url}) {issue.title}") + + page_txt = interaction.message.embeds[0].footer.text + curr_page = int(page_txt.split("Page ")[1].split(" - ")[0]) + paginator = Paginator(issues, self.page_size) - try: - await interaction.message.delete() - except: - embed = CEmbed(description="Unable to delete the interaction") - await interaction.response.send_message(embed=embed, ephemeral=True) \ No newline at end of file + paginator.curr_index = curr_page - 1 + paginator.curr_page = curr_page + + return paginator \ No newline at end of file From 5dd6fa66af0aaf39c922e803256801397f6cbd70 Mon Sep 17 00:00:00 2001 From: osam7a Date: Sun, 20 Aug 2023 16:04:37 +0400 Subject: [PATCH 25/26] Added some doc strings, typehinting for the code I edited previously --- micro/classes/github.py | 2 +- micro/classes/paginator.py | 22 ++++++++++++++++------ micro/cogs/github.py | 2 ++ micro/views/issue_list.py | 14 ++++++++++++-- 4 files changed, 31 insertions(+), 9 deletions(-) diff --git a/micro/classes/github.py b/micro/classes/github.py index b41f958..df22d5e 100644 --- a/micro/classes/github.py +++ b/micro/classes/github.py @@ -83,7 +83,7 @@ def list_issues( """Returns the list of issues of the repo, or the paginated list of issue""" if not self.repo: return - issues = list(self.repo.get_issues()) + issues = list(self.repo.get_issues()) # Convert a PaginatedList into a normal list of the issues return issues def create_issue( diff --git a/micro/classes/paginator.py b/micro/classes/paginator.py index b6fc71f..b9c3180 100644 --- a/micro/classes/paginator.py +++ b/micro/classes/paginator.py @@ -2,21 +2,31 @@ class Paginator: def __init__(self, items: list, page_size: int): self.items = items self.page_size = page_size - self.curr_page = 1 - self.curr_index = 0 - self.total_pages = len(self.items) // self.page_size + 1 + self.curr_page = 1 # For user-end + self.curr_index = 0 # For back-end + self.total_pages = len(self.items) // self.page_size + 1 # For user-end as well - def get_page(self): + def get_page(self) -> list: + """ + Returns the items of the current page + """ return self.items[self.curr_index:self.curr_index+self.page_size] - def next_page(self): + def next_page(self) -> list | None: + """ + Returns the items of the next page (None if there is no next page) + """ if self.curr_index + self.page_size < len(self.items): self.curr_index += self.page_size self.curr_page += 1 return self.get_page() + # No next page return None - def prev_page(self): + def prev_page(self) -> list | None: + """ + Returns the items of the previous page (None if there is no previous page) + """ if self.curr_index - self.page_size >= 0: self.curr_index -= self.page_size self.curr_page -= 1 diff --git a/micro/cogs/github.py b/micro/cogs/github.py index 66f6e04..248e9e1 100644 --- a/micro/cogs/github.py +++ b/micro/cogs/github.py @@ -33,6 +33,7 @@ async def issue_list(self, interaction: discord.Interaction): ) return await interaction.followup.send(embed=empty_embed) + # Format the list of issues into strings for the description issues_str = [] for issue in issues: @@ -40,6 +41,7 @@ async def issue_list(self, interaction: discord.Interaction): view = IssueListView(issues=issues_str, page_size=5) + # Get the current page from the paginator curr_page = view.paginator.get_page() embed = CEmbed(title="Issues", description='\n'.join(curr_page)) diff --git a/micro/views/issue_list.py b/micro/views/issue_list.py index 65dca92..03621e6 100644 --- a/micro/views/issue_list.py +++ b/micro/views/issue_list.py @@ -6,11 +6,13 @@ class IssueListView(View): def __init__(self, issues: list[str] = None, page_size: int = 5): + # page_size is the amount of issues per page + self.gh = GitHub() + if issues is not None: self.paginator = Paginator(issues, page_size) else: self.paginator = None - self.page_size = page_size super().__init__(timeout=None) @@ -22,6 +24,7 @@ async def prev_page(self, interaction: Interaction, button: Button): req_user = interaction.message.interaction.user if self.paginator == None: + # For persistent view self.paginator = self.get_paginator(interaction) if interaction.user != req_user: @@ -30,6 +33,7 @@ async def prev_page(self, interaction: Interaction, button: Button): ephemeral=True, ) + # Update disabled/enabled buttons if self.paginator.curr_page == 2: self.prev_page.disabled = True if self.paginator.curr_page == self.paginator.total_pages: self.next_page.disabled = True @@ -76,6 +80,7 @@ async def next_page(self, interaction: Interaction, button: Button): req_user = interaction.message.interaction.user if self.paginator == None: + # For persistent view self.paginator = self.get_paginator(interaction) if interaction.user != req_user: @@ -84,6 +89,7 @@ async def next_page(self, interaction: Interaction, button: Button): ephemeral=True, ) + # Update disabled/enabled buttons if self.paginator.curr_page == 1: self.prev_page.disabled = False if self.paginator.curr_page == self.paginator.total_pages - 1: self.next_page.disabled = True @@ -98,7 +104,11 @@ async def next_page(self, interaction: Interaction, button: Button): view=self ) - def get_paginator(self, interaction): + def get_paginator(self, interaction: Interaction) -> Paginator: + """ + !USED FOR PERSISTENT VIEW! + Returns a Paginator object based on the current page of the interaction message + """ issues = [] for issue in self.gh.list_issues(): issues.append(f"### - [#{issue.number}]({issue.html_url}) {issue.title}") From 9aea6b438e3557093e526a03f2b7a3ba6675d3b4 Mon Sep 17 00:00:00 2001 From: osam7a Date: Tue, 22 Aug 2023 17:24:10 +0400 Subject: [PATCH 26/26] split the `Commands` cog into `Miscellaneuos` and `Programming` --- micro/cogs/{commands.py => miscellaneous.py} | 76 +----------------- micro/cogs/programming.py | 83 ++++++++++++++++++++ 2 files changed, 87 insertions(+), 72 deletions(-) rename micro/cogs/{commands.py => miscellaneous.py} (73%) create mode 100644 micro/cogs/programming.py diff --git a/micro/cogs/commands.py b/micro/cogs/miscellaneous.py similarity index 73% rename from micro/cogs/commands.py rename to micro/cogs/miscellaneous.py index 513a35b..c5bb57c 100644 --- a/micro/cogs/commands.py +++ b/micro/cogs/miscellaneous.py @@ -5,18 +5,17 @@ from discord.utils import format_dt from discord.app_commands import Choice -from classes import Resources, CEmbed -from views import ResourcesView, RoleView +from classes import CEmbed +from views import RoleView from static.misc import get_current_guild import static.constants as constants -class Commands(commands.Cog): +class Miscellaneous(commands.Cog): def __init__(self, bot: commands.Bot): self.bot = bot - self.resources = Resources() @app_commands.command(name="chq", description=constants.USER_COMMANDS["chq"]) @app_commands.describe(option="Words that describe CHQ") @@ -79,73 +78,6 @@ async def count(self, interaction: discord.Interaction): self.bot.logger.error(f"Error sending a reply | {e}") - @app_commands.command( - name="resources", description=constants.USER_COMMANDS["resources"] - ) - @app_commands.describe(topic="Programming language that you want the resource for") - async def show_resources(self, interaction: discord.Interaction, topic: str): - await interaction.response.defer() - try: - self.resources.reload_resource() - - req_user = interaction.user - lang = topic - pages = [] - - data = self.resources.get_details(lang) - paginated = self.resources.prepare_pagination(data) - - for idx, page in enumerate(paginated, start=1): - embed = CEmbed(title=lang) - embed.set_footer(text=f"Page {idx} - {len(paginated)}") - - for d in page: - for category, contents in d.items(): - text = "" - if category == "Description": - text = contents - embed.add_field(name=category, value=text, inline=False) - else: - for content in contents: - title = content.split(" - ") - if len(title) > 1: - link = title[-1] - name = " - ".join(title[:-1]) - text += f"[{name}]({link})\n" - else: - link = content - text += f"{link}\n" - embed.add_field(name=category, value=text, inline=False) - - pages.append(embed) - - self.bot.logger.info( - f"Resource for {lang} was sent as requested by <@{req_user}>" - ) - - view = ResourcesView(pages, lang) - - await interaction.followup.send(embed=pages[view.current_page], view=view) - - except Exception as e: - embed_error = CEmbed( - description="Error in running /resources, try again later" - ) - await interaction.followup.send(embed=embed_error) - - self.bot.logger.error(f"Error in running /resources command: {e}") - - @show_resources.autocomplete("topic") - async def topic_autocomplete(self, interaction: discord.Interaction, current: str): - self.resources.reload_resource() - - topics = self.resources.get_all_langs() - return [ - Choice(name=lang, value=lang) - for lang in topics - if current.lower() in lang.lower() - ] - @app_commands.command(name="age", description=constants.USER_COMMANDS["age"]) @app_commands.describe(member="Member you want to see the age of, if any") @app_commands.guild_only() @@ -269,4 +201,4 @@ async def remove_role(self, interaction: discord.Interaction, role: Choice[str]) async def setup(bot): # async - await bot.add_cog(Commands(bot)) + await bot.add_cog(Miscellaneous(bot)) diff --git a/micro/cogs/programming.py b/micro/cogs/programming.py new file mode 100644 index 0000000..799b974 --- /dev/null +++ b/micro/cogs/programming.py @@ -0,0 +1,83 @@ +import discord +from discord import app_commands +from discord.ext import commands +from discord.app_commands import Choice + +from classes import Resources, CEmbed +from static import constants +from views import ResourcesView + +class Programming(commands.Cog): + def __init__(self, bot: commands.Bot): + self.bot = bot + self.resources = Resources() + + @app_commands.command( + name="resources", description=constants.USER_COMMANDS["resources"] + ) + @app_commands.describe(topic="Programming language that you want the resource for") + async def show_resources(self, interaction: discord.Interaction, topic: str): + await interaction.response.defer() + try: + self.resources.reload_resource() + + req_user = interaction.user + lang = topic + pages = [] + + data = self.resources.get_details(lang) + paginated = self.resources.prepare_pagination(data) + + for idx, page in enumerate(paginated, start=1): + embed = CEmbed(title=lang) + embed.set_footer(text=f"Page {idx} - {len(paginated)}") + + for d in page: + for category, contents in d.items(): + text = "" + if category == "Description": + text = contents + embed.add_field(name=category, value=text, inline=False) + else: + for content in contents: + title = content.split(" - ") + if len(title) > 1: + link = title[-1] + name = " - ".join(title[:-1]) + text += f"[{name}]({link})\n" + else: + link = content + text += f"{link}\n" + embed.add_field(name=category, value=text, inline=False) + + pages.append(embed) + + self.bot.logger.info( + f"Resource for {lang} was sent as requested by <@{req_user}>" + ) + + view = ResourcesView(pages, lang) + + await interaction.followup.send(embed=pages[view.current_page], view=view) + + except Exception as e: + embed_error = CEmbed( + description="Error in running /resources, try again later" + ) + await interaction.followup.send(embed=embed_error) + + self.bot.logger.error(f"Error in running /resources command: {e}") + + @show_resources.autocomplete("topic") + async def topic_autocomplete(self, interaction: discord.Interaction, current: str): + self.resources.reload_resource() + + topics = self.resources.get_all_langs() + return [ + Choice(name=lang, value=lang) + for lang in topics + if current.lower() in lang.lower() + ] + +async def setup(bot): # async + await bot.add_cog(Programming(bot)) \ No newline at end of file