From b315b2eb890a159a5cd31290b9904fc227cea972 Mon Sep 17 00:00:00 2001 From: Mantou <51238168+Mantou-9487@users.noreply.github.com> Date: Tue, 21 Nov 2023 20:36:43 +0800 Subject: [PATCH 01/18] Create README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..e94fd3b --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# KeyWord +A Discord Keyword Notification Bot. From dcee99085766172f0fc37730f54acbc38ef392e9 Mon Sep 17 00:00:00 2001 From: Mantou <51238168+Mantou-9487@users.noreply.github.com> Date: Wed, 29 Nov 2023 22:58:44 +0800 Subject: [PATCH 02/18] Add files via upload --- Dockerfile | 12 ++++++++++++ docker-compose.yml | 8 ++++++++ 2 files changed, 20 insertions(+) create mode 100644 Dockerfile create mode 100644 docker-compose.yml diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..ecd965b --- /dev/null +++ b/Dockerfile @@ -0,0 +1,12 @@ +FROM python:3.11.5-slim + +RUN mkdir -p /usr/src/bot + +WORKDIR /usr/src/bot + +COPY requirements.txt requirements.txt +RUN pip3 install -r requirements.txt + +COPY . . + +CMD [ "python3", "main.py" ] diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..595cb32 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,8 @@ +version: '3.9' +services: + bot: + build: . + restart: always + env_file: + - .env + volumes: -:/usr/src/bot From a2c717bbf0e7d642016c4a08aa06e1b42db45a16 Mon Sep 17 00:00:00 2001 From: Mantou <51238168+Mantou-9487@users.noreply.github.com> Date: Wed, 29 Nov 2023 23:07:45 +0800 Subject: [PATCH 03/18] Create docker-image.yml --- .github/workflows/docker-image.yml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 .github/workflows/docker-image.yml diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml new file mode 100644 index 0000000..83df665 --- /dev/null +++ b/.github/workflows/docker-image.yml @@ -0,0 +1,22 @@ +name: Docker Image CI + +on: + push: + branches: [ "main" ] + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Build the Docker image + run: docker build . --file Dockerfile -t ghcr.io/Mantou-9487/keyword:latest + + - name: Login to GitHub Registry + run: echo "${{ secrets.DOCKER_GITHUB_TOKEN }}" | docker login ghcr.io -u $ --password-stdin + + - name: Push to GitHub Registry + run: docker push ghcr.io/Mantou-9487/keyword:latest From b9bbab4e350ab7fde596336a11adc3f120d98b9c Mon Sep 17 00:00:00 2001 From: Mantou <51238168+Mantou-9487@users.noreply.github.com> Date: Wed, 29 Nov 2023 23:09:41 +0800 Subject: [PATCH 04/18] Update docker-image.yml --- .github/workflows/docker-image.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index 83df665..50c4138 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -13,10 +13,10 @@ jobs: - uses: actions/checkout@v4 - name: Build the Docker image - run: docker build . --file Dockerfile -t ghcr.io/Mantou-9487/keyword:latest + run: docker build . --file Dockerfile -t ghcr.io/mantou1031/keyword:latest - name: Login to GitHub Registry run: echo "${{ secrets.DOCKER_GITHUB_TOKEN }}" | docker login ghcr.io -u $ --password-stdin - name: Push to GitHub Registry - run: docker push ghcr.io/Mantou-9487/keyword:latest + run: docker push ghcr.io/mantou1031/keyword:latest From 41db2ade725429fbc78e22dc7f68bd0e50e9637a Mon Sep 17 00:00:00 2001 From: Mantou <51238168+Mantou-9487@users.noreply.github.com> Date: Wed, 29 Nov 2023 23:11:54 +0800 Subject: [PATCH 05/18] Update docker-image.yml --- .github/workflows/docker-image.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index 50c4138..d62942a 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -16,7 +16,7 @@ jobs: run: docker build . --file Dockerfile -t ghcr.io/mantou1031/keyword:latest - name: Login to GitHub Registry - run: echo "${{ secrets.DOCKER_GITHUB_TOKEN }}" | docker login ghcr.io -u $ --password-stdin + run: docker login -u mantou1031 -p ${{ secrets.DOCKER_GITHUB_TOKEN }} - name: Push to GitHub Registry run: docker push ghcr.io/mantou1031/keyword:latest From b4599e953244a023bcc4ef7d5990e832e9b4db10 Mon Sep 17 00:00:00 2001 From: Mantou <51238168+Mantou-9487@users.noreply.github.com> Date: Wed, 29 Nov 2023 23:24:25 +0800 Subject: [PATCH 06/18] Update docker-image.yml --- .github/workflows/docker-image.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index d62942a..50c4138 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -16,7 +16,7 @@ jobs: run: docker build . --file Dockerfile -t ghcr.io/mantou1031/keyword:latest - name: Login to GitHub Registry - run: docker login -u mantou1031 -p ${{ secrets.DOCKER_GITHUB_TOKEN }} + run: echo "${{ secrets.DOCKER_GITHUB_TOKEN }}" | docker login ghcr.io -u $ --password-stdin - name: Push to GitHub Registry run: docker push ghcr.io/mantou1031/keyword:latest From bdd994e9cf0638d15166e41355ea1d5d47dd01c8 Mon Sep 17 00:00:00 2001 From: Mantou <51238168+Mantou-9487@users.noreply.github.com> Date: Wed, 29 Nov 2023 23:25:24 +0800 Subject: [PATCH 07/18] Update docker-image.yml --- .github/workflows/docker-image.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index 50c4138..67811ca 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -13,10 +13,10 @@ jobs: - uses: actions/checkout@v4 - name: Build the Docker image - run: docker build . --file Dockerfile -t ghcr.io/mantou1031/keyword:latest + run: docker build . --file Dockerfile -t ghcr.io/mantou-9487/keyword:latest - name: Login to GitHub Registry run: echo "${{ secrets.DOCKER_GITHUB_TOKEN }}" | docker login ghcr.io -u $ --password-stdin - name: Push to GitHub Registry - run: docker push ghcr.io/mantou1031/keyword:latest + run: docker push ghcr.io/mantou-9487/keyword:latest From 320155a12cdb1b40364a32e01ea1926d394d637c Mon Sep 17 00:00:00 2001 From: Mantou <51238168+Mantou-9487@users.noreply.github.com> Date: Wed, 29 Nov 2023 23:27:20 +0800 Subject: [PATCH 08/18] Create .env --- .env | 1 + 1 file changed, 1 insertion(+) create mode 100644 .env diff --git a/.env b/.env new file mode 100644 index 0000000..b72cfd2 --- /dev/null +++ b/.env @@ -0,0 +1 @@ +TOKEN="" From 8f34294a80fbe4cb01e15c578a5dbb0824da7990 Mon Sep 17 00:00:00 2001 From: Mantou <51238168+Mantou-9487@users.noreply.github.com> Date: Thu, 30 Nov 2023 18:22:21 +0800 Subject: [PATCH 09/18] Change Volumes to /keyword --- .github/workflows/docker-image.yml | 44 +++++++++++++++--------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index 67811ca..626c8c1 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -1,22 +1,22 @@ -name: Docker Image CI - -on: - push: - branches: [ "main" ] - workflow_dispatch: - -jobs: - build: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - - name: Build the Docker image - run: docker build . --file Dockerfile -t ghcr.io/mantou-9487/keyword:latest - - - name: Login to GitHub Registry - run: echo "${{ secrets.DOCKER_GITHUB_TOKEN }}" | docker login ghcr.io -u $ --password-stdin - - - name: Push to GitHub Registry - run: docker push ghcr.io/mantou-9487/keyword:latest +name: Docker Image CI + +on: + push: + branches: [ "main" ] + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Build the Docker image + run: docker build . --file Dockerfile -t ghcr.io/mantou-9487/keyword:latest + + - name: Login to GitHub Registry + run: echo "${{ secrets.DOCKER_GITHUB_TOKEN }}" | docker login ghcr.io -u $ --password-stdin + + - name: Push to GitHub Registry + run: docker push ghcr.io/mantou-9487/keyword:latest \ No newline at end of file From 984e7042220c80a789513c1d912de03da0296e99 Mon Sep 17 00:00:00 2001 From: Mantou <51238168+Mantou-9487@users.noreply.github.com> Date: Thu, 30 Nov 2023 18:22:40 +0800 Subject: [PATCH 10/18] Change WORKDIR to /keyword --- Dockerfile | 4 ++-- docker-compose.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index ecd965b..5e5fc46 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,8 @@ FROM python:3.11.5-slim -RUN mkdir -p /usr/src/bot +RUN mkdir -p /keyword -WORKDIR /usr/src/bot +WORKDIR /keyword COPY requirements.txt requirements.txt RUN pip3 install -r requirements.txt diff --git a/docker-compose.yml b/docker-compose.yml index 595cb32..c4632d7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,4 +5,4 @@ services: restart: always env_file: - .env - volumes: -:/usr/src/bot + volumes: -:/keyword From b19260276cedf02d41c43861016c01d8058cb2ea Mon Sep 17 00:00:00 2001 From: Mantou <51238168+Mantou-9487@users.noreply.github.com> Date: Thu, 30 Nov 2023 18:25:51 +0800 Subject: [PATCH 11/18] Add a new things --- cogs/emoji.py | 35 +++ cogs/reply.py | 638 +++++++++++++++++++++++++------------------------- 2 files changed, 354 insertions(+), 319 deletions(-) create mode 100644 cogs/emoji.py diff --git a/cogs/emoji.py b/cogs/emoji.py new file mode 100644 index 0000000..2dd09a4 --- /dev/null +++ b/cogs/emoji.py @@ -0,0 +1,35 @@ +from disnake import ApplicationCommandInteraction, Option, OptionType, PartialEmoji, ButtonStyle +from disnake.ext import commands +from disnake.ui import Button +from core.bot import Bot +from core.embeds import SuccessEmbed, ErrorEmbed + + +class GetEmoji(commands.Cog): + def __init__(self, bot: Bot): + self.bot = bot + + @commands.slash_command( + name="getemoji", + description="Just get emoji then download it.", + options=[Option(name="emoji", description="你要抓的表情符號", type=OptionType.string, required=True)], + ) + async def getemoji(self, interaction: ApplicationCommandInteraction, emoji: str): + if (emoji.startswith("<:") or emoji.startswith(""): + emoji = PartialEmoji.from_str(emoji) + real = PartialEmoji.with_state(state=self.bot._connection, name=emoji.name, animated=emoji.animated, id=emoji.id) + + embed = SuccessEmbed(title=real.name) + embed.set_image(file=await real.to_file()) + + components = [ + Button(label="下載連結", style=ButtonStyle.link, url=real.url) + ] + + await interaction.response.send_message(embed=embed, components=components) + else: + return await interaction.response.send_message(embed=ErrorEmbed("請輸入正確的表情符號格式!")) + + +def setup(bot: commands.Bot): + bot.add_cog(GetEmoji(bot)) diff --git a/cogs/reply.py b/cogs/reply.py index 0d91550..b36a749 100644 --- a/cogs/reply.py +++ b/cogs/reply.py @@ -1,319 +1,319 @@ -from disnake import ( - ApplicationCommandInteraction, - Option, - OptionType, - Message, - OptionChoice, - AllowedMentions, -) -from disnake.ext import commands - -from core.bot import Bot -from core.embeds import SuccessEmbed, ErrorEmbed, InfoEmbed -from core.paginator import Paginator - -import json -import os - -# pylint: disable=C0116 -# pylint: disable=C0115 - - -class ReplySystem(commands.Cog): - def __init__(self, bot: Bot): - self.bot = bot - - def extract_id_text(self, key: str): - underline = key.rfind("_") - if underline != -1: - text = key[:underline] - number = int(key[underline + 1 :]) - return text, number - return key, None - - @commands.slash_command(name="回復", description="just a test") - async def reply(self, interaction: ApplicationCommandInteraction): - pass - - @reply.sub_command( - name="增加", - description="增加一個關鍵詞", - options=[ - Option( - name="keyword", - description="新增觸發關鍵詞", - type=OptionType.string, - required=True, - ), - Option( - name="reply", - description="觸發關鍵詞後的回覆", - type=OptionType.string, - required=True, - ), - ], - ) - async def add( - self, interaction: ApplicationCommandInteraction, keyword: str, reply: str - ): - if os.path.isfile(f"./guild/{interaction.guild_id}.json"): - with open( - f"./guild/{interaction.guild_id}.json", mode="r", encoding="utf-8" - ) as f: - data = json.load(f) - - data[keyword + "_" + str(interaction.user.id)] = reply - - with open( - f"./guild/{interaction.guild_id}.json", mode="w", encoding="utf-8" - ) as f: - json.dump(data, f, ensure_ascii=False, indent=4) - else: - with open( - f"./guild/{interaction.guild_id}.json", mode="w", encoding="utf-8" - ) as f: - f.write( - json.dumps( - {keyword + "_" + str(interaction.user.id): reply}, - ensure_ascii=False, - indent=4, - ) - ) - - return await interaction.response.send_message( - embed=SuccessEmbed( - title="新增成功!", description=f"已新增 `{keyword}` -> `{reply}`!" - ) - ) - - @reply.sub_command( - name="移除", - description="移除一個關鍵詞", - options=[ - Option( - name="keyword", - description="移除觸發關鍵詞", - type=OptionType.string, - required=True, - ), - ], - ) - async def remove(self, interaction: ApplicationCommandInteraction, keyword: str): - if os.path.isfile(f"./guild/{interaction.guild_id}.json"): - with open( - f"./guild/{interaction.guild_id}.json", mode="r", encoding="utf-8" - ) as f: - data: dict = json.load(f) - - if interaction.user.guild_permissions.manage_messages: - if data.get(keyword, None) is not None: - del data[keyword] - with open( - f"./guild/{interaction.guild_id}.json", - mode="w", - encoding="utf-8", - ) as f: - json.dump(data, f, ensure_ascii=False, indent=4) - text = self.extract_id_text(keyword)[0] - return await interaction.response.send_message( - embed=SuccessEmbed(title="移除成功!", description=f"已移除 `{text}`") - ) - return await interaction.response.send_message( - embed=ErrorEmbed(title="沒有這個關鍵詞!"), ephemeral=True - ) - number = self.extract_id_text(keyword)[1] - if number == interaction.user.id: - if data.get(keyword, None) is not None: - del data[keyword] - with open( - f"./guild/{interaction.guild_id}.json", - mode="w", - encoding="utf-8", - ) as f: - json.dump(data, f, ensure_ascii=False, indent=4) - text = self.extract_id_text(keyword)[0] - return await interaction.response.send_message( - embed=SuccessEmbed(title="移除成功!", description=f"已移除 `{text}`") - ) - return await interaction.response.send_message( - embed=ErrorEmbed(title="沒有這個關鍵詞!"), ephemeral=True - ) - return await interaction.response.send_message( - embed=ErrorEmbed(title="你不能刪除別人的關鍵詞!"), ephemeral=True - ) - - @remove.autocomplete("keyword") - async def search(self, interaction: ApplicationCommandInteraction, keyword: str): - if not keyword: - return [] - - choices = [] - - if os.path.isfile(f"./guild/{interaction.guild_id}.json"): - with open( - f"./guild/{interaction.guild_id}.json", mode="r", encoding="utf-8" - ) as f: - data: dict = json.load(f) - for key, value in data.items(): - text = self.extract_id_text(key)[0] - if keyword in key: - choices.append(OptionChoice(name=f"{text} 回覆詞為 {value}", value=key)) - return choices - return [] - - @reply.sub_command(name="清單", description="查看已有的關鍵詞清單") - async def replylist(self, interaction: ApplicationCommandInteraction): - if os.path.isfile(f"./guild/{interaction.guild_id}.json"): - with open( - f"./guild/{interaction.guild_id}.json", mode="r", encoding="utf-8" - ) as f: - data: dict = json.load(f) - - if len(data) != 0: - pages: list[InfoEmbed] = [] - - for i, (k, v) in enumerate(data.items()): - if i % 10 == 0: - pages.append( - InfoEmbed( - title="關鍵詞清單", - description="\n".join( - [ - f"{i+1}. `{self.extract_id_text(key)[0]}`{f' (由 {self.bot.get_user(self.extract_id_text(key)[1]).mention} 新增)' if self.extract_id_text(key)[0] != key else ''} -> `{value}`" - for key, value in list(data.items())[i : i + 10] - ] - ), - ) - ) - - await Paginator(timeout=None).start(interaction, pages=pages) - else: - return await interaction.response.send_message( - embed=ErrorEmbed(title="你的群組還沒有設定過"), ephemeral=True - ) - - @reply.sub_command( - name="變更", - description="變更一個關鍵詞的回覆", - options=[ - Option( - name="keyword", - description="觸發關鍵詞", - type=OptionType.string, - required=True, - ), - Option( - name="reply", - description="觸發關鍵詞後的新回覆", - type=OptionType.string, - required=True, - ), - ], - ) - async def modify( - self, interaction: ApplicationCommandInteraction, keyword: str, reply: str - ): - if os.path.isfile(f"./guild/{interaction.guild_id}.json"): - with open( - f"./guild/{interaction.guild_id}.json", mode="r", encoding="utf-8" - ) as f: - data: dict = json.load(f) - - if interaction.user.guild_permissions.manage_messages: - if data.get(keyword, None) is not None: - data[keyword] = reply - with open( - f"./guild/{interaction.guild_id}.json", - mode="w", - encoding="utf-8", - ) as f: - json.dump(data, f, ensure_ascii=False, indent=4) - text = self.extract_id_text(keyword)[0] - return await interaction.response.send_message( - embed=SuccessEmbed( - title="變更成功!", description=f"已變更 `{text}` 回復詞為 {reply}" - ) - ) - return await interaction.response.send_message( - embed=ErrorEmbed(title="沒有這個關鍵詞!"), ephemeral=True - ) - number = self.extract_id_text(keyword)[1] - if number == interaction.user.id: - if data.get(keyword, None) is not None: - del data[keyword] - with open( - f"./guild/{interaction.guild_id}.json", - mode="w", - encoding="utf-8", - ) as f: - json.dump(data, f, ensure_ascii=False, indent=4) - text = self.extract_id_text(keyword)[0] - return await interaction.response.send_message( - embed=SuccessEmbed( - title="變更成功!", description=f"已變更 `{text}` 回復詞為 {reply}" - ) - ) - return await interaction.response.send_message( - embed=ErrorEmbed(title="沒有這個關鍵詞!"), ephemeral=True - ) - return await interaction.response.send_message( - embed=ErrorEmbed(title="你不能變更別人的關鍵詞!"), ephemeral=True - ) - - @modify.autocomplete("keyword") - async def modify_search(self, interaction: ApplicationCommandInteraction, keyword: str): - choices = [] - if not keyword: - with open( - f"./guild/{interaction.guild_id}.json", mode="r", encoding="utf-8" - ) as f: - data: dict = json.load(f) - for key, value in data.items(): - text = self.extract_id_text(key)[0] - if keyword in key: - choices.append(OptionChoice(name=f"{text} 回覆詞為 {value}", value=key)) - return choices - - if os.path.isfile(f"./guild/{interaction.guild_id}.json"): - with open( - f"./guild/{interaction.guild_id}.json", mode="r", encoding="utf-8" - ) as f: - data: dict = json.load(f) - for key, value in data.items(): - text = self.extract_id_text(key)[0] - if keyword in key: - choices.append(OptionChoice(name=f"{text} 回覆詞為 {value}", value=key)) - return choices - return [] - - @commands.Cog.listener(name="on_slash_command_error") - async def on_slash_command_error( - self, interaction: ApplicationCommandInteraction, error - ): - return await interaction.response.send_message( - embed=ErrorEmbed(title="喔不,出現了個錯誤:(", description=f"```{error}```") - ) - - @commands.Cog.listener(name="on_message") - async def on_message(self, message: Message): - if message.author.bot: - return - - if os.path.isfile(f"./guild/{message.guild.id}.json"): - with open( - f"./guild/{message.guild.id}.json", mode="r", encoding="utf-8" - ) as f: - data: dict = json.load(f) - - for key, value in data.items(): - text = self.extract_id_text(key)[0] - if message.content.upper() == text.upper(): - await message.channel.send( - value, allowed_mentions=AllowedMentions.none() - ) - else: - return - - -def setup(bot: commands.Bot): - bot.add_cog(ReplySystem(bot)) +from disnake import ( + ApplicationCommandInteraction, + Option, + OptionType, + Message, + OptionChoice, + AllowedMentions, +) +from disnake.ext import commands + +from core.bot import Bot +from core.embeds import SuccessEmbed, ErrorEmbed, InfoEmbed +from core.paginator import Paginator + +import json +import os + +# pylint: disable=C0116 +# pylint: disable=C0115 + + +class ReplySystem(commands.Cog): + def __init__(self, bot: Bot): + self.bot = bot + + def extract_id_text(self, key: str): + underline = key.rfind("_") + if underline != -1: + text = key[:underline] + number = int(key[underline + 1 :]) + return text, number + return key, None + + @commands.slash_command(name="回復", description="just a test") + async def reply(self, interaction: ApplicationCommandInteraction): + pass + + @reply.sub_command( + name="增加", + description="增加一個關鍵詞", + options=[ + Option( + name="keyword", + description="新增觸發關鍵詞", + type=OptionType.string, + required=True, + ), + Option( + name="reply", + description="觸發關鍵詞後的回覆", + type=OptionType.string, + required=True, + ), + ], + ) + async def add( + self, interaction: ApplicationCommandInteraction, keyword: str, reply: str + ): + if os.path.isfile(f"./guild/{interaction.guild_id}.json"): + with open( + f"./guild/{interaction.guild_id}.json", mode="r", encoding="utf-8" + ) as f: + data = json.load(f) + + data[keyword + "_" + str(interaction.user.id)] = reply + + with open( + f"./guild/{interaction.guild_id}.json", mode="w", encoding="utf-8" + ) as f: + json.dump(data, f, ensure_ascii=False, indent=4) + else: + with open( + f"./guild/{interaction.guild_id}.json", mode="w", encoding="utf-8" + ) as f: + f.write( + json.dumps( + {keyword + "_" + str(interaction.user.id): reply}, + ensure_ascii=False, + indent=4, + ) + ) + + return await interaction.response.send_message( + embed=SuccessEmbed( + title="新增成功!", description=f"已新增 `{keyword}` -> `{reply}`!" + ) + ) + + @reply.sub_command( + name="移除", + description="移除一個關鍵詞", + options=[ + Option( + name="keyword", + description="移除觸發關鍵詞", + type=OptionType.string, + required=True, + ), + ], + ) + async def remove(self, interaction: ApplicationCommandInteraction, keyword: str): + if os.path.isfile(f"./guild/{interaction.guild_id}.json"): + with open( + f"./guild/{interaction.guild_id}.json", mode="r", encoding="utf-8" + ) as f: + data: dict = json.load(f) + + if interaction.user.guild_permissions.manage_messages: + if data.get(keyword, None) is not None: + del data[keyword] + with open( + f"./guild/{interaction.guild_id}.json", + mode="w", + encoding="utf-8", + ) as f: + json.dump(data, f, ensure_ascii=False, indent=4) + text = self.extract_id_text(keyword)[0] + return await interaction.response.send_message( + embed=SuccessEmbed(title="移除成功!", description=f"已移除 `{text}`") + ) + return await interaction.response.send_message( + embed=ErrorEmbed(title="沒有這個關鍵詞!"), ephemeral=True + ) + number = self.extract_id_text(keyword)[1] + if number == interaction.user.id: + if data.get(keyword, None) is not None: + del data[keyword] + with open( + f"./guild/{interaction.guild_id}.json", + mode="w", + encoding="utf-8", + ) as f: + json.dump(data, f, ensure_ascii=False, indent=4) + text = self.extract_id_text(keyword)[0] + return await interaction.response.send_message( + embed=SuccessEmbed(title="移除成功!", description=f"已移除 `{text}`") + ) + return await interaction.response.send_message( + embed=ErrorEmbed(title="沒有這個關鍵詞!"), ephemeral=True + ) + return await interaction.response.send_message( + embed=ErrorEmbed(title="你不能刪除別人的關鍵詞!"), ephemeral=True + ) + + @remove.autocomplete("keyword") + async def search(self, interaction: ApplicationCommandInteraction, keyword: str): + if not keyword: + return [] + + choices = [] + + if os.path.isfile(f"./guild/{interaction.guild_id}.json"): + with open( + f"./guild/{interaction.guild_id}.json", mode="r", encoding="utf-8" + ) as f: + data: dict = json.load(f) + for key, value in data.items(): + text = self.extract_id_text(key)[0] + if keyword in key: + choices.append(OptionChoice(name=f"{text} 回覆詞為 {value}", value=key)) + return choices + return [] + + @reply.sub_command(name="清單", description="查看已有的關鍵詞清單") + async def replylist(self, interaction: ApplicationCommandInteraction): + if os.path.isfile(f"./guild/{interaction.guild_id}.json"): + with open( + f"./guild/{interaction.guild_id}.json", mode="r", encoding="utf-8" + ) as f: + data: dict = json.load(f) + + if len(data) != 0: + pages: list[InfoEmbed] = [] + + for i, (k, v) in enumerate(data.items()): + if i % 10 == 0: + pages.append( + InfoEmbed( + title="關鍵詞清單", + description="\n".join( + [ + f"{i+1}. `{self.extract_id_text(key)[0]}`{f' (由 {self.bot.get_user(self.extract_id_text(key)[1]).mention} 新增)' if self.extract_id_text(key)[0] != key else ''} -> `{value}`" + for key, value in list(data.items())[i : i + 10] + ] + ), + ) + ) + + await Paginator(timeout=None).start(interaction, pages=pages) + else: + return await interaction.response.send_message( + embed=ErrorEmbed(title="你的群組還沒有設定過"), ephemeral=True + ) + + @reply.sub_command( + name="變更", + description="變更一個關鍵詞的回覆", + options=[ + Option( + name="keyword", + description="觸發關鍵詞", + type=OptionType.string, + required=True, + ), + Option( + name="reply", + description="觸發關鍵詞後的新回覆", + type=OptionType.string, + required=True, + ), + ], + ) + async def modify( + self, interaction: ApplicationCommandInteraction, keyword: str, reply: str + ): + if os.path.isfile(f"./guild/{interaction.guild_id}.json"): + with open( + f"./guild/{interaction.guild_id}.json", mode="r", encoding="utf-8" + ) as f: + data: dict = json.load(f) + + if interaction.user.guild_permissions.manage_messages: + if data.get(keyword, None) is not None: + data[keyword] = reply + with open( + f"./guild/{interaction.guild_id}.json", + mode="w", + encoding="utf-8", + ) as f: + json.dump(data, f, ensure_ascii=False, indent=4) + text = self.extract_id_text(keyword)[0] + return await interaction.response.send_message( + embed=SuccessEmbed( + title="變更成功!", description=f"已變更 `{text}` 回復詞為 {reply}" + ) + ) + return await interaction.response.send_message( + embed=ErrorEmbed(title="沒有這個關鍵詞!"), ephemeral=True + ) + number = self.extract_id_text(keyword)[1] + if number == interaction.user.id: + if data.get(keyword, None) is not None: + del data[keyword] + with open( + f"./guild/{interaction.guild_id}.json", + mode="w", + encoding="utf-8", + ) as f: + json.dump(data, f, ensure_ascii=False, indent=4) + text = self.extract_id_text(keyword)[0] + return await interaction.response.send_message( + embed=SuccessEmbed( + title="變更成功!", description=f"已變更 `{text}` 回復詞為 {reply}" + ) + ) + return await interaction.response.send_message( + embed=ErrorEmbed(title="沒有這個關鍵詞!"), ephemeral=True + ) + return await interaction.response.send_message( + embed=ErrorEmbed(title="你不能變更別人的關鍵詞!"), ephemeral=True + ) + + @modify.autocomplete("keyword") + async def modify_search(self, interaction: ApplicationCommandInteraction, keyword: str): + choices = [] + if not keyword: + with open( + f"./guild/{interaction.guild_id}.json", mode="r", encoding="utf-8" + ) as f: + data: dict = json.load(f) + for key, value in data.items(): + text = self.extract_id_text(key)[0] + if keyword in key: + choices.append(OptionChoice(name=f"{text} 回覆詞為 {value}", value=key)) + return choices + + if os.path.isfile(f"./guild/{interaction.guild_id}.json"): + with open( + f"./guild/{interaction.guild_id}.json", mode="r", encoding="utf-8" + ) as f: + data: dict = json.load(f) + for key, value in data.items(): + text = self.extract_id_text(key)[0] + if keyword in key: + choices.append(OptionChoice(name=f"{text} 回覆詞為 {value}", value=key)) + return choices + return [] + + @commands.Cog.listener(name="on_slash_command_error") + async def on_slash_command_error( + self, interaction: ApplicationCommandInteraction, error + ): + return await interaction.response.send_message( + embed=ErrorEmbed(title="喔不,出現了個錯誤:(", description=f"```{error}```") + ) + + @commands.Cog.listener(name="on_message") + async def on_message(self, message: Message): + if message.author.bot: + return + + if os.path.isfile(f"./guild/{message.guild.id}.json"): + with open( + f"./guild/{message.guild.id}.json", mode="r", encoding="utf-8" + ) as f: + data: dict = json.load(f) + + for key, value in data.items(): + text = self.extract_id_text(key)[0] + if message.content.upper() == text.upper(): + await message.channel.send( + value, allowed_mentions=AllowedMentions.none() + ) + else: + return + + +def setup(bot: commands.Bot): + bot.add_cog(ReplySystem(bot)) From b42df33b99ee08482acfd1805ef458ce5a7890f6 Mon Sep 17 00:00:00 2001 From: Mantou <51238168+Mantou-9487@users.noreply.github.com> Date: Thu, 30 Nov 2023 18:26:15 +0800 Subject: [PATCH 12/18] Add a new things too --- core/bot.py | 30 +-- core/embeds.py | 66 +++--- core/paginator.py | 562 +++++++++++++++++++++++----------------------- 3 files changed, 329 insertions(+), 329 deletions(-) diff --git a/core/bot.py b/core/bot.py index 53a02e5..51bf694 100644 --- a/core/bot.py +++ b/core/bot.py @@ -1,15 +1,15 @@ -from logging import Logger - -from disnake.ext.commands import Bot as OriginalBot - - - -class Bot(OriginalBot): - def __init__(self, logger: Logger, **kwargs): - super().__init__(**kwargs) - - self.logger = logger - - async def on_ready(self): - self.logger.info("The bot is ready! Logged in as %s" % self.user) - +from logging import Logger + +from disnake.ext.commands import Bot as OriginalBot + + + +class Bot(OriginalBot): + def __init__(self, logger: Logger, **kwargs): + super().__init__(**kwargs) + + self.logger = logger + + async def on_ready(self): + self.logger.info("The bot is ready! Logged in as %s" % self.user) + diff --git a/core/embeds.py b/core/embeds.py index 9cb6bb1..35b24b2 100644 --- a/core/embeds.py +++ b/core/embeds.py @@ -1,33 +1,33 @@ -from disnake import Embed - -# pylint: disable=C0116 -# pylint: disable=C0115 - -class SuccessEmbed(Embed): - def __init__(self, title: str, description: str = None, **kwargs): - super().__init__(title="✅ | " + title, description=description, color=0x0f9d58, **kwargs) - self._files = {} - - -class InfoEmbed(Embed): - def __init__(self, title: str, description: str = None, **kwargs): - super().__init__(title="ℹ️ | " + title, description=description, color=0x4285f4, **kwargs) - - -class LoadingEmbed(Embed): - def __init__(self, title: str, description: str = None, **kwargs): - super().__init__(title="⌛ | " + title, description=description, color=0x4285F4, **kwargs) - - -class WarningEmbed(Embed): - def __init__(self, title: str, description: str = None, **kwargs): - super().__init__(title="⚠ | " + title, description=description, color=0xf4b400, **kwargs) - - -class ErrorEmbed(Embed): - def __init__(self, title: str, description: str = None, **kwargs): - super().__init__(title="❌ | " + title, description=description, color=0xdb4437, **kwargs) - -class HelpEmbed(Embed): - def __init__(self, title: str, description: str = None, **kwargs): - super().__init__(title=title, description=description, color=0xdb4437, **kwargs) +from disnake import Embed + +# pylint: disable=C0116 +# pylint: disable=C0115 + +class SuccessEmbed(Embed): + def __init__(self, title: str, description: str = None, **kwargs): + super().__init__(title="✅ | " + title, description=description, color=0x0f9d58, **kwargs) + self._files = {} + + +class InfoEmbed(Embed): + def __init__(self, title: str, description: str = None, **kwargs): + super().__init__(title="ℹ️ | " + title, description=description, color=0x4285f4, **kwargs) + + +class LoadingEmbed(Embed): + def __init__(self, title: str, description: str = None, **kwargs): + super().__init__(title="⌛ | " + title, description=description, color=0x4285F4, **kwargs) + + +class WarningEmbed(Embed): + def __init__(self, title: str, description: str = None, **kwargs): + super().__init__(title="⚠ | " + title, description=description, color=0xf4b400, **kwargs) + + +class ErrorEmbed(Embed): + def __init__(self, title: str, description: str = None, **kwargs): + super().__init__(title="❌ | " + title, description=description, color=0xdb4437, **kwargs) + +class HelpEmbed(Embed): + def __init__(self, title: str, description: str = None, **kwargs): + super().__init__(title=title, description=description, color=0xdb4437, **kwargs) diff --git a/core/paginator.py b/core/paginator.py index c0808cf..649bf07 100644 --- a/core/paginator.py +++ b/core/paginator.py @@ -1,281 +1,281 @@ -from __future__ import annotations - -import os -import logging -import inspect - -import disnake - - -__all__ = ("Paginator",) - - -class Paginator(disnake.ui.View): - """ - Embed Paginator. - - Parameters: - ---------- - timeout: int - How long the Paginator should timeout in, after the last interaction. (In seconds) (Overrides default of 60) - previous_button: disnake.ui.Button - Overrides default previous button. - next_button: disnake.ui.Button - Overrides default next button. - trash_button: disnake.ui.Button. - Overrides default trash Button. - page_counter_separator: str - The custom separator between the pages numbers - in the page_counter button. - page_counter_style: disnake.ButtonStyle - Overrides default page counter style. - initial_page: int - Page to start the pagination on. - on_timeout_message: Optional[str] - Overrides default `on_timeout` str set as embed footer. - If `None` no message will appear `on_timeout`. - interaction_check: bool - Check whether the users interacting with the paginator are the onwer - of the command or not. Default set to `True`. - interaction_check_message: Union[disnake.Embed, str] - The message to send when an `interaction_check` fails e.g a user - who is not the command owner attempeted to interact with the paginator. - This feature can be disabled setting `interaction_check` to `False`. - ephemeral: bool - Whether the paginator should only be visible to the command invokator or - to anyone else. - persistent: bool - - """ - - __running_interaction_ids: list[int] = [] - - __slots__ = ( - "timeout", - "previous_button", - "next_button", - "trash_button", - "page_counter_separator", - "page_counter_style", - "initial_page", - "on_timeout_message", - "interaction_check_message", - "ephemeral", - "_interaction_check", - "pages", - "total_page_count", - "interaction", - "bot", - "current_page", - "original_message_deleted", - "page_counter", - "persistent", - ) - - def __init__( - self, - *, - timeout: int | float | None = 60, - previous_button: disnake.ui.Button | None = None, - next_button: disnake.ui.Button | None = None, - trash_button: disnake.ui.Button | None = None, - page_counter_separator: str = "/", - page_counter_style: disnake.ButtonStyle = disnake.ButtonStyle.grey, - initial_page: int = 0, - on_timeout_message: str | None = None, - interaction_check: bool = True, - interaction_check_message: disnake.Embed | str = disnake.Embed( - description="你不能控制這個分頁!", - color=disnake.Color.red(), - ), - ephemeral: bool = False, - persistent: bool = False, - ) -> None: - - # TODO: - # implement custom_id generation to use low_level_components instead of - # permanent views - # button.custom_id = "::" could be a valid - # custom_id format - - self.previous_button = previous_button - self.next_button = next_button - self.trash_button = trash_button - - self.page_counter_separator = page_counter_separator - self.page_counter_style = page_counter_style - self.initial_page = initial_page - - self.ephemeral = ephemeral - self.original_message_deleted: bool = False - self.on_timeout_message = on_timeout_message - self._interaction_check = interaction_check - self.interaction_check_message = interaction_check_message - self.persistent = persistent - - self.logger = logging.getLogger("disnake") - self.logger.setLevel(logging.WARNING) - self.handler = logging.StreamHandler() - self.logger.addHandler(self.handler) - - if self.persistent and timeout: - raise ValueError(f"To create a persistent Paginator the timeout must be None, not {timeout.__class__!r}") - - super().__init__(timeout=timeout) - - async def start( - self, - interaction: disnake.ApplicationCommandInteraction, - pages: list[disnake.Embed], - ) -> None: - """ - Starts the Paginator object. - - Parameters: - ---------- - interaction: disnake.ApplicationCommandInteraction - The command interaction. - pages: List[disnake.Embed] - A list of embeds wich compose the Paginator contents to send. - - Raises: - ---------- - RuntimeError: - If the user is attempting to starts two paginators in the same command function body - or if the user is attempting to use the same Paginator object to start a paginator twice. - - """ - if self.previous_button is None: - self.previous_button = disnake.ui.Button( - emoji=disnake.PartialEmoji(name="\U000025c0"), - custom_id=f"PREV_BTN:{interaction.id}:{interaction.author.id}", - ) - if self.next_button is None: - self.next_button = disnake.ui.Button( - emoji=disnake.PartialEmoji(name="\U000025b6"), - custom_id=f"NEXT_BTN:{interaction.id}:{interaction.author.id}", - ) - if self.trash_button is None: - self.trash_button = disnake.ui.Button( - emoji=disnake.PartialEmoji(name="\U0001f5d1"), - style=disnake.ButtonStyle.danger, - custom_id=f"TRASH_BTN:{interaction.id}:{interaction.author.id}", - ) - # Checks if a user is trying to use a Paginator instance - # in the same command function and raise a RuntimeError - # this is needed because in the same command the interaction - # is the same so the Paginator can't work correctly with two instances. - # Note: This error is not raised on __init__ method so you can create two Paginator instances - # but you can't start the Paginator with the start method. - # Note: this error is raised when trying to call the started method - # twice or more on the same Paginator object - # or when you're trying to start different Paginator object on the same command - self._current_instance_location = ( - f"{interaction.application_command.cog_name or os.path.abspath(inspect.getfile(interaction.application_command.callback))}" - f":{interaction.application_command.name}" - ) - if interaction.id in Paginator.__running_interaction_ids: - raise RuntimeError( - f"You can have only one Paginator instance per command! Check your '{self._current_instance_location}' command." - ) - - Paginator.__running_interaction_ids.append(interaction.id) - self.pages = pages - self.total_page_count = len(pages) - self.interaction = interaction - self.bot = interaction.bot - self.current_page = self.initial_page - self.original_message_deleted = False - self.next_button.disabled = self.previous_button.disabled = True - self.trash_button.disabled = False - - self.previous_button.callback = self.__previous_button_callback - self.next_button.callback = self.__next_button_callback - self.trash_button.callback = self.__trash_button_callback - - self.page_counter: disnake.ui.Button = disnake.ui.Button( - label=f"{self.initial_page + 1} {self.page_counter_separator} {self.total_page_count}", - style=self.page_counter_style, - disabled=True, - ) - - if self.persistent and not all((self.previous_button.custom_id, self.next_button.custom_id, self.trash_button.custom_id)): - raise ValueError("You need to set the custom ID of buttons to make the Paginator persistent.") - if self.total_page_count > 0: - print("?") - self.next_button.disabled = self.previous_button.disabled = False - - self.add_item(self.previous_button) - self.add_item(self.page_counter) - self.add_item(self.next_button) - self.add_item(self.trash_button) - - await interaction.send( - embed=self.pages[self.initial_page], view=self, ephemeral=self.ephemeral - ) - - if self.persistent: - self.bot.add_view(self) - - async def on_timeout(self) -> None: - if not self.original_message_deleted: - self.previous_button.disabled = self.next_button.disabled = self.trash_button.disabled = True # type: ignore - embed = self.pages[self.current_page] - if self.on_timeout_message: - embed.set_footer(text=self.on_timeout_message) - await self.interaction.edit_original_message(embed=embed, view=self) - - Paginator.__running_interaction_ids.remove(self.interaction.id) - - async def interaction_check(self, interaction: disnake.MessageInteraction) -> bool: - if isinstance(self._interaction_check, bool) and self._interaction_check: - if interaction.author.id != self.interaction.author.id: - if isinstance(self.interaction_check_message, disnake.Embed): - await interaction.response.send_message( - embed=self.interaction_check_message, ephemeral=True - ) - elif isinstance(self.interaction_check_message, str): - await interaction.response.send_message( - self.interaction_check_message, ephemeral=True - ) - return False - return True - return False - - async def __previous(self, interaction: disnake.MessageInteraction) -> None: - if self.current_page == 0: - self.current_page = self.total_page_count - 1 - else: - self.current_page -= 1 - - self.page_counter.label = f"{self.current_page + 1} {self.page_counter_separator} {self.total_page_count}" - await interaction.response.edit_message( - embed=self.pages[self.current_page], view=self - ) - - async def __next(self, interaction: disnake.MessageInteraction) -> None: - if self.current_page == self.total_page_count - 1: - self.current_page = 0 - else: - self.current_page += 1 - - self.page_counter.label = f"{self.current_page + 1} {self.page_counter_separator} {self.total_page_count}" - await interaction.response.edit_message( - embed=self.pages[self.current_page], view=self - ) - async def __next_button_callback( - self, interaction: disnake.MessageInteraction - ) -> None: - await self.__next(interaction) - - async def __previous_button_callback( - self, interaction: disnake.MessageInteraction - ) -> None: - await self.__previous(interaction) - - async def __trash_button_callback( - self, interaction: disnake.MessageInteraction - ) -> None: - self.original_message_deleted = True - await interaction.response.defer() - await interaction.delete_original_message() +from __future__ import annotations + +import os +import logging +import inspect + +import disnake + + +__all__ = ("Paginator",) + + +class Paginator(disnake.ui.View): + """ + Embed Paginator. + + Parameters: + ---------- + timeout: int + How long the Paginator should timeout in, after the last interaction. (In seconds) (Overrides default of 60) + previous_button: disnake.ui.Button + Overrides default previous button. + next_button: disnake.ui.Button + Overrides default next button. + trash_button: disnake.ui.Button. + Overrides default trash Button. + page_counter_separator: str + The custom separator between the pages numbers + in the page_counter button. + page_counter_style: disnake.ButtonStyle + Overrides default page counter style. + initial_page: int + Page to start the pagination on. + on_timeout_message: Optional[str] + Overrides default `on_timeout` str set as embed footer. + If `None` no message will appear `on_timeout`. + interaction_check: bool + Check whether the users interacting with the paginator are the onwer + of the command or not. Default set to `True`. + interaction_check_message: Union[disnake.Embed, str] + The message to send when an `interaction_check` fails e.g a user + who is not the command owner attempeted to interact with the paginator. + This feature can be disabled setting `interaction_check` to `False`. + ephemeral: bool + Whether the paginator should only be visible to the command invokator or + to anyone else. + persistent: bool + + """ + + __running_interaction_ids: list[int] = [] + + __slots__ = ( + "timeout", + "previous_button", + "next_button", + "trash_button", + "page_counter_separator", + "page_counter_style", + "initial_page", + "on_timeout_message", + "interaction_check_message", + "ephemeral", + "_interaction_check", + "pages", + "total_page_count", + "interaction", + "bot", + "current_page", + "original_message_deleted", + "page_counter", + "persistent", + ) + + def __init__( + self, + *, + timeout: int | float | None = 60, + previous_button: disnake.ui.Button | None = None, + next_button: disnake.ui.Button | None = None, + trash_button: disnake.ui.Button | None = None, + page_counter_separator: str = "/", + page_counter_style: disnake.ButtonStyle = disnake.ButtonStyle.grey, + initial_page: int = 0, + on_timeout_message: str | None = None, + interaction_check: bool = True, + interaction_check_message: disnake.Embed | str = disnake.Embed( + description="你不能控制這個分頁!", + color=disnake.Color.red(), + ), + ephemeral: bool = False, + persistent: bool = False, + ) -> None: + + # TODO: + # implement custom_id generation to use low_level_components instead of + # permanent views + # button.custom_id = "::" could be a valid + # custom_id format + + self.previous_button = previous_button + self.next_button = next_button + self.trash_button = trash_button + + self.page_counter_separator = page_counter_separator + self.page_counter_style = page_counter_style + self.initial_page = initial_page + + self.ephemeral = ephemeral + self.original_message_deleted: bool = False + self.on_timeout_message = on_timeout_message + self._interaction_check = interaction_check + self.interaction_check_message = interaction_check_message + self.persistent = persistent + + self.logger = logging.getLogger("disnake") + self.logger.setLevel(logging.WARNING) + self.handler = logging.StreamHandler() + self.logger.addHandler(self.handler) + + if self.persistent and timeout: + raise ValueError(f"To create a persistent Paginator the timeout must be None, not {timeout.__class__!r}") + + super().__init__(timeout=timeout) + + async def start( + self, + interaction: disnake.ApplicationCommandInteraction, + pages: list[disnake.Embed], + ) -> None: + """ + Starts the Paginator object. + + Parameters: + ---------- + interaction: disnake.ApplicationCommandInteraction + The command interaction. + pages: List[disnake.Embed] + A list of embeds wich compose the Paginator contents to send. + + Raises: + ---------- + RuntimeError: + If the user is attempting to starts two paginators in the same command function body + or if the user is attempting to use the same Paginator object to start a paginator twice. + + """ + if self.previous_button is None: + self.previous_button = disnake.ui.Button( + emoji=disnake.PartialEmoji(name="\U000025c0"), + custom_id=f"PREV_BTN:{interaction.id}:{interaction.author.id}", + ) + if self.next_button is None: + self.next_button = disnake.ui.Button( + emoji=disnake.PartialEmoji(name="\U000025b6"), + custom_id=f"NEXT_BTN:{interaction.id}:{interaction.author.id}", + ) + if self.trash_button is None: + self.trash_button = disnake.ui.Button( + emoji=disnake.PartialEmoji(name="\U0001f5d1"), + style=disnake.ButtonStyle.danger, + custom_id=f"TRASH_BTN:{interaction.id}:{interaction.author.id}", + ) + # Checks if a user is trying to use a Paginator instance + # in the same command function and raise a RuntimeError + # this is needed because in the same command the interaction + # is the same so the Paginator can't work correctly with two instances. + # Note: This error is not raised on __init__ method so you can create two Paginator instances + # but you can't start the Paginator with the start method. + # Note: this error is raised when trying to call the started method + # twice or more on the same Paginator object + # or when you're trying to start different Paginator object on the same command + self._current_instance_location = ( + f"{interaction.application_command.cog_name or os.path.abspath(inspect.getfile(interaction.application_command.callback))}" + f":{interaction.application_command.name}" + ) + if interaction.id in Paginator.__running_interaction_ids: + raise RuntimeError( + f"You can have only one Paginator instance per command! Check your '{self._current_instance_location}' command." + ) + + Paginator.__running_interaction_ids.append(interaction.id) + self.pages = pages + self.total_page_count = len(pages) + self.interaction = interaction + self.bot = interaction.bot + self.current_page = self.initial_page + self.original_message_deleted = False + self.next_button.disabled = self.previous_button.disabled = True + self.trash_button.disabled = False + + self.previous_button.callback = self.__previous_button_callback + self.next_button.callback = self.__next_button_callback + self.trash_button.callback = self.__trash_button_callback + + self.page_counter: disnake.ui.Button = disnake.ui.Button( + label=f"{self.initial_page + 1} {self.page_counter_separator} {self.total_page_count}", + style=self.page_counter_style, + disabled=True, + ) + + if self.persistent and not all((self.previous_button.custom_id, self.next_button.custom_id, self.trash_button.custom_id)): + raise ValueError("You need to set the custom ID of buttons to make the Paginator persistent.") + if self.total_page_count > 0: + print("?") + self.next_button.disabled = self.previous_button.disabled = False + + self.add_item(self.previous_button) + self.add_item(self.page_counter) + self.add_item(self.next_button) + self.add_item(self.trash_button) + + await interaction.send( + embed=self.pages[self.initial_page], view=self, ephemeral=self.ephemeral + ) + + if self.persistent: + self.bot.add_view(self) + + async def on_timeout(self) -> None: + if not self.original_message_deleted: + self.previous_button.disabled = self.next_button.disabled = self.trash_button.disabled = True # type: ignore + embed = self.pages[self.current_page] + if self.on_timeout_message: + embed.set_footer(text=self.on_timeout_message) + await self.interaction.edit_original_message(embed=embed, view=self) + + Paginator.__running_interaction_ids.remove(self.interaction.id) + + async def interaction_check(self, interaction: disnake.MessageInteraction) -> bool: + if isinstance(self._interaction_check, bool) and self._interaction_check: + if interaction.author.id != self.interaction.author.id: + if isinstance(self.interaction_check_message, disnake.Embed): + await interaction.response.send_message( + embed=self.interaction_check_message, ephemeral=True + ) + elif isinstance(self.interaction_check_message, str): + await interaction.response.send_message( + self.interaction_check_message, ephemeral=True + ) + return False + return True + return False + + async def __previous(self, interaction: disnake.MessageInteraction) -> None: + if self.current_page == 0: + self.current_page = self.total_page_count - 1 + else: + self.current_page -= 1 + + self.page_counter.label = f"{self.current_page + 1} {self.page_counter_separator} {self.total_page_count}" + await interaction.response.edit_message( + embed=self.pages[self.current_page], view=self + ) + + async def __next(self, interaction: disnake.MessageInteraction) -> None: + if self.current_page == self.total_page_count - 1: + self.current_page = 0 + else: + self.current_page += 1 + + self.page_counter.label = f"{self.current_page + 1} {self.page_counter_separator} {self.total_page_count}" + await interaction.response.edit_message( + embed=self.pages[self.current_page], view=self + ) + async def __next_button_callback( + self, interaction: disnake.MessageInteraction + ) -> None: + await self.__next(interaction) + + async def __previous_button_callback( + self, interaction: disnake.MessageInteraction + ) -> None: + await self.__previous(interaction) + + async def __trash_button_callback( + self, interaction: disnake.MessageInteraction + ) -> None: + self.original_message_deleted = True + await interaction.response.defer() + await interaction.delete_original_message() From 9d7b2ee0c24184bc52001ce79d780211a56fc7c9 Mon Sep 17 00:00:00 2001 From: Mantou <51238168+Mantou-9487@users.noreply.github.com> Date: Thu, 30 Nov 2023 21:05:33 +0800 Subject: [PATCH 13/18] Update Dockerfile --- Dockerfile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Dockerfile b/Dockerfile index 5e5fc46..48f0016 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,6 +7,9 @@ WORKDIR /keyword COPY requirements.txt requirements.txt RUN pip3 install -r requirements.txt +COPY .env .env +COPY /guild /guild + COPY . . CMD [ "python3", "main.py" ] From fdc62d18478edca1984a12d00110c3b219c4c0d7 Mon Sep 17 00:00:00 2001 From: Mantou <51238168+Mantou-9487@users.noreply.github.com> Date: Thu, 30 Nov 2023 21:24:17 +0800 Subject: [PATCH 14/18] Update Reply --- cogs/emoji.py | 71 ++--- cogs/ping.py | 42 +++ cogs/reply.py | 714 ++++++++++++++++++++++++++++---------------------- 3 files changed, 473 insertions(+), 354 deletions(-) create mode 100644 cogs/ping.py diff --git a/cogs/emoji.py b/cogs/emoji.py index 2dd09a4..8a73312 100644 --- a/cogs/emoji.py +++ b/cogs/emoji.py @@ -1,35 +1,36 @@ -from disnake import ApplicationCommandInteraction, Option, OptionType, PartialEmoji, ButtonStyle -from disnake.ext import commands -from disnake.ui import Button -from core.bot import Bot -from core.embeds import SuccessEmbed, ErrorEmbed - - -class GetEmoji(commands.Cog): - def __init__(self, bot: Bot): - self.bot = bot - - @commands.slash_command( - name="getemoji", - description="Just get emoji then download it.", - options=[Option(name="emoji", description="你要抓的表情符號", type=OptionType.string, required=True)], - ) - async def getemoji(self, interaction: ApplicationCommandInteraction, emoji: str): - if (emoji.startswith("<:") or emoji.startswith(""): - emoji = PartialEmoji.from_str(emoji) - real = PartialEmoji.with_state(state=self.bot._connection, name=emoji.name, animated=emoji.animated, id=emoji.id) - - embed = SuccessEmbed(title=real.name) - embed.set_image(file=await real.to_file()) - - components = [ - Button(label="下載連結", style=ButtonStyle.link, url=real.url) - ] - - await interaction.response.send_message(embed=embed, components=components) - else: - return await interaction.response.send_message(embed=ErrorEmbed("請輸入正確的表情符號格式!")) - - -def setup(bot: commands.Bot): - bot.add_cog(GetEmoji(bot)) +from disnake import ApplicationCommandInteraction, Option, OptionType, PartialEmoji, ButtonStyle +from disnake.ext import commands +from disnake.ui import Button + +from lava.bot import Bot +from lava.embeds import SuccessEmbed, ErrorEmbed + + +class GetEmoji(commands.Cog): + def __init__(self, bot: Bot): + self.bot = bot + + @commands.slash_command( + name="getemoji", + description="Just get emoji then download it.", + options=[Option(name="emoji", description="你要抓的表情符號", type=OptionType.string, required=True)], + ) + async def getemoji(self, interaction: ApplicationCommandInteraction, emoji: str): + if (emoji.startswith("<:") or emoji.startswith(""): + emoji = PartialEmoji.from_str(emoji) + real = PartialEmoji.with_state(state=self.bot._connection, name=emoji.name, animated=emoji.animated, id=emoji.id) + + embed = SuccessEmbed(title=real.name) + embed.set_image(file=await real.to_file()) + + components = [ + Button(label="下載連結", style=ButtonStyle.link, url=real.url) + ] + + await interaction.response.send_message(embed=embed, components=components) + else: + return await interaction.response.send_message(embed=ErrorEmbed("請輸入正確的表情符號格式!")) + + +def setup(bot: commands.Bot): + bot.add_cog(GetEmoji(bot)) diff --git a/cogs/ping.py b/cogs/ping.py new file mode 100644 index 0000000..abaea98 --- /dev/null +++ b/cogs/ping.py @@ -0,0 +1,42 @@ +import disnake +from disnake.ext import commands +from disnake import ApplicationCommandInteraction +class View(disnake.ui.View): + def __init__(self): + self.temp = 1 + super().__init__(timeout=None) + + @disnake.ui.button(label="繼續戳", style=disnake.ButtonStyle.green, emoji="<:cutefox:1033313959203323924>",custom_id="update") + async def update(self, button: disnake.ui.Button, interaction: ApplicationCommandInteraction): + if self.temp < 2: + self.temp = stab + self.temp += 1 + embed = disnake.Embed(title=":ping_pong: | Pong! {} ms".format(round(bot.latency * 1000)),description=f"我被戳了`{self.temp}`次(,,・ω・,,)",colour=disnake.Colour.random()) + await interaction.response.edit_message(embed=embed, view=self) + else: + self.temp += 1 + embed = disnake.Embed(title=":ping_pong: | Pong! {} ms".format(round(bot.latency * 1000)),description=f"我被戳了`{self.temp}`次(,,・ω・,,)",colour=disnake.Colour.random()) + await interaction.response.edit_message(embed=embed, view=self) + +class Ping(commands.Cog): + def __init__(self, bot:commands.Bot): + self.bot = bot + super().__init__() + @commands.Cog.listener() + async def on_ready(self): + self.bot.add_view(View()) + print("Ping Ready!") + + @commands.slash_command(name="ping", description="偷戳一下鰻頭(。・ω・。)") + async def ping(self, interaction: ApplicationCommandInteraction): + global bot + global message + global stab #戳 + stab = 1 + bot = self.bot + embed = disnake.Embed(title=":ping_pong: | Pong! {} ms".format(round(bot.latency * 1000)),description=f"我被戳了`{stab}`次(,,・ω・,,)",colour=disnake.Colour.random()) + view = View() + message = await interaction.response.send_message(embed=embed,view=view) + +def setup(bot): + bot.add_cog(Ping(bot)) \ No newline at end of file diff --git a/cogs/reply.py b/cogs/reply.py index b36a749..4a117c9 100644 --- a/cogs/reply.py +++ b/cogs/reply.py @@ -1,319 +1,395 @@ -from disnake import ( - ApplicationCommandInteraction, - Option, - OptionType, - Message, - OptionChoice, - AllowedMentions, -) -from disnake.ext import commands - -from core.bot import Bot -from core.embeds import SuccessEmbed, ErrorEmbed, InfoEmbed -from core.paginator import Paginator - -import json -import os - -# pylint: disable=C0116 -# pylint: disable=C0115 - - -class ReplySystem(commands.Cog): - def __init__(self, bot: Bot): - self.bot = bot - - def extract_id_text(self, key: str): - underline = key.rfind("_") - if underline != -1: - text = key[:underline] - number = int(key[underline + 1 :]) - return text, number - return key, None - - @commands.slash_command(name="回復", description="just a test") - async def reply(self, interaction: ApplicationCommandInteraction): - pass - - @reply.sub_command( - name="增加", - description="增加一個關鍵詞", - options=[ - Option( - name="keyword", - description="新增觸發關鍵詞", - type=OptionType.string, - required=True, - ), - Option( - name="reply", - description="觸發關鍵詞後的回覆", - type=OptionType.string, - required=True, - ), - ], - ) - async def add( - self, interaction: ApplicationCommandInteraction, keyword: str, reply: str - ): - if os.path.isfile(f"./guild/{interaction.guild_id}.json"): - with open( - f"./guild/{interaction.guild_id}.json", mode="r", encoding="utf-8" - ) as f: - data = json.load(f) - - data[keyword + "_" + str(interaction.user.id)] = reply - - with open( - f"./guild/{interaction.guild_id}.json", mode="w", encoding="utf-8" - ) as f: - json.dump(data, f, ensure_ascii=False, indent=4) - else: - with open( - f"./guild/{interaction.guild_id}.json", mode="w", encoding="utf-8" - ) as f: - f.write( - json.dumps( - {keyword + "_" + str(interaction.user.id): reply}, - ensure_ascii=False, - indent=4, - ) - ) - - return await interaction.response.send_message( - embed=SuccessEmbed( - title="新增成功!", description=f"已新增 `{keyword}` -> `{reply}`!" - ) - ) - - @reply.sub_command( - name="移除", - description="移除一個關鍵詞", - options=[ - Option( - name="keyword", - description="移除觸發關鍵詞", - type=OptionType.string, - required=True, - ), - ], - ) - async def remove(self, interaction: ApplicationCommandInteraction, keyword: str): - if os.path.isfile(f"./guild/{interaction.guild_id}.json"): - with open( - f"./guild/{interaction.guild_id}.json", mode="r", encoding="utf-8" - ) as f: - data: dict = json.load(f) - - if interaction.user.guild_permissions.manage_messages: - if data.get(keyword, None) is not None: - del data[keyword] - with open( - f"./guild/{interaction.guild_id}.json", - mode="w", - encoding="utf-8", - ) as f: - json.dump(data, f, ensure_ascii=False, indent=4) - text = self.extract_id_text(keyword)[0] - return await interaction.response.send_message( - embed=SuccessEmbed(title="移除成功!", description=f"已移除 `{text}`") - ) - return await interaction.response.send_message( - embed=ErrorEmbed(title="沒有這個關鍵詞!"), ephemeral=True - ) - number = self.extract_id_text(keyword)[1] - if number == interaction.user.id: - if data.get(keyword, None) is not None: - del data[keyword] - with open( - f"./guild/{interaction.guild_id}.json", - mode="w", - encoding="utf-8", - ) as f: - json.dump(data, f, ensure_ascii=False, indent=4) - text = self.extract_id_text(keyword)[0] - return await interaction.response.send_message( - embed=SuccessEmbed(title="移除成功!", description=f"已移除 `{text}`") - ) - return await interaction.response.send_message( - embed=ErrorEmbed(title="沒有這個關鍵詞!"), ephemeral=True - ) - return await interaction.response.send_message( - embed=ErrorEmbed(title="你不能刪除別人的關鍵詞!"), ephemeral=True - ) - - @remove.autocomplete("keyword") - async def search(self, interaction: ApplicationCommandInteraction, keyword: str): - if not keyword: - return [] - - choices = [] - - if os.path.isfile(f"./guild/{interaction.guild_id}.json"): - with open( - f"./guild/{interaction.guild_id}.json", mode="r", encoding="utf-8" - ) as f: - data: dict = json.load(f) - for key, value in data.items(): - text = self.extract_id_text(key)[0] - if keyword in key: - choices.append(OptionChoice(name=f"{text} 回覆詞為 {value}", value=key)) - return choices - return [] - - @reply.sub_command(name="清單", description="查看已有的關鍵詞清單") - async def replylist(self, interaction: ApplicationCommandInteraction): - if os.path.isfile(f"./guild/{interaction.guild_id}.json"): - with open( - f"./guild/{interaction.guild_id}.json", mode="r", encoding="utf-8" - ) as f: - data: dict = json.load(f) - - if len(data) != 0: - pages: list[InfoEmbed] = [] - - for i, (k, v) in enumerate(data.items()): - if i % 10 == 0: - pages.append( - InfoEmbed( - title="關鍵詞清單", - description="\n".join( - [ - f"{i+1}. `{self.extract_id_text(key)[0]}`{f' (由 {self.bot.get_user(self.extract_id_text(key)[1]).mention} 新增)' if self.extract_id_text(key)[0] != key else ''} -> `{value}`" - for key, value in list(data.items())[i : i + 10] - ] - ), - ) - ) - - await Paginator(timeout=None).start(interaction, pages=pages) - else: - return await interaction.response.send_message( - embed=ErrorEmbed(title="你的群組還沒有設定過"), ephemeral=True - ) - - @reply.sub_command( - name="變更", - description="變更一個關鍵詞的回覆", - options=[ - Option( - name="keyword", - description="觸發關鍵詞", - type=OptionType.string, - required=True, - ), - Option( - name="reply", - description="觸發關鍵詞後的新回覆", - type=OptionType.string, - required=True, - ), - ], - ) - async def modify( - self, interaction: ApplicationCommandInteraction, keyword: str, reply: str - ): - if os.path.isfile(f"./guild/{interaction.guild_id}.json"): - with open( - f"./guild/{interaction.guild_id}.json", mode="r", encoding="utf-8" - ) as f: - data: dict = json.load(f) - - if interaction.user.guild_permissions.manage_messages: - if data.get(keyword, None) is not None: - data[keyword] = reply - with open( - f"./guild/{interaction.guild_id}.json", - mode="w", - encoding="utf-8", - ) as f: - json.dump(data, f, ensure_ascii=False, indent=4) - text = self.extract_id_text(keyword)[0] - return await interaction.response.send_message( - embed=SuccessEmbed( - title="變更成功!", description=f"已變更 `{text}` 回復詞為 {reply}" - ) - ) - return await interaction.response.send_message( - embed=ErrorEmbed(title="沒有這個關鍵詞!"), ephemeral=True - ) - number = self.extract_id_text(keyword)[1] - if number == interaction.user.id: - if data.get(keyword, None) is not None: - del data[keyword] - with open( - f"./guild/{interaction.guild_id}.json", - mode="w", - encoding="utf-8", - ) as f: - json.dump(data, f, ensure_ascii=False, indent=4) - text = self.extract_id_text(keyword)[0] - return await interaction.response.send_message( - embed=SuccessEmbed( - title="變更成功!", description=f"已變更 `{text}` 回復詞為 {reply}" - ) - ) - return await interaction.response.send_message( - embed=ErrorEmbed(title="沒有這個關鍵詞!"), ephemeral=True - ) - return await interaction.response.send_message( - embed=ErrorEmbed(title="你不能變更別人的關鍵詞!"), ephemeral=True - ) - - @modify.autocomplete("keyword") - async def modify_search(self, interaction: ApplicationCommandInteraction, keyword: str): - choices = [] - if not keyword: - with open( - f"./guild/{interaction.guild_id}.json", mode="r", encoding="utf-8" - ) as f: - data: dict = json.load(f) - for key, value in data.items(): - text = self.extract_id_text(key)[0] - if keyword in key: - choices.append(OptionChoice(name=f"{text} 回覆詞為 {value}", value=key)) - return choices - - if os.path.isfile(f"./guild/{interaction.guild_id}.json"): - with open( - f"./guild/{interaction.guild_id}.json", mode="r", encoding="utf-8" - ) as f: - data: dict = json.load(f) - for key, value in data.items(): - text = self.extract_id_text(key)[0] - if keyword in key: - choices.append(OptionChoice(name=f"{text} 回覆詞為 {value}", value=key)) - return choices - return [] - - @commands.Cog.listener(name="on_slash_command_error") - async def on_slash_command_error( - self, interaction: ApplicationCommandInteraction, error - ): - return await interaction.response.send_message( - embed=ErrorEmbed(title="喔不,出現了個錯誤:(", description=f"```{error}```") - ) - - @commands.Cog.listener(name="on_message") - async def on_message(self, message: Message): - if message.author.bot: - return - - if os.path.isfile(f"./guild/{message.guild.id}.json"): - with open( - f"./guild/{message.guild.id}.json", mode="r", encoding="utf-8" - ) as f: - data: dict = json.load(f) - - for key, value in data.items(): - text = self.extract_id_text(key)[0] - if message.content.upper() == text.upper(): - await message.channel.send( - value, allowed_mentions=AllowedMentions.none() - ) - else: - return - - -def setup(bot: commands.Bot): - bot.add_cog(ReplySystem(bot)) +from disnake import ( + ApplicationCommandInteraction, + Option, + OptionType, + Message, + OptionChoice, + AllowedMentions, +) +from disnake.ext import commands + +from lava.bot import Bot +from lava.embeds import SuccessEmbed, ErrorEmbed, InfoEmbed +from lava.paginator import Paginator + +import json +import os + +# pylint: disable=C0116 +# pylint: disable=C0115 + + +class ReplySystem(commands.Cog): + def __init__(self, bot: Bot): + self.bot = bot + + def extract_id_text(self, key: str): + underline = key.rfind("_") + if underline != -1: + text = key[:underline] + number = int(key[underline + 1 :]) + return text, number + else: + return key, None + + @commands.slash_command(name="回復", description="just a test") + async def reply(self, interaction: ApplicationCommandInteraction): + pass + + @reply.sub_command( + name="增加", + description="增加一個關鍵詞", + options=[ + Option( + name="keyword", + description="新增觸發關鍵詞", + type=OptionType.string, + required=True, + ), + Option( + name="reply", + description="觸發關鍵詞後的回覆", + type=OptionType.string, + required=True, + ), + Option( + name="ephemeral", + description="是否讓訊息僅限自己能看到", + type=OptionType.boolean, + choices=[ + OptionChoice(name="是", value=True), + OptionChoice(name="否", value=False), + ], + ), + ], + ) + async def add( + self, + interaction: ApplicationCommandInteraction, + keyword: str, + reply: str, + ephemeral: bool = True, + ): + if os.path.isfile(f"./guild/{interaction.guild_id}.json"): + with open( + f"./guild/{interaction.guild_id}.json", mode="r", encoding="utf-8" + ) as f: + data = json.load(f) + + data[keyword + "_" + str(interaction.user.id)] = reply + + with open( + f"./guild/{interaction.guild_id}.json", mode="w", encoding="utf-8" + ) as f: + json.dump(data, f, ensure_ascii=False, indent=4) + else: + with open( + f"./guild/{interaction.guild_id}.json", mode="w", encoding="utf-8" + ) as f: + f.write( + json.dumps( + {keyword + "_" + str(interaction.user.id): reply}, + ensure_ascii=False, + indent=4, + ) + ) + + return await interaction.response.send_message( + embed=SuccessEmbed( + title="新增成功!", description=f"已新增 `{keyword}` -> `{reply}`!" + ), + ephemeral=ephemeral, + ) + + @reply.sub_command( + name="移除", + description="移除一個關鍵詞", + options=[ + Option( + name="keyword", + description="移除觸發關鍵詞", + type=OptionType.string, + required=True, + ), + Option( + name="ephemeral", + description="是否讓訊息僅限自己能看到", + type=OptionType.boolean, + choices=[ + OptionChoice(name="是", value=True), + OptionChoice(name="否", value=False), + ], + ), + ], + ) + async def remove( + self, + interaction: ApplicationCommandInteraction, + keyword: str, + ephemeral: bool = True, + ): + if os.path.isfile(f"./guild/{interaction.guild_id}.json"): + with open( + f"./guild/{interaction.guild_id}.json", mode="r", encoding="utf-8" + ) as f: + data: dict = json.load(f) + + if interaction.user.guild_permissions.manage_messages: + if data.get(keyword, None) is not None: + del data[keyword] + with open( + f"./guild/{interaction.guild_id}.json", + mode="w", + encoding="utf-8", + ) as f: + json.dump(data, f, ensure_ascii=False, indent=4) + text = self.extract_id_text(keyword)[0] + return await interaction.response.send_message( + embed=SuccessEmbed(title="移除成功!", description=f"已移除 `{text}`"), + ephemeral=ephemeral, + ) + else: + return await interaction.response.send_message( + embed=ErrorEmbed(title="沒有這個關鍵詞!"), ephemeral=True + ) + else: + number = self.extract_id_text(keyword)[1] + if number == interaction.user.id: + if data.get(keyword, None) is not None: + del data[keyword] + with open( + f"./guild/{interaction.guild_id}.json", + mode="w", + encoding="utf-8", + ) as f: + json.dump(data, f, ensure_ascii=False, indent=4) + text = self.extract_id_text(keyword)[0] + return await interaction.response.send_message( + embed=SuccessEmbed( + title="移除成功!", description=f"已移除 `{text}`" + ), + ephemeral=ephemeral, + ) + else: + return await interaction.response.send_message( + embed=ErrorEmbed(title="沒有這個關鍵詞!"), ephemeral=True + ) + else: + return await interaction.response.send_message( + embed=ErrorEmbed(title="你不能刪除別人的關鍵詞!"), ephemeral=True + ) + + @remove.autocomplete("keyword") + async def search(self, interaction: ApplicationCommandInteraction, keyword: str): + if not keyword: + return [] + + choices = [] + + if os.path.isfile(f"./guild/{interaction.guild_id}.json"): + with open( + f"./guild/{interaction.guild_id}.json", mode="r", encoding="utf-8" + ) as f: + data: dict = json.load(f) + for key, value in data.items(): + text = self.extract_id_text(key)[0] + if keyword in key: + choices.append(OptionChoice(name=f"{text} 回覆詞為 {value}", value=key)) + return choices + else: + return [] + + @reply.sub_command( + name="清單", + description="查看已有的關鍵詞清單", + options=[ + Option( + name="ephemeral", + description="是否讓訊息僅限自己能看到", + type=OptionType.boolean, + choices=[ + OptionChoice(name="是", value=True), + OptionChoice(name="否", value=False), + ], + ) + ], + ) + async def replylist( + self, interaction: ApplicationCommandInteraction, ephemeral: bool = True + ): + if os.path.isfile(f"./guild/{interaction.guild_id}.json"): + with open( + f"./guild/{interaction.guild_id}.json", mode="r", encoding="utf-8" + ) as f: + data: dict = json.load(f) + + if len(data) != 0: + pages: list[InfoEmbed] = [] + + for i, (k, v) in enumerate(data.items()): + if i % 10 == 0: + pages.append( + InfoEmbed( + title="關鍵詞清單", + description="\n".join( + [ + f"{i+1}. `{self.extract_id_text(key)[0]}`{f' (由 {self.bot.get_user(self.extract_id_text(key)[1]).mention} 新增)' if self.extract_id_text(key)[0] != key else ''} -> `{value}`" + for key, value in list(data.items())[i : i + 10] + ] + ), + ) + ) + + await Paginator(timeout=None, ephemeral=ephemeral).start( + interaction, pages=pages + ) + else: + return await interaction.response.send_message( + embed=ErrorEmbed(title="你的群組還沒有設定過"), ephemeral=True + ) + + @reply.sub_command( + name="變更", + description="變更一個關鍵詞的回覆", + options=[ + Option( + name="keyword", + description="觸發關鍵詞", + type=OptionType.string, + required=True, + ), + Option( + name="reply", + description="觸發關鍵詞後的新回覆", + type=OptionType.string, + required=True, + ), + Option( + name="ephemeral", + description="是否讓訊息僅限自己能看到", + type=OptionType.boolean, + choices=[ + OptionChoice(name="是", value=True), + OptionChoice(name="否", value=False), + ], + ), + ], + ) + async def modify( + self, + interaction: ApplicationCommandInteraction, + keyword: str, + reply: str, + ephemeral: bool = True, + ): + if os.path.isfile(f"./guild/{interaction.guild_id}.json"): + with open( + f"./guild/{interaction.guild_id}.json", mode="r", encoding="utf-8" + ) as f: + data: dict = json.load(f) + + if interaction.user.guild_permissions.manage_messages: + if data.get(keyword, None) is not None: + data[keyword] = reply + with open( + f"./guild/{interaction.guild_id}.json", + mode="w", + encoding="utf-8", + ) as f: + json.dump(data, f, ensure_ascii=False, indent=4) + text = self.extract_id_text(keyword)[0] + return await interaction.response.send_message( + embed=SuccessEmbed( + title="變更成功!", description=f"已變更 `{text}` 回復詞為 `{reply}`!" + ), + ephemeral=ephemeral, + ) + else: + return await interaction.response.send_message( + embed=ErrorEmbed(title="沒有這個關鍵詞!"), ephemeral=True + ) + else: + number = self.extract_id_text(keyword)[1] + if number == interaction.user.id: + if data.get(keyword, None) is not None: + data[keyword] = reply + with open( + f"./guild/{interaction.guild_id}.json", + mode="w", + encoding="utf-8", + ) as f: + json.dump(data, f, ensure_ascii=False, indent=4) + text = self.extract_id_text(keyword)[0] + return await interaction.response.send_message( + embed=SuccessEmbed( + title="變更成功!", + description=f"已變更 `{text}` 回復詞為 `{reply}`!", + ) + ) + else: + return await interaction.response.send_message( + embed=ErrorEmbed(title="沒有這個關鍵詞!"), ephemeral=True + ) + else: + return await interaction.response.send_message( + embed=ErrorEmbed(title="你不能變更別人的關鍵詞!"), ephemeral=True + ) + + @modify.autocomplete("keyword") + async def search(self, interaction: ApplicationCommandInteraction, keyword: str): + choices = [] + if not keyword: + with open( + f"./guild/{interaction.guild_id}.json", mode="r", encoding="utf-8" + ) as f: + data: dict = json.load(f) + for key, value in data.items(): + text, number = self.extract_id_text(key) + if keyword in key and interaction.author.id == number: + choices.append(OptionChoice(name=f"{text} 回覆詞為 {value}", value=key)) + return choices + + if os.path.isfile(f"./guild/{interaction.guild_id}.json"): + with open( + f"./guild/{interaction.guild_id}.json", mode="r", encoding="utf-8" + ) as f: + data: dict = json.load(f) + for key, value in data.items(): + text, number = self.extract_id_text(key) + if keyword in key and interaction.author.id == number: + choices.append(OptionChoice(name=f"{text} 回覆詞為 {value}", value=key)) + return choices + else: + return [] + + @commands.Cog.listener(name="on_slash_command_error") + async def on_slash_command_error( + self, interaction: ApplicationCommandInteraction, error + ): + return await interaction.response.send_message( + embed=ErrorEmbed(title="喔不,出現了個錯誤:(", description=f"```{error}```") + ) + + @commands.Cog.listener(name="on_message") + async def on_message(self, message: Message): + if message.author.bot: + return + + if os.path.isfile(f"./guild/{message.guild.id}.json"): + with open( + f"./guild/{message.guild.id}.json", mode="r", encoding="utf-8" + ) as f: + data: dict = json.load(f) + + for key, value in data.items(): + text = self.extract_id_text(key)[0] + if message.content.upper() == text.upper(): + await message.channel.send( + value, allowed_mentions=AllowedMentions.none() + ) + else: + return + + +def setup(bot: commands.Bot): + bot.add_cog(ReplySystem(bot)) From 7860261cb9a52c2ea6fe9ffa7ec2f9f6eaabd4d4 Mon Sep 17 00:00:00 2001 From: Mantou <51238168+Mantou-9487@users.noreply.github.com> Date: Thu, 30 Nov 2023 21:27:53 +0800 Subject: [PATCH 15/18] Update reply.py --- cogs/reply.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cogs/reply.py b/cogs/reply.py index 4a117c9..3250991 100644 --- a/cogs/reply.py +++ b/cogs/reply.py @@ -8,9 +8,9 @@ ) from disnake.ext import commands -from lava.bot import Bot -from lava.embeds import SuccessEmbed, ErrorEmbed, InfoEmbed -from lava.paginator import Paginator +from core.bot import Bot +from core.embeds import SuccessEmbed, ErrorEmbed, InfoEmbed +from core.paginator import Paginator import json import os From d9fcd843cdc872c8122560ac881d1070760de8a8 Mon Sep 17 00:00:00 2001 From: Mantou <51238168+Mantou-9487@users.noreply.github.com> Date: Fri, 1 Dec 2023 21:21:35 +0800 Subject: [PATCH 16/18] Update docker-image.yml --- .github/workflows/docker-image.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index 626c8c1..8df0113 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -3,6 +3,8 @@ name: Docker Image CI on: push: branches: [ "main" ] + tags: + - 'v*' workflow_dispatch: jobs: @@ -13,10 +15,10 @@ jobs: - uses: actions/checkout@v4 - name: Build the Docker image - run: docker build . --file Dockerfile -t ghcr.io/mantou-9487/keyword:latest + run: docker build . --file Dockerfile -t ghcr.io/mantou-9487/keyword:${{ github.ref_name}} - name: Login to GitHub Registry run: echo "${{ secrets.DOCKER_GITHUB_TOKEN }}" | docker login ghcr.io -u $ --password-stdin - name: Push to GitHub Registry - run: docker push ghcr.io/mantou-9487/keyword:latest \ No newline at end of file + run: docker push ghcr.io/mantou-9487/keyword:${{ github.ref_name}} From f7ea281bc808338dc7fc3fd8e2c57c720a380138 Mon Sep 17 00:00:00 2001 From: Mantou <51238168+Mantou-9487@users.noreply.github.com> Date: Fri, 1 Dec 2023 21:23:56 +0800 Subject: [PATCH 17/18] Update docker-image.yml --- .github/workflows/docker-image.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index 8df0113..8c05918 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -2,7 +2,6 @@ name: Docker Image CI on: push: - branches: [ "main" ] tags: - 'v*' workflow_dispatch: From 5a072d3f9db36bc69d0e55399f38da89010b383a Mon Sep 17 00:00:00 2001 From: Mantou <51238168+Mantou-9487@users.noreply.github.com> Date: Fri, 1 Dec 2023 21:27:57 +0800 Subject: [PATCH 18/18] Update docker-image.yml --- .github/workflows/docker-image.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index 8c05918..9bc290f 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -2,6 +2,7 @@ name: Docker Image CI on: push: + branches: [ "main" ] tags: - 'v*' workflow_dispatch: @@ -14,10 +15,10 @@ jobs: - uses: actions/checkout@v4 - name: Build the Docker image - run: docker build . --file Dockerfile -t ghcr.io/mantou-9487/keyword:${{ github.ref_name}} + run: docker build . --file Dockerfile -t ghcr.io/mantou-9487/keyword:latest - name: Login to GitHub Registry run: echo "${{ secrets.DOCKER_GITHUB_TOKEN }}" | docker login ghcr.io -u $ --password-stdin - name: Push to GitHub Registry - run: docker push ghcr.io/mantou-9487/keyword:${{ github.ref_name}} + run: docker push ghcr.io/mantou-9487/keyword:latest