From b398385e0f48ab1e06013158d2d7b7cb57a3b429 Mon Sep 17 00:00:00 2001 From: Raiden Date: Tue, 24 Jan 2023 02:57:30 +0800 Subject: [PATCH 01/51] Add genshinutils cog --- genshinutils/README.md | 22 ++++++ genshinutils/__init__.py | 17 +++++ genshinutils/genshinutils.py | 140 +++++++++++++++++++++++++++++++++++ genshinutils/info.json | 17 +++++ throw/throw.py | 2 +- 5 files changed, 197 insertions(+), 1 deletion(-) create mode 100644 genshinutils/README.md create mode 100644 genshinutils/__init__.py create mode 100644 genshinutils/genshinutils.py create mode 100644 genshinutils/info.json diff --git a/genshinutils/README.md b/genshinutils/README.md new file mode 100644 index 0000000..6691559 --- /dev/null +++ b/genshinutils/README.md @@ -0,0 +1,22 @@ +

+

Genshin

+
+ + + +
+ +
+
+

Genshin - A Genshin Impact oriented cog.

+ + +

+ +

Installation

+ +```ini +[p]load downloader +[p]repo add raiden-cogs https://github.com/raidensakura/raiden-cogs/ +[p]cog install raiden-cogs genshin +``` diff --git a/genshinutils/__init__.py b/genshinutils/__init__.py new file mode 100644 index 0000000..c09efae --- /dev/null +++ b/genshinutils/__init__.py @@ -0,0 +1,17 @@ +import json +from pathlib import Path + +from redbot.core.bot import Red + +from .genshinutils import GenshinUtils + +with open(Path(__file__).parent / "info.json") as fp: + __red_end_user_data_statement__ = json.load(fp)["end_user_data_statement"] + + +async def setup(bot: Red) -> None: + cog = GenshinUtils(bot) + + r = bot.add_cog(cog) + if r is not None: + await r diff --git a/genshinutils/genshinutils.py b/genshinutils/genshinutils.py new file mode 100644 index 0000000..fc5aff0 --- /dev/null +++ b/genshinutils/genshinutils.py @@ -0,0 +1,140 @@ +import asyncio +import logging +from typing import Union + +import discord +import genshin +from redbot.core import Config, checks, commands +from redbot.core.bot import Red +from redbot.core.commands import Context + +log = logging.getLogger("red.raidensakura.genshinutils") + + +class GenshinUtils(commands.Cog): + """ + A Genshin Impact cog. + """ + + __author__ = ["raidensakura"] + __version__ = "1.0.0" + + def __init__(self, bot): + self.bot = bot + self.config = Config.get_conf(self, 243316261264556032, force_registration=True) + default_global = {"schema_version": 1} + default_user = {"UID": 000000000} + self.config.register_user(**default_user) + + def format_help_for_context(self, ctx: commands.Context) -> str: + """ + Thanks Sinbad! + """ + pre_processed = super().format_help_for_context(ctx) + s = "s" if len(self.__author__) > 1 else "" + return f"{pre_processed}\n\nAuthor{s}: {', '.join(self.__author__)}\nCog Version: {self.__version__}" + + async def red_delete_data_for_user(self, **kwargs) -> None: + """Nothing to delete""" + return + + @checks.is_owner() + @commands.group() + async def genshinset(self, ctx: commands.Context): + """ + Settings for GenshinUtils cog. + """ + + @genshinset.command() + async def ltoken(self, ctx: commands.Context): + """Instructions on how to set the `ltoken` secret.""" + msg = f"Use `{ctx.prefix}set api genshinutils ltoken your_ltoken_here`." + await ctx.send(msg) + + @genshinset.command() + async def ltuid(self, ctx: commands.Context): + """Instructions on how to set the `ltuid` secret.""" + msg = f"Use `{ctx.prefix}set api genshinutils ltuid your_ltuid_here`." + await ctx.send(msg) + + @commands.group() + async def genshin(self, ctx: commands.Context): + """ + GenshinUtils main command. + """ + + @genshin.command() + @commands.guild_only() + @commands.bot_has_permissions(embed_links=True) + @commands.cooldown(2, 5, commands.BucketType.member) + async def profile(self, ctx: Context, *, member: Union[discord.Member, str, None]): + """ + Display a Genshin Impact profile. + If a UID is provided, it will display data for that player. + If a Discord user is provided, it will display data for that user (if a Genshin UID is linked). + If no argument is provided, it will display data for your account (if a Genshin UID is linked). + """ + + api_keys = await self.bot.get_shared_api_tokens("hoyolab") + if api_keys.get("ltuid") is None or api_keys.get("ltoken") is None: + return await ctx.send(f"API keys not set.") + + cookies = {"ltuid": api_keys.get("ltuid"), "ltoken": api_keys.get("ltoken")} + + client = genshin.Client(cookies) + + if not member: + log.debug("Fetch own UID from config") + uid = None + return await ctx.send("todo") + + elif isinstance(member, discord.Member) and member.id != ctx.me.id: + log.debug("Fetch mentioned user's UID from config") + uid = None + return await ctx.send("todo") + + elif isinstance(member, discord.Member) and member.id == ctx.me.id: + return await ctx.send(f"Sorry, but I do not play Genshin.") + + elif isinstance(member, str) and len(member) == 9 and member.isdigit(): + uid = member + try: + data = await client.get_genshin_user(uid) + except Exception as exc: + log.exception("Error trying to fetch data from API.", exc_info=exc) + return await ctx.send( + "Oops, I encountered an error while trying to fetch data from Hoyolab." + ) + + e = discord.Embed( + color=(await ctx.embed_colour()), + title=f"Data for {data.info.nickname} (AR{data.info.level})", + ) + e.add_field(name="Achievements", value=data.stats.achievements, inline=True) + e.add_field(name="Days Active", value=data.stats.days_active, inline=True) + e.add_field( + name="Characters Unlocked", value=data.stats.characters, inline=True + ) + e.add_field( + name="Highest Spiral Abyss Climb", + value=data.stats.spiral_abyss, + inline=True, + ) + e.add_field( + name="Oculi Collected", + value=( + f"Anemoculi: {data.stats.anemoculi}\n" + f"Geoculi: {data.stats.geoculi}\n" + f"Electroculi: {data.stats.electroculi}\n" + f"Dendroculi: {data.stats.dendroculi}" + ), + inline=True, + ) + + try: + return await ctx.send(embed=e) + except Exception as exc: + log.exception("Error trying to send choose embed.", exc_info=exc) + return await ctx.send( + "Oops, I encountered an error while trying to send the embed." + ) diff --git a/genshinutils/info.json b/genshinutils/info.json new file mode 100644 index 0000000..c1ac06a --- /dev/null +++ b/genshinutils/info.json @@ -0,0 +1,17 @@ +{ + "name": "GenshinUtils", + "short": "A Genshin Impact cog", + "description": "", + "install_msg": "", + "end_user_data_statement": "This cog does not persistently store any data about users.", + "author": ["raidensakura"], + "required_cogs": {}, + "requirements": ["genshin"], + "tags": [ + "genshin" + ], + "min_bot_version": "3.4.12", + "hidden": false, + "disabled": false, + "type": "COG" +} \ No newline at end of file diff --git a/throw/throw.py b/throw/throw.py index f01739b..0a56376 100644 --- a/throw/throw.py +++ b/throw/throw.py @@ -19,7 +19,7 @@ class Throw(commands.Cog): def __init__(self, bot: Red): self.bot = bot - self.config = Config.get_conf(self, 180109040514130509, force_registration=True) + self.config = Config.get_conf(self, 243316261264556032, force_registration=True) default_global = {"schema_version": 1} self.possible_actions = ["THROW"] default_user = {"ITEMS_THROWN": 0, "TIMES_HIT": 0} From 164c5c5d489f787866ba540dce5e0cf6b8256d94 Mon Sep 17 00:00:00 2001 From: Raiden Date: Wed, 25 Jan 2023 01:06:55 +0800 Subject: [PATCH 02/51] Add verification for profile linking, restructure commands --- genshinutils/genshinutils.py | 123 ++++------------------------------- genshinutils/profile.py | 116 +++++++++++++++++++++++++++++++++ genshinutils/settings.py | 69 ++++++++++++++++++++ 3 files changed, 198 insertions(+), 110 deletions(-) create mode 100644 genshinutils/profile.py create mode 100644 genshinutils/settings.py diff --git a/genshinutils/genshinutils.py b/genshinutils/genshinutils.py index fc5aff0..3858ea1 100644 --- a/genshinutils/genshinutils.py +++ b/genshinutils/genshinutils.py @@ -1,20 +1,16 @@ -import asyncio -import logging -from typing import Union +import logging, aiohttp -import discord -import genshin from redbot.core import Config, checks, commands from redbot.core.bot import Red from redbot.core.commands import Context +from .settings import GenshinSet +from .profile import GenshinProfile log = logging.getLogger("red.raidensakura.genshinutils") -class GenshinUtils(commands.Cog): - """ - A Genshin Impact cog. - """ +class GenshinUtils(GenshinSet, GenshinProfile, commands.Cog): + """GenshinUtils commands.""" __author__ = ["raidensakura"] __version__ = "1.0.0" @@ -24,12 +20,15 @@ def __init__(self, bot): self.config = Config.get_conf(self, 243316261264556032, force_registration=True) default_global = {"schema_version": 1} default_user = {"UID": 000000000} + self.config.register_global(**default_global) self.config.register_user(**default_user) + self.session = aiohttp.ClientSession() + + def cog_unload(self): + self.bot.loop.create_task(self.session.close()) def format_help_for_context(self, ctx: commands.Context) -> str: - """ - Thanks Sinbad! - """ + """Thanks Sinbad!""" pre_processed = super().format_help_for_context(ctx) s = "s" if len(self.__author__) > 1 else "" return f"{pre_processed}\n\nAuthor{s}: {', '.join(self.__author__)}\nCog Version: {self.__version__}" @@ -38,103 +37,7 @@ async def red_delete_data_for_user(self, **kwargs) -> None: """Nothing to delete""" return - @checks.is_owner() - @commands.group() - async def genshinset(self, ctx: commands.Context): - """ - Settings for GenshinUtils cog. - """ - - @genshinset.command() - async def ltoken(self, ctx: commands.Context): - """Instructions on how to set the `ltoken` secret.""" - msg = f"Use `{ctx.prefix}set api genshinutils ltoken your_ltoken_here`." - await ctx.send(msg) - - @genshinset.command() - async def ltuid(self, ctx: commands.Context): - """Instructions on how to set the `ltuid` secret.""" - msg = f"Use `{ctx.prefix}set api genshinutils ltuid your_ltuid_here`." - await ctx.send(msg) - @commands.group() - async def genshin(self, ctx: commands.Context): - """ - GenshinUtils main command. - """ - - @genshin.command() @commands.guild_only() - @commands.bot_has_permissions(embed_links=True) - @commands.cooldown(2, 5, commands.BucketType.member) - async def profile(self, ctx: Context, *, member: Union[discord.Member, str, None]): - """ - Display a Genshin Impact profile. - If a UID is provided, it will display data for that player. - If a Discord user is provided, it will display data for that user (if a Genshin UID is linked). - If no argument is provided, it will display data for your account (if a Genshin UID is linked). - """ - - api_keys = await self.bot.get_shared_api_tokens("hoyolab") - if api_keys.get("ltuid") is None or api_keys.get("ltoken") is None: - return await ctx.send(f"API keys not set.") - - cookies = {"ltuid": api_keys.get("ltuid"), "ltoken": api_keys.get("ltoken")} - - client = genshin.Client(cookies) - - if not member: - log.debug("Fetch own UID from config") - uid = None - return await ctx.send("todo") - - elif isinstance(member, discord.Member) and member.id != ctx.me.id: - log.debug("Fetch mentioned user's UID from config") - uid = None - return await ctx.send("todo") - - elif isinstance(member, discord.Member) and member.id == ctx.me.id: - return await ctx.send(f"Sorry, but I do not play Genshin.") - - elif isinstance(member, str) and len(member) == 9 and member.isdigit(): - uid = member - try: - data = await client.get_genshin_user(uid) - except Exception as exc: - log.exception("Error trying to fetch data from API.", exc_info=exc) - return await ctx.send( - "Oops, I encountered an error while trying to fetch data from Hoyolab." - ) - - e = discord.Embed( - color=(await ctx.embed_colour()), - title=f"Data for {data.info.nickname} (AR{data.info.level})", - ) - e.add_field(name="Achievements", value=data.stats.achievements, inline=True) - e.add_field(name="Days Active", value=data.stats.days_active, inline=True) - e.add_field( - name="Characters Unlocked", value=data.stats.characters, inline=True - ) - e.add_field( - name="Highest Spiral Abyss Climb", - value=data.stats.spiral_abyss, - inline=True, - ) - e.add_field( - name="Oculi Collected", - value=( - f"Anemoculi: {data.stats.anemoculi}\n" - f"Geoculi: {data.stats.geoculi}\n" - f"Electroculi: {data.stats.electroculi}\n" - f"Dendroculi: {data.stats.dendroculi}" - ), - inline=True, - ) - - try: - return await ctx.send(embed=e) - except Exception as exc: - log.exception("Error trying to send choose embed.", exc_info=exc) - return await ctx.send( - "Oops, I encountered an error while trying to send the embed." - ) + async def genshin(self, ctx: commands.Context): + """GenshinUtils main command.""" diff --git a/genshinutils/profile.py b/genshinutils/profile.py new file mode 100644 index 0000000..c379572 --- /dev/null +++ b/genshinutils/profile.py @@ -0,0 +1,116 @@ +import logging +from typing import Union + +import discord +import genshin as genshinpy +from redbot.core import commands +from redbot.core.commands import Context + +log = logging.getLogger("red.raidensakura.genshinutils") + + +class GenshinProfile(commands.Cog): + """GenshinUtils profile commands.""" + + # This will get replaced by genshinutils.py's `genshin` + # Thanks Jojo#7791! + @commands.group() + @commands.guild_only() + async def genshin(self, ctx: commands.Context): + """GenshinUtils main command.""" + + @genshin.command() + @commands.guild_only() + @commands.bot_has_permissions(embed_links=True) + @commands.cooldown(2, 5, commands.BucketType.member) + async def profile(self, ctx: Context, *, user_or_uid: Union[discord.Member, str, None]): + """ + Display a Genshin Impact profile. + If a UID is provided, it will display data for that UID. + If a Discord user is provided, it will display data for that user (if UID is linked). + If no argument is provided, it will display data for your account (if UID is linked). + """ + + api_keys = await self.bot.get_shared_api_tokens("hoyolab") + if api_keys.get("ltuid") is None or api_keys.get("ltoken") is None: + return await ctx.send(f"API keys not set.") + + cookies = {"ltuid": api_keys.get("ltuid"), "ltoken": api_keys.get("ltoken")} + + client = genshinpy.Client(cookies) + + async def get_profile(uid): + try: + data = await client.get_genshin_user(uid) + except Exception as exc: + log.debug("Error trying to fetch profile data from Hoyolab API.") + return await ctx.send("No profile was found with that UID.") + + e = discord.Embed( + color=(await ctx.embed_colour()), + description=( + f"```fix\n" \ + f"✨ :: Profile for {data.info.nickname} [AR {data.info.level}]```" + ), + ) + e.set_thumbnail(url=data.characters[0].icon) + e.add_field(name="Achievements", value=data.stats.achievements, inline=True) + e.add_field(name="Days Active", value=data.stats.days_active, inline=True) + e.add_field( + name="Characters Unlocked", value=data.stats.characters, inline=True + ) + e.add_field( + name="Highest Spiral Abyss Climb", + value=data.stats.spiral_abyss, + inline=True, + ) + e.add_field( + name="Total Oculi Collected", + value=(f"{data.stats.anemoculi + data.stats.geoculi + data.stats.electroculi + data.stats.dendroculi}"), + inline=True, + ) + e.add_field( + name="Waypoints Unlocked", + value=(f"{data.stats.unlocked_waypoints}"), + inline=True + ) + e.add_field( + name="Total Chests Opened", + value=(f"{data.stats.common_chests + data.stats.precious_chests + data.stats.exquisite_chests + data.stats.luxurious_chests + data.stats.remarkable_chests}"), + inline=True + ) + e.add_field( + name="Domains Unlocked", + value=(f"{data.stats.unlocked_domains}"), + inline=True + ) + + try: + return await ctx.send(embed=e) + except Exception as exc: + log.exception("Error trying to send embed.", exc_info=exc) + return await ctx.send( + "Oops, I encountered an error while trying to send the embed." + ) + + if not user_or_uid: + uid = await self.config.user(ctx.author).get_raw("UID") + if not uid or uid == "000000000": + return await ctx.send("You don't have any UID linked.") + async with ctx.typing(): + return await get_profile(uid) + + elif isinstance(user_or_uid, discord.Member) and user_or_uid.id != ctx.me.id: + uid = await self.config.user(user_or_uid).get_raw("UID") + if not uid or uid == "000000000": + return await ctx.send("That user has not linked a UID yet.") + async with ctx.typing(): + return await get_profile(uid) + + elif isinstance(user_or_uid, discord.Member) and user_or_uid.id == ctx.me.id: + return await ctx.send(f"Sorry, but I do not play Genshin.") + + elif isinstance(user_or_uid, str) and len(user_or_uid) == 9 and user_or_uid.isdigit(): + uid = user_or_uid + async with ctx.typing(): + return await get_profile(uid) diff --git a/genshinutils/settings.py b/genshinutils/settings.py new file mode 100644 index 0000000..e2a523c --- /dev/null +++ b/genshinutils/settings.py @@ -0,0 +1,69 @@ +import logging, json + +from redbot.core import checks, commands +from redbot.core.commands import Context + +log = logging.getLogger("red.raidensakura.genshinutils") + + +class GenshinSet(commands.Cog): + """ + Settings for GenshinUtils cog. + """ + + @commands.group() + async def genshinset(self, ctx): + """Settings for GenshinUtils cog.""" + + @checks.is_owner() + @genshinset.command() + async def ltoken(self, ctx: commands.Context): + """Instructions on how to set the `ltoken` secret.""" + await ctx.send(f"Use `{ctx.prefix}set api hoyolab ltoken your_ltoken_here`.") + + @checks.is_owner() + @genshinset.command() + async def ltuid(self, ctx: commands.Context): + """Instructions on how to set the `ltuid` secret.""" + await ctx.send(f"Use `{ctx.prefix}set api hoyolab ltuid your_ltuid_here`.") + + @genshinset.command(name="uid", usage="") + @commands.guild_only() + @commands.cooldown(2, 5, commands.BucketType.member) + async def set_uid(self, ctx: commands.Context, uid_or_remove: str): + """ + Link or unlink a Genshin Impact UID to your Discord account. + For verification purpose, you will need to add your Discord tag to your in-game signature. + It can take up to 15 minutes for your signature to be refreshed. + """ + if uid_or_remove.lower() == "remove" or uid_or_remove.lower() == "unlink": + await self.config.user(ctx.author).UID.clear() + return await ctx.send(f"Successfully removed UID for {ctx.author.name}") + else: + uid = uid_or_remove + if not len(uid) == 9 or not uid.isdigit(): + return await ctx.send("Not a valid UID.") + + try: + reqmethod = self.session.get + url = f"https://enka.network/u/{uid}/__data.json" + async with reqmethod(url, headers={}, data={}) as req: + data = await req.text() + status = req.status + try: + parsed = json.loads(data) + except json.JSONDecodeError: + parsed = data + except Exception as exc: + log.error(exc) + return await ctx.send("Error trying to fetch data from API [enka.network].") + + author_discord_id = f"{ctx.author.name}#{ctx.author.discriminator}" + signature = parsed["playerInfo"]["signature"] + if author_discord_id in signature: + log.debug("UID and signature match") + await self.config.user(ctx.author).UID.set(uid) + return await ctx.send(f"Successfully set UID for {ctx.author.name} to {uid}") + else: + log.debug("UID and signature does not match") + return await ctx.send(f"Your signature does not contain your Discord tag.\nNote that may take up to 15 minutes for changes to be reflected.") From 1953070a7f7bc6d69ff57cb9bade34fa142ec5f5 Mon Sep 17 00:00:00 2001 From: Raiden Date: Thu, 26 Jan 2023 22:44:00 +0800 Subject: [PATCH 03/51] Add dictionary --- genshinutils/constants.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 genshinutils/constants.py diff --git a/genshinutils/constants.py b/genshinutils/constants.py new file mode 100644 index 0000000..cd38510 --- /dev/null +++ b/genshinutils/constants.py @@ -0,0 +1,13 @@ +""" +List of commonly accepted character names +Key name always need to be properly capitalized and match in-game character name +First index always need to match formal name but in all lowercase +This make sure the profile command accepts a variation of valid character names +""" +common_names = { + "Kamisato Ayato": ["kamisato ayato", "ayato"], + "Kamisato Ayaka": ["kamisato ayaka", "ayaka", "ayaya"], + "Raiden Shogun": ["raiden shogun", "raiden", "shogun", "ei", "beelzebul"], + "Arataki Itto": ["arataki itto", "itto", "arataki"], + # Add more stuff here +} From 15baef8de755212aed3db3d36aa7d1b0e1b5cfb5 Mon Sep 17 00:00:00 2001 From: Raiden Date: Thu, 26 Jan 2023 22:45:48 +0800 Subject: [PATCH 04/51] =?UTF-8?q?More=20stuff=20=E2=9C=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- genshinutils/genshinutils.py | 28 +++-- genshinutils/info.json | 10 +- genshinutils/profile.py | 219 ++++++++++++++++++++++++----------- genshinutils/settings.py | 76 +++++++----- 4 files changed, 219 insertions(+), 114 deletions(-) diff --git a/genshinutils/genshinutils.py b/genshinutils/genshinutils.py index 3858ea1..75bd2c5 100644 --- a/genshinutils/genshinutils.py +++ b/genshinutils/genshinutils.py @@ -1,10 +1,15 @@ -import logging, aiohttp +import logging +from typing import Literal +from enkanetwork import EnkaNetworkAPI from redbot.core import Config, checks, commands from redbot.core.bot import Red from redbot.core.commands import Context -from .settings import GenshinSet + from .profile import GenshinProfile +from .settings import GenshinSet + +enka_client = EnkaNetworkAPI() log = logging.getLogger("red.raidensakura.genshinutils") @@ -18,14 +23,15 @@ class GenshinUtils(GenshinSet, GenshinProfile, commands.Cog): def __init__(self, bot): self.bot = bot self.config = Config.get_conf(self, 243316261264556032, force_registration=True) - default_global = {"schema_version": 1} - default_user = {"UID": 000000000} + default_global = {"schema_version": 1, "verification": True} + default_user = {"UID": ""} self.config.register_global(**default_global) self.config.register_user(**default_user) - self.session = aiohttp.ClientSession() + self.enka_client = enka_client def cog_unload(self): - self.bot.loop.create_task(self.session.close()) + log.debug(f"[Cog Unload] Executing tasks.") + enka_client._close() def format_help_for_context(self, ctx: commands.Context) -> str: """Thanks Sinbad!""" @@ -33,9 +39,13 @@ def format_help_for_context(self, ctx: commands.Context) -> str: s = "s" if len(self.__author__) > 1 else "" return f"{pre_processed}\n\nAuthor{s}: {', '.join(self.__author__)}\nCog Version: {self.__version__}" - async def red_delete_data_for_user(self, **kwargs) -> None: - """Nothing to delete""" - return + async def red_delete_data_for_user( + self, + *, + requester: Literal["discord", "owner", "user", "user_strict"], + user_id: int, + ): + await self.config.user_from_id(user_id).clear() @commands.group() @commands.guild_only() diff --git a/genshinutils/info.json b/genshinutils/info.json index c1ac06a..bf9a05d 100644 --- a/genshinutils/info.json +++ b/genshinutils/info.json @@ -1,12 +1,12 @@ { "name": "GenshinUtils", - "short": "A Genshin Impact cog", - "description": "", - "install_msg": "", - "end_user_data_statement": "This cog does not persistently store any data about users.", + "short": "A Genshin Impact utility cog", + "description": "Various useful utilities for Genshin Impact, such as retrieving profile data and many more.", + "install_msg": "Thank you for installing my cog. This is continuously being worked on, expect rapid changes.\nI test stuff in my Discord: https://dsc.gg/transience", + "end_user_data_statement": "This cog interact with a public API to store your linked game UID.", "author": ["raidensakura"], "required_cogs": {}, - "requirements": ["genshin"], + "requirements": ["genshin", "enkanetwork", "aioenkanetworkcard"], "tags": [ "genshin" ], diff --git a/genshinutils/profile.py b/genshinutils/profile.py index c379572..38db6d4 100644 --- a/genshinutils/profile.py +++ b/genshinutils/profile.py @@ -1,17 +1,22 @@ +import io import logging +import time from typing import Union import discord -import genshin as genshinpy +from aioenkanetworkcard import encbanner from redbot.core import commands from redbot.core.commands import Context +from .constants import common_names + log = logging.getLogger("red.raidensakura.genshinutils") class GenshinProfile(commands.Cog): - """GenshinUtils profile commands.""" + """GenshinUtils profile command class.""" + # https://discord.com/channels/133049272517001216/160386989819035648/1067445744497348639 # This will get replaced by genshinutils.py's `genshin` # Thanks Jojo#7791! @commands.group() @@ -22,95 +27,171 @@ async def genshin(self, ctx: commands.Context): @genshin.command() @commands.guild_only() @commands.bot_has_permissions(embed_links=True) - @commands.cooldown(2, 5, commands.BucketType.member) - async def profile(self, ctx: Context, *, user_or_uid: Union[discord.Member, str, None]): + @commands.cooldown(2, 10, commands.BucketType.member) + async def profile( + self, + ctx: Context, + user_or_uid: Union[discord.Member, str, None], + *, + character: Union[str, None], + ): """ - Display a Genshin Impact profile. - If a UID is provided, it will display data for that UID. - If a Discord user is provided, it will display data for that user (if UID is linked). - If no argument is provided, it will display data for your account (if UID is linked). + Display a Genshin Impact profile for a UID, Discord user or yourself. + If a character name is provided, it will display character infographic instead. """ - api_keys = await self.bot.get_shared_api_tokens("hoyolab") - if api_keys.get("ltuid") is None or api_keys.get("ltoken") is None: - return await ctx.send(f"API keys not set.") - - cookies = {"ltuid": api_keys.get("ltuid"), "ltoken": api_keys.get("ltoken")} - - client = genshinpy.Client(cookies) - async def get_profile(uid): try: - data = await client.get_genshin_user(uid) + data = await self.enka_client.fetch_user(uid) except Exception as exc: - log.debug("Error trying to fetch profile data from Hoyolab API.") - return await ctx.send("No profile was found with that UID.") + return await ctx.send( + f"Unable to retrieve data from enka.network:\n`{exc}`" + ) e = discord.Embed( color=(await ctx.embed_colour()), description=( - f"```fix\n" \ - f"✨ :: Profile for {data.info.nickname} [AR {data.info.level}]```" - ), - ) - e.set_thumbnail(url=data.characters[0].icon) - e.add_field(name="Achievements", value=data.stats.achievements, inline=True) - e.add_field(name="Days Active", value=data.stats.days_active, inline=True) - e.add_field( - name="Characters Unlocked", value=data.stats.characters, inline=True - ) - e.add_field( - name="Highest Spiral Abyss Climb", - value=data.stats.spiral_abyss, - inline=True, - ) - e.add_field( - name="Total Oculi Collected", - value=(f"{data.stats.anemoculi + data.stats.geoculi + data.stats.electroculi + data.stats.dendroculi}"), - inline=True, - ) - e.add_field( - name="Waypoints Unlocked", - value=(f"{data.stats.unlocked_waypoints}"), - inline=True - ) - e.add_field( - name="Total Chests Opened", - value=(f"{data.stats.common_chests + data.stats.precious_chests + data.stats.exquisite_chests + data.stats.luxurious_chests + data.stats.remarkable_chests}"), - inline=True + f"```fix\n" + f"✨ :: Profile for {data.player.nickname} [AR {data.player.level}]```\n" + ), ) + if data.player.characters_preview: + char_str = "" + for character in data.player.characters_preview: + if character.name == data.player.characters_preview[0].name: + char_str += f"{character.name}" + else: + char_str += f", {character.name}" + e.add_field( + name="Characters in Showcase", + value=(f"```fix\n" f"{char_str}" f"```"), + inline=False, + ) + e.set_thumbnail(url=data.player.avatar.icon.url) + e.set_image(url=data.player.namecard.banner.url) + e.add_field(name="Signature", value=f"{data.player.signature}") + e.add_field(name="World Level", value=f"{data.player.world_level}") + e.add_field(name="Achievements", value=data.player.achievement) e.add_field( - name="Domains Unlocked", - value=(f"{data.stats.unlocked_domains}"), - inline=True + name="Current Spiral Abyss Floor", + value=f"{data.player.abyss_floor} - {data.player.abyss_room}", ) try: return await ctx.send(embed=e) except Exception as exc: - log.exception("Error trying to send embed.", exc_info=exc) + log.exception( + f"[{get_profile.__name__}] Error trying to send embed.", + exc_info=exc, + ) return await ctx.send( "Oops, I encountered an error while trying to send the embed." ) - if not user_or_uid: - uid = await self.config.user(ctx.author).get_raw("UID") - if not uid or uid == "000000000": - return await ctx.send("You don't have any UID linked.") - async with ctx.typing(): - return await get_profile(uid) + async def get_character_card(uid, char_name): + async with encbanner.ENC( + lang="en", splashArt=True, characterName=char_name + ) as encard: + ENCpy = await encard.enc(uids=uid) + return await encard.creat(ENCpy, 2) + + async def validate_uid(u): + if isinstance(u, discord.Member): + uid = await self.config.user(u).get_raw("UID") + if uid: + exist = "exist" + else: + exist = "does not exist" + log.debug(f"[{validate_uid.__name__}] UID {exist} in config.") + + elif isinstance(u, str) and len(u) == 9 and u.isdigit(): + uid = u + log.debug(f"[{validate_uid.__name__}] This is a valid UID.") + + else: + uid = None + log.debug(f"[{validate_uid.__name__}] This is not a valid UID.") + + return uid + + def validate_char_name(arg): + formal_name = {i for i in common_names if arg in common_names[i]} + if formal_name: + return str(formal_name).strip("{'\"}") + + async def generate_char_info(uid, char_name): + """Generate character info""" + with io.BytesIO() as image_binary: + char_card = await get_character_card(uid, char_name) + if not char_card: + return await ctx.send( + "This user does not have that character featured." + ) + temp_filename = str(time.time()).split(".")[0] + ".png" + log.debug( + f"[{generate_char_info.__name__}] Pillow object for character card:\n{char_card}" + ) + char_card[uid][char_name].save( + image_binary, "PNG", optimize=True, quality=95 + ) + image_binary.seek(0) + return await ctx.send( + file=discord.File(fp=image_binary, filename=temp_filename) + ) - elif isinstance(user_or_uid, discord.Member) and user_or_uid.id != ctx.me.id: - uid = await self.config.user(user_or_uid).get_raw("UID") - if not uid or uid == "000000000": - return await ctx.send("That user has not linked a UID yet.") - async with ctx.typing(): - return await get_profile(uid) + log.debug(f"[Args] user_or_uid: {user_or_uid}") + log.debug(f"[Args] character: {character}") - elif isinstance(user_or_uid, discord.Member) and user_or_uid.id == ctx.me.id: - return await ctx.send(f"Sorry, but I do not play Genshin.") + """If nothing is passed at all, we assume user is trying to generate their own profile""" + if not user_or_uid and not character: + uid = await validate_uid(ctx.author) + if not uid: + return await ctx.send("You do not have a UID linked.") - elif isinstance(user_or_uid, str) and len(user_or_uid) == 9 and user_or_uid.isdigit(): - uid = user_or_uid - async with ctx.typing(): + with ctx.typing(): return await get_profile(uid) + + """ + Since both args are optional: [user_or_uid] [character] + [character] could be passed as [user_or_uid] + We check and handle it appropriately + """ + if user_or_uid and not character: + uid = await validate_uid(user_or_uid) + if uid: + with ctx.typing(): + return await get_profile(uid) + + log.debug( + f"[{get_profile.__name__}] Not a UID, assuming it's a character name..." + ) + char = validate_char_name(user_or_uid) + if not char: + return await ctx.send( + "Not a valid UID or character name that's not in dictionary." + ) + + log.debug( + f"[{get_profile.__name__}] Valid character name found, trying to fetch author UID..." + ) + uid = await validate_uid(ctx.author) + if not uid: + return await ctx.send("You do not have a UID linked.") + + with ctx.typing(): + return await generate_char_info(uid, char) + + """This handles if both [user_or_uid] and [character] are appropriately passed""" + if user_or_uid and character: + uid = await validate_uid(user_or_uid) + if not uid: + return await ctx.send( + "Not a valid UID or user does not have a UID linked." + ) + + char = validate_char_name(character) + if not char: + return await ctx.send("Character name invalid or not in dictionary.") + + with ctx.typing(): + await generate_char_info(uid, char) diff --git a/genshinutils/settings.py b/genshinutils/settings.py index e2a523c..420e615 100644 --- a/genshinutils/settings.py +++ b/genshinutils/settings.py @@ -1,4 +1,4 @@ -import logging, json +import logging from redbot.core import checks, commands from redbot.core.commands import Context @@ -7,63 +7,77 @@ class GenshinSet(commands.Cog): - """ - Settings for GenshinUtils cog. - """ + """GenshinUtils genshinset command class.""" @commands.group() async def genshinset(self, ctx): - """Settings for GenshinUtils cog.""" + """Various settings for GenshinUtils cog.""" @checks.is_owner() @genshinset.command() async def ltoken(self, ctx: commands.Context): - """Instructions on how to set the `ltoken` secret.""" + """(Unused) Instructions on how to set the `ltoken` secret.""" await ctx.send(f"Use `{ctx.prefix}set api hoyolab ltoken your_ltoken_here`.") @checks.is_owner() @genshinset.command() async def ltuid(self, ctx: commands.Context): - """Instructions on how to set the `ltuid` secret.""" + """(Unused) Instructions on how to set the `ltuid` secret.""" await ctx.send(f"Use `{ctx.prefix}set api hoyolab ltuid your_ltuid_here`.") + @checks.is_owner() + @genshinset.command() + async def verification(self, ctx: commands.Context, toggle: bool): + """Globally enable or disable UID verification for GenshinUtils cog.""" + await self.config.verification.set(toggle) + status = "enabled" if toggle else "disabled" + return await ctx.send(f"Global UID verification has been {status}.") + @genshinset.command(name="uid", usage="") @commands.guild_only() @commands.cooldown(2, 5, commands.BucketType.member) async def set_uid(self, ctx: commands.Context, uid_or_remove: str): """ Link or unlink a Genshin Impact UID to your Discord account. - For verification purpose, you will need to add your Discord tag to your in-game signature. + If verification is enabled, you need to add your Discord tag to your in-game signature. It can take up to 15 minutes for your signature to be refreshed. """ + + async def verification_enabled(): + enabled = ( + await self.config.verification() + # or await self.config.guild.verification() + ) + return enabled + + def pass_verification(discordtag, signature): + if discordtag == signature: + return True + if uid_or_remove.lower() == "remove" or uid_or_remove.lower() == "unlink": await self.config.user(ctx.author).UID.clear() - return await ctx.send(f"Successfully removed UID for {ctx.author.name}") - else: - uid = uid_or_remove + return await ctx.send(f"Successfully removed UID for {ctx.author.name}.") + + uid = uid_or_remove + if not len(uid) == 9 or not uid.isdigit(): - return await ctx.send("Not a valid UID.") + return await ctx.send("Invalid UID provided, it must consist of 9 digits.") try: - reqmethod = self.session.get - url = f"https://enka.network/u/{uid}/__data.json" - async with reqmethod(url, headers={}, data={}) as req: - data = await req.text() - status = req.status - try: - parsed = json.loads(data) - except json.JSONDecodeError: - parsed = data + with ctx.typing(): + data = await self.enka_client.fetch_user(uid) except Exception as exc: - log.error(exc) - return await ctx.send("Error trying to fetch data from API [enka.network].") + return await ctx.send( + f"Unable to retrieve data from enka.network:\n`{exc}`" + ) author_discord_id = f"{ctx.author.name}#{ctx.author.discriminator}" - signature = parsed["playerInfo"]["signature"] - if author_discord_id in signature: - log.debug("UID and signature match") - await self.config.user(ctx.author).UID.set(uid) - return await ctx.send(f"Successfully set UID for {ctx.author.name} to {uid}") - else: - log.debug("UID and signature does not match") - return await ctx.send(f"Your signature does not contain your Discord tag.\nNote that may take up to 15 minutes for changes to be reflected.") + + if await verification_enabled() and not pass_verification( + author_discord_id, data.player.signature + ): + return await ctx.send( + f"Your signature does not contain your Discord tag.\nNote that it may take up to 15 minutes for changes to be reflected." + ) + await self.config.user(ctx.author).UID.set(uid) + return await ctx.send(f"Successfully set UID for {ctx.author.name} to {uid}.") From 1df04d22796742c4eba4f27f962e8acd6c1b0e6e Mon Sep 17 00:00:00 2001 From: Raiden Date: Sat, 28 Jan 2023 21:42:38 +0800 Subject: [PATCH 05/51] =?UTF-8?q?Too=20much=20stuff=20=E2=9C=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- genshinutils/genshinutils.py | 17 +-- genshinutils/info.json | 2 +- genshinutils/profile.py | 80 +++---------- genshinutils/register.py | 218 +++++++++++++++++++++++++++++++++++ genshinutils/settings.py | 63 ++-------- genshinutils/utils.py | 80 +++++++++++++ 6 files changed, 335 insertions(+), 125 deletions(-) create mode 100644 genshinutils/register.py create mode 100644 genshinutils/utils.py diff --git a/genshinutils/genshinutils.py b/genshinutils/genshinutils.py index 75bd2c5..86d30bd 100644 --- a/genshinutils/genshinutils.py +++ b/genshinutils/genshinutils.py @@ -3,10 +3,10 @@ from enkanetwork import EnkaNetworkAPI from redbot.core import Config, checks, commands -from redbot.core.bot import Red from redbot.core.commands import Context from .profile import GenshinProfile +from .register import GenshinRegister from .settings import GenshinSet enka_client = EnkaNetworkAPI() @@ -14,7 +14,7 @@ log = logging.getLogger("red.raidensakura.genshinutils") -class GenshinUtils(GenshinSet, GenshinProfile, commands.Cog): +class GenshinUtils(GenshinSet, GenshinRegister, GenshinProfile, commands.Cog): """GenshinUtils commands.""" __author__ = ["raidensakura"] @@ -23,14 +23,17 @@ class GenshinUtils(GenshinSet, GenshinProfile, commands.Cog): def __init__(self, bot): self.bot = bot self.config = Config.get_conf(self, 243316261264556032, force_registration=True) - default_global = {"schema_version": 1, "verification": True} - default_user = {"UID": ""} + default_global = { + "schema_version": 1, + "verification": True, + "encryption_key": "", + } + default_user = {"UID": "", "ltuid": "", "ltoken": ""} self.config.register_global(**default_global) self.config.register_user(**default_user) self.enka_client = enka_client def cog_unload(self): - log.debug(f"[Cog Unload] Executing tasks.") enka_client._close() def format_help_for_context(self, ctx: commands.Context) -> str: @@ -42,12 +45,12 @@ def format_help_for_context(self, ctx: commands.Context) -> str: async def red_delete_data_for_user( self, *, - requester: Literal["discord", "owner", "user", "user_strict"], + requester: Literal["discord_deleted_user", "owner", "user_strict", "user"], user_id: int, ): await self.config.user_from_id(user_id).clear() @commands.group() - @commands.guild_only() async def genshin(self, ctx: commands.Context): """GenshinUtils main command.""" + # TODO: Embed explaining what this cog does and its info diff --git a/genshinutils/info.json b/genshinutils/info.json index bf9a05d..00b68d1 100644 --- a/genshinutils/info.json +++ b/genshinutils/info.json @@ -6,7 +6,7 @@ "end_user_data_statement": "This cog interact with a public API to store your linked game UID.", "author": ["raidensakura"], "required_cogs": {}, - "requirements": ["genshin", "enkanetwork", "aioenkanetworkcard"], + "requirements": ["genshin", "enkanetwork", "aioenkanetworkcard", "fernet"], "tags": [ "genshin" ], diff --git a/genshinutils/profile.py b/genshinutils/profile.py index 38db6d4..a133b9e 100644 --- a/genshinutils/profile.py +++ b/genshinutils/profile.py @@ -4,11 +4,9 @@ from typing import Union import discord -from aioenkanetworkcard import encbanner -from redbot.core import commands -from redbot.core.commands import Context +from redbot.core import checks, commands -from .constants import common_names +from .utils import get_character_card, validate_char_name, validate_uid log = logging.getLogger("red.raidensakura.genshinutils") @@ -20,7 +18,6 @@ class GenshinProfile(commands.Cog): # This will get replaced by genshinutils.py's `genshin` # Thanks Jojo#7791! @commands.group() - @commands.guild_only() async def genshin(self, ctx: commands.Context): """GenshinUtils main command.""" @@ -30,7 +27,7 @@ async def genshin(self, ctx: commands.Context): @commands.cooldown(2, 10, commands.BucketType.member) async def profile( self, - ctx: Context, + ctx: commands.Context, user_or_uid: Union[discord.Member, str, None], *, character: Union[str, None], @@ -40,7 +37,7 @@ async def profile( If a character name is provided, it will display character infographic instead. """ - async def get_profile(uid): + async def generate_profile(uid): try: data = await self.enka_client.fetch_user(uid) except Exception as exc: @@ -77,50 +74,9 @@ async def get_profile(uid): value=f"{data.player.abyss_floor} - {data.player.abyss_room}", ) - try: - return await ctx.send(embed=e) - except Exception as exc: - log.exception( - f"[{get_profile.__name__}] Error trying to send embed.", - exc_info=exc, - ) - return await ctx.send( - "Oops, I encountered an error while trying to send the embed." - ) - - async def get_character_card(uid, char_name): - async with encbanner.ENC( - lang="en", splashArt=True, characterName=char_name - ) as encard: - ENCpy = await encard.enc(uids=uid) - return await encard.creat(ENCpy, 2) - - async def validate_uid(u): - if isinstance(u, discord.Member): - uid = await self.config.user(u).get_raw("UID") - if uid: - exist = "exist" - else: - exist = "does not exist" - log.debug(f"[{validate_uid.__name__}] UID {exist} in config.") - - elif isinstance(u, str) and len(u) == 9 and u.isdigit(): - uid = u - log.debug(f"[{validate_uid.__name__}] This is a valid UID.") - - else: - uid = None - log.debug(f"[{validate_uid.__name__}] This is not a valid UID.") - - return uid - - def validate_char_name(arg): - formal_name = {i for i in common_names if arg in common_names[i]} - if formal_name: - return str(formal_name).strip("{'\"}") + return await ctx.send(embed=e) async def generate_char_info(uid, char_name): - """Generate character info""" with io.BytesIO() as image_binary: char_card = await get_character_card(uid, char_name) if not char_card: @@ -129,11 +85,11 @@ async def generate_char_info(uid, char_name): ) temp_filename = str(time.time()).split(".")[0] + ".png" log.debug( - f"[{generate_char_info.__name__}] Pillow object for character card:\n{char_card}" - ) - char_card[uid][char_name].save( - image_binary, "PNG", optimize=True, quality=95 + f"[generate_char_info] Pillow object for character card:\n{char_card}" ) + first_card = next(iter(char_card.values())) + card_object = next(iter(first_card.values())) + card_object.save(image_binary, "PNG", optimize=True, quality=95) image_binary.seek(0) return await ctx.send( file=discord.File(fp=image_binary, filename=temp_filename) @@ -144,12 +100,12 @@ async def generate_char_info(uid, char_name): """If nothing is passed at all, we assume user is trying to generate their own profile""" if not user_or_uid and not character: - uid = await validate_uid(ctx.author) + uid = await validate_uid(ctx.author, self) if not uid: return await ctx.send("You do not have a UID linked.") with ctx.typing(): - return await get_profile(uid) + return await generate_profile(uid) """ Since both args are optional: [user_or_uid] [character] @@ -157,13 +113,13 @@ async def generate_char_info(uid, char_name): We check and handle it appropriately """ if user_or_uid and not character: - uid = await validate_uid(user_or_uid) + uid = await validate_uid(user_or_uid, self) if uid: with ctx.typing(): - return await get_profile(uid) + return await generate_profile(uid) log.debug( - f"[{get_profile.__name__}] Not a UID, assuming it's a character name..." + f"[{ctx.command.name}] Not a UID, assuming it's a character name..." ) char = validate_char_name(user_or_uid) if not char: @@ -172,9 +128,9 @@ async def generate_char_info(uid, char_name): ) log.debug( - f"[{get_profile.__name__}] Valid character name found, trying to fetch author UID..." + f"[{ctx.command.name}] Valid character name found, trying to fetch author UID..." ) - uid = await validate_uid(ctx.author) + uid = await validate_uid(ctx.author, self) if not uid: return await ctx.send("You do not have a UID linked.") @@ -183,7 +139,7 @@ async def generate_char_info(uid, char_name): """This handles if both [user_or_uid] and [character] are appropriately passed""" if user_or_uid and character: - uid = await validate_uid(user_or_uid) + uid = await validate_uid(user_or_uid, self) if not uid: return await ctx.send( "Not a valid UID or user does not have a UID linked." @@ -194,4 +150,4 @@ async def generate_char_info(uid, char_name): return await ctx.send("Character name invalid or not in dictionary.") with ctx.typing(): - await generate_char_info(uid, char) + return await generate_char_info(uid, char) diff --git a/genshinutils/register.py b/genshinutils/register.py new file mode 100644 index 0000000..e0360a7 --- /dev/null +++ b/genshinutils/register.py @@ -0,0 +1,218 @@ +import logging +import re +from operator import attrgetter +from re import escape + +import genshin +from discord import Embed +from discord.channel import DMChannel +from redbot.core import checks, commands + +from .utils import decrypt_config, encrypt_config + +log = logging.getLogger("red.raidensakura.genshinutils") + + +class GenshinRegister(commands.Cog): + """GenshinUtils register command class.""" + + # https://discord.com/channels/133049272517001216/160386989819035648/1067445744497348639 + # This will get replaced by genshinutils.py's `genshin` + # Thanks Jojo#7791! + @commands.group() + async def genshin(self, ctx: commands.Context): + """GenshinUtils main command.""" + + @genshin.group() + async def register(self, ctx: commands.Context): + """Registration commands for GenshinUtils cog.""" + + @register.command(name="uid", usage="") + @commands.cooldown(2, 5, commands.BucketType.member) + async def set_uid(self, ctx: commands.Context, uid_or_remove: str): + """ + Link or unlink a Genshin Impact UID to your Discord account. + If verification is enabled, you need to add your Discord tag to your in-game signature. + It can take up to 15 minutes for your signature to be refreshed. + """ + + async def verification_enabled(): + enabled = await self.config.verification() + return enabled + + def pass_verification(discordtag, signature): + if discordtag == signature: + return True + + if uid_or_remove.lower() == "remove" or uid_or_remove.lower() == "unlink": + await self.config.user(ctx.author).UID.clear() + return await ctx.send(f"Successfully removed UID for {ctx.author.name}.") + + uid = uid_or_remove + + if not len(uid) == 9 or not uid.isdigit(): + return await ctx.send("Invalid UID provided, it must consist of 9 digits.") + + try: + with ctx.typing(): + data = await self.enka_client.fetch_user(uid) + except Exception as exc: + return await ctx.send( + f"Unable to retrieve data from enka.network:\n`{exc}`" + ) + + author_discord_id = f"{ctx.author.name}#{ctx.author.discriminator}" + + if await verification_enabled() and not pass_verification( + author_discord_id, data.player.signature + ): + return await ctx.send( + f"Your signature does not contain your Discord tag.\nNote that it may take up to 15 minutes for changes to be reflected." + ) + await self.config.user(ctx.author).UID.set(uid) + return await ctx.send(f"Successfully set UID for {ctx.author.name} to {uid}.") + + """ + Important Notes: + 1. Command has proprietary DM check since I want it to preface a a disclaimer when run in a server. + This command deals with sensitive information and I want it to be taken very seriously. + 2. I fully acknowledge storing the encryption key along with the encrypted data itself is terrible security practice. + Hoyolab account token can be used to perform destructive account actions, and potentially get your account banned for abuse. + Since the cog is open-source, the purpose of the encryption is to prevent bot owners from having plaintext access to them + in a way such that is require a bit of coding and encryption knowledge to access them on demand. + """ + + @register.command() + @commands.bot_has_permissions(embed_links=True) + @commands.cooldown(2, 10, commands.BucketType.member) + async def hoyolab(self, ctx: commands.Context, *, cookie: str = None): + """Link or unlink a Hoyolab account token to your Discord account.""" + + if not isinstance(ctx.channel, DMChannel): + + if cookie: + try: + await ctx.message.delete() + except: + pass + + # Preface disclaimer + app_info = await self.bot.application_info() + if app_info.team: + owner = app_info.team.name + else: + owner = app_info.owner + desc = ( + f"This command lets the bot perform Hoyolab account actions on your behalf, authenticated using your token. " + f"The token will then be linked to your Discord ID and stored encrypted in the bot's config, along with the encryption key. " + f"Make sure **you fully acknowledge the risks of sharing your account token online** before proceeding.\n\n" + f"For security reason, please run this command in a DM channel when setting token.\n\n" + f"Read on how to obtain your token [here](https://project-mei.xyz/genshinutils)." + ) + e = Embed( + color=(await ctx.embed_colour()), + title="Important Disclaimer", + description=desc, + ) + if app_info.bot_public: + public = "Can be invited by anyone." + else: + public = "Can only be invited by the owner." + e.add_field(name="Bot Owner", value=owner) + e.add_field(name="Invite Link Privacy", value=public) + if ctx.me.avatar_url: + e.set_thumbnail(url=ctx.me.avatar_url) + e.set_footer(text=f"Command invoked by {ctx.author}.") + return await ctx.send(embed=e) + + if not cookie: + msg = ( + f"**Provide a valid cookie to bind your Discord account to.**\n\n" + f"` » ` Instruction on how to obtain your Hoyolab cookie:\n\n\n" + f"` » ` For command help context: `{escape(ctx.prefix)}help genshin register {escape(ctx.command.name)}`\n\n" + f"` » ` To read disclaimers, this command again in any server." + ) + return await ctx.send(msg) + + # Captures 2 groups: "ltuid=" and "abcd1234" + re_uid = re.search(r"(ltuid=)([^;]*)", cookie) + re_ltoken = re.search(r"(ltoken=)([^;]*)", cookie) + + if not re_uid: + return await ctx.send("Not a valid `ltuid`.") + if not re_ltoken: + return await ctx.send("Not a valid `ltoken`.") + + ltuid = re_uid.group(2) + ltoken = re_ltoken.group(2) + + # Verify if cookie is valid + async with ctx.typing(): + try: + cookies = {"ltuid": ltuid, "ltoken": ltoken} + client = genshin.Client(cookies) + accounts = await client.get_game_accounts() + except Exception as exc: + return await ctx.send(f"Unable to retrieve data from Hoyolab API:\n`{exc}`") + """ + Accounts: [ GenshinAccount(lang="", game_biz="", level=int...), GenshinAccount(...) ] + Recognized game_biz: + bh3_global: Honkai Impact 3 Global + hk4e_global: Genshin Impact + """ + + # Filter Genshin accounts only + genshin_acc_list = [] + for account in accounts: + if account.game_biz == "hk4e_global": + genshin_acc_list.append(account) + + if not genshin_acc_list: + return await ctx.send( + "Couldn't find a linked Genshin UID in your Hoyolab account." + ) + + # https://www.geeksforgeeks.org/python-get-the-object-with-the-max-attribute-value-in-a-list-of-objects/ + # get genshin account with the highest level + highest_level_acc = max(genshin_acc_list, key=attrgetter("level")) + uid = highest_level_acc.uid + + # Save cookie in config + encoded_ltuid = await encrypt_config(self.config, ltuid) + encoded_ltoken = await encrypt_config(self.config, ltoken) + await self.config.user(ctx.author).UID.set(uid) + await self.config.user(ctx.author).ltuid.set(encoded_ltuid) + await self.config.user(ctx.author).ltoken.set(encoded_ltoken) + + # Send success embed + desc = f"Successfully bound a Genshin Impact account to your Discord account. Details are as follow." + e = Embed( + color=(await ctx.embed_colour()), + title="Account Binding Success", + description=desc, + ) + e.add_field(name="UID", value=highest_level_acc.uid) + e.add_field(name="Nickname", value=highest_level_acc.nickname) + e.add_field(name="Server", value=highest_level_acc.server_name) + e.add_field(name="AR Level", value=highest_level_acc.level) + e.add_field(name="Language", value=highest_level_acc.lang) + e.set_thumbnail(url=ctx.message.author.avatar_url) + + return await ctx.send(embed=e) + + # Debugging stuff + log.debug( + f"[Register Hoyolab] Encrypted ltuid saved: {await self.config.user(ctx.author).ltuid()}" + ) + log.debug( + f"[Register Hoyolab] Encrypted ltoken saved: {await self.config.user(ctx.author).ltoken()}" + ) + log.debug( + f"[Register Hoyolab] Encryption key saved: {await self.config.encryption_key()}" + ) + + decoded_ltuid = await decrypt_config(self.config, encoded_ltuid) + decoded_ltoken = await decrypt_config(self.config, encoded_ltoken) + + log.debug(f"[Register Hoyolab] Decoded ltuid: {decoded_ltuid}") + log.debug(f"[Register Hoyolab] Decoded ltoken: {decoded_ltoken}") diff --git a/genshinutils/settings.py b/genshinutils/settings.py index 420e615..96901e2 100644 --- a/genshinutils/settings.py +++ b/genshinutils/settings.py @@ -1,7 +1,6 @@ import logging from redbot.core import checks, commands -from redbot.core.commands import Context log = logging.getLogger("red.raidensakura.genshinutils") @@ -10,74 +9,28 @@ class GenshinSet(commands.Cog): """GenshinUtils genshinset command class.""" @commands.group() - async def genshinset(self, ctx): - """Various settings for GenshinUtils cog.""" + async def genshinset(self, ctx: commands.Context): + """Various global settings for GenshinUtils cog.""" @checks.is_owner() @genshinset.command() async def ltoken(self, ctx: commands.Context): - """(Unused) Instructions on how to set the `ltoken` secret.""" + """Instructions on how to set global `ltoken` secret.""" await ctx.send(f"Use `{ctx.prefix}set api hoyolab ltoken your_ltoken_here`.") @checks.is_owner() @genshinset.command() async def ltuid(self, ctx: commands.Context): - """(Unused) Instructions on how to set the `ltuid` secret.""" + """Instructions on how to set global `ltuid` secret.""" await ctx.send(f"Use `{ctx.prefix}set api hoyolab ltuid your_ltuid_here`.") @checks.is_owner() @genshinset.command() async def verification(self, ctx: commands.Context, toggle: bool): - """Globally enable or disable UID verification for GenshinUtils cog.""" + """ + Globally enable or disable UID verification for GenshinUtils cog. + Only applicable for account-linking via signature check. + """ await self.config.verification.set(toggle) status = "enabled" if toggle else "disabled" return await ctx.send(f"Global UID verification has been {status}.") - - @genshinset.command(name="uid", usage="") - @commands.guild_only() - @commands.cooldown(2, 5, commands.BucketType.member) - async def set_uid(self, ctx: commands.Context, uid_or_remove: str): - """ - Link or unlink a Genshin Impact UID to your Discord account. - If verification is enabled, you need to add your Discord tag to your in-game signature. - It can take up to 15 minutes for your signature to be refreshed. - """ - - async def verification_enabled(): - enabled = ( - await self.config.verification() - # or await self.config.guild.verification() - ) - return enabled - - def pass_verification(discordtag, signature): - if discordtag == signature: - return True - - if uid_or_remove.lower() == "remove" or uid_or_remove.lower() == "unlink": - await self.config.user(ctx.author).UID.clear() - return await ctx.send(f"Successfully removed UID for {ctx.author.name}.") - - uid = uid_or_remove - - if not len(uid) == 9 or not uid.isdigit(): - return await ctx.send("Invalid UID provided, it must consist of 9 digits.") - - try: - with ctx.typing(): - data = await self.enka_client.fetch_user(uid) - except Exception as exc: - return await ctx.send( - f"Unable to retrieve data from enka.network:\n`{exc}`" - ) - - author_discord_id = f"{ctx.author.name}#{ctx.author.discriminator}" - - if await verification_enabled() and not pass_verification( - author_discord_id, data.player.signature - ): - return await ctx.send( - f"Your signature does not contain your Discord tag.\nNote that it may take up to 15 minutes for changes to be reflected." - ) - await self.config.user(ctx.author).UID.set(uid) - return await ctx.send(f"Successfully set UID for {ctx.author.name} to {uid}.") diff --git a/genshinutils/utils.py b/genshinutils/utils.py new file mode 100644 index 0000000..74c5c12 --- /dev/null +++ b/genshinutils/utils.py @@ -0,0 +1,80 @@ +from __future__ import annotations + +import logging + +import discord +from aioenkanetworkcard import encbanner +from cryptography.fernet import Fernet + +from .constants import common_names + +log = logging.getLogger("red.raidensakura.genshinutils") + + +def bytes_to_string(bytes): + str = bytes.decode() + return str + + +def string_to_bytes(str): + bytes = str.encode() + return bytes + + +# https://stackoverflow.com/questions/44432945/generating-own-key-with-python-fernet +async def get_encryption_key(config): + key = string_to_bytes(await config.encryption_key()) + if not key or key is None: + key = Fernet.generate_key() + await config.encryption_key.set(bytes_to_string(key)) + return key + + +async def decrypt_config(config, encoded): + to_decode = string_to_bytes(encoded) + cipher_suite = Fernet(await get_encryption_key(config)) + decoded_bytes = cipher_suite.decrypt(to_decode) + decoded = bytes_to_string(decoded_bytes) + return decoded + + +async def encrypt_config(config, decoded): + to_encode = string_to_bytes(decoded) + cipher_suite = Fernet(await get_encryption_key(config)) + encoded_bytes = cipher_suite.encrypt(to_encode) + encoded = bytes_to_string(encoded_bytes) + return encoded + + +async def validate_uid(u, self): + if isinstance(u, discord.Member): + uid = await self.config.user(u).get_raw("UID") + if uid: + exist = "exist" + else: + exist = "does not exist" + log.debug(f"[validate_uid] UID {exist} in config.") + + elif isinstance(u, str) and len(u) == 9 and u.isdigit(): + uid = u + log.debug(f"[validate_uid] This is a valid UID.") + + else: + uid = None + log.debug(f"[validate_uid] This is not a valid UID.") + + return uid + + +def validate_char_name(arg): + formal_name = {i for i in common_names if arg in common_names[i]} + if formal_name: + return str(formal_name).strip("{'\"}") + + +async def get_character_card(uid, char_name): + async with encbanner.ENC( + lang="en", splashArt=True, characterName=char_name + ) as encard: + ENCpy = await encard.enc(uids=uid) + return await encard.creat(ENCpy, 2) From 46ee2e704269f2aecae97a672697bdac4df9212a Mon Sep 17 00:00:00 2001 From: Raiden Date: Sat, 28 Jan 2023 22:21:41 +0800 Subject: [PATCH 06/51] Update README.md --- genshinutils/README.md | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/genshinutils/README.md b/genshinutils/README.md index 6691559..3be701e 100644 --- a/genshinutils/README.md +++ b/genshinutils/README.md @@ -1,5 +1,5 @@

-

Genshin

+

GenshinUtils

@@ -8,8 +8,8 @@

-

Genshin - A Genshin Impact oriented cog.

- +

GenshinUtils - Multipurpose Genshin Impact cog. For now, it's able to display in-game profile information and featured character build cards.

+

@@ -20,3 +20,11 @@ [p]repo add raiden-cogs https://github.com/raidensakura/raiden-cogs/ [p]cog install raiden-cogs genshin ``` + +

Obtaining Account Cookie (Hoyolab)

+
    +
  1. Go to Hoyolab and log into your account.
  2. +
  3. Press F12 to open Developer Tools and click on Console tab.
  4. +
  5. In the terminal, next to a right arrow > type in document.cookie and copy the output.
  6. +
  7. Paste the cookie next to the registration command for the bot, example:
    ?genshin register hoyolab your_cookie
    Make sure to replace ? in the command with your bot prefix.
  8. +
From 9d5d659baa41f301801ab9dd973975cf77b11618 Mon Sep 17 00:00:00 2001 From: Raiden Date: Sat, 28 Jan 2023 22:30:40 +0800 Subject: [PATCH 07/51] Update README.md --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index b0b4df9..792a550 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,11 @@ choose A cog to replace Red Bot's General cog choose command to something more intuitive. + + + genshinutils + Multipurpose Genshin Impact oriented cog. + From ab53fc001f24d1f959aeb5c75dcd3c8571f65642 Mon Sep 17 00:00:00 2001 From: Raiden Date: Sat, 28 Jan 2023 22:31:28 +0800 Subject: [PATCH 08/51] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 792a550..cfb9a90 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ genshinutils - Multipurpose Genshin Impact oriented cog. + Multipurpose Genshin Impact oriented cog. Able to retrieve game profile data, character data and many more. From f5a94f09939ba9beeeb5f76db4d00b289b91a51b Mon Sep 17 00:00:00 2001 From: Raiden Date: Mon, 30 Jan 2023 01:46:51 +0800 Subject: [PATCH 09/51] Too many stuff again --- genshinutils/assets/global/cog_icon.png | Bin 0 -> 9820 bytes genshinutils/constants.py | 19 +++- genshinutils/genshinutils.py | 6 +- genshinutils/info.json | 2 +- genshinutils/notes.py | 116 ++++++++++++++++++++++++ genshinutils/profile.py | 100 ++++++++++++++++---- genshinutils/register.py | 4 +- genshinutils/utils.py | 67 +++++++++++++- 8 files changed, 283 insertions(+), 31 deletions(-) create mode 100644 genshinutils/assets/global/cog_icon.png create mode 100644 genshinutils/notes.py diff --git a/genshinutils/assets/global/cog_icon.png b/genshinutils/assets/global/cog_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..d7487c0e28a8483338186cd0aa5c130aca3948b6 GIT binary patch literal 9820 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 z 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", + ) + if data.expeditions: + e.add_field( + name="🚩 Expedition Status", + value=f"You can deploy a maximum of **{data.max_expeditions} characters**.", + inline=False, + ) + for expedition in data.expeditions: + e.add_field( + name=f"{expedition.character.name} ({expedition.status}) ", + value=f"Time left: **{expedition.remaining_time}**", + ) + + return await ctx.send(embed=e) + + async def test_honkai(): + try: + client = genshin.Client(cookie) + data = await client.get_full_honkai_user(20177789) + except Exception as exc: + return await ctx.send( + f"Unable to retrieve data from Hoyolab API:\n`{exc}`" + ) + return await log.debug(f"```{data}```") + + uid = await validate_uid(ctx.author, self.config) + if not uid: + return await ctx.send("You do not have a UID linked.") + + cookie = await get_user_cookie(self.config, ctx.author) + if not cookie: + return await ctx.send("No cookie.") + + with ctx.typing(): + # return await test_honkai() + return await generate_diary(uid) diff --git a/genshinutils/profile.py b/genshinutils/profile.py index a133b9e..24e27d2 100644 --- a/genshinutils/profile.py +++ b/genshinutils/profile.py @@ -3,10 +3,19 @@ import time from typing import Union +import genshin + import discord from redbot.core import checks, commands +from .constants import character_namecards +from .utils import generate_embed -from .utils import get_character_card, validate_char_name, validate_uid +from .utils import ( + enka_get_character_card, + validate_char_name, + validate_uid, + get_user_cookie, +) log = logging.getLogger("red.raidensakura.genshinutils") @@ -37,7 +46,7 @@ async def profile( If a character name is provided, it will display character infographic instead. """ - async def generate_profile(uid): + async def enka_generate_profile(uid): try: data = await self.enka_client.fetch_user(uid) except Exception as exc: @@ -45,13 +54,7 @@ async def generate_profile(uid): f"Unable to retrieve data from enka.network:\n`{exc}`" ) - e = discord.Embed( - color=(await ctx.embed_colour()), - description=( - f"```fix\n" - f"✨ :: Profile for {data.player.nickname} [AR {data.player.level}]```\n" - ), - ) + e = generate_embed(f"Profile for {data.player.nickname} [AR {data.player.level}]", await ctx.embed_color()) if data.player.characters_preview: char_str = "" for character in data.player.characters_preview: @@ -71,14 +74,14 @@ async def generate_profile(uid): e.add_field(name="Achievements", value=data.player.achievement) e.add_field( name="Current Spiral Abyss Floor", - value=f"{data.player.abyss_floor} - {data.player.abyss_room}", + value=f"{data.player.abyss_floor}-{data.player.abyss_room}", ) return await ctx.send(embed=e) - async def generate_char_info(uid, char_name): + async def enka_generate_char_img(uid, char_name): with io.BytesIO() as image_binary: - char_card = await get_character_card(uid, char_name) + char_card = await enka_get_character_card(uid, char_name) if not char_card: return await ctx.send( "This user does not have that character featured." @@ -95,17 +98,74 @@ async def generate_char_info(uid, char_name): file=discord.File(fp=image_binary, filename=temp_filename) ) + async def genshin_generate_profile(uid): + try: + client = genshin.Client(cookie) + data = await client.get_partial_genshin_user(uid) + except Exception as exc: + return await ctx.send( + f"Unable to retrieve data from Hoyolab API:\n`{exc}`" + ) + + e = generate_embed(f"Profile for {data.info.nickname} [AR {data.info.level}]", await ctx.embed_color()) + if data.characters: + e.set_thumbnail(url=data.characters[0].icon) + if character_namecards[data.characters[0].name.title()]: + namecard_url = character_namecards[data.characters[0].name.title()] + e.set_image(url=namecard_url) + e.add_field(name="Achievements", value=data.stats.achievements, inline=True) + e.add_field(name="Days Active", value=data.stats.days_active, inline=True) + e.add_field( + name="Characters Unlocked", value=data.stats.characters, inline=True + ) + e.add_field( + name="Current Spiral Abyss Floor", + value=data.stats.spiral_abyss, + inline=True, + ) + e.add_field( + name="Total Oculi Collected", + value=( + f"{data.stats.anemoculi + data.stats.geoculi + data.stats.electroculi + data.stats.dendroculi}" + ), + inline=True, + ) + e.add_field( + name="Waypoints Unlocked", + value=(f"{data.stats.unlocked_waypoints}"), + inline=True, + ) + e.add_field( + name="Total Chests Opened", + value=( + f"{data.stats.common_chests + data.stats.precious_chests + data.stats.exquisite_chests + data.stats.luxurious_chests + data.stats.remarkable_chests}" + ), + inline=True, + ) + e.add_field( + name="Domains Unlocked", + value=(f"{data.stats.unlocked_domains}"), + inline=True, + ) + + return await ctx.send(embed=e) + log.debug(f"[Args] user_or_uid: {user_or_uid}") log.debug(f"[Args] character: {character}") """If nothing is passed at all, we assume user is trying to generate their own profile""" if not user_or_uid and not character: - uid = await validate_uid(ctx.author, self) + uid = await validate_uid(ctx.author, self.config) if not uid: return await ctx.send("You do not have a UID linked.") + cookie = await get_user_cookie(self.config, ctx.author) + with ctx.typing(): - return await generate_profile(uid) + if not cookie: + return await enka_generate_profile(uid) + + return await genshin_generate_profile(uid) """ Since both args are optional: [user_or_uid] [character] @@ -113,10 +173,10 @@ async def generate_char_info(uid, char_name): We check and handle it appropriately """ if user_or_uid and not character: - uid = await validate_uid(user_or_uid, self) + uid = await validate_uid(user_or_uid, self.config) if uid: with ctx.typing(): - return await generate_profile(uid) + return await enka_generate_profile(uid) log.debug( f"[{ctx.command.name}] Not a UID, assuming it's a character name..." @@ -130,16 +190,16 @@ async def generate_char_info(uid, char_name): log.debug( f"[{ctx.command.name}] Valid character name found, trying to fetch author UID..." ) - uid = await validate_uid(ctx.author, self) + uid = await validate_uid(ctx.author, self.config) if not uid: return await ctx.send("You do not have a UID linked.") with ctx.typing(): - return await generate_char_info(uid, char) + return await enka_generate_char_img(uid, char) """This handles if both [user_or_uid] and [character] are appropriately passed""" if user_or_uid and character: - uid = await validate_uid(user_or_uid, self) + uid = await validate_uid(user_or_uid, self.config) if not uid: return await ctx.send( "Not a valid UID or user does not have a UID linked." @@ -150,4 +210,4 @@ async def generate_char_info(uid, char_name): return await ctx.send("Character name invalid or not in dictionary.") with ctx.typing(): - return await generate_char_info(uid, char) + return await enka_generate_char_img(uid, char) diff --git a/genshinutils/register.py b/genshinutils/register.py index e0360a7..7904c31 100644 --- a/genshinutils/register.py +++ b/genshinutils/register.py @@ -153,7 +153,9 @@ async def hoyolab(self, ctx: commands.Context, *, cookie: str = None): client = genshin.Client(cookies) accounts = await client.get_game_accounts() except Exception as exc: - return await ctx.send(f"Unable to retrieve data from Hoyolab API:\n`{exc}`") + return await ctx.send( + f"Unable to retrieve data from Hoyolab API:\n`{exc}`" + ) """ Accounts: [ GenshinAccount(lang="", game_biz="", level=int...), GenshinAccount(...) ] Recognized game_biz: diff --git a/genshinutils/utils.py b/genshinutils/utils.py index 74c5c12..a1d323d 100644 --- a/genshinutils/utils.py +++ b/genshinutils/utils.py @@ -10,17 +10,22 @@ log = logging.getLogger("red.raidensakura.genshinutils") - +# Used internally def bytes_to_string(bytes): str = bytes.decode() return str +# Used internally def string_to_bytes(str): bytes = str.encode() return bytes +""" +Accepts: config +Returns: str(encryption_key) or None +""" # https://stackoverflow.com/questions/44432945/generating-own-key-with-python-fernet async def get_encryption_key(config): key = string_to_bytes(await config.encryption_key()) @@ -30,6 +35,11 @@ async def get_encryption_key(config): return key +""" +Accepts: config, str(encoded) +Returns: str(decoded) +""" +# decrypt config async def decrypt_config(config, encoded): to_decode = string_to_bytes(encoded) cipher_suite = Fernet(await get_encryption_key(config)) @@ -38,6 +48,11 @@ async def decrypt_config(config, encoded): return decoded +""" +Accepts: config, str(decoded) +Returns: str(encoded) +""" +# encrypt config async def encrypt_config(config, decoded): to_encode = string_to_bytes(decoded) cipher_suite = Fernet(await get_encryption_key(config)) @@ -46,9 +61,14 @@ async def encrypt_config(config, decoded): return encoded -async def validate_uid(u, self): +""" +Accepts: ( str(uid) | discord.Member ), self.config +Returns: str(uid) or None +""" +# validate uid +async def validate_uid(u, config): if isinstance(u, discord.Member): - uid = await self.config.user(u).get_raw("UID") + uid = await config.user(u).UID() if uid: exist = "exist" else: @@ -66,15 +86,54 @@ async def validate_uid(u, self): return uid +""" +Accepts: str(name_query) +Returns: str(formal_name) or None +""" +# validate_char_name def validate_char_name(arg): formal_name = {i for i in common_names if arg in common_names[i]} if formal_name: return str(formal_name).strip("{'\"}") -async def get_character_card(uid, char_name): +""" +Accepts: str(uid), formal_name +Returns: { UID: { Character: } } +""" +# enka_get_character_card +async def enka_get_character_card(uid, char_name): async with encbanner.ENC( lang="en", splashArt=True, characterName=char_name ) as encard: ENCpy = await encard.enc(uids=uid) return await encard.creat(ENCpy, 2) + + +""" +Accepts: config, discord.Member +Returns: +""" +# get_user_cookie +async def get_user_cookie(config, user): + ltuid_config = await config.user(user).ltuid() + ltoken_config = await config.user(user).ltoken() + + if ltuid_config and ltoken_config: + ltuid = await decrypt_config(config, ltuid_config) + ltoken = await decrypt_config(config, ltoken_config) + cookie = {"ltuid": ltuid, "ltoken": ltoken} + + return cookie + + +""" +Accepts: str(title), str(author), color +Returns: discord.Embed +""" +# generate_embed +def generate_embed(title, color): + cog_url = "https://project-mei.xyz/genshinutils" + e = discord.Embed(title=title, color=color, url=cog_url) + e.set_footer(text="genshinutils cog by raidensakura", icon_url="https://avatars.githubusercontent.com/u/120461773?s=64&v=4") + return e From eeeec877f989782788ebbc91efbf15e6c632d4da Mon Sep 17 00:00:00 2001 From: Raiden Date: Mon, 30 Jan 2023 01:47:43 +0800 Subject: [PATCH 10/51] Cleanup --- genshinutils/genshinutils.py | 2 +- genshinutils/notes.py | 7 ++----- genshinutils/profile.py | 24 ++++++++++++------------ genshinutils/register.py | 2 +- genshinutils/utils.py | 7 ++++++- 5 files changed, 22 insertions(+), 20 deletions(-) diff --git a/genshinutils/genshinutils.py b/genshinutils/genshinutils.py index 1a11026..339a852 100644 --- a/genshinutils/genshinutils.py +++ b/genshinutils/genshinutils.py @@ -4,10 +4,10 @@ from enkanetwork import EnkaNetworkAPI from redbot.core import Config, checks, commands +from .notes import GenshinNotes from .profile import GenshinProfile from .register import GenshinRegister from .settings import GenshinSet -from .notes import GenshinNotes enka_client = EnkaNetworkAPI() diff --git a/genshinutils/notes.py b/genshinutils/notes.py index 7f97fca..88b096b 100644 --- a/genshinutils/notes.py +++ b/genshinutils/notes.py @@ -1,13 +1,10 @@ import logging -from typing import Union +from time import mktime import genshin - -import discord -from time import mktime from redbot.core import checks, commands -from .utils import validate_uid, get_user_cookie, generate_embed +from .utils import generate_embed, get_user_cookie, validate_uid log = logging.getLogger("red.raidensakura.genshinutils") diff --git a/genshinutils/profile.py b/genshinutils/profile.py index 24e27d2..f9ca086 100644 --- a/genshinutils/profile.py +++ b/genshinutils/profile.py @@ -3,19 +3,13 @@ import time from typing import Union -import genshin - import discord +import genshin from redbot.core import checks, commands -from .constants import character_namecards -from .utils import generate_embed -from .utils import ( - enka_get_character_card, - validate_char_name, - validate_uid, - get_user_cookie, -) +from .constants import character_namecards +from .utils import (enka_get_character_card, generate_embed, get_user_cookie, + validate_char_name, validate_uid) log = logging.getLogger("red.raidensakura.genshinutils") @@ -54,7 +48,10 @@ async def enka_generate_profile(uid): f"Unable to retrieve data from enka.network:\n`{exc}`" ) - e = generate_embed(f"Profile for {data.player.nickname} [AR {data.player.level}]", await ctx.embed_color()) + e = generate_embed( + f"Profile for {data.player.nickname} [AR {data.player.level}]", + await ctx.embed_color(), + ) if data.player.characters_preview: char_str = "" for character in data.player.characters_preview: @@ -107,7 +104,10 @@ async def genshin_generate_profile(uid): f"Unable to retrieve data from Hoyolab API:\n`{exc}`" ) - e = generate_embed(f"Profile for {data.info.nickname} [AR {data.info.level}]", await ctx.embed_color()) + e = generate_embed( + f"Profile for {data.info.nickname} [AR {data.info.level}]", + await ctx.embed_color(), + ) if data.characters: e.set_thumbnail(url=data.characters[0].icon) if character_namecards[data.characters[0].name.title()]: diff --git a/genshinutils/register.py b/genshinutils/register.py index 7904c31..93eaaf4 100644 --- a/genshinutils/register.py +++ b/genshinutils/register.py @@ -67,7 +67,7 @@ def pass_verification(discordtag, signature): author_discord_id, data.player.signature ): return await ctx.send( - f"Your signature does not contain your Discord tag.\nNote that it may take up to 15 minutes for changes to be reflected." + "Your signature does not contain your Discord tag.\nNote that it may take up to 15 minutes for changes to be reflected." ) await self.config.user(ctx.author).UID.set(uid) return await ctx.send(f"Successfully set UID for {ctx.author.name} to {uid}.") diff --git a/genshinutils/utils.py b/genshinutils/utils.py index a1d323d..2b1d124 100644 --- a/genshinutils/utils.py +++ b/genshinutils/utils.py @@ -10,12 +10,14 @@ log = logging.getLogger("red.raidensakura.genshinutils") +"""I make it a function so it's more readable""" # Used internally def bytes_to_string(bytes): str = bytes.decode() return str +"""I make it a function so it's more readable""" # Used internally def string_to_bytes(str): bytes = str.encode() @@ -135,5 +137,8 @@ async def get_user_cookie(config, user): def generate_embed(title, color): cog_url = "https://project-mei.xyz/genshinutils" e = discord.Embed(title=title, color=color, url=cog_url) - e.set_footer(text="genshinutils cog by raidensakura", icon_url="https://avatars.githubusercontent.com/u/120461773?s=64&v=4") + e.set_footer( + text="genshinutils cog by raidensakura", + icon_url="https://avatars.githubusercontent.com/u/120461773?s=64&v=4", + ) return e From baf4c96ebbc2a101ffc5e5d32f33511a6bfa3a5a Mon Sep 17 00:00:00 2001 From: Raiden Date: Mon, 30 Jan 2023 18:01:57 +0800 Subject: [PATCH 11/51] Add daily command and flake8 cleanup --- choose/__init__.py | 2 +- choose/choose.py | 7 +- genshinutils/assets/global/login_check.png | Bin 0 -> 6664 bytes genshinutils/daily.py | 71 +++++++++++++++++++++ genshinutils/genshinutils.py | 10 ++- genshinutils/notes.py | 5 +- genshinutils/profile.py | 10 +-- genshinutils/register.py | 16 ++--- genshinutils/utils.py | 10 +-- throw/throw.py | 6 +- 10 files changed, 109 insertions(+), 28 deletions(-) create mode 100644 genshinutils/assets/global/login_check.png create mode 100644 genshinutils/daily.py diff --git a/choose/__init__.py b/choose/__init__.py index f2b247d..ae5db39 100644 --- a/choose/__init__.py +++ b/choose/__init__.py @@ -1,7 +1,7 @@ import json from pathlib import Path -from .choose import setup +from .choose import setup # noqa: F401 with open(Path(__file__).parent / "info.json") as fp: __red_end_user_data_statement__ = json.load(fp)["end_user_data_statement"] diff --git a/choose/choose.py b/choose/choose.py index 9e5e256..b7825d4 100644 --- a/choose/choose.py +++ b/choose/choose.py @@ -26,7 +26,7 @@ def cog_unload(self): if old_choose: try: self.bot.remove_command("choose") - except: + except Exception: pass self.bot.add_command(old_choose) @@ -36,7 +36,10 @@ def format_help_for_context(self, ctx: commands.Context) -> str: """ pre_processed = super().format_help_for_context(ctx) s = "s" if len(self.__author__) > 1 else "" - return f"{pre_processed}\n\nAuthor{s}: {', '.join(self.__author__)}\nCog Version: {self.__version__}" + return ( + f"{pre_processed}\n\nAuthor{s}: {', '.join(self.__author__)}\n" + f"Cog Version: {self.__version__}" + ) async def red_delete_data_for_user(self, **kwargs) -> None: """Nothing to delete""" diff --git a/genshinutils/assets/global/login_check.png b/genshinutils/assets/global/login_check.png new file mode 100644 index 0000000000000000000000000000000000000000..73f6cb89115e3f2fe2d997020fba5cf149b8d294 GIT binary patch literal 6664 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 literal 0 HcmV?d00001 diff --git a/genshinutils/daily.py b/genshinutils/daily.py new file mode 100644 index 0000000..4f42ff8 --- /dev/null +++ b/genshinutils/daily.py @@ -0,0 +1,71 @@ +import logging + +import genshin +from redbot.core import commands + +from .utils import generate_embed, get_user_cookie, validate_uid + +log = logging.getLogger("red.raidensakura.genshinutils") + + +class GenshinDaily(commands.Cog): + """GenshinUtils daily command class.""" + + # https://discord.com/channels/133049272517001216/160386989819035648/1067445744497348639 + # This will get replaced by genshinutils.py's `genshin` + # Thanks Jojo#7791! + @commands.group() + async def genshin(self, ctx: commands.Context): + """GenshinUtils main command.""" + + @genshin.command() + @commands.guild_only() + @commands.bot_has_permissions(embed_links=True) + @commands.cooldown(2, 10, commands.BucketType.member) + async def daily(self, ctx: commands.Context): + """ + Redeem your daily login reward from Hoyolab for Genshin Impact. + Command require cookie registration. + """ + + async def redeem_daily(): + try: + client = genshin.Client(cookie) + client.default_game = genshin.Game.GENSHIN + signed_in, claimed_rewards = await client.get_reward_info() + reward = await client.claim_daily_reward() + except genshin.AlreadyClaimed: + e = generate_embed( + title="Genshin Impact Daily Login", + desc="Daily reward already claimed.", + color=await ctx.embed_color(), + ) + e.add_field(name="Total Login", value=f"{claimed_rewards} days") + except Exception as exc: + return await ctx.send( + f"Unable to retrieve data from Hoyolab API:\n`{exc}`" + ) + else: + signed_in = "✅" + e = generate_embed( + title="Genshin Impact Daily Login", + desc="Daily reward successfully claimed.", + color=await ctx.embed_color(), + ) + e.set_thumbnail(reward.icon) + e.add_field(name="Reward", value=f"{reward.name} x{reward.amount}") + e.add_field( + name="Total Login", value=f"{signed_in} {claimed_rewards + 1} days" + ) + return await ctx.send(embed=e) + + uid = await validate_uid(ctx.author, self.config) + if not uid: + return await ctx.send("You do not have a UID linked.") + + cookie = await get_user_cookie(self.config, ctx.author) + if not cookie: + return await ctx.send("No cookie.") + + with ctx.typing(): + return await redeem_daily() diff --git a/genshinutils/genshinutils.py b/genshinutils/genshinutils.py index 339a852..109d3a8 100644 --- a/genshinutils/genshinutils.py +++ b/genshinutils/genshinutils.py @@ -2,8 +2,9 @@ from typing import Literal from enkanetwork import EnkaNetworkAPI -from redbot.core import Config, checks, commands +from redbot.core import Config, commands +from .daily import GenshinDaily from .notes import GenshinNotes from .profile import GenshinProfile from .register import GenshinRegister @@ -15,7 +16,12 @@ class GenshinUtils( - GenshinSet, GenshinRegister, GenshinProfile, GenshinNotes, commands.Cog + GenshinSet, + GenshinRegister, + GenshinProfile, + GenshinNotes, + GenshinDaily, + commands.Cog, ): """GenshinUtils commands.""" diff --git a/genshinutils/notes.py b/genshinutils/notes.py index 88b096b..ee5295e 100644 --- a/genshinutils/notes.py +++ b/genshinutils/notes.py @@ -2,7 +2,7 @@ from time import mktime import genshin -from redbot.core import checks, commands +from redbot.core import commands from .utils import generate_embed, get_user_cookie, validate_uid @@ -38,7 +38,8 @@ async def generate_diary(uid): f"Unable to retrieve data from Hoyolab API:\n`{exc}`" ) e = generate_embed( - f"Game Notes for {ctx.author.display_name}", await ctx.embed_color() + title=f"Game Notes for {ctx.author.display_name}", + color=await ctx.embed_color(), ) e.add_field( name="🌙 Resin", diff --git a/genshinutils/profile.py b/genshinutils/profile.py index f9ca086..42e5ac8 100644 --- a/genshinutils/profile.py +++ b/genshinutils/profile.py @@ -5,7 +5,7 @@ import discord import genshin -from redbot.core import checks, commands +from redbot.core import commands from .constants import character_namecards from .utils import (enka_get_character_card, generate_embed, get_user_cookie, @@ -49,8 +49,8 @@ async def enka_generate_profile(uid): ) e = generate_embed( - f"Profile for {data.player.nickname} [AR {data.player.level}]", - await ctx.embed_color(), + title=f"Profile for {data.player.nickname} [AR {data.player.level}]", + color=await ctx.embed_color(), ) if data.player.characters_preview: char_str = "" @@ -105,8 +105,8 @@ async def genshin_generate_profile(uid): ) e = generate_embed( - f"Profile for {data.info.nickname} [AR {data.info.level}]", - await ctx.embed_color(), + title=f"Profile for {data.info.nickname} [AR {data.info.level}]", + color=await ctx.embed_color(), ) if data.characters: e.set_thumbnail(url=data.characters[0].icon) diff --git a/genshinutils/register.py b/genshinutils/register.py index 93eaaf4..813ae8b 100644 --- a/genshinutils/register.py +++ b/genshinutils/register.py @@ -6,7 +6,7 @@ import genshin from discord import Embed from discord.channel import DMChannel -from redbot.core import checks, commands +from redbot.core import commands from .utils import decrypt_config, encrypt_config @@ -93,7 +93,7 @@ async def hoyolab(self, ctx: commands.Context, *, cookie: str = None): if cookie: try: await ctx.message.delete() - except: + except Exception: pass # Preface disclaimer @@ -103,11 +103,11 @@ async def hoyolab(self, ctx: commands.Context, *, cookie: str = None): else: owner = app_info.owner desc = ( - f"This command lets the bot perform Hoyolab account actions on your behalf, authenticated using your token. " - f"The token will then be linked to your Discord ID and stored encrypted in the bot's config, along with the encryption key. " - f"Make sure **you fully acknowledge the risks of sharing your account token online** before proceeding.\n\n" - f"For security reason, please run this command in a DM channel when setting token.\n\n" - f"Read on how to obtain your token [here](https://project-mei.xyz/genshinutils)." + "This command lets the bot perform Hoyolab account actions on your behalf, authenticated using your token. " + "The token will then be linked to your Discord ID and stored encrypted in the bot's config, along with the encryption key. " + "Make sure **you fully acknowledge the risks of sharing your account token online** before proceeding.\n\n" + "For security reason, please run this command in a DM channel when setting token.\n\n" + "Read on how to obtain your token [here](https://project-mei.xyz/genshinutils)." ) e = Embed( color=(await ctx.embed_colour()), @@ -187,7 +187,7 @@ async def hoyolab(self, ctx: commands.Context, *, cookie: str = None): await self.config.user(ctx.author).ltoken.set(encoded_ltoken) # Send success embed - desc = f"Successfully bound a Genshin Impact account to your Discord account. Details are as follow." + desc = "Successfully bound a Genshin Impact account to your Discord account. Details are as follow." e = Embed( color=(await ctx.embed_colour()), title="Account Binding Success", diff --git a/genshinutils/utils.py b/genshinutils/utils.py index 2b1d124..0daf89a 100644 --- a/genshinutils/utils.py +++ b/genshinutils/utils.py @@ -79,11 +79,11 @@ async def validate_uid(u, config): elif isinstance(u, str) and len(u) == 9 and u.isdigit(): uid = u - log.debug(f"[validate_uid] This is a valid UID.") + log.debug("[validate_uid] This is a valid UID.") else: uid = None - log.debug(f"[validate_uid] This is not a valid UID.") + log.debug("[validate_uid] This is not a valid UID.") return uid @@ -130,13 +130,13 @@ async def get_user_cookie(config, user): """ -Accepts: str(title), str(author), color +Accepts: str(title), str(desc), color Returns: discord.Embed """ # generate_embed -def generate_embed(title, color): +def generate_embed(title="", desc="", color=""): cog_url = "https://project-mei.xyz/genshinutils" - e = discord.Embed(title=title, color=color, url=cog_url) + e = discord.Embed(title=title, description=desc, color=color, url=cog_url) e.set_footer( text="genshinutils cog by raidensakura", icon_url="https://avatars.githubusercontent.com/u/120461773?s=64&v=4", diff --git a/throw/throw.py b/throw/throw.py index 0a56376..ddecc74 100644 --- a/throw/throw.py +++ b/throw/throw.py @@ -4,11 +4,11 @@ from redbot.core import Config, commands from redbot.core.bot import Red from redbot.core.commands import Context -from redbot.core.utils.chat_formatting import bold, box, quote +from redbot.core.utils.chat_formatting import box from redbot.core.utils.menus import DEFAULT_CONTROLS, menu from tabulate import tabulate -from .constants import * +from .constants import ITEMS, HIT, MISS class Throw(commands.Cog): @@ -119,7 +119,7 @@ async def throw_stats(self, ctx: Context, *, member: discord.Member = None): def parse_actions(data, array, action: str): for key, value in data.items(): if action in key: - sent = str(data.get(f"ITEMS_THROWN", " ")).replace("0", " ") + sent = str(data.get("ITEMS_THROWN", " ")).replace("0", " ") received = str(data.get(f"TIMES_HIT", " ")).replace("0", " ") array.append([action.lower(), received, sent]) From 773dff0b01dde99a3f1aea0fdae681ab3a50b9b0 Mon Sep 17 00:00:00 2001 From: Raiden Date: Mon, 30 Jan 2023 18:20:42 +0800 Subject: [PATCH 12/51] Add workflows --- .github/workflows/checks.yml | 68 +++++++++++++++++++ .github/workflows/loadcheck.yml | 67 +++++++++++++++++++ .github/workflows/scripts/loadcheck.py | 92 ++++++++++++++++++++++++++ 3 files changed, 227 insertions(+) create mode 100644 .github/workflows/checks.yml create mode 100644 .github/workflows/loadcheck.yml create mode 100644 .github/workflows/scripts/loadcheck.py diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml new file mode 100644 index 0000000..fae08f6 --- /dev/null +++ b/.github/workflows/checks.yml @@ -0,0 +1,68 @@ +# https://github.com/Vexed01/Vex-Cogs/blob/master/.github/workflows/checks.yml +name: Checks + +on: + push: + branches: + - master + - genshin + pull_request: + +# thanks red or wherever you got it from +jobs: + tox: + runs-on: ubuntu-latest + strategy: + matrix: + python_version: + - "3.8" + - "3.9" + tox_env: + - style-black + - style-isort + - lint-flake8 + # - type-pyright + - docs + - pytest + include: + - tox_env: style-black + friendly_name: Style (black) + - tox_env: style-isort + friendly_name: Style (isort) + - tox_env: lint-flake8 + friendly_name: Lint (flake8) + # - tox_env: type-pyright + # friendly_name: Type check (pyright) + - tox_env: docs + friendly_name: Docs + - tox_env: pytest + friendly_name: Tests + + fail-fast: false + + name: Tox - ${{ matrix.python_version }} - ${{ matrix.friendly_name }} + steps: + - uses: actions/checkout@v2 + with: + ref: ${{ env.ref }} + - name: Set up Python ${{ matrix.python_version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python_version }} + + # caching cuts down time for tox (for example black) from ~40 secs to 4 + - name: Cache tox + uses: actions/cache@v2 + with: + path: .tox + key: tox-${{ matrix.python_version }}-${{ matrix.tox_env }}-${{ hashFiles('tox.ini') }} + + - name: Install tox + run: | + python -m pip install --upgrade pip + pip install tox + - name: "Run tox: ${{ matrix.friendly_name }}" + env: + TOXENV: ${{ matrix.tox_env }} + run: | + tox \ No newline at end of file diff --git a/.github/workflows/loadcheck.yml b/.github/workflows/loadcheck.yml new file mode 100644 index 0000000..e642228 --- /dev/null +++ b/.github/workflows/loadcheck.yml @@ -0,0 +1,67 @@ +name: "Cog load test" + +on: + push: + branches: + - master + - genshin + +jobs: + loadcheck: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.8", "3.9"] + red-version: + # this workflow required pr #5453 commit d27dbde, which is in dev & pypi 3.4.15+ + - "git+https://github.com/Cog-Creators/Red-DiscordBot@V3/develop#egg=Red-DiscordBot" + - "Red-DiscordBot==3.4.16" + include: + - red-version: "git+https://github.com/Cog-Creators/Red-DiscordBot@V3/develop#egg=Red-DiscordBot" + friendly-red: "Red (dev version)" + - red-version: "Red-DiscordBot==3.4.16" + friendly-red: "Red 3.4.16" + fail-fast: false + + name: Cog load test - Python ${{ matrix.python-version }} & ${{ matrix.friendly-red }} + steps: + - uses: actions/checkout@v2 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + + - name: Cache venv + id: cache-venv + uses: actions/cache@v2 + with: + path: .venv + key: ${{ matrix.red-version }}-${{ matrix.python-version }}-${{ hashFiles('dev-requirements.txt') }}-${{ secrets.CACHE_V }} + + - name: Maybe make venv + if: steps.cache-venv.outputs.cache-hit != 'true' + run: | + python3 -m venv .venv + source .venv/bin/activate + pip install setuptools wheel + pip install ${{ matrix.red-version }} + pip install -r dev-requirements.txt + pip install jsonrpc-websocket + - name: Prepare files + run: | + mkdir -p /home/runner/.config/Red-DiscordBot + echo '{"workflow": {"DATA_PATH": "/home/runner/work/Vex-Cogs/Vex-Cogs/.github/red_data", "COG_PATH_APPEND": "cogs", "CORE_PATH_APPEND": "core", "STORAGE_TYPE": "JSON", "STORAGE_DETAILS": {}}}' > /home/runner/.config/Red-DiscordBot/config.json + - name: Run script loadcheck.py + run: | + source .venv/bin/activate + python .github/workflows/scripts/loadcheck.py + env: + DISCORD_BOT_TOKEN: ${{ secrets.DISCORD_TEST_BOT }} + + - name: Save Red output as Artifact + if: always() # still run if prev step failed + uses: actions/upload-artifact@v2 + with: + name: "Red log - Python ${{ matrix.python-version }} & ${{ matrix.friendly-red }}" + path: red.log \ No newline at end of file diff --git a/.github/workflows/scripts/loadcheck.py b/.github/workflows/scripts/loadcheck.py new file mode 100644 index 0000000..6010f51 --- /dev/null +++ b/.github/workflows/scripts/loadcheck.py @@ -0,0 +1,92 @@ +# https://github.com/Vexed01/Vex-Cogs/blob/master/.github/workflows/scripts/loadcheck.py +import asyncio +import os +import subprocess +import sys +import time +from typing import Any, Dict, Tuple + +from dotenv import load_dotenv +from jsonrpc_websocket import Server +from redbot import __version__ as red_str_ver + +load_dotenv(".env") + +token = os.environ.get("DISCORD_BOT_TOKEN") + +python_version = subprocess.check_output(["python", "-V"]).decode("utf-8") + +print("=== Red's logs are available to view as an Artifact on the main matrix page ===\n") + +print(f"Starting Red {red_str_ver} with {python_version}") + +file = open("red.log", "w") +proc = subprocess.Popen( + f"redbot workflow --no-prompt --token {token} --rpc --debug", + stdout=file, + stderr=subprocess.STDOUT, + shell=True, +) + +# let Red boot up +time.sleep(10) + +cogs = [ + "choose", + "genshinutils", + "longcat", + "throw", +] + + +async def leswebsockets() -> Tuple[Dict[str, Any], Dict[str, Any]]: + print("Connecting to Red via RPC") + + server = Server("ws://localhost:6133") + try: + await server.ws_connect() + + print("Loading cogs") + load_results: Dict[str, Any] = await server.CORE__LOAD(cogs) + await asyncio.sleep(1) + print("Unloading cogs") + unload_results: Dict[str, Any] = await server.CORE__UNLOAD(cogs) + finally: + await server.close() + + return load_results, unload_results + + +load, unload = asyncio.run(leswebsockets()) + +print("Stopping Red") + +proc.terminate() + +exit_code = 0 + +fail_load = [] +for i in ( + "failed_packages", + "invalid_pkg_names", + "notfound_packages", + "alreadyloaded_packages", + "failed_with_reason_packages", +): + fail_load.extend(load[i]) + +if fail_load: + exit_code = 1 + print("\N{CROSS MARK} Failed to load cogs " + ", ".join(fail_load)) + print("See the artifact on the main matrix page for more information") +else: + print("\N{HEAVY CHECK MARK} Loaded all cogs successfully") + +if unload["notloaded_packages"]: + exit_code = 1 + print("\N{CROSS MARK} Failed to unload cogs " + ", ".join(unload["notloaded_packages"])) + print("See the artifact on the main matrix page for more information") +else: + print("\N{HEAVY CHECK MARK} Unloaded all cogs successfully") + +sys.exit(exit_code) \ No newline at end of file From 2b6b9d8411e0ae88aeff35529bf6cb5fe7b6fe57 Mon Sep 17 00:00:00 2001 From: Raiden Date: Mon, 30 Jan 2023 18:27:53 +0800 Subject: [PATCH 13/51] More stuff... --- .flake8 | 5 +++ .pre-commit-config.yaml | 33 +++++++++++++++++ dev-requirements.txt | 42 ++++++++++++++++++++++ pyproject.toml | 14 ++++++++ tox.ini | 80 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 174 insertions(+) create mode 100644 .flake8 create mode 100644 .pre-commit-config.yaml create mode 100644 dev-requirements.txt create mode 100644 pyproject.toml create mode 100644 tox.ini diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..33d2356 --- /dev/null +++ b/.flake8 @@ -0,0 +1,5 @@ +[flake8] +ignore = W503,E203,E402 +per-file-ignores = __init__.py:F401,consts.py:E501 +max-line-length = 99 +exclude = .git,.venv,.tox,__pycache__,docs,vexutils \ No newline at end of file diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..342b0e2 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,33 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v2.3.0 + hooks: + - id: check-ast + - id: check-json + - id: pretty-format-json + args: ["--autofix", "--indent", "4"] + - id: end-of-file-fixer + - id: mixed-line-ending + - repo: https://github.com/psf/black + rev: "22.3.0" + hooks: + - id: black + - repo: https://github.com/PyCQA/isort + rev: "5.8.0" + hooks: + - id: isort + - repo: https://github.com/myint/autoflake + rev: "v1.4" + hooks: + - id: autoflake + args: + [ + "--in-place", + "--remove-unused-variables", + "--remove-all-unused-imports", + "--ignore-init-module-imports", + ] + - repo: https://github.com/pycqa/flake8 + rev: "3.9.2" + hooks: + - id: flake8 \ No newline at end of file diff --git a/dev-requirements.txt b/dev-requirements.txt new file mode 100644 index 0000000..40161a1 --- /dev/null +++ b/dev-requirements.txt @@ -0,0 +1,42 @@ +# Red itself is also a requriement, however it is not listed here because +# some people may have special setups (eg dev version installed) + +# Required for utils +tabulate +cachetools +asyncache + +# Required for optional PandasSQLDriver in utils +pandas + +# Cogs +plotly # betteruptime, stattrack, googletrends +kaleido # betteruptime, stattrack, googletrends +gidgethub # ghissues, gitub +pytrends # googletrends +psutil # stattrack, system +markdownify # status +rapidfuzz # timechannel +wakeonlan # wol +expr.py # calc + +# checking tools +pyright +black +isort +flake8 +pytest +tox +pre-commit + +# docs +sphinx +sphinx-rtd-theme +furo + +# checks +python-dotenv + +# docs +sphinx_rtd_theme +furo \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..b3bb428 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,14 @@ +[tool.black] +line-length = 99 +target-version = ["py38"] +extend-exclude = "vexutils|.stubs" + +[tool.isort] +profile = "black" +line_length = 99 +extend_skip = "vexutils" + +[tool.pyright] +stubPath = "./.stubs" +exclude = ["*/vexutils", ".tox", "**/__pycache__", ".venv", ".github", ".stubs"] +useLibraryCodeForTypes = true \ No newline at end of file diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..75f172d --- /dev/null +++ b/tox.ini @@ -0,0 +1,80 @@ +[tox] +envlist = py38, style-black, style-isort, lint-flake8, type-pyright, docs, pytest +skipsdist = true + +[testenv] +description = Run style and static type checking. +deps = + # style + black + isort + + # lint + flake8 + gidgethub + wakeonlan + + # non-typeshed stubs + pandas-stubs + + tabulate + asyncache + rapidfuzz + plotly + pytrends + pyjson5 + expr.py + + # docs + sphinx + sphinx-rtd-theme==1.0.0 + furo + + # pytest + pytest + red-discordbot==3.4.18 + markdownify + + # type + # (some are covered under below) + pyright + asyncache + +[testenv:style-black] +description = Check the style conforms with black. +envdir = {toxworkdir}/py38 + +commands = black --check . + +[testenv:style-isort] +description = Check imports conform with isort. +envdir = {toxworkdir}/py38 + +commands = isort --check . + +[testenv:lint-flake8] +description = Lint with flake8. +envdir = {toxworkdir}/py38 + +commands = flake8 . + +; [testenv:type-pyright] +; description = Type checking with pyright. +; envdir = {toxworkdir}/py38 + +; commands = +; pip install --force-reinstall git+https://github.com/Rapptz/discord.py +; pyright + +[testenv:docs] +description = Try to build the docs (HTML) +envdir = {toxworkdir}/py38 + +commands = sphinx-build -d "{toxworkdir}/docs_doctree" docs "{toxworkdir}/docs_out" --keep-going + +[testenv:pytest] +description = Run pytest +envdir = {toxworkdir}/py38 + +commands = + pytest tests \ No newline at end of file From 96ff5c2427ca5375a6089e57f42b7177bb18dc49 Mon Sep 17 00:00:00 2001 From: Raiden Date: Mon, 30 Jan 2023 18:40:39 +0800 Subject: [PATCH 14/51] Tweak workflow --- .github/workflows/checks.yml | 4 --- .github/workflows/scripts/loadcheck.py | 2 +- choose/__init__.py | 2 +- choose/choose.py | 8 ++--- genshinutils/daily.py | 8 ++--- genshinutils/notes.py | 8 ++--- genshinutils/profile.py | 41 ++++++++++---------------- genshinutils/register.py | 16 +++------- genshinutils/utils.py | 4 +-- throw/throw.py | 18 ++++------- tox.ini | 19 ------------ 11 files changed, 33 insertions(+), 97 deletions(-) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index fae08f6..10d21bd 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -31,10 +31,6 @@ jobs: friendly_name: Style (isort) - tox_env: lint-flake8 friendly_name: Lint (flake8) - # - tox_env: type-pyright - # friendly_name: Type check (pyright) - - tox_env: docs - friendly_name: Docs - tox_env: pytest friendly_name: Tests diff --git a/.github/workflows/scripts/loadcheck.py b/.github/workflows/scripts/loadcheck.py index 6010f51..7c9c217 100644 --- a/.github/workflows/scripts/loadcheck.py +++ b/.github/workflows/scripts/loadcheck.py @@ -89,4 +89,4 @@ async def leswebsockets() -> Tuple[Dict[str, Any], Dict[str, Any]]: else: print("\N{HEAVY CHECK MARK} Unloaded all cogs successfully") -sys.exit(exit_code) \ No newline at end of file +sys.exit(exit_code) diff --git a/choose/__init__.py b/choose/__init__.py index ae5db39..7bf1181 100644 --- a/choose/__init__.py +++ b/choose/__init__.py @@ -1,7 +1,7 @@ import json from pathlib import Path -from .choose import setup # noqa: F401 +from .choose import setup # noqa: F401 with open(Path(__file__).parent / "info.json") as fp: __red_end_user_data_statement__ = json.load(fp)["end_user_data_statement"] diff --git a/choose/choose.py b/choose/choose.py index b7825d4..89f7cfe 100644 --- a/choose/choose.py +++ b/choose/choose.py @@ -58,9 +58,7 @@ async def choose(self, ctx, *, options): choosearray = split(r";|,|\n|\||#", options) if len(choosearray) > 1: - e = discord.Embed( - color=(await ctx.embed_colour()), title=random.choice(choosearray) - ) + e = discord.Embed(color=(await ctx.embed_colour()), title=random.choice(choosearray)) e.set_footer( text=f"✨ Choosing for {ctx.author.display_name}, from a list of {len(choosearray)} options." ) @@ -71,9 +69,7 @@ async def choose(self, ctx, *, options): return await ctx.send(embed=e) except Exception as exc: log.exception("Error trying to send choose embed.", exc_info=exc) - return await ctx.send( - "Oops, I encountered an error while trying to send the embed." - ) + return await ctx.send("Oops, I encountered an error while trying to send the embed.") async def setup(bot: Red) -> None: diff --git a/genshinutils/daily.py b/genshinutils/daily.py index 4f42ff8..ca6a365 100644 --- a/genshinutils/daily.py +++ b/genshinutils/daily.py @@ -42,9 +42,7 @@ async def redeem_daily(): ) e.add_field(name="Total Login", value=f"{claimed_rewards} days") except Exception as exc: - return await ctx.send( - f"Unable to retrieve data from Hoyolab API:\n`{exc}`" - ) + return await ctx.send(f"Unable to retrieve data from Hoyolab API:\n`{exc}`") else: signed_in = "✅" e = generate_embed( @@ -54,9 +52,7 @@ async def redeem_daily(): ) e.set_thumbnail(reward.icon) e.add_field(name="Reward", value=f"{reward.name} x{reward.amount}") - e.add_field( - name="Total Login", value=f"{signed_in} {claimed_rewards + 1} days" - ) + e.add_field(name="Total Login", value=f"{signed_in} {claimed_rewards + 1} days") return await ctx.send(embed=e) uid = await validate_uid(ctx.author, self.config) diff --git a/genshinutils/notes.py b/genshinutils/notes.py index ee5295e..91d7892 100644 --- a/genshinutils/notes.py +++ b/genshinutils/notes.py @@ -34,9 +34,7 @@ async def generate_diary(uid): client = genshin.Client(cookie) data = await client.get_notes(uid) except Exception as exc: - return await ctx.send( - f"Unable to retrieve data from Hoyolab API:\n`{exc}`" - ) + return await ctx.send(f"Unable to retrieve data from Hoyolab API:\n`{exc}`") e = generate_embed( title=f"Game Notes for {ctx.author.display_name}", color=await ctx.embed_color(), @@ -96,9 +94,7 @@ async def test_honkai(): client = genshin.Client(cookie) data = await client.get_full_honkai_user(20177789) except Exception as exc: - return await ctx.send( - f"Unable to retrieve data from Hoyolab API:\n`{exc}`" - ) + return await ctx.send(f"Unable to retrieve data from Hoyolab API:\n`{exc}`") return await log.debug(f"```{data}```") uid = await validate_uid(ctx.author, self.config) diff --git a/genshinutils/profile.py b/genshinutils/profile.py index 42e5ac8..f67fcaa 100644 --- a/genshinutils/profile.py +++ b/genshinutils/profile.py @@ -8,8 +8,13 @@ from redbot.core import commands from .constants import character_namecards -from .utils import (enka_get_character_card, generate_embed, get_user_cookie, - validate_char_name, validate_uid) +from .utils import ( + enka_get_character_card, + generate_embed, + get_user_cookie, + validate_char_name, + validate_uid, +) log = logging.getLogger("red.raidensakura.genshinutils") @@ -44,9 +49,7 @@ async def enka_generate_profile(uid): try: data = await self.enka_client.fetch_user(uid) except Exception as exc: - return await ctx.send( - f"Unable to retrieve data from enka.network:\n`{exc}`" - ) + return await ctx.send(f"Unable to retrieve data from enka.network:\n`{exc}`") e = generate_embed( title=f"Profile for {data.player.nickname} [AR {data.player.level}]", @@ -80,29 +83,21 @@ async def enka_generate_char_img(uid, char_name): with io.BytesIO() as image_binary: char_card = await enka_get_character_card(uid, char_name) if not char_card: - return await ctx.send( - "This user does not have that character featured." - ) + return await ctx.send("This user does not have that character featured.") temp_filename = str(time.time()).split(".")[0] + ".png" - log.debug( - f"[generate_char_info] Pillow object for character card:\n{char_card}" - ) + log.debug(f"[generate_char_info] Pillow object for character card:\n{char_card}") first_card = next(iter(char_card.values())) card_object = next(iter(first_card.values())) card_object.save(image_binary, "PNG", optimize=True, quality=95) image_binary.seek(0) - return await ctx.send( - file=discord.File(fp=image_binary, filename=temp_filename) - ) + return await ctx.send(file=discord.File(fp=image_binary, filename=temp_filename)) async def genshin_generate_profile(uid): try: client = genshin.Client(cookie) data = await client.get_partial_genshin_user(uid) except Exception as exc: - return await ctx.send( - f"Unable to retrieve data from Hoyolab API:\n`{exc}`" - ) + return await ctx.send(f"Unable to retrieve data from Hoyolab API:\n`{exc}`") e = generate_embed( title=f"Profile for {data.info.nickname} [AR {data.info.level}]", @@ -115,9 +110,7 @@ async def genshin_generate_profile(uid): e.set_image(url=namecard_url) e.add_field(name="Achievements", value=data.stats.achievements, inline=True) e.add_field(name="Days Active", value=data.stats.days_active, inline=True) - e.add_field( - name="Characters Unlocked", value=data.stats.characters, inline=True - ) + e.add_field(name="Characters Unlocked", value=data.stats.characters, inline=True) e.add_field( name="Current Spiral Abyss Floor", value=data.stats.spiral_abyss, @@ -178,9 +171,7 @@ async def genshin_generate_profile(uid): with ctx.typing(): return await enka_generate_profile(uid) - log.debug( - f"[{ctx.command.name}] Not a UID, assuming it's a character name..." - ) + log.debug(f"[{ctx.command.name}] Not a UID, assuming it's a character name...") char = validate_char_name(user_or_uid) if not char: return await ctx.send( @@ -201,9 +192,7 @@ async def genshin_generate_profile(uid): if user_or_uid and character: uid = await validate_uid(user_or_uid, self.config) if not uid: - return await ctx.send( - "Not a valid UID or user does not have a UID linked." - ) + return await ctx.send("Not a valid UID or user does not have a UID linked.") char = validate_char_name(character) if not char: diff --git a/genshinutils/register.py b/genshinutils/register.py index 813ae8b..e2c08eb 100644 --- a/genshinutils/register.py +++ b/genshinutils/register.py @@ -57,9 +57,7 @@ def pass_verification(discordtag, signature): with ctx.typing(): data = await self.enka_client.fetch_user(uid) except Exception as exc: - return await ctx.send( - f"Unable to retrieve data from enka.network:\n`{exc}`" - ) + return await ctx.send(f"Unable to retrieve data from enka.network:\n`{exc}`") author_discord_id = f"{ctx.author.name}#{ctx.author.discriminator}" @@ -153,9 +151,7 @@ async def hoyolab(self, ctx: commands.Context, *, cookie: str = None): client = genshin.Client(cookies) accounts = await client.get_game_accounts() except Exception as exc: - return await ctx.send( - f"Unable to retrieve data from Hoyolab API:\n`{exc}`" - ) + return await ctx.send(f"Unable to retrieve data from Hoyolab API:\n`{exc}`") """ Accounts: [ GenshinAccount(lang="", game_biz="", level=int...), GenshinAccount(...) ] Recognized game_biz: @@ -170,9 +166,7 @@ async def hoyolab(self, ctx: commands.Context, *, cookie: str = None): genshin_acc_list.append(account) if not genshin_acc_list: - return await ctx.send( - "Couldn't find a linked Genshin UID in your Hoyolab account." - ) + return await ctx.send("Couldn't find a linked Genshin UID in your Hoyolab account.") # https://www.geeksforgeeks.org/python-get-the-object-with-the-max-attribute-value-in-a-list-of-objects/ # get genshin account with the highest level @@ -209,9 +203,7 @@ async def hoyolab(self, ctx: commands.Context, *, cookie: str = None): log.debug( f"[Register Hoyolab] Encrypted ltoken saved: {await self.config.user(ctx.author).ltoken()}" ) - log.debug( - f"[Register Hoyolab] Encryption key saved: {await self.config.encryption_key()}" - ) + log.debug(f"[Register Hoyolab] Encryption key saved: {await self.config.encryption_key()}") decoded_ltuid = await decrypt_config(self.config, encoded_ltuid) decoded_ltoken = await decrypt_config(self.config, encoded_ltoken) diff --git a/genshinutils/utils.py b/genshinutils/utils.py index 0daf89a..16a9060 100644 --- a/genshinutils/utils.py +++ b/genshinutils/utils.py @@ -105,9 +105,7 @@ def validate_char_name(arg): """ # enka_get_character_card async def enka_get_character_card(uid, char_name): - async with encbanner.ENC( - lang="en", splashArt=True, characterName=char_name - ) as encard: + async with encbanner.ENC(lang="en", splashArt=True, characterName=char_name) as encard: ENCpy = await encard.enc(uids=uid) return await encard.creat(ENCpy, 2) diff --git a/throw/throw.py b/throw/throw.py index ddecc74..d6efb80 100644 --- a/throw/throw.py +++ b/throw/throw.py @@ -8,7 +8,7 @@ from redbot.core.utils.menus import DEFAULT_CONTROLS, menu from tabulate import tabulate -from .constants import ITEMS, HIT, MISS +from .constants import HIT, ITEMS, MISS class Throw(commands.Cog): @@ -132,9 +132,7 @@ def get_avatar(user): return str(user.avatar_url) pages = [] - dedupe_list_1 = [ - x for i, x in enumerate(people_with_no_creativity, 1) if i % 2 != 0 - ] + dedupe_list_1 = [x for i, x in enumerate(people_with_no_creativity, 1) if i % 2 != 0] server_table = tabulate( dedupe_list_1, headers=header, colalign=colalign, tablefmt="psql" ) @@ -148,21 +146,15 @@ def get_avatar(user): for action in self.possible_actions: parse_actions(global_actions_data, global_actions_array, action) - dedupe_list_2 = [ - x for i, x in enumerate(global_actions_array, 1) if i % 2 != 0 - ] + dedupe_list_2 = [x for i, x in enumerate(global_actions_array, 1) if i % 2 != 0] global_table = tabulate( dedupe_list_2, headers=header, colalign=colalign, tablefmt="psql" ) embed = discord.Embed( colour=await ctx.embed_colour(), description=box(global_table, "nim") ) - embed.set_author( - name=f"Global Throw Stats | {user.name}", icon_url=get_avatar(user) - ) - embed.set_footer( - text=f"Requester: {ctx.author}", icon_url=get_avatar(ctx.author) - ) + embed.set_author(name=f"Global Throw Stats | {user.name}", icon_url=get_avatar(user)) + embed.set_footer(text=f"Requester: {ctx.author}", icon_url=get_avatar(ctx.author)) pages.append(embed) await menu(ctx, pages, DEFAULT_CONTROLS, timeout=60.0) diff --git a/tox.ini b/tox.ini index 75f172d..f88e9ef 100644 --- a/tox.ini +++ b/tox.ini @@ -25,11 +25,6 @@ deps = pyjson5 expr.py - # docs - sphinx - sphinx-rtd-theme==1.0.0 - furo - # pytest pytest red-discordbot==3.4.18 @@ -58,20 +53,6 @@ envdir = {toxworkdir}/py38 commands = flake8 . -; [testenv:type-pyright] -; description = Type checking with pyright. -; envdir = {toxworkdir}/py38 - -; commands = -; pip install --force-reinstall git+https://github.com/Rapptz/discord.py -; pyright - -[testenv:docs] -description = Try to build the docs (HTML) -envdir = {toxworkdir}/py38 - -commands = sphinx-build -d "{toxworkdir}/docs_doctree" docs "{toxworkdir}/docs_out" --keep-going - [testenv:pytest] description = Run pytest envdir = {toxworkdir}/py38 From dad5b42ab095f6bdd1454001e19e66410d8a2e42 Mon Sep 17 00:00:00 2001 From: Raiden Date: Mon, 30 Jan 2023 19:08:59 +0800 Subject: [PATCH 15/51] Hmm --- .github/workflows/checks.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 10d21bd..2aa1776 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -21,8 +21,6 @@ jobs: - style-black - style-isort - lint-flake8 - # - type-pyright - - docs - pytest include: - tox_env: style-black From 93dacc3044d5fe38a390177ad0987552a5c572a4 Mon Sep 17 00:00:00 2001 From: Raiden Date: Mon, 30 Jan 2023 19:15:50 +0800 Subject: [PATCH 16/51] Test again... --- .github/red_data/cogs/CogManager/settings.json | 10 ++++++++++ .github/red_data/core/settings.json | 11 +++++++++++ .github/workflows/loadcheck.yml | 2 +- 3 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 .github/red_data/cogs/CogManager/settings.json create mode 100644 .github/red_data/core/settings.json diff --git a/.github/red_data/cogs/CogManager/settings.json b/.github/red_data/cogs/CogManager/settings.json new file mode 100644 index 0000000..b841a53 --- /dev/null +++ b/.github/red_data/cogs/CogManager/settings.json @@ -0,0 +1,10 @@ +{ + "2938473984732": { + "GLOBAL": { + "paths": [ + "C:\\Data\\Git\\raiden-cogs", + "/home/runner/work/raiden-cogs/raiden-cogs" + ] + } + } +} \ No newline at end of file diff --git a/.github/red_data/core/settings.json b/.github/red_data/core/settings.json new file mode 100644 index 0000000..e0217d2 --- /dev/null +++ b/.github/red_data/core/settings.json @@ -0,0 +1,11 @@ +{ + "0": { + "CUSTOM_GROUPS": {}, + "GLOBAL": { + "prefix": [ + "!" + ], + "schema_version": 2 + } + } +} \ No newline at end of file diff --git a/.github/workflows/loadcheck.yml b/.github/workflows/loadcheck.yml index e642228..9295809 100644 --- a/.github/workflows/loadcheck.yml +++ b/.github/workflows/loadcheck.yml @@ -51,7 +51,7 @@ jobs: - name: Prepare files run: | mkdir -p /home/runner/.config/Red-DiscordBot - echo '{"workflow": {"DATA_PATH": "/home/runner/work/Vex-Cogs/Vex-Cogs/.github/red_data", "COG_PATH_APPEND": "cogs", "CORE_PATH_APPEND": "core", "STORAGE_TYPE": "JSON", "STORAGE_DETAILS": {}}}' > /home/runner/.config/Red-DiscordBot/config.json + echo '{"workflow": {"DATA_PATH": "/home/runner/work/raiden-cogs/raiden-cogs/.github/red_data", "COG_PATH_APPEND": "cogs", "CORE_PATH_APPEND": "core", "STORAGE_TYPE": "JSON", "STORAGE_DETAILS": {}}}' > /home/runner/.config/Red-DiscordBot/config.json - name: Run script loadcheck.py run: | source .venv/bin/activate From 3443cf8e31bf85046fbde0798c72e43fd5a28db8 Mon Sep 17 00:00:00 2001 From: Raiden Date: Mon, 30 Jan 2023 19:29:10 +0800 Subject: [PATCH 17/51] again, blegh --- .flake8 | 2 +- .github/workflows/loadcheck.yml | 12 ++++-------- pyproject.toml | 8 +------- 3 files changed, 6 insertions(+), 16 deletions(-) diff --git a/.flake8 b/.flake8 index 33d2356..7cf6fb3 100644 --- a/.flake8 +++ b/.flake8 @@ -2,4 +2,4 @@ ignore = W503,E203,E402 per-file-ignores = __init__.py:F401,consts.py:E501 max-line-length = 99 -exclude = .git,.venv,.tox,__pycache__,docs,vexutils \ No newline at end of file +exclude = .git,.venv,.tox,__pycache__ \ No newline at end of file diff --git a/.github/workflows/loadcheck.yml b/.github/workflows/loadcheck.yml index 9295809..b3932a0 100644 --- a/.github/workflows/loadcheck.yml +++ b/.github/workflows/loadcheck.yml @@ -13,14 +13,10 @@ jobs: matrix: python-version: ["3.8", "3.9"] red-version: - # this workflow required pr #5453 commit d27dbde, which is in dev & pypi 3.4.15+ - - "git+https://github.com/Cog-Creators/Red-DiscordBot@V3/develop#egg=Red-DiscordBot" - - "Red-DiscordBot==3.4.16" + - "Red-DiscordBot==3.4.18" include: - - red-version: "git+https://github.com/Cog-Creators/Red-DiscordBot@V3/develop#egg=Red-DiscordBot" - friendly-red: "Red (dev version)" - - red-version: "Red-DiscordBot==3.4.16" - friendly-red: "Red 3.4.16" + - red-version: "Red-DiscordBot==3.4.18" + friendly-red: "Red 3.4.18" fail-fast: false name: Cog load test - Python ${{ matrix.python-version }} & ${{ matrix.friendly-red }} @@ -44,7 +40,7 @@ jobs: run: | python3 -m venv .venv source .venv/bin/activate - pip install setuptools wheel + pip install setuptools wheel pillow pip install ${{ matrix.red-version }} pip install -r dev-requirements.txt pip install jsonrpc-websocket diff --git a/pyproject.toml b/pyproject.toml index b3bb428..41ea6ae 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,14 +1,8 @@ [tool.black] line-length = 99 target-version = ["py38"] -extend-exclude = "vexutils|.stubs" +extend-exclude = ".stubs" [tool.isort] profile = "black" line_length = 99 -extend_skip = "vexutils" - -[tool.pyright] -stubPath = "./.stubs" -exclude = ["*/vexutils", ".tox", "**/__pycache__", ".venv", ".github", ".stubs"] -useLibraryCodeForTypes = true \ No newline at end of file From fc4f31f224b7d3ff7738e1e91f84a3cb82123c84 Mon Sep 17 00:00:00 2001 From: Raiden Date: Mon, 30 Jan 2023 19:32:04 +0800 Subject: [PATCH 18/51] I hope it work now --- .github/workflows/loadcheck.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/loadcheck.yml b/.github/workflows/loadcheck.yml index b3932a0..5401235 100644 --- a/.github/workflows/loadcheck.yml +++ b/.github/workflows/loadcheck.yml @@ -40,10 +40,11 @@ jobs: run: | python3 -m venv .venv source .venv/bin/activate - pip install setuptools wheel pillow + pip install setuptools wheel pip install ${{ matrix.red-version }} pip install -r dev-requirements.txt pip install jsonrpc-websocket + pip install pillow genshin enkanetwork aioenkanetworkcard fernet - name: Prepare files run: | mkdir -p /home/runner/.config/Red-DiscordBot From be9871fb00fd7a546c9af0ef7414ad84d04964bf Mon Sep 17 00:00:00 2001 From: Raiden Date: Mon, 30 Jan 2023 19:35:37 +0800 Subject: [PATCH 19/51] Cmon pip --- .github/workflows/loadcheck.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/loadcheck.yml b/.github/workflows/loadcheck.yml index 5401235..cc19e91 100644 --- a/.github/workflows/loadcheck.yml +++ b/.github/workflows/loadcheck.yml @@ -40,6 +40,7 @@ jobs: run: | python3 -m venv .venv source .venv/bin/activate + pip install --upgrade pip pip install setuptools wheel pip install ${{ matrix.red-version }} pip install -r dev-requirements.txt From fb6498e335937c54a8cc65f7e76708adc8bdd174 Mon Sep 17 00:00:00 2001 From: Raiden Date: Mon, 30 Jan 2023 19:47:37 +0800 Subject: [PATCH 20/51] =?UTF-8?q?=F0=9F=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dev-requirements.txt | 37 +++++++++++-------------------------- 1 file changed, 11 insertions(+), 26 deletions(-) diff --git a/dev-requirements.txt b/dev-requirements.txt index 40161a1..0ce7649 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -1,24 +1,18 @@ -# Red itself is also a requriement, however it is not listed here because +# Red itself is also a requirement, however it is not listed here because # some people may have special setups (eg dev version installed) -# Required for utils -tabulate -cachetools -asyncache - -# Required for optional PandasSQLDriver in utils -pandas - # Cogs -plotly # betteruptime, stattrack, googletrends -kaleido # betteruptime, stattrack, googletrends gidgethub # ghissues, gitub -pytrends # googletrends -psutil # stattrack, system -markdownify # status -rapidfuzz # timechannel -wakeonlan # wol -expr.py # calc +pillow # genshinutils +genshin # genshinutils +enkanetwork # genshinutils +aioenkanetworkcard # genshinutils +fernet # genshinutils + +# Required by EnkaNetwork.py +pydantic +aiohttp +cachetools # checking tools pyright @@ -29,14 +23,5 @@ pytest tox pre-commit -# docs -sphinx -sphinx-rtd-theme -furo - # checks python-dotenv - -# docs -sphinx_rtd_theme -furo \ No newline at end of file From 783c6260f1e5ad6d411c80fb2ebd2e3c7c73751c Mon Sep 17 00:00:00 2001 From: Raiden Date: Mon, 30 Jan 2023 19:48:33 +0800 Subject: [PATCH 21/51] :( --- dev-requirements.txt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/dev-requirements.txt b/dev-requirements.txt index 0ce7649..b3f76a7 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -1,6 +1,11 @@ # Red itself is also a requirement, however it is not listed here because # some people may have special setups (eg dev version installed) +# Required by EnkaNetwork.py +pydantic +aiohttp +cachetools + # Cogs gidgethub # ghissues, gitub pillow # genshinutils @@ -9,11 +14,6 @@ enkanetwork # genshinutils aioenkanetworkcard # genshinutils fernet # genshinutils -# Required by EnkaNetwork.py -pydantic -aiohttp -cachetools - # checking tools pyright black From c1d40934bc59c313a648ffd29a4b1595f824e325 Mon Sep 17 00:00:00 2001 From: Raiden Date: Mon, 30 Jan 2023 19:58:32 +0800 Subject: [PATCH 22/51] . --- .github/workflows/loadcheck.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/loadcheck.yml b/.github/workflows/loadcheck.yml index cc19e91..3f08a59 100644 --- a/.github/workflows/loadcheck.yml +++ b/.github/workflows/loadcheck.yml @@ -45,7 +45,6 @@ jobs: pip install ${{ matrix.red-version }} pip install -r dev-requirements.txt pip install jsonrpc-websocket - pip install pillow genshin enkanetwork aioenkanetworkcard fernet - name: Prepare files run: | mkdir -p /home/runner/.config/Red-DiscordBot From 1f0d3d3c3d39f737a604be5c6b07db216a73bc50 Mon Sep 17 00:00:00 2001 From: Raiden Date: Mon, 30 Jan 2023 20:06:58 +0800 Subject: [PATCH 23/51] TYPO OOF --- dev-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev-requirements.txt b/dev-requirements.txt index b3f76a7..28e037e 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -10,7 +10,7 @@ cachetools gidgethub # ghissues, gitub pillow # genshinutils genshin # genshinutils -enkanetwork # genshinutils +enkanetwork.py # genshinutils aioenkanetworkcard # genshinutils fernet # genshinutils From ef1764ad16144f63215a542611be342c88505d50 Mon Sep 17 00:00:00 2001 From: Raiden Date: Mon, 30 Jan 2023 20:08:49 +0800 Subject: [PATCH 24/51] bruh --- dev-requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/dev-requirements.txt b/dev-requirements.txt index 28e037e..fa219ef 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -9,6 +9,7 @@ cachetools # Cogs gidgethub # ghissues, gitub pillow # genshinutils +tabulate # throw genshin # genshinutils enkanetwork.py # genshinutils aioenkanetworkcard # genshinutils From e54e5773380b0561bc4b7f45265faca6af8b2c21 Mon Sep 17 00:00:00 2001 From: Raiden Date: Mon, 30 Jan 2023 20:10:23 +0800 Subject: [PATCH 25/51] Remove more stuff --- dev-requirements.txt | 5 ----- 1 file changed, 5 deletions(-) diff --git a/dev-requirements.txt b/dev-requirements.txt index fa219ef..9674a66 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -1,11 +1,6 @@ # Red itself is also a requirement, however it is not listed here because # some people may have special setups (eg dev version installed) -# Required by EnkaNetwork.py -pydantic -aiohttp -cachetools - # Cogs gidgethub # ghissues, gitub pillow # genshinutils From 9e27955bdc03c620baf797cd92be3ba052b9b8ba Mon Sep 17 00:00:00 2001 From: Raiden Date: Mon, 30 Jan 2023 21:15:36 +0800 Subject: [PATCH 26/51] Link & format --- .flake8 | 8 ++++---- choose/choose.py | 5 ++++- genshinutils/profile.py | 17 ++++++++-------- genshinutils/register.py | 42 ++++++++++++++++++++++------------------ throw/throw.py | 2 +- 5 files changed, 41 insertions(+), 33 deletions(-) diff --git a/.flake8 b/.flake8 index 7cf6fb3..2f2d6c1 100644 --- a/.flake8 +++ b/.flake8 @@ -1,5 +1,5 @@ [flake8] -ignore = W503,E203,E402 -per-file-ignores = __init__.py:F401,consts.py:E501 -max-line-length = 99 -exclude = .git,.venv,.tox,__pycache__ \ No newline at end of file +ignore = E302 +per-file-ignores = __init__.py:F401, constants.py:E501 +max-line-length = 110 +exclude = .git, .venv, .tox, __pycache__ \ No newline at end of file diff --git a/choose/choose.py b/choose/choose.py index 89f7cfe..42da803 100644 --- a/choose/choose.py +++ b/choose/choose.py @@ -60,7 +60,10 @@ async def choose(self, ctx, *, options): if len(choosearray) > 1: e = discord.Embed(color=(await ctx.embed_colour()), title=random.choice(choosearray)) e.set_footer( - text=f"✨ Choosing for {ctx.author.display_name}, from a list of {len(choosearray)} options." + text=( + f"✨ Choosing for {ctx.author.display_name}, " + f"from a list of {len(choosearray)} options." + ) ) else: return await ctx.send("Not enough options to pick from.") diff --git a/genshinutils/profile.py b/genshinutils/profile.py index f67fcaa..eb89d06 100644 --- a/genshinutils/profile.py +++ b/genshinutils/profile.py @@ -116,28 +116,29 @@ async def genshin_generate_profile(uid): value=data.stats.spiral_abyss, inline=True, ) + total_oculi = data.stats.anemoculi + data.stats.geoculi + + data.stats.electroculi + data.stats.dendroculi e.add_field( name="Total Oculi Collected", - value=( - f"{data.stats.anemoculi + data.stats.geoculi + data.stats.electroculi + data.stats.dendroculi}" - ), + value=total_oculi, inline=True, ) e.add_field( name="Waypoints Unlocked", - value=(f"{data.stats.unlocked_waypoints}"), + value=data.stats.unlocked_waypoints, inline=True, ) + total_chest = data.stats.common_chests + data.stats.precious_chests + + data.stats.exquisite_chests + data.stats.luxurious_chests + + data.stats.remarkable_chests e.add_field( name="Total Chests Opened", - value=( - f"{data.stats.common_chests + data.stats.precious_chests + data.stats.exquisite_chests + data.stats.luxurious_chests + data.stats.remarkable_chests}" - ), + value=total_chest, inline=True, ) e.add_field( name="Domains Unlocked", - value=(f"{data.stats.unlocked_domains}"), + value=data.stats.unlocked_domains, inline=True, ) diff --git a/genshinutils/register.py b/genshinutils/register.py index e2c08eb..75c7e7f 100644 --- a/genshinutils/register.py +++ b/genshinutils/register.py @@ -8,7 +8,7 @@ from discord.channel import DMChannel from redbot.core import commands -from .utils import decrypt_config, encrypt_config +from .utils import decrypt_config, encrypt_config, generate_embed log = logging.getLogger("red.raidensakura.genshinutils") @@ -65,7 +65,10 @@ def pass_verification(discordtag, signature): author_discord_id, data.player.signature ): return await ctx.send( - "Your signature does not contain your Discord tag.\nNote that it may take up to 15 minutes for changes to be reflected." + ( + "Your signature does not contain your Discord tag.\n" + "Note that it may take up to 15 minutes for changes to be reflected." + ) ) await self.config.user(ctx.author).UID.set(uid) return await ctx.send(f"Successfully set UID for {ctx.author.name} to {uid}.") @@ -74,9 +77,9 @@ def pass_verification(discordtag, signature): Important Notes: 1. Command has proprietary DM check since I want it to preface a a disclaimer when run in a server. This command deals with sensitive information and I want it to be taken very seriously. - 2. I fully acknowledge storing the encryption key along with the encrypted data itself is terrible security practice. - Hoyolab account token can be used to perform destructive account actions, and potentially get your account banned for abuse. - Since the cog is open-source, the purpose of the encryption is to prevent bot owners from having plaintext access to them + 2. I fully acknowledge storing the encryption key along with the encrypted data itself is bad practice. + Hoyolab account token can be used to perform potentially dangerous account actions. + Since the cog is OSS, the purpose is to prevent bot owners from having plaintext access to them in a way such that is require a bit of coding and encryption knowledge to access them on demand. """ @@ -101,34 +104,35 @@ async def hoyolab(self, ctx: commands.Context, *, cookie: str = None): else: owner = app_info.owner desc = ( - "This command lets the bot perform Hoyolab account actions on your behalf, authenticated using your token. " - "The token will then be linked to your Discord ID and stored encrypted in the bot's config, along with the encryption key. " - "Make sure **you fully acknowledge the risks of sharing your account token online** before proceeding.\n\n" - "For security reason, please run this command in a DM channel when setting token.\n\n" - "Read on how to obtain your token [here](https://project-mei.xyz/genshinutils)." - ) - e = Embed( - color=(await ctx.embed_colour()), - title="Important Disclaimer", - description=desc, + "This command links your Hoyolab account token to your Discord account. " + "Your account token allow the bot to perform various account actions on your behalf, " + "such as claiming daily login, fetching character data etc. " + "Your token will be stored in the bot's config, and bot owner will always have access to it. " + "Make sure **you fully understand the risk of sharing your token online** before proceeding." + "\n\nFor security reason, please run this command in a DM channel when setting token." + "\n\nRead on how to obtain your token [here](https://project-mei.xyz/genshinutils)." ) + e = generate_embed(title="Important Disclaimer", desc=desc, color=await ctx.embed_color()) if app_info.bot_public: public = "Can be invited by anyone." else: public = "Can only be invited by the owner." e.add_field(name="Bot Owner", value=owner) - e.add_field(name="Invite Link Privacy", value=public) + e.add_field(name="Bot Invite Link Privacy", value=public) if ctx.me.avatar_url: e.set_thumbnail(url=ctx.me.avatar_url) e.set_footer(text=f"Command invoked by {ctx.author}.") return await ctx.send(embed=e) if not cookie: + cog_url = "https://project-mei.xyz/genshinutils" + bot_prefix = f"{escape(ctx.prefix)}" + command_name = f"{escape(ctx.command.name)}" msg = ( f"**Provide a valid cookie to bind your Discord account to.**\n\n" - f"` » ` Instruction on how to obtain your Hoyolab cookie:\n\n\n" - f"` » ` For command help context: `{escape(ctx.prefix)}help genshin register {escape(ctx.command.name)}`\n\n" - f"` » ` To read disclaimers, this command again in any server." + f"` » ` Instruction on how to obtain your Hoyolab cookie:\n<{cog_url}>\n\n" + f"` » ` For command help context: `{bot_prefix}help genshin register {command_name}`\n\n" + f"` » ` To read disclaimers, type this command again in any server." ) return await ctx.send(msg) diff --git a/throw/throw.py b/throw/throw.py index d6efb80..17146e8 100644 --- a/throw/throw.py +++ b/throw/throw.py @@ -120,7 +120,7 @@ def parse_actions(data, array, action: str): for key, value in data.items(): if action in key: sent = str(data.get("ITEMS_THROWN", " ")).replace("0", " ") - received = str(data.get(f"TIMES_HIT", " ")).replace("0", " ") + received = str(data.get("TIMES_HIT", " ")).replace("0", " ") array.append([action.lower(), received, sent]) for act in self.possible_actions: From bc15cb25b290258d98f3332dd3abf4bd268256f4 Mon Sep 17 00:00:00 2001 From: Raiden Date: Mon, 30 Jan 2023 21:18:58 +0800 Subject: [PATCH 27/51] Reformat again ugh --- genshinutils/profile.py | 6 +++--- genshinutils/register.py | 4 +++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/genshinutils/profile.py b/genshinutils/profile.py index eb89d06..1b52743 100644 --- a/genshinutils/profile.py +++ b/genshinutils/profile.py @@ -117,7 +117,7 @@ async def genshin_generate_profile(uid): inline=True, ) total_oculi = data.stats.anemoculi + data.stats.geoculi - + data.stats.electroculi + data.stats.dendroculi + +data.stats.electroculi + data.stats.dendroculi e.add_field( name="Total Oculi Collected", value=total_oculi, @@ -129,8 +129,8 @@ async def genshin_generate_profile(uid): inline=True, ) total_chest = data.stats.common_chests + data.stats.precious_chests - + data.stats.exquisite_chests + data.stats.luxurious_chests - + data.stats.remarkable_chests + +data.stats.exquisite_chests + data.stats.luxurious_chests + +data.stats.remarkable_chests e.add_field( name="Total Chests Opened", value=total_chest, diff --git a/genshinutils/register.py b/genshinutils/register.py index 75c7e7f..14e8cd6 100644 --- a/genshinutils/register.py +++ b/genshinutils/register.py @@ -112,7 +112,9 @@ async def hoyolab(self, ctx: commands.Context, *, cookie: str = None): "\n\nFor security reason, please run this command in a DM channel when setting token." "\n\nRead on how to obtain your token [here](https://project-mei.xyz/genshinutils)." ) - e = generate_embed(title="Important Disclaimer", desc=desc, color=await ctx.embed_color()) + e = generate_embed( + title="Important Disclaimer", desc=desc, color=await ctx.embed_color() + ) if app_info.bot_public: public = "Can be invited by anyone." else: From 543e635842f17e2072b07e4be2ffeafd4c5a914d Mon Sep 17 00:00:00 2001 From: Raiden Date: Mon, 30 Jan 2023 21:54:32 +0800 Subject: [PATCH 28/51] Bump Github Actions ver. --- .github/workflows/checks.yml | 7 ++----- tox.ini | 12 +----------- 2 files changed, 3 insertions(+), 16 deletions(-) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 2aa1776..0f33229 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -21,7 +21,6 @@ jobs: - style-black - style-isort - lint-flake8 - - pytest include: - tox_env: style-black friendly_name: Style (black) @@ -29,14 +28,12 @@ jobs: friendly_name: Style (isort) - tox_env: lint-flake8 friendly_name: Lint (flake8) - - tox_env: pytest - friendly_name: Tests fail-fast: false name: Tox - ${{ matrix.python_version }} - ${{ matrix.friendly_name }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: ref: ${{ env.ref }} - name: Set up Python ${{ matrix.python_version }} @@ -46,7 +43,7 @@ jobs: # caching cuts down time for tox (for example black) from ~40 secs to 4 - name: Cache tox - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: .tox key: tox-${{ matrix.python_version }}-${{ matrix.tox_env }}-${{ hashFiles('tox.ini') }} diff --git a/tox.ini b/tox.ini index f88e9ef..cb47ac0 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py38, style-black, style-isort, lint-flake8, type-pyright, docs, pytest +envlist = py38, style-black, style-isort, lint-flake8 skipsdist = true [testenv] @@ -25,10 +25,7 @@ deps = pyjson5 expr.py - # pytest - pytest red-discordbot==3.4.18 - markdownify # type # (some are covered under below) @@ -52,10 +49,3 @@ description = Lint with flake8. envdir = {toxworkdir}/py38 commands = flake8 . - -[testenv:pytest] -description = Run pytest -envdir = {toxworkdir}/py38 - -commands = - pytest tests \ No newline at end of file From 8f74ad27205c73445ab1b2c473fbd3ad149fd81d Mon Sep 17 00:00:00 2001 From: Raiden Date: Mon, 30 Jan 2023 22:06:11 +0800 Subject: [PATCH 29/51] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index cfb9a90..55bd976 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,7 @@ black . ; isort .

From 123a804bb7b2d4daf7cac2481188fb373655c2e4 Mon Sep 17 00:00:00 2001 From: Raiden Date: Mon, 30 Jan 2023 23:13:34 +0800 Subject: [PATCH 30/51] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 55bd976..afb7c01 100644 --- a/README.md +++ b/README.md @@ -56,8 +56,8 @@

To make sure things stay nice and clean.

```py -pip install -U black isort -black . ; isort . +pip install -U black isort flake8 +black . ; isort . ; flake8 . ```

.vscode/settings.json

From f667cd213c901304a0a32f0419644e0613d9227e Mon Sep 17 00:00:00 2001 From: Raiden Date: Tue, 31 Jan 2023 15:31:31 +0800 Subject: [PATCH 31/51] Bump pre-commit versions, docs, format json --- .flake8 | 4 +- .../red_data/cogs/CogManager/settings.json | 4 +- .github/workflows/checks.yml | 3 +- .github/workflows/loadcheck.yml | 10 +- .gitignore | 1 - .pre-commit-config.yaml | 12 +- .vscode/settings.json | 6 + choose/info.json | 18 +-- genshinutils/info.json | 25 ++-- genshinutils/utils.py | 113 +++++++++--------- longcat/info.json | 21 ++-- throw/info.json | 19 +-- 12 files changed, 128 insertions(+), 108 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.flake8 b/.flake8 index 2f2d6c1..7706534 100644 --- a/.flake8 +++ b/.flake8 @@ -1,5 +1,5 @@ [flake8] -ignore = E302 +# ignore = E302 per-file-ignores = __init__.py:F401, constants.py:E501 max-line-length = 110 -exclude = .git, .venv, .tox, __pycache__ \ No newline at end of file +exclude = .git, .venv, .tox, __pycache__ diff --git a/.github/red_data/cogs/CogManager/settings.json b/.github/red_data/cogs/CogManager/settings.json index b841a53..9c68377 100644 --- a/.github/red_data/cogs/CogManager/settings.json +++ b/.github/red_data/cogs/CogManager/settings.json @@ -2,9 +2,9 @@ "2938473984732": { "GLOBAL": { "paths": [ - "C:\\Data\\Git\\raiden-cogs", + "C:\\Projects\\raiden-cogs", "/home/runner/work/raiden-cogs/raiden-cogs" ] } } -} \ No newline at end of file +} diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 0f33229..5df7742 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -1,4 +1,3 @@ -# https://github.com/Vexed01/Vex-Cogs/blob/master/.github/workflows/checks.yml name: Checks on: @@ -56,4 +55,4 @@ jobs: env: TOXENV: ${{ matrix.tox_env }} run: | - tox \ No newline at end of file + tox diff --git a/.github/workflows/loadcheck.yml b/.github/workflows/loadcheck.yml index 3f08a59..4aa47ac 100644 --- a/.github/workflows/loadcheck.yml +++ b/.github/workflows/loadcheck.yml @@ -21,7 +21,7 @@ jobs: name: Cog load test - Python ${{ matrix.python-version }} & ${{ matrix.friendly-red }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v2 @@ -30,12 +30,12 @@ jobs: - name: Cache venv id: cache-venv - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: .venv key: ${{ matrix.red-version }}-${{ matrix.python-version }}-${{ hashFiles('dev-requirements.txt') }}-${{ secrets.CACHE_V }} - - name: Maybe make venv + - name: Make venv if not cached if: steps.cache-venv.outputs.cache-hit != 'true' run: | python3 -m venv .venv @@ -58,7 +58,7 @@ jobs: - name: Save Red output as Artifact if: always() # still run if prev step failed - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: "Red log - Python ${{ matrix.python-version }} & ${{ matrix.friendly-red }}" - path: red.log \ No newline at end of file + path: red.log diff --git a/.gitignore b/.gitignore index f5aa38f..d161acc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ -.vscode/ Pipfile Pipfile.lock diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 342b0e2..347f5e2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v2.3.0 + rev: v4.4.0 hooks: - id: check-ast - id: check-json @@ -9,15 +9,15 @@ repos: - id: end-of-file-fixer - id: mixed-line-ending - repo: https://github.com/psf/black - rev: "22.3.0" + rev: "22.12.0" hooks: - id: black - repo: https://github.com/PyCQA/isort - rev: "5.8.0" + rev: "5.12.0" hooks: - id: isort - repo: https://github.com/myint/autoflake - rev: "v1.4" + rev: "v2.0.0" hooks: - id: autoflake args: @@ -28,6 +28,6 @@ repos: "--ignore-init-module-imports", ] - repo: https://github.com/pycqa/flake8 - rev: "3.9.2" + rev: "6.0.0" hooks: - - id: flake8 \ No newline at end of file + - id: flake8 diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..9ae86e4 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "autoDocstring.docstringFormat": "one-line-sphinx", + "autoDocstring.guessTypes": true, + "python.terminal.activateEnvInCurrentTerminal": true, + "python.terminal.activateEnvironment": true +} diff --git a/choose/info.json b/choose/info.json index 8848e40..9479fdf 100644 --- a/choose/info.json +++ b/choose/info.json @@ -1,17 +1,19 @@ { - "name": "Choose", - "short": "Choose between multiple options", + "author": [ + "raidensakura" + ], "description": "A better replacement for core `choose` command.", - "install_msg": "Thanks for installing. This cog replaces core's `choose` command with a more intuitive one.", + "disabled": false, "end_user_data_statement": "This cog does not persistently store any data about users.", - "author": ["raidensakura"], + "hidden": false, + "install_msg": "Thanks for installing. This cog replaces core's `choose` command with a more intuitive one.", + "min_bot_version": "3.4.12", + "name": "Choose", "required_cogs": {}, "requirements": [], + "short": "Choose between multiple options", "tags": [ "choose" ], - "min_bot_version": "3.4.12", - "hidden": false, - "disabled": false, "type": "COG" -} \ No newline at end of file +} diff --git a/genshinutils/info.json b/genshinutils/info.json index ba52f34..2bf2521 100644 --- a/genshinutils/info.json +++ b/genshinutils/info.json @@ -1,17 +1,24 @@ { - "name": "GenshinUtils", - "short": "A Genshin Impact utility cog", + "author": [ + "raidensakura" + ], "description": "Various useful utilities for Genshin Impact, such as retrieving profile data and many more.", - "install_msg": "Thank you for installing my cog. This is continuously being worked on, expect rapid changes.\nI test stuff in my Discord: https://dsc.gg/transience", + "disabled": false, "end_user_data_statement": "This cog stores your Genshin UID, Hoyolab ID and Hoyolab Account token if provided.", - "author": ["raidensakura"], + "hidden": false, + "install_msg": "Thank you for installing my cog. This is continuously being worked on, expect rapid changes.\nI test stuff in my Discord: https://dsc.gg/transience", + "min_bot_version": "3.4.12", + "name": "GenshinUtils", "required_cogs": {}, - "requirements": ["genshin", "enkanetwork", "aioenkanetworkcard", "fernet"], + "requirements": [ + "genshin", + "enkanetwork", + "aioenkanetworkcard", + "fernet" + ], + "short": "A Genshin Impact utility cog", "tags": [ "genshin" ], - "min_bot_version": "3.4.12", - "hidden": false, - "disabled": false, "type": "COG" -} \ No newline at end of file +} diff --git a/genshinutils/utils.py b/genshinutils/utils.py index 16a9060..ab2be09 100644 --- a/genshinutils/utils.py +++ b/genshinutils/utils.py @@ -10,65 +10,58 @@ log = logging.getLogger("red.raidensakura.genshinutils") -"""I make it a function so it's more readable""" -# Used internally -def bytes_to_string(bytes): - str = bytes.decode() - return str - -"""I make it a function so it's more readable""" -# Used internally -def string_to_bytes(str): - bytes = str.encode() - return bytes - - -""" -Accepts: config -Returns: str(encryption_key) or None -""" # https://stackoverflow.com/questions/44432945/generating-own-key-with-python-fernet async def get_encryption_key(config): - key = string_to_bytes(await config.encryption_key()) + """Fetch and convert encryption key from config + + :param object config: Red V3 Config object + :return str: Plaintext encryption key + """ + key = await config.encryption_key() if not key or key is None: key = Fernet.generate_key() - await config.encryption_key.set(bytes_to_string(key)) + await config.encryption_key.set(key.decode()) + else: + key = key.encode() return key -""" -Accepts: config, str(encoded) -Returns: str(decoded) -""" -# decrypt config async def decrypt_config(config, encoded): - to_decode = string_to_bytes(encoded) + """Decrypt encrypted config data + + :param object config: Red V3 Config object + :param str encoded: encoded data + :return str: decoded data + """ + to_decode = encoded.encode() cipher_suite = Fernet(await get_encryption_key(config)) decoded_bytes = cipher_suite.decrypt(to_decode) - decoded = bytes_to_string(decoded_bytes) + decoded = decoded_bytes.decode() return decoded -""" -Accepts: config, str(decoded) -Returns: str(encoded) -""" -# encrypt config async def encrypt_config(config, decoded): - to_encode = string_to_bytes(decoded) + """Encrypt unencrypted data to store in config + + :param object config: Red V3 Config object + :param str decoded: data to encrypt + :return str: encoded data + """ + to_encode = decoded.encode() cipher_suite = Fernet(await get_encryption_key(config)) encoded_bytes = cipher_suite.encrypt(to_encode) - encoded = bytes_to_string(encoded_bytes) + encoded = encoded_bytes.decode() return encoded -""" -Accepts: ( str(uid) | discord.Member ), self.config -Returns: str(uid) or None -""" -# validate uid async def validate_uid(u, config): + """Return user UID from config or check if UID is valid + + :param discord.Member or str u: User or UID to check + :param object config: Red V3 Config object + :return str: UID of the user if exist or valid + """ if isinstance(u, discord.Member): uid = await config.user(u).UID() if uid: @@ -88,34 +81,36 @@ async def validate_uid(u, config): return uid -""" -Accepts: str(name_query) -Returns: str(formal_name) or None -""" -# validate_char_name def validate_char_name(arg): + """Validate character name against constants + + :param str arg: name to check + :return str: Formal name of the character if exist + """ formal_name = {i for i in common_names if arg in common_names[i]} if formal_name: return str(formal_name).strip("{'\"}") -""" -Accepts: str(uid), formal_name -Returns: { UID: { Character: } } -""" -# enka_get_character_card async def enka_get_character_card(uid, char_name): + """Generate one or more character build image objects in a dict + + :param str uid: UID of the player + :param str char_name: formal name of the character + :return dict: dict containing Pillow image object for the character + """ async with encbanner.ENC(lang="en", splashArt=True, characterName=char_name) as encard: ENCpy = await encard.enc(uids=uid) return await encard.creat(ENCpy, 2) -""" -Accepts: config, discord.Member -Returns: -""" -# get_user_cookie async def get_user_cookie(config, user): + """Retrieve user cookie from config + + :param object config: Red V3 Config object + :param discord.Member user: Discord user to check for + :return cookie: Cookie object for the user + """ ltuid_config = await config.user(user).ltuid() ltoken_config = await config.user(user).ltoken() @@ -127,12 +122,14 @@ async def get_user_cookie(config, user): return cookie -""" -Accepts: str(title), str(desc), color -Returns: discord.Embed -""" -# generate_embed def generate_embed(title="", desc="", color=""): + """Generate standardized Discord Embed usable for the whole cog + + :param str title: Title of the embed, defaults to "" + :param str desc: Description of the embed, defaults to "" + :param str color: Color of the embed, defaults to "" + :return discord.Embed: Discord Embed object + """ cog_url = "https://project-mei.xyz/genshinutils" e = discord.Embed(title=title, description=desc, color=color, url=cog_url) e.set_footer( diff --git a/longcat/info.json b/longcat/info.json index d5812c2..8a1372f 100644 --- a/longcat/info.json +++ b/longcat/info.json @@ -1,17 +1,22 @@ { - "name": "Longcat", - "short": "All hail Longcat. Improved from Aioxas' Longcat cog.", + "author": [ + "raidensakura", + "Aioxas" + ], "description": "Send a looooongcat based on how long you typed the command.", - "install_msg": "It's a looooooong loooooooong cat", + "disabled": false, "end_user_data_statement": "This cog does not persistently store any data about users.", - "author": ["raidensakura", "Aioxas"], + "hidden": false, + "install_msg": "It's a looooooong loooooooong cat", + "min_bot_version": "3.4.12", + "name": "Longcat", "required_cogs": {}, - "requirements": ["Pillow"], + "requirements": [ + "Pillow" + ], + "short": "All hail Longcat. Improved from Aioxas' Longcat cog.", "tags": [ "fun" ], - "min_bot_version": "3.4.12", - "hidden": false, - "disabled": false, "type": "COG" } diff --git a/throw/info.json b/throw/info.json index e13f47f..10f97e1 100644 --- a/throw/info.json +++ b/throw/info.json @@ -1,16 +1,21 @@ { - "name": "Throw", - "short": "Throw random stuff at your Discord friends", + "author": [ + "raidensakura", + "ow0x" + ], "description": "A cog to throw random things at your friends that may or may not upset them. Originally a roleplay cog by owo-cogs, modified by raidensakura.", + "disabled": false, "end_user_data_statement": "This cog does not persistently store any PII data or metadata about users.", - "author": ["raidensakura", "ow0x"], + "hidden": false, + "min_bot_version": "3.4.12", + "name": "Throw", "required_cogs": {}, - "requirements": ["tabulate"], + "requirements": [ + "tabulate" + ], + "short": "Throw random stuff at your Discord friends", "tags": [ "throw" ], - "min_bot_version": "3.4.12", - "hidden": false, - "disabled": false, "type": "COG" } From 235d895882c5374653fe5aa8a96b84b762a2bc19 Mon Sep 17 00:00:00 2001 From: Raiden Date: Tue, 31 Jan 2023 15:53:54 +0800 Subject: [PATCH 32/51] Update README.md --- README.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index afb7c01..1639359 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ +

@@ -16,6 +17,7 @@ [p]repo add raiden-cogs https://github.com/raidensakura/raiden-cogs/ [p]cog install raiden-cogs ``` +[p] is your prefix

List of Cogs

@@ -53,23 +55,19 @@

Dev Stuff

Formatting

-

To make sure things stay nice and clean.

+

For manual formatting, this repo uses these three:

```py pip install -U black isort flake8 black . ; isort . ; flake8 . ``` -

.vscode/settings.json

-

To make sure the venv always open when I work on cogs.

- -```json -{ - "python.terminal.activateEnvironment": true, - "python.terminal.activateEnvInCurrentTerminal": true, - "python.defaultInterpreterPath": "C:\\Users\\Raiden\\redenv\\Scripts\\python.exe" -} +

Pre-commit hooks

+

Optional but it keeps manual formatting work away from you.

+```py +pip install pre-commit +pre-commit install ```

Credits

@@ -78,7 +76,9 @@ black . ; isort . ; flake8 .

+ +

(Back to top)

From b94b12211b17da019a4c3dbf8bf31d98b5e5e416 Mon Sep 17 00:00:00 2001 From: Raiden Date: Tue, 31 Jan 2023 16:02:48 +0800 Subject: [PATCH 33/51] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 1639359..73708e7 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,8 @@

-

A collection of badly written cogs I made for fun in the process of learning Python.

+

A collection of badly written homemade cogs I made for fun in the process of learning Python.

+

Support: Join my Discord server or mention Raiden#5008 in Red Cog Support server.

Installation

From dd0d1dd15a4c2f6ac90f232a0f991f70bfb480ec Mon Sep 17 00:00:00 2001 From: Raiden Date: Tue, 31 Jan 2023 22:35:31 +0800 Subject: [PATCH 34/51] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 73708e7..6b3225b 100644 --- a/README.md +++ b/README.md @@ -82,4 +82,4 @@ pre-commit install

-

(Back to top)

+

(Back to top)

From c839aa5bb000fd3271270a9c6dea08f344a1d5b6 Mon Sep 17 00:00:00 2001 From: Raiden Date: Wed, 1 Feb 2023 23:13:25 +0800 Subject: [PATCH 35/51] add task --- .flake8 | 2 ++ .github/workflows/checks.yml | 5 ++--- .github/workflows/loadcheck.yml | 8 ++++---- genshinutils/genshinutils.py | 13 ++++++++++++- 4 files changed, 20 insertions(+), 8 deletions(-) diff --git a/.flake8 b/.flake8 index 7706534..e480388 100644 --- a/.flake8 +++ b/.flake8 @@ -1,5 +1,7 @@ [flake8] # ignore = E302 per-file-ignores = __init__.py:F401, constants.py:E501 +# So flake8 won't whine when my comments are too long +# Black and isort still set at 99 max-line-length = 110 exclude = .git, .venv, .tox, __pycache__ diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 5df7742..c8de40f 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -3,8 +3,7 @@ name: Checks on: push: branches: - - master - - genshin + - main pull_request: # thanks red or wherever you got it from @@ -36,7 +35,7 @@ jobs: with: ref: ${{ env.ref }} - name: Set up Python ${{ matrix.python_version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python_version }} diff --git a/.github/workflows/loadcheck.yml b/.github/workflows/loadcheck.yml index 4aa47ac..aee3fdd 100644 --- a/.github/workflows/loadcheck.yml +++ b/.github/workflows/loadcheck.yml @@ -3,8 +3,8 @@ name: "Cog load test" on: push: branches: - - master - - genshin + - main + pull_request: jobs: loadcheck: @@ -19,12 +19,12 @@ jobs: friendly-red: "Red 3.4.18" fail-fast: false - name: Cog load test - Python ${{ matrix.python-version }} & ${{ matrix.friendly-red }} + name: Python ${{ matrix.python-version }} & ${{ matrix.friendly-red }} steps: - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} diff --git a/genshinutils/genshinutils.py b/genshinutils/genshinutils.py index 109d3a8..9d2b34c 100644 --- a/genshinutils/genshinutils.py +++ b/genshinutils/genshinutils.py @@ -1,6 +1,7 @@ import logging from typing import Literal +from discord.ext import tasks from enkanetwork import EnkaNetworkAPI from redbot.core import Config, commands @@ -40,9 +41,11 @@ def __init__(self, bot): self.config.register_global(**default_global) self.config.register_user(**default_user) self.enka_client = enka_client + self.run_tasks.start() def cog_unload(self): - enka_client._close() + log.debug("Cog unload") + self.run_tasks.stop() def format_help_for_context(self, ctx: commands.Context) -> str: """Thanks Sinbad!""" @@ -62,3 +65,11 @@ async def red_delete_data_for_user( async def genshin(self, ctx: commands.Context): """GenshinUtils main command.""" # TODO: Embed explaining what this cog does and its info + + @tasks.loop(hours=24) + async def run_tasks(self): + """Schedule tasks to run based on a set loop""" + + @run_tasks.before_loop + async def before_run_tasks(self): + await self.bot.wait_until_ready() From 40607b7a72589338ec73b15961a2ba6b10e47a51 Mon Sep 17 00:00:00 2001 From: Raiden Date: Thu, 2 Feb 2023 20:56:53 +0800 Subject: [PATCH 36/51] Fix cog requirements --- genshinutils/info.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/genshinutils/info.json b/genshinutils/info.json index 2bf2521..988c49b 100644 --- a/genshinutils/info.json +++ b/genshinutils/info.json @@ -11,10 +11,10 @@ "name": "GenshinUtils", "required_cogs": {}, "requirements": [ - "genshin", - "enkanetwork", + "enkanetwork.py", "aioenkanetworkcard", - "fernet" + "cryptography", + "genshin" ], "short": "A Genshin Impact utility cog", "tags": [ From d59bbe4811b0d85db78bc4593eaf4bc8c799cd8c Mon Sep 17 00:00:00 2001 From: Raiden Date: Thu, 2 Feb 2023 22:27:36 +0800 Subject: [PATCH 37/51] Change checks decorator to commands --- genshinutils/settings.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/genshinutils/settings.py b/genshinutils/settings.py index 96901e2..a23d05f 100644 --- a/genshinutils/settings.py +++ b/genshinutils/settings.py @@ -1,6 +1,6 @@ import logging -from redbot.core import checks, commands +from redbot.core import commands log = logging.getLogger("red.raidensakura.genshinutils") @@ -12,19 +12,19 @@ class GenshinSet(commands.Cog): async def genshinset(self, ctx: commands.Context): """Various global settings for GenshinUtils cog.""" - @checks.is_owner() + @commands.is_owner() @genshinset.command() async def ltoken(self, ctx: commands.Context): """Instructions on how to set global `ltoken` secret.""" await ctx.send(f"Use `{ctx.prefix}set api hoyolab ltoken your_ltoken_here`.") - @checks.is_owner() + @commands.is_owner() @genshinset.command() async def ltuid(self, ctx: commands.Context): """Instructions on how to set global `ltuid` secret.""" await ctx.send(f"Use `{ctx.prefix}set api hoyolab ltuid your_ltuid_here`.") - @checks.is_owner() + @commands.is_owner() @genshinset.command() async def verification(self, ctx: commands.Context, toggle: bool): """ From 45c2c231e4e87ff7a965329e401d4a51bcc64d34 Mon Sep 17 00:00:00 2001 From: Raiden Date: Sun, 5 Feb 2023 21:18:29 +0800 Subject: [PATCH 38/51] Use async in typing --- dev-requirements.txt | 6 ++---- genshinutils/assets/global/cog_icon.png | Bin 9820 -> 0 bytes genshinutils/assets/global/login_check.png | Bin 6664 -> 0 bytes genshinutils/daily.py | 2 +- genshinutils/notes.py | 2 +- genshinutils/profile.py | 8 ++++---- genshinutils/register.py | 2 +- 7 files changed, 9 insertions(+), 11 deletions(-) delete mode 100644 genshinutils/assets/global/cog_icon.png delete mode 100644 genshinutils/assets/global/login_check.png diff --git a/dev-requirements.txt b/dev-requirements.txt index 9674a66..ba90fee 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -2,20 +2,18 @@ # some people may have special setups (eg dev version installed) # Cogs -gidgethub # ghissues, gitub -pillow # genshinutils tabulate # throw +pillow # genshinutils genshin # genshinutils enkanetwork.py # genshinutils aioenkanetworkcard # genshinutils -fernet # genshinutils +cryptography # genshinutils # checking tools pyright black isort flake8 -pytest tox pre-commit diff --git a/genshinutils/assets/global/cog_icon.png b/genshinutils/assets/global/cog_icon.png deleted file mode 100644 index d7487c0e28a8483338186cd0aa5c130aca3948b6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9820 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 z 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 diff --git a/genshinutils/daily.py b/genshinutils/daily.py index ca6a365..0414cf5 100644 --- a/genshinutils/daily.py +++ b/genshinutils/daily.py @@ -63,5 +63,5 @@ async def redeem_daily(): if not cookie: return await ctx.send("No cookie.") - with ctx.typing(): + async with ctx.typing(): return await redeem_daily() diff --git a/genshinutils/notes.py b/genshinutils/notes.py index 91d7892..d894396 100644 --- a/genshinutils/notes.py +++ b/genshinutils/notes.py @@ -105,6 +105,6 @@ async def test_honkai(): if not cookie: return await ctx.send("No cookie.") - with ctx.typing(): + async with ctx.typing(): # return await test_honkai() return await generate_diary(uid) diff --git a/genshinutils/profile.py b/genshinutils/profile.py index 1b52743..72ce4a8 100644 --- a/genshinutils/profile.py +++ b/genshinutils/profile.py @@ -155,7 +155,7 @@ async def genshin_generate_profile(uid): cookie = await get_user_cookie(self.config, ctx.author) - with ctx.typing(): + async with ctx.typing(): if not cookie: return await enka_generate_profile(uid) @@ -169,7 +169,7 @@ async def genshin_generate_profile(uid): if user_or_uid and not character: uid = await validate_uid(user_or_uid, self.config) if uid: - with ctx.typing(): + async with ctx.typing(): return await enka_generate_profile(uid) log.debug(f"[{ctx.command.name}] Not a UID, assuming it's a character name...") @@ -186,7 +186,7 @@ async def genshin_generate_profile(uid): if not uid: return await ctx.send("You do not have a UID linked.") - with ctx.typing(): + async with ctx.typing(): return await enka_generate_char_img(uid, char) """This handles if both [user_or_uid] and [character] are appropriately passed""" @@ -199,5 +199,5 @@ async def genshin_generate_profile(uid): if not char: return await ctx.send("Character name invalid or not in dictionary.") - with ctx.typing(): + async with ctx.typing(): return await enka_generate_char_img(uid, char) diff --git a/genshinutils/register.py b/genshinutils/register.py index 14e8cd6..108e1ad 100644 --- a/genshinutils/register.py +++ b/genshinutils/register.py @@ -54,7 +54,7 @@ def pass_verification(discordtag, signature): return await ctx.send("Invalid UID provided, it must consist of 9 digits.") try: - with ctx.typing(): + async with ctx.typing(): data = await self.enka_client.fetch_user(uid) except Exception as exc: return await ctx.send(f"Unable to retrieve data from enka.network:\n`{exc}`") From c94c8d37c75bc4e42b5f1b12f816369fd1c8f437 Mon Sep 17 00:00:00 2001 From: Raiden Date: Sun, 5 Feb 2023 22:06:08 +0800 Subject: [PATCH 39/51] Add pillow into requirements --- genshinutils/info.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/genshinutils/info.json b/genshinutils/info.json index 988c49b..881422b 100644 --- a/genshinutils/info.json +++ b/genshinutils/info.json @@ -14,7 +14,8 @@ "enkanetwork.py", "aioenkanetworkcard", "cryptography", - "genshin" + "genshin", + "pillow" ], "short": "A Genshin Impact utility cog", "tags": [ From 0c27fcf1997681382d9853f8ca85c74239e5b2fa Mon Sep 17 00:00:00 2001 From: Raiden Date: Thu, 20 Apr 2023 22:32:00 +0800 Subject: [PATCH 40/51] Switch flake8 to ruff --- .flake8 | 4 +-- .github/workflows/loadcheck.yml | 17 ++++++----- .gitignore | 2 ++ .pre-commit-config.yaml | 21 ++----------- dev-requirements.txt | 21 ------------- pyproject.toml | 53 +++++++++++++++++++++++++++++++++ 6 files changed, 69 insertions(+), 49 deletions(-) delete mode 100644 dev-requirements.txt diff --git a/.flake8 b/.flake8 index e480388..d86e497 100644 --- a/.flake8 +++ b/.flake8 @@ -1,7 +1,5 @@ [flake8] # ignore = E302 per-file-ignores = __init__.py:F401, constants.py:E501 -# So flake8 won't whine when my comments are too long -# Black and isort still set at 99 -max-line-length = 110 +max-line-length = 99 exclude = .git, .venv, .tox, __pycache__ diff --git a/.github/workflows/loadcheck.yml b/.github/workflows/loadcheck.yml index aee3fdd..3d188c8 100644 --- a/.github/workflows/loadcheck.yml +++ b/.github/workflows/loadcheck.yml @@ -4,7 +4,6 @@ on: push: branches: - main - pull_request: jobs: loadcheck: @@ -13,13 +12,17 @@ jobs: matrix: python-version: ["3.8", "3.9"] red-version: - - "Red-DiscordBot==3.4.18" + # this workflow required pr #5453 commit d27dbde, which is in dev & pypi 3.4.15+ + - "git+https://github.com/Cog-Creators/Red-DiscordBot@V3/develop#egg=Red-DiscordBot" + - "Red-DiscordBot" include: - - red-version: "Red-DiscordBot==3.4.18" - friendly-red: "Red 3.4.18" + - red-version: "git+https://github.com/Cog-Creators/Red-DiscordBot@V3/develop#egg=Red-DiscordBot" + friendly-red: "Red (Latest)" + - red-version: "Red-DiscordBot" + friendly-red: "Red (Stable)" fail-fast: false - name: Python ${{ matrix.python-version }} & ${{ matrix.friendly-red }} + name: Cog load test - Python ${{ matrix.python-version }} & ${{ matrix.friendly-red }} steps: - uses: actions/checkout@v3 @@ -35,7 +38,7 @@ jobs: path: .venv key: ${{ matrix.red-version }}-${{ matrix.python-version }}-${{ hashFiles('dev-requirements.txt') }}-${{ secrets.CACHE_V }} - - name: Make venv if not cached + - name: Maybe make venv if: steps.cache-venv.outputs.cache-hit != 'true' run: | python3 -m venv .venv @@ -43,7 +46,7 @@ jobs: pip install --upgrade pip pip install setuptools wheel pip install ${{ matrix.red-version }} - pip install -r dev-requirements.txt + pip install .[dev] pip install jsonrpc-websocket - name: Prepare files run: | diff --git a/.gitignore b/.gitignore index d161acc..71ce0f6 100644 --- a/.gitignore +++ b/.gitignore @@ -153,3 +153,5 @@ cython_debug/ # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ + +.ruff_cache diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 347f5e2..3cfc03b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -12,22 +12,7 @@ repos: rev: "22.12.0" hooks: - id: black - - repo: https://github.com/PyCQA/isort - rev: "5.12.0" + - repo: https://github.com/charliermarsh/ruff-pre-commit + rev: 'v0.0.261' hooks: - - id: isort - - repo: https://github.com/myint/autoflake - rev: "v2.0.0" - hooks: - - id: autoflake - args: - [ - "--in-place", - "--remove-unused-variables", - "--remove-all-unused-imports", - "--ignore-init-module-imports", - ] - - repo: https://github.com/pycqa/flake8 - rev: "6.0.0" - hooks: - - id: flake8 + - id: ruff diff --git a/dev-requirements.txt b/dev-requirements.txt deleted file mode 100644 index ba90fee..0000000 --- a/dev-requirements.txt +++ /dev/null @@ -1,21 +0,0 @@ -# Red itself is also a requirement, however it is not listed here because -# some people may have special setups (eg dev version installed) - -# Cogs -tabulate # throw -pillow # genshinutils -genshin # genshinutils -enkanetwork.py # genshinutils -aioenkanetworkcard # genshinutils -cryptography # genshinutils - -# checking tools -pyright -black -isort -flake8 -tox -pre-commit - -# checks -python-dotenv diff --git a/pyproject.toml b/pyproject.toml index 41ea6ae..019589e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,47 @@ +[project] +name = "raiden-cogs" +version = "1.0.0" +description = "Cogs for Red bot made by raidensakura" +readme = "README.md" +authors = [ + {name = "raidensakura"}, +] +license = {file = "LICENSE"} +requires-python = ">=3.8" + +[project.urls] +"Issue Tracker" = "https://github.com/raidensakura/raiden-cogs/issues" +"Source Code" = "https://github.com/raidensakura/raiden-cogs" + +[options] +requires = [ + # throw + "tabulate", + # genshinutils + "pillow", + "genshin", + "enkanetwork.py", + "aioenkanetworkcard", + "cryptography", +] + +[project.optional-dependencies] +dev = [ + "black", + "ruff", + "tox", + "pre-commit", + "python-dotenv" +] + +[tool.setuptools] +py-modules = [ + "choose", + "genshinutils", + "longcat", + "throw" +] + [tool.black] line-length = 99 target-version = ["py38"] @@ -6,3 +50,12 @@ extend-exclude = ".stubs" [tool.isort] profile = "black" line_length = 99 + +[tool.ruff] +target-version = "py38" +line-length = 99 +select = ["C90", "E", "F", "I001", "PGH004", "RUF100"] +fix = true +fixable = ["I001"] +isort.combine-as-imports = true +force-exclude = true From b9e1f2264f02abaec69a3ca4ddbbebf0e675af7b Mon Sep 17 00:00:00 2001 From: Raiden Date: Thu, 20 Apr 2023 22:33:40 +0800 Subject: [PATCH 41/51] Include branch in load test --- .github/workflows/loadcheck.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/loadcheck.yml b/.github/workflows/loadcheck.yml index 3d188c8..7c5d7fb 100644 --- a/.github/workflows/loadcheck.yml +++ b/.github/workflows/loadcheck.yml @@ -4,6 +4,7 @@ on: push: branches: - main + - genshin jobs: loadcheck: From 422e57ef5c97e74f2468b16ccb1798aca0366634 Mon Sep 17 00:00:00 2001 From: Raiden Date: Thu, 20 Apr 2023 22:40:04 +0800 Subject: [PATCH 42/51] Attempt fix loadcheck failing --- .github/workflows/loadcheck.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/loadcheck.yml b/.github/workflows/loadcheck.yml index 7c5d7fb..257e599 100644 --- a/.github/workflows/loadcheck.yml +++ b/.github/workflows/loadcheck.yml @@ -47,7 +47,7 @@ jobs: pip install --upgrade pip pip install setuptools wheel pip install ${{ matrix.red-version }} - pip install .[dev] + pip install . pip install jsonrpc-websocket - name: Prepare files run: | From 0a90012e6813a92e2a13c59d155b76564e811bbf Mon Sep 17 00:00:00 2001 From: Raiden Date: Thu, 20 Apr 2023 23:02:31 +0800 Subject: [PATCH 43/51] Fix deps installation --- pyproject.toml | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 019589e..5db5db2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,23 +8,19 @@ authors = [ ] license = {file = "LICENSE"} requires-python = ">=3.8" +dependencies = [ + "tabulate", + "Pillow", + "genshin", + "enkanetwork.py", + "aioenkanetworkcard", + "cryptography" +] [project.urls] "Issue Tracker" = "https://github.com/raidensakura/raiden-cogs/issues" "Source Code" = "https://github.com/raidensakura/raiden-cogs" -[options] -requires = [ - # throw - "tabulate", - # genshinutils - "pillow", - "genshin", - "enkanetwork.py", - "aioenkanetworkcard", - "cryptography", -] - [project.optional-dependencies] dev = [ "black", From 0b5b136e39cc2267226a90c673f137ef83c46a65 Mon Sep 17 00:00:00 2001 From: Raiden Date: Thu, 20 Apr 2023 23:17:29 +0800 Subject: [PATCH 44/51] Attempt number 2 --- pyproject.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 5db5db2..50520a5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,7 +14,8 @@ dependencies = [ "genshin", "enkanetwork.py", "aioenkanetworkcard", - "cryptography" + "cryptography", + "python-dotenv" ] [project.urls] @@ -26,8 +27,7 @@ dev = [ "black", "ruff", "tox", - "pre-commit", - "python-dotenv" + "pre-commit" ] [tool.setuptools] From 91c3efcba815b7a93de3a51ae5c1fb5433159c2d Mon Sep 17 00:00:00 2001 From: Raiden Date: Thu, 20 Apr 2023 23:40:47 +0800 Subject: [PATCH 45/51] Update README.md --- README.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 6b3225b..398deff 100644 --- a/README.md +++ b/README.md @@ -56,18 +56,17 @@

Dev Stuff

Formatting

-

For manual formatting, this repo uses these three:

+

For manual formatting, this repo uses these:

```py -pip install -U black isort flake8 -black . ; isort . ; flake8 . +pip install .[dev] +black . ; ruff . ```

Pre-commit hooks

Optional but it keeps manual formatting work away from you.

```py -pip install pre-commit pre-commit install ``` From 707535769ea95b49503ab895db23cb029ff27853 Mon Sep 17 00:00:00 2001 From: Raiden Date: Fri, 21 Apr 2023 01:49:16 +0800 Subject: [PATCH 46/51] Implement ABC class --- genshinutils/abc.py | 14 ++++++++++++++ genshinutils/genshinutils.py | 12 +++++++++++- 2 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 genshinutils/abc.py diff --git a/genshinutils/abc.py b/genshinutils/abc.py new file mode 100644 index 0000000..85cbd5d --- /dev/null +++ b/genshinutils/abc.py @@ -0,0 +1,14 @@ +from abc import ABC + +from redbot.core import Config +from redbot.core.bot import Red + + +class MixinMeta(ABC): + """Base class for well behaved type hint detection with composite class. + Basically, to keep developers sane when not all attributes are defined in each mixin. + """ + + def __init__(self, *_args): + self.config: Config + self.bot: Red diff --git a/genshinutils/genshinutils.py b/genshinutils/genshinutils.py index 9d2b34c..f218ebd 100644 --- a/genshinutils/genshinutils.py +++ b/genshinutils/genshinutils.py @@ -1,4 +1,5 @@ import logging +from abc import ABC from typing import Literal from discord.ext import tasks @@ -16,6 +17,11 @@ log = logging.getLogger("red.raidensakura.genshinutils") +class CompositeMetaClass(type(commands.Cog), type(ABC)): + """This allows the metaclass used for proper type detection to coexist with discord.py's + metaclass.""" + + class GenshinUtils( GenshinSet, GenshinRegister, @@ -23,6 +29,7 @@ class GenshinUtils( GenshinNotes, GenshinDaily, commands.Cog, + metaclass=CompositeMetaClass, ): """GenshinUtils commands.""" @@ -51,7 +58,10 @@ def format_help_for_context(self, ctx: commands.Context) -> str: """Thanks Sinbad!""" pre_processed = super().format_help_for_context(ctx) s = "s" if len(self.__author__) > 1 else "" - return f"{pre_processed}\n\nAuthor{s}: {', '.join(self.__author__)}\nCog Version: {self.__version__}" + return ( + f"{pre_processed}\n\nAuthor{s}: {', '.join(self.__author__)}" + "\nCog Version: {self.__version__}" + ) async def red_delete_data_for_user( self, From 22e0f744ac388e9afbbd68d0d357fee6738242e0 Mon Sep 17 00:00:00 2001 From: Raiden Date: Fri, 21 Apr 2023 14:21:30 +0800 Subject: [PATCH 47/51] Shorten long lines --- genshinutils/profile.py | 9 +++---- genshinutils/register.py | 53 ++++++++++++++++++---------------------- genshinutils/utils.py | 6 ++--- longcat/longcat.py | 5 +++- pyproject.toml | 4 +++ throw/throw.py | 5 +++- 6 files changed, 43 insertions(+), 39 deletions(-) diff --git a/genshinutils/profile.py b/genshinutils/profile.py index 72ce4a8..888fbe6 100644 --- a/genshinutils/profile.py +++ b/genshinutils/profile.py @@ -81,14 +81,13 @@ async def enka_generate_profile(uid): async def enka_generate_char_img(uid, char_name): with io.BytesIO() as image_binary: - char_card = await enka_get_character_card(uid, char_name) + char_card = await enka_get_character_card(uid) if not char_card: - return await ctx.send("This user does not have that character featured.") + return await ctx.send("This user does not have any character featured.") temp_filename = str(time.time()).split(".")[0] + ".png" log.debug(f"[generate_char_info] Pillow object for character card:\n{char_card}") - first_card = next(iter(char_card.values())) - card_object = next(iter(first_card.values())) - card_object.save(image_binary, "PNG", optimize=True, quality=95) + first_card = char_card.get("card").get("1-4") + first_card.save(image_binary, "PNG", optimize=True, quality=95) image_binary.seek(0) return await ctx.send(file=discord.File(fp=image_binary, filename=temp_filename)) diff --git a/genshinutils/register.py b/genshinutils/register.py index 108e1ad..4598831 100644 --- a/genshinutils/register.py +++ b/genshinutils/register.py @@ -67,7 +67,7 @@ def pass_verification(discordtag, signature): return await ctx.send( ( "Your signature does not contain your Discord tag.\n" - "Note that it may take up to 15 minutes for changes to be reflected." + "It may take up to 15 minutes for changes to be reflected." ) ) await self.config.user(ctx.author).UID.set(uid) @@ -75,12 +75,14 @@ def pass_verification(discordtag, signature): """ Important Notes: - 1. Command has proprietary DM check since I want it to preface a a disclaimer when run in a server. - This command deals with sensitive information and I want it to be taken very seriously. - 2. I fully acknowledge storing the encryption key along with the encrypted data itself is bad practice. - Hoyolab account token can be used to perform potentially dangerous account actions. - Since the cog is OSS, the purpose is to prevent bot owners from having plaintext access to them - in a way such that is require a bit of coding and encryption knowledge to access them on demand. + 1. This has proprietary DM check since to preface a disclaimer. + 2. I fully acknowledge storing the encryption key along + with the encrypted data itself is bad practice. + Hoyolab account token can be used to performpotentially + dangerous account actions. Since the cog is OSS, the purpose is + to prevent bot owners from having plaintext access to them + in a way such that is require a bit of coding and encryption + knowledge to access them on demand. """ @register.command() @@ -90,7 +92,6 @@ async def hoyolab(self, ctx: commands.Context, *, cookie: str = None): """Link or unlink a Hoyolab account token to your Discord account.""" if not isinstance(ctx.channel, DMChannel): - if cookie: try: await ctx.message.delete() @@ -105,11 +106,10 @@ async def hoyolab(self, ctx: commands.Context, *, cookie: str = None): owner = app_info.owner desc = ( "This command links your Hoyolab account token to your Discord account. " - "Your account token allow the bot to perform various account actions on your behalf, " + "This allow the bot to perform various account actions on your behalf, " "such as claiming daily login, fetching character data etc. " - "Your token will be stored in the bot's config, and bot owner will always have access to it. " - "Make sure **you fully understand the risk of sharing your token online** before proceeding." - "\n\nFor security reason, please run this command in a DM channel when setting token." + "Make sure you understand the risk of sharing your token online before proceeding." + "\n\nPlease run this command in a DM channel when setting token." "\n\nRead on how to obtain your token [here](https://project-mei.xyz/genshinutils)." ) e = generate_embed( @@ -133,7 +133,8 @@ async def hoyolab(self, ctx: commands.Context, *, cookie: str = None): msg = ( f"**Provide a valid cookie to bind your Discord account to.**\n\n" f"` » ` Instruction on how to obtain your Hoyolab cookie:\n<{cog_url}>\n\n" - f"` » ` For command help context: `{bot_prefix}help genshin register {command_name}`\n\n" + f"` » ` For command help context: " + f"`{bot_prefix}help genshin register {command_name}`\n\n" f"` » ` To read disclaimers, type this command again in any server." ) return await ctx.send(msg) @@ -186,8 +187,17 @@ async def hoyolab(self, ctx: commands.Context, *, cookie: str = None): await self.config.user(ctx.author).ltuid.set(encoded_ltuid) await self.config.user(ctx.author).ltoken.set(encoded_ltoken) + # Debugging stuff + log.debug(f"Encrypted credentials saved for {ctx.author}") + + decoded_ltuid = await decrypt_config(self.config, encoded_ltuid) + + log.debug(f"Decoded ltuid for {ctx.author}: {decoded_ltuid}") + # Send success embed - desc = "Successfully bound a Genshin Impact account to your Discord account. Details are as follow." + desc = ( + "Successfully bound Genshin account to your Discord account. " "Details are as follow." + ) e = Embed( color=(await ctx.embed_colour()), title="Account Binding Success", @@ -201,18 +211,3 @@ async def hoyolab(self, ctx: commands.Context, *, cookie: str = None): e.set_thumbnail(url=ctx.message.author.avatar_url) return await ctx.send(embed=e) - - # Debugging stuff - log.debug( - f"[Register Hoyolab] Encrypted ltuid saved: {await self.config.user(ctx.author).ltuid()}" - ) - log.debug( - f"[Register Hoyolab] Encrypted ltoken saved: {await self.config.user(ctx.author).ltoken()}" - ) - log.debug(f"[Register Hoyolab] Encryption key saved: {await self.config.encryption_key()}") - - decoded_ltuid = await decrypt_config(self.config, encoded_ltuid) - decoded_ltoken = await decrypt_config(self.config, encoded_ltoken) - - log.debug(f"[Register Hoyolab] Decoded ltuid: {decoded_ltuid}") - log.debug(f"[Register Hoyolab] Decoded ltoken: {decoded_ltoken}") diff --git a/genshinutils/utils.py b/genshinutils/utils.py index ab2be09..3d50abe 100644 --- a/genshinutils/utils.py +++ b/genshinutils/utils.py @@ -92,16 +92,16 @@ def validate_char_name(arg): return str(formal_name).strip("{'\"}") -async def enka_get_character_card(uid, char_name): +async def enka_get_character_card(uid, char_name=None): """Generate one or more character build image objects in a dict :param str uid: UID of the player :param str char_name: formal name of the character :return dict: dict containing Pillow image object for the character """ - async with encbanner.ENC(lang="en", splashArt=True, characterName=char_name) as encard: + async with encbanner.ENC(lang="en", splashArt=True, miniInfo=True) as encard: ENCpy = await encard.enc(uids=uid) - return await encard.creat(ENCpy, 2) + return await encard.creat(ENCpy, 4) async def get_user_cookie(config, user): diff --git a/longcat/longcat.py b/longcat/longcat.py index ee8b9da..7c50045 100644 --- a/longcat/longcat.py +++ b/longcat/longcat.py @@ -27,7 +27,10 @@ def format_help_for_context(self, ctx: commands.Context) -> str: """ pre_processed = super().format_help_for_context(ctx) s = "s" if len(self.__author__) > 1 else "" - return f"{pre_processed}\n\nAuthor{s}: {', '.join(self.__author__)}\nCog Version: {self.__version__}" + return ( + f"{pre_processed}\n\nAuthor{s}: {', '.join(self.__author__)}" + f"\nCog Version: {self.__version__}" + ) async def red_delete_data_for_user(self, **kwargs) -> None: """Nothing to delete""" diff --git a/pyproject.toml b/pyproject.toml index 50520a5..49547df 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,3 +55,7 @@ fix = true fixable = ["I001"] isort.combine-as-imports = true force-exclude = true + +[tool.ruff.mccabe] +# Unlike Flake8, default to a complexity level of 10. +max-complexity = 25 diff --git a/throw/throw.py b/throw/throw.py index 17146e8..8ae226d 100644 --- a/throw/throw.py +++ b/throw/throw.py @@ -33,7 +33,10 @@ def format_help_for_context(self, ctx: commands.Context) -> str: """ pre_processed = super().format_help_for_context(ctx) s = "s" if len(self.__author__) > 1 else "" - return f"{pre_processed}\n\nAuthor{s}: {', '.join(self.__author__)}\nCog Version: {self.__version__}" + return ( + f"{pre_processed}\n\nAuthor{s}: {', '.join(self.__author__)}" + f"\nCog Version: {self.__version__}" + ) # TODO: Delete user throw stats async def red_delete_data_for_user(self, **kwargs): From 6996b5c26ce56ce272b4cac0cbe252eb9ef58060 Mon Sep 17 00:00:00 2001 From: Raiden Date: Fri, 21 Apr 2023 14:27:54 +0800 Subject: [PATCH 48/51] pls flake8 --- genshinutils/register.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/genshinutils/register.py b/genshinutils/register.py index 4598831..d5f834b 100644 --- a/genshinutils/register.py +++ b/genshinutils/register.py @@ -76,10 +76,10 @@ def pass_verification(discordtag, signature): """ Important Notes: 1. This has proprietary DM check since to preface a disclaimer. - 2. I fully acknowledge storing the encryption key along + 2. I fully acknowledge storing the encryption key along with the encrypted data itself is bad practice. Hoyolab account token can be used to performpotentially - dangerous account actions. Since the cog is OSS, the purpose is + dangerous account actions. Since the cog is OSS, the purpose is to prevent bot owners from having plaintext access to them in a way such that is require a bit of coding and encryption knowledge to access them on demand. @@ -110,7 +110,7 @@ async def hoyolab(self, ctx: commands.Context, *, cookie: str = None): "such as claiming daily login, fetching character data etc. " "Make sure you understand the risk of sharing your token online before proceeding." "\n\nPlease run this command in a DM channel when setting token." - "\n\nRead on how to obtain your token [here](https://project-mei.xyz/genshinutils)." + "\n\nRead how to obtain your token [here](https://project-mei.xyz/genshinutils)." ) e = generate_embed( title="Important Disclaimer", desc=desc, color=await ctx.embed_color() From 54b81329184b5fcd1a174c42663ca4e37bcd1cbb Mon Sep 17 00:00:00 2001 From: Raiden Sakura Date: Thu, 29 Jun 2023 00:52:46 +0800 Subject: [PATCH 49/51] Update workflow --- .github/workflows/checks.yml | 12 +++--------- .github/workflows/loadcheck.yml | 6 +----- tox.ini | 19 ++++++------------- 3 files changed, 10 insertions(+), 27 deletions(-) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index c8de40f..2a09d39 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -2,8 +2,6 @@ name: Checks on: push: - branches: - - main pull_request: # thanks red or wherever you got it from @@ -14,18 +12,14 @@ jobs: matrix: python_version: - "3.8" - - "3.9" tox_env: - style-black - - style-isort - - lint-flake8 + - style-ruff include: - tox_env: style-black friendly_name: Style (black) - - tox_env: style-isort - friendly_name: Style (isort) - - tox_env: lint-flake8 - friendly_name: Lint (flake8) + - tox_env: style-ruff + friendly_name: Style (ruff) fail-fast: false diff --git a/.github/workflows/loadcheck.yml b/.github/workflows/loadcheck.yml index 257e599..7abe9c5 100644 --- a/.github/workflows/loadcheck.yml +++ b/.github/workflows/loadcheck.yml @@ -2,18 +2,14 @@ name: "Cog load test" on: push: - branches: - - main - - genshin jobs: loadcheck: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.8", "3.9"] + python-version: ["3.8", "3.9", "3.10", "3.11"] red-version: - # this workflow required pr #5453 commit d27dbde, which is in dev & pypi 3.4.15+ - "git+https://github.com/Cog-Creators/Red-DiscordBot@V3/develop#egg=Red-DiscordBot" - "Red-DiscordBot" include: diff --git a/tox.ini b/tox.ini index cb47ac0..9d53708 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py38, style-black, style-isort, lint-flake8 +envlist = py38, style-black, style-ruff skipsdist = true [testenv] @@ -7,10 +7,9 @@ description = Run style and static type checking. deps = # style black - isort + ruff # lint - flake8 gidgethub wakeonlan @@ -25,7 +24,7 @@ deps = pyjson5 expr.py - red-discordbot==3.4.18 + red-discordbot # type # (some are covered under below) @@ -38,14 +37,8 @@ envdir = {toxworkdir}/py38 commands = black --check . -[testenv:style-isort] -description = Check imports conform with isort. +[testenv:style-ruff] +description = Check style conform with ruff. envdir = {toxworkdir}/py38 -commands = isort --check . - -[testenv:lint-flake8] -description = Lint with flake8. -envdir = {toxworkdir}/py38 - -commands = flake8 . +commands = ruff check . From 15c55be9690d6fad274950e3ccd54bc9f70d2eb0 Mon Sep 17 00:00:00 2001 From: Raiden Date: Wed, 12 Jul 2023 00:55:09 +0800 Subject: [PATCH 50/51] Update README.md --- genshinutils/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/genshinutils/README.md b/genshinutils/README.md index 3be701e..0d76f40 100644 --- a/genshinutils/README.md +++ b/genshinutils/README.md @@ -5,7 +5,7 @@
- +

GenshinUtils - Multipurpose Genshin Impact cog. For now, it's able to display in-game profile information and featured character build cards.

From a16c482f43f5c475ef8d343cd26459f12f2d04e8 Mon Sep 17 00:00:00 2001 From: Raiden Date: Wed, 12 Jul 2023 01:01:21 +0800 Subject: [PATCH 51/51] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 398deff..fafe013 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ -

+


- +

A collection of badly written homemade cogs I made for fun in the process of learning Python.