From 65fe559ece03371f07b54dda0c2ed51b18eee398 Mon Sep 17 00:00:00 2001 From: Robert Date: Mon, 8 Feb 2021 17:23:24 +0100 Subject: [PATCH 1/2] Search now uses interactive embed --- cogs/search.py | 175 ++++++++++++++++++++++--------------------- utils/interactive.py | 93 +++++++++++++++++++++++ utils/jisho.py | 2 + 3 files changed, 183 insertions(+), 87 deletions(-) create mode 100644 utils/interactive.py diff --git a/cogs/search.py b/cogs/search.py index a55c1eb..da6e426 100644 --- a/cogs/search.py +++ b/cogs/search.py @@ -1,106 +1,107 @@ +from inspect import currentframe import discord from discord.ext import commands -from utils import jisho +from utils import jisho, interactive -class JishoObject(): - def __init__(self, query, owner): - self.response = jisho.JishoResponse(query) - self.total_pages = self.response.entries - self.page = 0 - self.owner = owner +class SearchEmbed(interactive.InteractiveEmbed): + REACTIONS = { + "prev_page": "⬅️", + "next_page": "➡️" + } - def prev(self): - self.page -= 1 - if self.page < 0: - self.page = self.total_pages - 1 + def __init__(self, parent, ctx, response): + super(SearchEmbed, self).__init__(parent.bot, ctx, 60.0) + self.parent = parent + self.owner = self.ctx.author + self.response = response - def next(self): - self.page += 1 - if self.page >= self.total_pages: - self.page = 0 + self.current_page = 0 + + def prev_page(self): + self.current_page -= 1 + if self.current_page < 0: + self.current_page = self.response.size - 1 + + def next_page(self): + self.current_page += 1 + if self.current_page >= self.response.size: + self.current_page = 0 + + async def on_reaction(self, reaction, user): + if reaction.emoji == SearchEmbed.REACTIONS["prev_page"]: + self.prev_page() + await reaction.remove(user) + + if reaction.emoji == SearchEmbed.REACTIONS["next_page"]: + self.next_page() + await reaction.remove(user) + + async def add_navigation(self, message): + await message.add_reaction(SearchEmbed.REACTIONS["prev_page"]) + await message.add_reaction(SearchEmbed.REACTIONS["next_page"]) + + def make_embed(self): + node = self.response.nodes[self.current_page] + embed = discord.Embed( + title = node.japanese[0][0], + url = f"https://jisho.org/word/{node.slug}", + description = node.japanese[0][1], + colour = 0x56d926 + ) + + i = 1 + for sense in node.senses: + embed.add_field(name=f"{i}. {sense.fenglish_definitions}", value=sense.fparts_of_speech, inline=False) + i += 1 + + if len(node.japanese) > 1: + other = "" + for word, reading in node.japanese[1:]: + other += word + if reading != "": + other += f"【{reading}】" + other += "\n" + embed.add_field(name="Other forms", value=other) + + + embed.set_footer( + text = f"{node.ftags} \t\t {self.current_page + 1}/{self.response.size}" + ) + + return embed + + async def on_close(self): + self.parent.activeObjects.pop(self.ctx.channel.id) class Search(commands.Cog): def __init__(self, bot: commands.Bot): self.bot = bot - self.activeObject = None - self.latestMessage = 0 - - async def createEmbed(self): - if self.activeObject.total_pages == 0: - embed = discord.Embed( - title = "No search results", - description = "The search returned nothing. Did you make a typo?", - colour = 0x56d926 - ) - else: - response = self.activeObject.response - node = response.nodes[self.activeObject.page] - embed = discord.Embed( - title = node.japanese[0][0], - url = f"https://jisho.org/word/{node.slug}", - description = node.japanese[0][1], - colour = 0x56d926 - ) - - i = 1 - for sense in node.senses: - embed.add_field(name=f"{i}. {sense.fenglish_definitions}", value=sense.fparts_of_speech, inline=False) - i += 1 - - if len(node.japanese) > 1: - other = "" - for word, reading in node.japanese[1:]: - other += word - if reading != "": - other += f"【{reading}】" - other += "\n" - embed.add_field(name="Other forms", value=other) - - - embed.set_footer( - text = f"{node.ftags} \t\t {self.activeObject.page + 1}/{self.activeObject.total_pages}" - ) - - return embed - - @commands.Cog.listener() - async def on_reaction_add(self, reaction, user): - message = reaction.message - if message.id != self.latestMessage: - return - - if user == self.bot.user: - return - - if user.id != self.activeObject.owner: - return - - if reaction.me: - if reaction.emoji == "⬅️": - self.activeObject.prev() - await reaction.remove(user) - - if reaction.emoji == "➡️": - self.activeObject.next() - await reaction.remove(user) - - embed = await self.createEmbed() - await message.edit(embed=embed) - + self.activeObjects = {} @commands.command(name="search", description="Searches Jisho", usage="", aliases=["s"]) @commands.cooldown(1, 5) async def search(self, ctx: commands.Context, *, query: str = None): if query is None: + embed = discord.Embed( + title = "No search results", + description = "The search returned nothing. Did you make a typo?", + colour = 0x56d926 + ) + await ctx.send(embed=embed) return - self.activeObject = JishoObject(query, ctx.author.id) - embed = await self.createEmbed() - message = await ctx.send(embed=embed) - self.latestMessage = message.id - if self.activeObject.total_pages > 0: - await message.add_reaction("⬅️") - await message.add_reaction("➡️") + result = jisho.JishoResponse(query) + if result.size == 0: + embed = discord.Embed( + title = "No search results", + description = "The search returned nothing. Did you make a typo?", + colour = 0x56d926 + ) + await ctx.send(embed=embed) + return + + self.activeObjects[ctx.channel.id] = SearchEmbed(self, ctx, result) + await self.activeObjects[ctx.channel.id].show_embed() @search.error async def search_error(self, ctx, error): diff --git a/utils/interactive.py b/utils/interactive.py new file mode 100644 index 0000000..b8c8e75 --- /dev/null +++ b/utils/interactive.py @@ -0,0 +1,93 @@ +import discord +from discord.ext import commands +from abc import ABC, abstractmethod +import asyncio + +class InteractiveEmbed(ABC): + """ + This abstract base class can be used to create interactive embeds + """ + def __init__(self, bot, ctx, timeout): + """ + Sets up the embed with needed parameters + bot: The bot hosting this embed + ctx: The context that caused this embed + timeuout: The time until the embed times out (in seconds) + """ + self.bot = bot + self.ctx = ctx + self.message = None + self.timeout = timeout + + @abstractmethod + async def on_reaction(self, reaction, user): + """ + Gets called when the user interacted with the embed + """ + pass + + @abstractmethod + def make_embed(self): + """ + Creates and returns a new embed + """ + pass + + @abstractmethod + async def add_navigation(self, message): + """ + Adds the navigational emotes to the embed + """ + pass + + async def on_close(self): + """ + Can be overridden. Gets called when the embed is closed + """ + pass + + def additional_checks(self, reaction, user): + """ + Can be overridden. Additional checks to do before calling on_reaction() + """ + return True + + async def show_embed(self): + """ + Displays an embed / Creates an embed + """ + if self.message is None: + self.message = await self.ctx.send(embed=self.make_embed()) + await self.add_navigation(self.message) + + else: + await self.message.edit(embed=self.make_embed()) + + def check(reaction, user): + if self.message is None: + return False + + if user.id != self.ctx.author.id: + return False + + if reaction.message.id != self.message.id: + return False + + if not reaction.me: + return False + + return self.additional_checks(reaction, user) + + try: + reaction, user = await self.bot.wait_for("reaction_add", check=check, timeout=self.timeout) + await self.on_reaction(reaction, user) + await self.show_embed() + except asyncio.TimeoutError: + await self.close_embed() + + async def close_embed(self): + """ + Close this embed + """ + await self.message.clear_reactions() + await self.on_close() \ No newline at end of file diff --git a/utils/jisho.py b/utils/jisho.py index ccf2ec6..b72a777 100644 --- a/utils/jisho.py +++ b/utils/jisho.py @@ -64,6 +64,8 @@ class JishoResponse(): self.nodes = [] self.disassemble() + self.size = len(self.nodes) + def query(self): url = TEMPLATE_URL.format(urllib.parse.quote_plus(self.query_string)) r = requests.get(url, headers=HEADER) From 387fd75b381a203850e669697619e089dabee215 Mon Sep 17 00:00:00 2001 From: Robert Date: Mon, 8 Feb 2021 17:38:52 +0100 Subject: [PATCH 2/2] Kanji now uses interactive --- cogs/kanji.py | 161 +++++++++++++++++++++++++------------------------- 1 file changed, 80 insertions(+), 81 deletions(-) diff --git a/cogs/kanji.py b/cogs/kanji.py index e0f1ba4..fbbaf57 100644 --- a/cogs/kanji.py +++ b/cogs/kanji.py @@ -1,90 +1,85 @@ import discord from discord.ext import commands -from utils import jisho +from utils import jisho, interactive -class JishoKanjiObject(): - def __init__(self, query, owner): - self.response = jisho.JishoKanji(query) - self.total_pages = self.response.entries - self.page = 0 - self.owner = owner +class KanjiEmbed(interactive.InteractiveEmbed): + REACTIONS = { + "prev_kanji": "⬅️", + "next_kanji": "➡️" + } - def prev(self): - self.page -= 1 - if self.page < 0: - self.page = self.total_pages - 1 + def __init__(self, parent, ctx, response): + super(KanjiEmbed, self).__init__(parent.bot, ctx, 60.0) + self.parent = parent + self.owner = self.ctx.author + self.response = response - def next(self): - self.page += 1 - if self.page >= self.total_pages: - self.page = 0 + self.current_kanji = 0 + + def prev_kanji(self): + self.current_kanji -= 1 + if self.current_kanji < 0: + self.current_kanji = self.response.entries - 1 + + def next_kanji(self): + self.current_kanji += 1 + if self.current_kanji >= self.response.entries: + self.current_kanji = 0 + + async def on_reaction(self, reaction, user): + if reaction.emoji == KanjiEmbed.REACTIONS["prev_kanji"]: + self.prev_kanji() + await reaction.remove(user) + + if reaction.emoji == KanjiEmbed.REACTIONS["next_kanji"]: + self.next_kanji() + await reaction.remove(user) + + async def add_navigation(self, message): + if self.response.entries > 1: + await message.add_reaction(KanjiEmbed.REACTIONS["prev_kanji"]) + await message.add_reaction(KanjiEmbed.REACTIONS["next_kanji"]) + + def make_embed(self): + node = self.response.nodes[self.current_kanji] + + embed = discord.Embed( + title = node.meaning, + url = node.url, + description = f"{node.strokes} strokes", + colour = 0x56d926 + ) + + if node.kun: + embed.add_field(name="Kun", value="、 ".join(node.kun), inline=False) + if node.on: + embed.add_field(name="On", value="、 ".join(node.on), inline=False) + + embed.add_field(name=f"Radical: {node.radical[0]}", value=node.radical[1], inline=False) + embed.set_thumbnail(url=node.image_url) + embed.set_footer(text=f"Jōyō kanji (Grade {node.grade}) | JLPT level {node.jlpt}\t\t{self.current_kanji + 1}/{self.response.entries}") + + return embed + + async def on_close(self): + self.parent.activeObjects.pop(self.ctx.channel.id) class Kanji(commands.Cog): def __init__(self, bot): self.bot = bot - self.activeObject = None - self.latestMessage = 0 - - async def createEmbed(self): - if self.activeObject.total_pages == 0: - embed = discord.Embed( - title = "No search results", - description = "The search returned nothing. Did you make a typo?", - colour = 0x56d926 - ) - else: - response = self.activeObject.response - node = response.nodes[self.activeObject.page] - - embed = discord.Embed( - title = node.meaning, - url = node.url, - description = f"{node.strokes} strokes", - colour = 0x56d926 - ) - - if node.kun: - embed.add_field(name="Kun", value="、 ".join(node.kun), inline=False) - if node.on: - embed.add_field(name="On", value="、 ".join(node.on), inline=False) - - embed.add_field(name=f"Radical: {node.radical[0]}", value=node.radical[1], inline=False) - - embed.set_thumbnail(url=node.image_url) - - embed.set_footer(text=f"Jōyō kanji (Grade {node.grade}) | JLPT level {node.jlpt}\t\t{self.activeObject.page + 1}/{self.activeObject.total_pages}") - - return embed - - @commands.Cog.listener() - async def on_reaction_add(self, reaction, user): - message = reaction.message - if message.id != self.latestMessage: - return - - if user == self.bot.user: - return - - if user.id != self.activeObject.owner: - return - - if reaction.me: - if reaction.emoji == "⬅️": - self.activeObject.prev() - await reaction.remove(user) - - if reaction.emoji == "➡️": - self.activeObject.next() - await reaction.remove(user) - - embed = await self.createEmbed() - await message.edit(embed=embed) + self.activeObjects = {} @commands.command(name="kanji", description="Performs a Kanji search", usage="", aliases=["k"]) @commands.cooldown(1, 5) async def kanji(self, ctx, *, kanji: str = None): if kanji is None: + embed = discord.Embed( + title = "No search results", + description = "The search returned nothing. Did you make a typo?", + colour = 0x56d926 + ) + await ctx.send(embed=embed) return embed = discord.Embed( @@ -92,19 +87,23 @@ class Kanji(commands.Cog): description = "This might take a few seconds", colour = 0x56d926 ) - message = await ctx.send(embed=embed) kanji = kanji[:5] + response = jisho.JishoKanji(kanji) + + if response.entries < 1: + embed = discord.Embed( + title = "No search results", + description = "The search returned nothing. Did you make a typo?", + colour = 0x56d926 + ) + await message.edit(embed=embed) + return - self.activeObject = JishoKanjiObject(kanji, ctx.author.id) - embed = await self.createEmbed() - await message.edit(embed=embed) - self.latestMessage = message.id - - if self.activeObject.total_pages > 1: - await message.add_reaction("⬅️") - await message.add_reaction("➡️") + await message.delete() + self.activeObjects[ctx.channel.id] = KanjiEmbed(self, ctx, response) + await self.activeObjects[ctx.channel.id].show_embed() def setup(bot): bot.add_cog(Kanji(bot)) \ No newline at end of file