diff --git a/.env-template b/.env-template new file mode 100755 index 0000000..d57c065 --- /dev/null +++ b/.env-template @@ -0,0 +1,10 @@ +TOKEN= +TEST_TOKEN= +ANILIST_ID= +ANILIST_TOKEN= +MONGO_SRV= +INVITE=https://discord.com/api/oauth2/authorize?client_id=991739924250362047&permissions=414464732224&scope=bot%20applications.commands +SECRET_KEY= +SPOTIFY_CLIENT_ID= +SPOTIFY_CLIENT_SECRET= +YOUTUBE_API_KEY= diff --git a/Procfile b/Procfile deleted file mode 100644 index c445cb1..0000000 --- a/Procfile +++ /dev/null @@ -1 +0,0 @@ -worker: python3 main.py \ No newline at end of file diff --git a/cogs/logger.py b/cogs/logger.py index 5738604..ccdb0f8 100644 --- a/cogs/logger.py +++ b/cogs/logger.py @@ -13,9 +13,6 @@ class Logger(commands.Cog): - bot: commands.Bot = None - server_log_channel: TextChannel = None - general_chat_channel_names = [ "general", "chat", @@ -60,8 +57,10 @@ async def on_guild_join(self, guild: Guild): if channel == guild.system_channel or any( x in channel.name for x in self.general_chat_channel_names ): - await channel.send(embed=self.welcome_embd) - break + if isinstance(channel, TextChannel): + if self.welcome_embd is not None: + await channel.send(embed=self.welcome_embd) + break else: logging.error(f"Can't send messages in # {channel.name}") continue diff --git a/cogs/search.py b/cogs/search.py index ca46fdc..8ebffb9 100644 --- a/cogs/search.py +++ b/cogs/search.py @@ -11,57 +11,88 @@ class SearchModule(commands.Cog): """Find Group""" - @commands.group(name="search", aliases=["find"], description="Commands for searching Anime and Manga") + @commands.group( + name="search", + aliases=["find"], + description="Commands for searching Anime and Manga", + ) @general_helper.short_cooldown() async def search_group(self, ctx: commands.Context): if ctx.subcommand_passed is None: - return await ctx.reply(f"Please provide a valid subcommand! {config.YUI_SHY_EMOTE}") + return await ctx.reply( + f"Please provide a valid subcommand! {config.YUI_SHY_EMOTE}" + ) """Anime Search""" - @search_group.command(name="anime", description="Returns the anime details with provided name") + @search_group.command( + name="anime", description="Returns the anime details with provided name" + ) @general_helper.with_typing_ctx() async def anime_details(self, ctx: commands.Context, *name): name = " ".join(name) result = await search_helper.get_anime_details_embed(name, ctx.author) + if result.get("error", False) is True: + return await ctx.send( + embed=result.get("embeds", {}).get("error", Embed(title="ERROR")) + ) + info_view = media_info_view.MediaInfoView(result["embeds"]) if result.get("isAdult") is False: return await ctx.send(embed=result["embeds"]["details"], view=info_view) async def proceed_callback(interaction: Interaction): - return await interaction.followup.send(embed=result["embeds"]["details"], ephemeral=True, view=info_view) + return await interaction.followup.send( + embed=result["embeds"]["details"], ephemeral=True, view=info_view + ) - confirmation_embd = Embed(title="Watch Out!", description="This media entry is marked as **18+**.") + confirmation_embd = Embed( + title="Watch Out!", description="This media entry is marked as **18+**." + ) confirmation_view = warning_view.WarningView(proceed_callback) await ctx.send(embed=confirmation_embd, view=confirmation_view) """Manga Search""" - @search_group.command(name="manga", description="Returns the manga details with provide name") + @search_group.command( + name="manga", description="Returns the manga details with provide name" + ) @general_helper.with_typing_ctx() async def manga_details(self, ctx: commands.Context, *name): name = " ".join(name) result = await search_helper.get_manga_details_embed(name, ctx.author) + if result.get("error", False) is True: + return await ctx.send( + embed=result.get("embeds", {}).get("error", Embed(title="ERROR")) + ) if result.get("isAdult") is False: return await ctx.send(embed=result["embed"]) async def proceed_callback(interaction: Interaction): - return await interaction.followup.send(embed=result["embed"], ephemeral=True) + return await interaction.followup.send( + embed=result["embed"], ephemeral=True + ) - confirmation_embd = Embed(title="Watch Out!", description="This media entry is marked as **18+**.") + confirmation_embd = Embed( + title="Watch Out!", description="This media entry is marked as **18+**." + ) confirmation_view = warning_view.WarningView(proceed_callback) await ctx.send(embed=confirmation_embd, view=confirmation_view) """Character Search""" - @search_group.command(name="character", aliases=["char"], description="Returns the character details for the character with provided name") + @search_group.command( + name="character", + aliases=["char"], + description="Returns the character details for the character with provided name", + ) @general_helper.with_typing_ctx() async def character_details(self, ctx: commands.Context, *name): name = " ".join(name) @@ -72,7 +103,9 @@ async def character_details(self, ctx: commands.Context, *name): """Studio Search""" - @search_group.command(name="studio", description="Returns the studio details with provided name") + @search_group.command( + name="studio", description="Returns the studio details with provided name" + ) @general_helper.with_typing_ctx() async def studio_details(self, ctx: commands.Context, *name): name = " ".join(name) @@ -83,7 +116,11 @@ async def studio_details(self, ctx: commands.Context, *name): """Top ANIME By Genre""" - @search_group.command(name="topanime", aliases=["ta"], description="Returns the top anime from a particular genre") + @search_group.command( + name="topanime", + aliases=["ta"], + description="Returns the top anime from a particular genre", + ) @general_helper.with_typing_ctx() async def top_genre_anime(self, ctx: commands.Context, *genres): for genre in genres: @@ -96,7 +133,11 @@ async def top_genre_anime(self, ctx: commands.Context, *genres): """Top MANGA by genre""" - @search_group.command(name="topmanga", aliases=["tm"], description="Returns the top manga from a particular genre") + @search_group.command( + name="topmanga", + aliases=["tm"], + description="Returns the top manga from a particular genre", + ) @general_helper.with_typing_ctx() async def top_genre_manga(self, ctx: commands.Context, *genres): for genre in genres: diff --git a/helpers/search_helper.py b/helpers/search_helper.py index 6f4f50c..035ea2c 100644 --- a/helpers/search_helper.py +++ b/helpers/search_helper.py @@ -1,12 +1,22 @@ +from os import error from discord import Embed, Member import enum +import pdb import requests from views.scroller import Scroller from managers import mongo_manager from helpers import general_helper -from queries.search_queries import * # noqa: F403 +from queries.search_queries import ( + anime_query_with_stats, + anime_query_without_stats, + manga_query_with_stats, + manga_query_without_stats, + character_query_with_stats, + character_query_without_stats, + top_genre_query, +) from queries.studio_queries import studio_query import config @@ -18,7 +28,13 @@ class MediaType(enum.Enum): async def get_error_embed(data_raw: dict) -> Embed: errors = [error["message"] for error in data_raw["errors"]] - return await general_helper.get_information_embed(title="Error Occurred!", color=config.ERROR_COLOR, description="{}{}".format(config.BULLET_EMOTE, "\n{}".format(config.BULLET_EMOTE).join(errors))) + return await general_helper.get_information_embed( + title="Error Occurred!", + color=config.ERROR_COLOR, + description="{}{}".format( + config.BULLET_EMOTE, "\n{}".format(config.BULLET_EMOTE).join(errors) + ), + ) async def get_query_from_media_type_and_user(type, anilist_user): @@ -41,9 +57,15 @@ async def get_media_details(name: str, type: MediaType, user: Member) -> dict: query = await get_query_from_media_type_and_user(type, anilist_user) try: - resp = requests.post(url=config.ANILIST_BASE, json={"query": query, "variables": {"search": name}}, headers={"Authorization": anilist_user["token"]}) + resp = requests.post( + url=config.ANILIST_BASE, + json={"query": query, "variables": {"search": name}}, + ) except Exception: - resp = requests.post(url=config.ANILIST_BASE, json={"query": query, "variables": {"search": name}}) + resp = requests.post( + url=config.ANILIST_BASE, + json={"query": query, "variables": {"search": name}}, + ) return resp.json() @@ -54,9 +76,15 @@ async def get_character_details(name: str, user: Member) -> dict: variables = {"search": name} if anilist_user is None: - resp = requests.post(config.ANILIST_BASE, json={"query": character_query_without_stats, "variables": variables}) + resp = requests.post( + config.ANILIST_BASE, + json={"query": character_query_without_stats, "variables": variables}, + ) else: - resp = requests.post(config.ANILIST_BASE, json={"query": character_query_with_stats, "variables": variables}, headers={"Authorization": anilist_user["token"]}) + resp = requests.post( + config.ANILIST_BASE, + json={"query": character_query_with_stats, "variables": variables}, + ) return resp.json() @@ -64,15 +92,22 @@ async def get_character_details(name: str, user: Member) -> dict: async def get_studio_details(name: str) -> dict: variables = {"search": name} - resp = requests.post(config.ANILIST_BASE, json={"query": studio_query, "variables": variables}) + resp = requests.post( + config.ANILIST_BASE, json={"query": studio_query, "variables": variables} + ) return resp.json() async def get_details_embd(data, title): - details_embd: Embed = Embed(title="DETAILS - " + title, color=config.NORMAL_COLOR, url=data["siteUrl"]) + details_embd: Embed = Embed( + title="DETAILS - " + title, color=config.NORMAL_COLOR, url=data["siteUrl"] + ) - details_embd.description = data["description"][:300] + "... [read more]({})".format(data["siteUrl"]) + desc = data.get("description", "") or "" + details_embd.description = desc[:300] + "... [read more]({})".format( + data["siteUrl"] + ) details_embd.set_thumbnail(url=data["coverImage"]["large"]) @@ -84,46 +119,102 @@ async def get_details_embd(data, title): details_embd.add_field(name="Genres", value=genres or "None", inline=True) startDate = [str(x) for x in list(data["startDate"].values())] - details_embd.add_field(name="Start Date", value="\n".join(startDate) or "None", inline=True) + details_embd.add_field( + name="Start Date", value="\n".join(startDate) or "None", inline=True + ) - details_embd.add_field(name="Average Score", value=str(data["averageScore"]) or "None", inline=True) + details_embd.add_field( + name="Average Score", value=str(data["averageScore"]) or "None", inline=True + ) - details_embd.add_field(name="Mean Score", value=str(data["meanScore"]) or "None", inline=True) + details_embd.add_field( + name="Mean Score", value=str(data["meanScore"]) or "None", inline=True + ) - details_embd.add_field(name="Favourites", value=str(data["favourites"]) or "None", inline=True) + details_embd.add_field( + name="Favourites", value=str(data["favourites"]) or "None", inline=True + ) details_embd.add_field(name="Episodes", value=str(data["episodes"]), inline=True) - details_embd.add_field(name="Duration", value=str(data["duration"]) or "None", inline=True) + details_embd.add_field( + name="Duration", value=str(data["duration"]) or "None", inline=True + ) details_embd.add_field(name="Status", value=data["status"] or "None", inline=True) details_embd.add_field(name="Format", value=data["format"] or "None", inline=True) - studios = data["studios"]["nodes"] if len(data["studios"]["nodes"]) <= 3 else data["studios"]["nodes"][:3] - studios_str = "\n".join(["[{name}]({url})".format(name=studio["name"], url=studio["siteUrl"] if studio["siteUrl"] is not None else "") for studio in studios]) + studios = ( + data["studios"]["nodes"] + if len(data["studios"]["nodes"]) <= 3 + else data["studios"]["nodes"][:3] + ) + studios_str = "\n".join( + [ + "[{name}]({url})".format( + name=studio["name"], + url=studio["siteUrl"] if studio["siteUrl"] is not None else "", + ) + for studio in studios + ] + ) details_embd.add_field(name="Studios", value=studios_str or "None", inline=True) - trailer_link = "[click here](https://www.youtube.com/watch?v={})".format(data["trailer"]["id"]) if data["trailer"] is not None and data["trailer"]["site"] == "youtube" else "Not Available" + trailer_link = ( + "[click here](https://www.youtube.com/watch?v={})".format(data["trailer"]["id"]) + if data["trailer"] is not None and data["trailer"]["site"] == "youtube" + else "Not Available" + ) details_embd.add_field(name="Trailer", value=trailer_link, inline=True) # Footer try: isFav = "🔘FAV : Yes" if data["isFavourite"] is True else "" - status = "STATUS : " + data["mediaListEntry"]["status"] if data["mediaListEntry"] is not None else "" - score = ("🔘SCORE : " + str(data["mediaListEntry"]["score"]) if data["mediaListEntry"]["score"] != 0 else "") if data["mediaListEntry"] is not None else "" - progress = "🔘PROGRESS : " + str(data["mediaListEntry"]["progress"] if data["mediaListEntry"] is not None else "") - total = "/" + str(data["mediaListEntry"]["media"]["episodes"] if data["mediaListEntry"] is not None else "") + status = ( + "STATUS : " + data["mediaListEntry"]["status"] + if data["mediaListEntry"] is not None + else "" + ) + score = ( + ( + "🔘SCORE : " + str(data["mediaListEntry"]["score"]) + if data["mediaListEntry"]["score"] != 0 + else "" + ) + if data["mediaListEntry"] is not None + else "" + ) + progress = "🔘PROGRESS : " + str( + data["mediaListEntry"]["progress"] + if data["mediaListEntry"] is not None + else "" + ) + total = "/" + str( + data["mediaListEntry"]["media"]["episodes"] + if data["mediaListEntry"] is not None + else "" + ) if not (isFav == "" and status == ""): - details_embd.set_footer(text="{status}{fav}{score}{progress}{total}".format(status=status, fav=isFav, score=score, progress=progress, total=total)) + details_embd.set_footer( + text="{status}{fav}{score}{progress}{total}".format( + status=status, + fav=isFav, + score=score, + progress=progress, + total=total, + ) + ) except Exception: pass return details_embd async def get_tags_embd(data, title, isAdultAllowed: bool = False) -> Embed: - tags_embd: Embed = Embed(title="TAGS - " + title, color=config.NORMAL_COLOR, url=data["siteUrl"]) + tags_embd: Embed = Embed( + title="TAGS - " + title, color=config.NORMAL_COLOR, url=data["siteUrl"] + ) tags_embd.set_thumbnail(url=data["coverImage"]["large"]) @@ -137,37 +228,59 @@ async def get_tags_embd(data, title, isAdultAllowed: bool = False) -> Embed: if tag.get("isAdult") and not isAdultAllowed: continue - tags_embd.add_field(name=tag.get("name", "not found!"), value="Similarity : " + str(tag.get("rank")) + "%") + tags_embd.add_field( + name=tag.get("name", "not found!"), + value="Similarity : " + str(tag.get("rank")) + "%", + ) return tags_embd -async def get_anime_details_embed(name: str, user: Member) -> Embed: +async def get_anime_details_embed(name: str, user: Member) -> dict: data_raw = await get_media_details(name, MediaType.ANIME, user) - data = data_raw["data"]["Media"] + data = data_raw.get("data", {}).get("Media", None) if data is None: - return await get_error_embed(data_raw) - - title = "#{id} - {eng_name} {is_adult}".format(id=data["id"], eng_name=(data["title"]["english"] if data["title"]["english"] is not None else data["title"]["romaji"]), is_adult=" - 18+" if data["isAdult"] else "") + error_embd = await get_error_embed(data_raw) + return {"embeds": {"error": error_embd}, "error": True, "isAdult": False} + + title = "#{id} - {eng_name} {is_adult}".format( + id=data["id"], + eng_name=( + data["title"]["english"] + if data["title"]["english"] is not None + else data["title"]["romaji"] + ), + is_adult=" - 18+" if data["isAdult"] else "", + ) details_embd = await get_details_embd(data, title) tags_embd = await get_tags_embd(data, title, data["isAdult"]) - return {"embeds": {"details": details_embd, "tags": tags_embd}, "isAdult": data["isAdult"]} + return { + "embeds": {"details": details_embd, "tags": tags_embd}, + "isAdult": data["isAdult"], + } -async def get_manga_details_embed(name: str, user: Member) -> Embed: +async def get_manga_details_embed(name: str, user: Member) -> dict: data_raw = await get_media_details(name, MediaType.MANGA, user) - data = data_raw["data"]["Media"] + data = data_raw.get("data", {}).get("Media", None) if data is None: - return await get_error_embed(data_raw) + error_embd = await get_error_embed(data_raw) + return {"error": True, "embeds": {"error": error_embd}, "isAdult": False} - title = "#{id} - {eng_name} {is_adult}".format(id=data["id"], eng_name=data["title"]["english"], is_adult=" - 18+" if data["isAdult"] else "") + title = "#{id} - {eng_name} {is_adult}".format( + id=data["id"], + eng_name=data["title"]["english"], + is_adult=" - 18+" if data["isAdult"] else "", + ) embd: Embed = Embed(title=title, color=config.NORMAL_COLOR, url=data["siteUrl"]) - embd.description = data["description"][:200] + "... [read more]({})".format(data["siteUrl"]) + + desc = data.get("description", "") or "" + embd.description = desc[:200] + "... [read more]({})".format(data["siteUrl"]) embd.set_thumbnail(url=data["coverImage"]["large"]) titles = [x if x is not None else "" for x in list(data["title"].values())] @@ -182,11 +295,17 @@ async def get_manga_details_embed(name: str, user: Member) -> Embed: startDate = [str(x) for x in list(data["startDate"].values())] embd.add_field(name="Start Date", value="\n".join(startDate) or "None", inline=True) - embd.add_field(name="Average Score", value=str(data["averageScore"]) or "None", inline=True) + embd.add_field( + name="Average Score", value=str(data["averageScore"]) or "None", inline=True + ) - embd.add_field(name="Mean Score", value=str(data["meanScore"]) or "None", inline=True) + embd.add_field( + name="Mean Score", value=str(data["meanScore"]) or "None", inline=True + ) - embd.add_field(name="Favorites", value=str(data["favourites"]) or "None", inline=True) + embd.add_field( + name="Favorites", value=str(data["favourites"]) or "None", inline=True + ) embd.add_field(name="Chapters", value=str(data["chapters"]) or "None", inline=True) @@ -198,19 +317,51 @@ async def get_manga_details_embed(name: str, user: Member) -> Embed: embd.add_field(name="Popularity", value=data["popularity"] or "None", inline=True) - trailer_link = "[click here](https://www.youtube.com/watch?v={})".format(data["trailer"]["id"]) if data["trailer"] is not None and data["trailer"]["site"] == "youtube" else "Not Available" + trailer_link = ( + "[click here](https://www.youtube.com/watch?v={})".format(data["trailer"]["id"]) + if data["trailer"] is not None and data["trailer"]["site"] == "youtube" + else "Not Available" + ) embd.add_field(name="Trailer", value=trailer_link or "None", inline=True) try: # Footer isFav = "🔘FAV : Yes" if data["isFavourite"] is True else "" - status = "STATUS : " + data["mediaListEntry"]["status"] if data["mediaListEntry"] is not None else "" - score = ("🔘SCORE : " + str(data["mediaListEntry"]["score"]) if data["mediaListEntry"]["score"] != 0 else "") if data["mediaListEntry"] is not None else "" - progress = "🔘PROGRESS : " + str(data["mediaListEntry"]["progress"] if data["mediaListEntry"] is not None else "") - total = "/" + str(data["mediaListEntry"]["media"]["chapters"] if data["mediaListEntry"] is not None else "") + status = ( + "STATUS : " + data["mediaListEntry"]["status"] + if data["mediaListEntry"] is not None + else "" + ) + score = ( + ( + "🔘SCORE : " + str(data["mediaListEntry"]["score"]) + if data["mediaListEntry"]["score"] != 0 + else "" + ) + if data["mediaListEntry"] is not None + else "" + ) + progress = "🔘PROGRESS : " + str( + data["mediaListEntry"]["progress"] + if data["mediaListEntry"] is not None + else "" + ) + total = "/" + str( + data["mediaListEntry"]["media"]["chapters"] + if data["mediaListEntry"] is not None + else "" + ) if not (isFav == "" and status == ""): - embd.set_footer(text="{status}{fav}{score}{progress}{total}".format(status=status, fav=isFav, score=score, progress=progress, total=total)) + embd.set_footer( + text="{status}{fav}{score}{progress}{total}".format( + status=status, + fav=isFav, + score=score, + progress=progress, + total=total, + ) + ) except Exception: pass @@ -219,15 +370,22 @@ async def get_manga_details_embed(name: str, user: Member) -> Embed: async def get_character_details_embed(name: str, user: Member) -> Embed: data_raw = await get_character_details(name, user) - data = data_raw["data"]["Character"] + data = data_raw.get("data", {}).get("Character", None) if data is None: return await get_error_embed(data_raw) - title = "#{} - {}".format(data["id"], data["name"]["full"] if data["name"]["full"] is not None else data["name"]["native"]) + title = "#{} - {}".format( + data["id"], + data["name"]["full"] + if data["name"]["full"] is not None + else data["name"]["native"], + ) embd: Embed = Embed(title=title, color=config.NORMAL_COLOR, url=data["siteUrl"]) - embd.description = data["description"][:300] + "... *[read more]({})*".format(data["siteUrl"]) + + desc = data.get("description", "") or "" + embd.description = desc[:300] + "... *[read more]({})*".format(data["siteUrl"]) embd.set_thumbnail(url=data["image"]["medium"]) @@ -243,9 +401,21 @@ async def get_character_details_embed(name: str, user: Member) -> Embed: embd.add_field(name="Age", value=data["age"] or "None", inline=True) dateOfBirth = [str(date) for date in list(data["dateOfBirth"].values())] - embd.add_field(name="Date Of Birth", value="\n".join(dateOfBirth) or "None", inline=True) - - appearances_str = "\n".join(["[{name}]({url})".format(name=media["title"]["english"] if media["title"]["english"] is not None else media["title"]["romaji"], url=media["siteUrl"] if media["siteUrl"] is not None else "") for media in data["media"]["nodes"][:3]]) + embd.add_field( + name="Date Of Birth", value="\n".join(dateOfBirth) or "None", inline=True + ) + + appearances_str = "\n".join( + [ + "[{name}]({url})".format( + name=media["title"]["english"] + if media["title"]["english"] is not None + else media["title"]["romaji"], + url=media["siteUrl"] if media["siteUrl"] is not None else "", + ) + for media in data["media"]["nodes"][:3] + ] + ) embd.add_field(name="Appearances", value=appearances_str or "None", inline=True) try: @@ -268,14 +438,29 @@ async def get_studio_details_embed(name: str) -> Embed: embd: Embed = Embed(title=title, color=config.NORMAL_COLOR, url=data["siteUrl"]) - embd.description = "**Animation Studio ? : **" + ("Yes" if data["isAnimationStudio"] is True else "No") + embd.description = "**Animation Studio ? : **" + ( + "Yes" if data["isAnimationStudio"] is True else "No" + ) embd.description += "\n" embd.description += "**Favourites : **" + str(data["favourites"]) work_str = "" - medias = data["media"]["nodes"] if len(data["media"]["nodes"]) <= 5 else data["media"]["nodes"][:6] + medias = ( + data["media"]["nodes"] + if len(data["media"]["nodes"]) <= 5 + else data["media"]["nodes"][:6] + ) for media in medias: - work_str += "{bullet} [{name}]({url}) {dot} {format} {dot} {status} \n".format(bullet=config.BULLET_EMOTE, dot=config.DOT_EMOTE, name=media["title"]["english"] if media["title"]["english"] is not None else media["title"]["romaji"], url=media["siteUrl"], format=media["format"], status=media["status"]) + work_str += "{bullet} [{name}]({url}) {dot} {format} {dot} {status} \n".format( + bullet=config.BULLET_EMOTE, + dot=config.DOT_EMOTE, + name=media["title"]["english"] + if media["title"]["english"] is not None + else media["title"]["romaji"], + url=media["siteUrl"], + format=media["format"], + status=media["status"], + ) embd.add_field(name="Works ", value=work_str, inline=False) @@ -285,7 +470,13 @@ async def get_studio_details_embed(name: str) -> Embed: async def get_top_by_genre(genres: list, media_type: str = "ANIME") -> Scroller: genres = [config.ALL_GENRE_ALTS[genre.lower()] for genre in genres] - resp = requests.post(url=config.ANILIST_BASE, json={"query": top_genre_query, "variables": {"genre": genres, "type": media_type}}).json() + resp = requests.post( + url=config.ANILIST_BASE, + json={ + "query": top_genre_query, + "variables": {"genre": genres, "type": media_type}, + }, + ).json() data = resp["data"]["Page"]["media"] @@ -293,15 +484,32 @@ async def get_top_by_genre(genres: list, media_type: str = "ANIME") -> Scroller: pages = [] - embd = await general_helper.get_information_embed(title="Top {type} in {genre}".format(type=media_type, genre=", ".join(genres).upper()), description="") + embd = await general_helper.get_information_embed( + title="Top {type} in {genre}".format( + type=media_type, genre=", ".join(genres).upper() + ), + description="", + ) MAX_PER_EMBED = 10 entry_count = 1 total_entry_count = 1 for media in data: - title = media["title"]["english"] if media["title"]["english"] is not None else media["title"]["romaji"] - embd.description += "{number}. [{name}]({link}) - {meanScore}".format(number=total_entry_count, name=title, link=media["siteUrl"], meanScore=media["meanScore"]) + "\n" + title = ( + media["title"]["english"] + if media["title"]["english"] is not None + else media["title"]["romaji"] + ) + embd.description = ( + "{number}. [{name}]({link}) - {meanScore}".format( + number=total_entry_count, + name=title, + link=media["siteUrl"], + meanScore=media["meanScore"], + ) + + "\n" + ) entry_count += 1 total_entry_count += 1 @@ -309,6 +517,11 @@ async def get_top_by_genre(genres: list, media_type: str = "ANIME") -> Scroller: if entry_count > MAX_PER_EMBED: entry_count = 1 pages.append(embd) - embd = await general_helper.get_information_embed(title="Top {type} in {genre}".format(type=media_type, genre=", ".join(genres).capitalize()), description="") + embd = await general_helper.get_information_embed( + title="Top {type} in {genre}".format( + type=media_type, genre=", ".join(genres).capitalize() + ), + description="", + ) return Scroller(embed_pages=pages, show_all_btns=True) diff --git a/logs/yuibot.log.2026-01-27 b/logs/yuibot.log.2026-01-27 new file mode 100644 index 0000000..a09b664 --- /dev/null +++ b/logs/yuibot.log.2026-01-27 @@ -0,0 +1,114 @@ +2026-01-27 23:19:54 - ERROR - Error occurred while trying to cache Config Vars! +'TOKEN' +2026-01-27 23:27:10 - INFO - Config LOADED! +2026-01-27 23:27:11 - INFO - 47 Servers Cached! +2026-01-27 23:27:11 - INFO - Database Initialized +2026-01-27 23:27:11 - WARNING - PyNaCl is not installed, voice will NOT be supported +2026-01-27 23:27:11 - INFO - logging in using static token +2026-01-27 23:27:12 - INFO - Shard ID None has sent the IDENTIFY payload. +2026-01-27 23:27:12 - INFO - Shard ID None has connected to Gateway: ["gateway-prd-arm-us-east1-d-n3wm",{"micros":190437,"calls":["id_created",{"micros":435,"calls":[]},"session_lookup_time",{"micros":215,"calls":[]},"session_lookup_finished",{"micros":12,"calls":[]},"discord-sessions-prd-2-111",{"micros":189447,"calls":["start_session",{"micros":178886,"calls":["discord-api-rpc-686cbbfd85-hcwpm",{"micros":112014,"calls":["get_user",{"micros":6131},"get_guilds",{"micros":15659},"send_scheduled_deletion_message",{"micros":14},"guild_join_requests",{"micros":2},"authorized_ip_coro",{"micros":46},"pending_payments",{"micros":737},"apex_experiments",{"micros":59158},"user_activities",{"micros":14},"played_application_ids",{"micros":4},"linked_users",{"micros":4},"ad_personalization_toggles_disabled",{"micros":3},"regional_feature_config",{"micros":7}]}]},"starting_guild_connect",{"micros":40,"calls":[]},"presence_started",{"micros":4120,"calls":[]},"guilds_started",{"micros":140,"calls":[]},"lobbies_started",{"micros":2,"calls":[]},"guilds_connect",{"micros":2,"calls":[]},"presence_connect",{"micros":6230,"calls":[]},"connect_finished",{"micros":6244,"calls":[]},"build_ready",{"micros":12,"calls":[]},"clean_ready",{"micros":1,"calls":[]},"optimize_ready",{"micros":0,"calls":[]},"split_ready",{"micros":0,"calls":[]}]}]}] (Session ID: 9caf8bbe27ec2886f8471309d7e110e3). +2026-01-27 23:27:14 - INFO - Logged in as Ui Bot#3454 +2026-01-27 23:27:14 - INFO - Discord Version : 2.4.0 +2026-01-27 23:29:05 - INFO - Cleaning up tasks. +2026-01-27 23:29:05 - INFO - Cleaning up after 2 tasks. +2026-01-27 23:29:05 - INFO - Closing the event loop. +2026-01-27 23:29:05 - ERROR - Task was destroyed but it is pending! +task: .runner() running at /home/dev/EXTRAS/Projects/YuiBot/.venv/lib/python3.11/site-packages/discord/client.py:699> wait_for= cb=[gather.._done_callback() at /home/dev/.local/share/uv/python/cpython-3.11.14-linux-x86_64-gnu/lib/python3.11/asyncio/tasks.py:764]> +2026-01-27 23:29:05 - ERROR - Unclosed client session +client_session: +2026-01-27 23:29:08 - INFO - Config LOADED! +2026-01-27 23:29:08 - INFO - 47 Servers Cached! +2026-01-27 23:29:08 - INFO - Database Initialized +2026-01-27 23:29:08 - WARNING - PyNaCl is not installed, voice will NOT be supported +2026-01-27 23:29:08 - INFO - logging in using static token +2026-01-27 23:29:09 - INFO - Shard ID None has sent the IDENTIFY payload. +2026-01-27 23:29:09 - INFO - Shard ID None has connected to Gateway: ["gateway-prd-arm-us-east1-b-s500",{"micros":120117,"calls":["id_created",{"micros":322,"calls":[]},"session_lookup_time",{"micros":818,"calls":[]},"session_lookup_finished",{"micros":11,"calls":[]},"discord-sessions-prd-2-10",{"micros":118575,"calls":["start_session",{"micros":112186,"calls":["discord-api-rpc-686cbbfd85-fg8lr",{"micros":65443,"calls":["get_user",{"micros":6818},"get_guilds",{"micros":5482},"send_scheduled_deletion_message",{"micros":17},"guild_join_requests",{"micros":6376},"authorized_ip_coro",{"micros":11},"pending_payments",{"micros":588},"apex_experiments",{"micros":38875},"user_activities",{"micros":6},"played_application_ids",{"micros":3},"linked_users",{"micros":4},"ad_personalization_toggles_disabled",{"micros":3},"regional_feature_config",{"micros":11}]}]},"starting_guild_connect",{"micros":33,"calls":[]},"presence_started",{"micros":366,"calls":[]},"guilds_started",{"micros":121,"calls":[]},"lobbies_started",{"micros":1,"calls":[]},"guilds_connect",{"micros":1,"calls":[]},"presence_connect",{"micros":5838,"calls":[]},"connect_finished",{"micros":5852,"calls":[]},"build_ready",{"micros":14,"calls":[]},"clean_ready",{"micros":0,"calls":[]},"optimize_ready",{"micros":1,"calls":[]},"split_ready",{"micros":0,"calls":[]}]}]}] (Session ID: 5c93541165dee5e8aec4c31f0776b79b). +2026-01-27 23:29:11 - INFO - Logged in as Ui Bot#3454 +2026-01-27 23:29:11 - INFO - Discord Version : 2.4.0 +2026-01-27 23:29:40 - INFO - Cleaning up tasks. +2026-01-27 23:29:40 - INFO - Cleaning up after 2 tasks. +2026-01-27 23:29:40 - INFO - Closing the event loop. +2026-01-27 23:29:41 - ERROR - Task was destroyed but it is pending! +task: .runner() running at /home/dev/EXTRAS/Projects/YuiBot/.venv/lib/python3.11/site-packages/discord/client.py:699> wait_for= cb=[gather.._done_callback() at /home/dev/.local/share/uv/python/cpython-3.11.14-linux-x86_64-gnu/lib/python3.11/asyncio/tasks.py:764]> +2026-01-27 23:29:41 - ERROR - Unclosed client session +client_session: +2026-01-27 23:29:41 - ERROR - Unclosed connector +connections: ['[(, 1774.294106028)]'] +connector: +2026-01-27 23:31:21 - INFO - Config LOADED! +2026-01-27 23:31:23 - INFO - 47 Servers Cached! +2026-01-27 23:31:25 - INFO - Database Initialized +2026-01-27 23:31:27 - WARNING - PyNaCl is not installed, voice will NOT be supported +2026-01-27 23:31:41 - INFO - logging in using static token +2026-01-27 23:31:42 - INFO - Shard ID None has sent the IDENTIFY payload. +2026-01-27 23:31:42 - INFO - Shard ID None has connected to Gateway: ["gateway-prd-arm-us-east1-d-r5jq",{"micros":124777,"calls":["id_created",{"micros":936,"calls":[]},"session_lookup_time",{"micros":243,"calls":[]},"session_lookup_finished",{"micros":17,"calls":[]},"discord-sessions-prd-2-144",{"micros":122997,"calls":["start_session",{"micros":96571,"calls":["discord-api-rpc-686cbbfd85-ws45b",{"micros":44005,"calls":["get_user",{"micros":9750},"get_guilds",{"micros":3216},"send_scheduled_deletion_message",{"micros":10},"guild_join_requests",{"micros":3315},"authorized_ip_coro",{"micros":15},"pending_payments",{"micros":633},"apex_experiments",{"micros":39443},"user_activities",{"micros":7},"played_application_ids",{"micros":4},"linked_users",{"micros":4},"ad_personalization_toggles_disabled",{"micros":3},"regional_feature_config",{"micros":3}]}]},"starting_guild_connect",{"micros":33,"calls":[]},"presence_started",{"micros":2738,"calls":[]},"guilds_started",{"micros":128,"calls":[]},"lobbies_started",{"micros":1,"calls":[]},"guilds_connect",{"micros":1,"calls":[]},"presence_connect",{"micros":23502,"calls":[]},"connect_finished",{"micros":23515,"calls":[]},"build_ready",{"micros":10,"calls":[]},"clean_ready",{"micros":0,"calls":[]},"optimize_ready",{"micros":0,"calls":[]},"split_ready",{"micros":1,"calls":[]}]}]}] (Session ID: cf6646f3b97d0e6d3c08f50b3308a22d). +2026-01-27 23:31:42 - INFO - Cleaning up tasks. +2026-01-27 23:31:42 - INFO - Cleaning up after 5 tasks. +2026-01-27 23:31:42 - INFO - Closing the event loop. +2026-01-27 23:31:43 - ERROR - Task was destroyed but it is pending! +task: .runner() running at /home/dev/EXTRAS/Projects/YuiBot/.venv/lib/python3.11/site-packages/discord/client.py:699> wait_for= cb=[gather.._done_callback() at /home/dev/.local/share/uv/python/cpython-3.11.14-linux-x86_64-gnu/lib/python3.11/asyncio/tasks.py:764]> +2026-01-27 23:31:43 - ERROR - Unclosed client session +client_session: +2026-01-27 23:47:41 - INFO - Config LOADED! +2026-01-27 23:47:42 - INFO - 47 Servers Cached! +2026-01-27 23:47:42 - INFO - Database Initialized +2026-01-27 23:47:42 - WARNING - PyNaCl is not installed, voice will NOT be supported +2026-01-27 23:47:42 - INFO - logging in using static token +2026-01-27 23:47:42 - INFO - Shard ID None has sent the IDENTIFY payload. +2026-01-27 23:47:43 - INFO - Shard ID None has connected to Gateway: ["gateway-prd-arm-us-east1-d-qs93",{"micros":161867,"calls":["id_created",{"micros":430,"calls":[]},"session_lookup_time",{"micros":325,"calls":[]},"session_lookup_finished",{"micros":10,"calls":[]},"discord-sessions-prd-2-168",{"micros":160732,"calls":["start_session",{"micros":145390,"calls":["discord-api-rpc-6946bd8d8f-hd5lc",{"micros":60498,"calls":["get_user",{"micros":7220},"get_guilds",{"micros":4544},"send_scheduled_deletion_message",{"micros":20},"guild_join_requests",{"micros":3},"authorized_ip_coro",{"micros":8},"pending_payments",{"micros":489},"apex_experiments",{"micros":71617},"user_activities",{"micros":7},"played_application_ids",{"micros":4},"linked_users",{"micros":4},"ad_personalization_toggles_disabled",{"micros":4},"regional_feature_config",{"micros":4}]}]},"starting_guild_connect",{"micros":37,"calls":[]},"presence_started",{"micros":383,"calls":[]},"guilds_started",{"micros":135,"calls":[]},"lobbies_started",{"micros":1,"calls":[]},"guilds_connect",{"micros":1,"calls":[]},"presence_connect",{"micros":14750,"calls":[]},"connect_finished",{"micros":14766,"calls":[]},"build_ready",{"micros":16,"calls":[]},"clean_ready",{"micros":1,"calls":[]},"optimize_ready",{"micros":0,"calls":[]},"split_ready",{"micros":1,"calls":[]}]}]}] (Session ID: 4123ef54c657eaf7b69943a5722271da). +2026-01-27 23:47:45 - INFO - Logged in as Ui Bot#3454 +2026-01-27 23:47:45 - INFO - Discord Version : 2.4.0 +2026-01-27 23:48:05 - INFO - Cleaning up tasks. +2026-01-27 23:48:05 - INFO - Cleaning up after 2 tasks. +2026-01-27 23:48:05 - INFO - Closing the event loop. +2026-01-27 23:48:05 - ERROR - Task was destroyed but it is pending! +task: .runner() running at /home/dev/EXTRAS/Projects/YuiBot/.venv/lib/python3.11/site-packages/discord/client.py:699> wait_for= cb=[gather.._done_callback() at /home/dev/.local/share/uv/python/cpython-3.11.14-linux-x86_64-gnu/lib/python3.11/asyncio/tasks.py:764]> +2026-01-27 23:48:05 - ERROR - Unclosed client session +client_session: +2026-01-27 23:48:05 - ERROR - Unclosed connector +connections: ['[(, 2887.801286209)]'] +connector: +2026-01-27 23:50:04 - INFO - Config LOADED! +2026-01-27 23:50:05 - INFO - 47 Servers Cached! +2026-01-27 23:50:05 - INFO - Database Initialized +2026-01-27 23:50:05 - WARNING - PyNaCl is not installed, voice will NOT be supported +2026-01-27 23:50:05 - INFO - logging in using static token +2026-01-27 23:50:05 - INFO - Shard ID None has sent the IDENTIFY payload. +2026-01-27 23:50:06 - INFO - Shard ID None has connected to Gateway: ["gateway-prd-arm-us-east1-d-9513",{"micros":400748,"calls":["id_created",{"micros":700,"calls":[]},"session_lookup_time",{"micros":1317,"calls":[]},"session_lookup_finished",{"micros":23,"calls":[]},"discord-sessions-prd-2-165",{"micros":398414,"calls":["start_session",{"micros":382295,"calls":["discord-api-rpc-6946bd8d8f-cvtbf",{"micros":256774,"calls":["get_user",{"micros":108265},"get_guilds",{"micros":26398},"send_scheduled_deletion_message",{"micros":26},"guild_join_requests",{"micros":4},"authorized_ip_coro",{"micros":18},"pending_payments",{"micros":829},"apex_experiments",{"micros":104489},"user_activities",{"micros":8},"played_application_ids",{"micros":4},"linked_users",{"micros":4},"ad_personalization_toggles_disabled",{"micros":4},"regional_feature_config",{"micros":13}]}]},"starting_guild_connect",{"micros":32,"calls":[]},"presence_started",{"micros":406,"calls":[]},"guilds_started",{"micros":137,"calls":[]},"lobbies_started",{"micros":1,"calls":[]},"guilds_connect",{"micros":2,"calls":[]},"presence_connect",{"micros":15515,"calls":[]},"connect_finished",{"micros":15530,"calls":[]},"build_ready",{"micros":12,"calls":[]},"clean_ready",{"micros":0,"calls":[]},"optimize_ready",{"micros":0,"calls":[]},"split_ready",{"micros":1,"calls":[]}]}]}] (Session ID: ae4655dd16c7bc07cf0c474a6ae0b684). +2026-01-27 23:50:08 - INFO - Logged in as Ui Bot#3454 +2026-01-27 23:50:08 - INFO - Discord Version : 2.4.0 +2026-01-27 23:52:13 - INFO - Cleaning up tasks. +2026-01-27 23:52:13 - INFO - Cleaning up after 2 tasks. +2026-01-27 23:52:14 - INFO - Closing the event loop. +2026-01-27 23:52:14 - ERROR - Task was destroyed but it is pending! +task: .runner() running at /home/dev/EXTRAS/Projects/YuiBot/.venv/lib/python3.11/site-packages/discord/client.py:699> wait_for= cb=[gather.._done_callback() at /home/dev/.local/share/uv/python/cpython-3.11.14-linux-x86_64-gnu/lib/python3.11/asyncio/tasks.py:764]> +2026-01-27 23:52:14 - ERROR - Unclosed client session +client_session: +2026-01-27 23:52:41 - INFO - Config LOADED! +2026-01-27 23:52:42 - INFO - 47 Servers Cached! +2026-01-27 23:52:42 - INFO - Database Initialized +2026-01-27 23:52:42 - WARNING - PyNaCl is not installed, voice will NOT be supported +2026-01-27 23:52:42 - INFO - logging in using static token +2026-01-27 23:52:43 - INFO - Shard ID None has sent the IDENTIFY payload. +2026-01-27 23:52:43 - INFO - Shard ID None has connected to Gateway: ["gateway-prd-arm-us-east1-b-mwr9",{"micros":105553,"calls":["id_created",{"micros":439,"calls":[]},"session_lookup_time",{"micros":359,"calls":[]},"session_lookup_finished",{"micros":10,"calls":[]},"discord-sessions-prd-2-130",{"micros":104286,"calls":["start_session",{"micros":91226,"calls":["discord-api-rpc-6946bd8d8f-fpbwx",{"micros":47021,"calls":["get_user",{"micros":6628},"get_guilds",{"micros":5556},"send_scheduled_deletion_message",{"micros":15},"guild_join_requests",{"micros":3},"authorized_ip_coro",{"micros":21},"pending_payments",{"micros":632},"apex_experiments",{"micros":33999},"user_activities",{"micros":7},"played_application_ids",{"micros":4},"linked_users",{"micros":3},"ad_personalization_toggles_disabled",{"micros":12},"regional_feature_config",{"micros":3}]}]},"starting_guild_connect",{"micros":30,"calls":[]},"presence_started",{"micros":4877,"calls":[]},"guilds_started",{"micros":717,"calls":[]},"lobbies_started",{"micros":1,"calls":[]},"guilds_connect",{"micros":15,"calls":[]},"presence_connect",{"micros":7361,"calls":[]},"connect_finished",{"micros":7389,"calls":[]},"build_ready",{"micros":44,"calls":[]},"clean_ready",{"micros":0,"calls":[]},"optimize_ready",{"micros":0,"calls":[]},"split_ready",{"micros":1,"calls":[]}]}]}] (Session ID: 135b3b5e0812581a7546e11bb1ee6504). +2026-01-27 23:52:46 - INFO - Logged in as YuiBot#9359 +2026-01-27 23:52:46 - INFO - Discord Version : 2.4.0 +2026-01-27 23:54:19 - INFO - Cleaning up tasks. +2026-01-27 23:54:19 - INFO - Cleaning up after 2 tasks. +2026-01-27 23:54:19 - INFO - Closing the event loop. +2026-01-27 23:54:20 - ERROR - Task was destroyed but it is pending! +task: .runner() running at /home/dev/EXTRAS/Projects/YuiBot/.venv/lib/python3.11/site-packages/discord/client.py:699> wait_for= cb=[gather.._done_callback() at /home/dev/.local/share/uv/python/cpython-3.11.14-linux-x86_64-gnu/lib/python3.11/asyncio/tasks.py:764]> +2026-01-27 23:54:20 - ERROR - Unclosed client session +client_session: +2026-01-27 23:54:21 - INFO - Config LOADED! +2026-01-27 23:54:22 - INFO - 47 Servers Cached! +2026-01-27 23:54:22 - INFO - Database Initialized +2026-01-27 23:54:22 - WARNING - PyNaCl is not installed, voice will NOT be supported +2026-01-27 23:54:22 - INFO - logging in using static token +2026-01-27 23:54:23 - INFO - Shard ID None has sent the IDENTIFY payload. +2026-01-27 23:54:23 - INFO - Shard ID None has connected to Gateway: ["gateway-prd-arm-us-east1-b-jrt0",{"micros":220497,"calls":["id_created",{"micros":495,"calls":[]},"session_lookup_time",{"micros":346,"calls":[]},"session_lookup_finished",{"micros":24,"calls":[]},"discord-sessions-prd-2-79",{"micros":219154,"calls":["start_session",{"micros":202114,"calls":["discord-api-rpc-6946bd8d8f-brn9s",{"micros":118961,"calls":["get_user",{"micros":10441},"get_guilds",{"micros":20291},"send_scheduled_deletion_message",{"micros":10},"guild_join_requests",{"micros":1004},"authorized_ip_coro",{"micros":8},"pending_payments",{"micros":958},"apex_experiments",{"micros":70346},"user_activities",{"micros":5},"played_application_ids",{"micros":3},"linked_users",{"micros":3},"ad_personalization_toggles_disabled",{"micros":3},"regional_feature_config",{"micros":2}]}]},"starting_guild_connect",{"micros":36,"calls":[]},"presence_started",{"micros":7264,"calls":[]},"guilds_started",{"micros":687,"calls":[]},"lobbies_started",{"micros":2,"calls":[]},"guilds_connect",{"micros":16,"calls":[]},"presence_connect",{"micros":8961,"calls":[]},"connect_finished",{"micros":8992,"calls":[]},"build_ready",{"micros":56,"calls":[]},"clean_ready",{"micros":1,"calls":[]},"optimize_ready",{"micros":0,"calls":[]},"split_ready",{"micros":1,"calls":[]}]}]}] (Session ID: 45546c8331f855084e39af635095845e). +2026-01-27 23:54:31 - INFO - Logged in as YuiBot#9359 +2026-01-27 23:54:36 - INFO - Discord Version : 2.4.0 +2026-01-27 23:54:39 - INFO - Cleaning up tasks. +2026-01-27 23:54:39 - INFO - Cleaning up after 2 tasks. +2026-01-27 23:54:40 - INFO - Closing the event loop. diff --git a/main.py b/main.py index ffb39d1..7006453 100644 --- a/main.py +++ b/main.py @@ -1,12 +1,11 @@ import sys from utils.bot import Bot -from managers import mongo_manager, cache_manager, logging_manager +from utils import logger +from managers import mongo_manager, cache_manager import config -logging_manager.setup_logging() - def main(test=False): print(""" @@ -18,12 +17,14 @@ def main(test=False): ................................................ """) - config.initialize_config_vars() + logger.setup_logger(test) + config.initialize_config_vars() cache_manager.init() - mongo_manager.init_motor() + logger.logger.warning("Components Loaded!") + bot: Bot = Bot() if test: diff --git a/managers/cache_manager.py b/managers/cache_manager.py index 508ad1d..7e11485 100644 --- a/managers/cache_manager.py +++ b/managers/cache_manager.py @@ -76,7 +76,7 @@ async def get_server( server_id: int, register_if_not_found: bool = False, server_name: str = "", - ) -> dict: + ) -> dict | None: """Fetch server from cache""" server_details = self.server_cache.get(server_id, None) diff --git a/managers/logging_manager.py b/managers/logging_manager.py deleted file mode 100644 index 21fe498..0000000 --- a/managers/logging_manager.py +++ /dev/null @@ -1,21 +0,0 @@ -import logging -import os -from logging.handlers import TimedRotatingFileHandler - - -def setup_logging(): - if not os.path.exists("logs/"): - os.mkdir("logs/") - - logger = logging.getLogger() - logger.setLevel(logging.INFO) - - file_handler = TimedRotatingFileHandler( - "logs/yuibot.log", when="midnight", interval=1, backupCount=7 - ) - - formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s") - formatter.datefmt = "%Y-%m-%d %H:%M:%S" - file_handler.setFormatter(formatter) - - logger.addHandler(file_handler) diff --git a/utils/bot.py b/utils/bot.py index 003970c..e4c2f50 100644 --- a/utils/bot.py +++ b/utils/bot.py @@ -1,6 +1,5 @@ import os import re -import logging from discord.ext import commands from discord import Intents, Message, Embed, Guild @@ -9,28 +8,56 @@ from managers import cache_manager from views.spotify_view import SpotifyView from helpers import spotify_helper, general_helper +from utils import logger import config @general_helper.with_typing_msg() async def process_spotify_links(message: Message): + """ + Process Spotify Links and return Youtube Music links for the same song + """ + + # ignore DMs + if message.guild is None: + return await message.channel.send("This feature is not supported in DMs.") + server_details = await cache_manager.manager.get_server(message.guild.id, True) - if server_details.get("spotify").get("enabled") is True: + if server_details is None: + return + + if server_details.get("spotify", {}).get("enabled") is False: + return await message.channel.send("Your Server doesn't support this feature") + + if server_details.get("spotify", {}).get("enabled") is True: splits = message.content.strip().split() - track_id_match = re.findall(r"(?<=track/)\w+", splits[0]) if len(splits) > 0 else None + track_id_match = ( + re.findall(r"(?<=track/)\w+", splits[1]) if len(splits) > 0 else None + ) + + if track_id_match is None: + return if len(track_id_match) > 0: - links: spotify_helper.SpotifyTrackAlternative = await spotify_helper.get_alternatives(message=message, spotify_track_id=track_id_match[0]) + links: spotify_helper.SpotifyTrackAlternative = ( + await spotify_helper.get_alternatives( + message=message, spotify_track_id=track_id_match[0] + ) + ) - if server_details.get("spotify").get("style") == "embed": - embd: Embed = await general_helper.get_information_embed(title="Alternate Links", description="") - embd.description += "**Name : **" + links.track_name + if server_details.get("spotify", {}).get("style") == "embed": + embd: Embed = await general_helper.get_information_embed( + title="Alternate Links", description="" + ) + embd.description = "**Name : **" + links.track_name embd.description += "\n**Artists: **" + links.track_artists + view = SpotifyView(links) await message.reply(embed=embd, view=view) - elif server_details.get("spotify").get("style") == "text": + + elif server_details.get("spotify", {}).get("style") == "text": message = await message.reply( "**Name :** {}, **Artists :** {} | [Youtube Music]({})".format( links.track_name, @@ -43,17 +70,12 @@ async def process_spotify_links(message: Message): class Bot(commands.Bot): - intents: Intents = Intents.default() - - mentions = ["<@991739924250362047>", "<@!991739924250362047>"] - - mention_embed = None - - def prefix_callable(self, bot, msg): - return ["yui ", "Yui ", "<@991739924250362047> ", "<@!991739924250362047> "] + custom_intents: Intents = Intents.default() def __init__(self): - super().__init__(command_prefix=self.prefix_callable, intents=self.intents) + super().__init__( + command_prefix=commands.when_mentioned, intents=self.custom_intents + ) self.remove_command("help") # load extensions @@ -61,24 +83,36 @@ def __init__(self): if file.endswith(".py"): self.load_extension(f"cogs.{file[:-3]}") + self.mention_embed = Embed( + title="Ya-Ho :wave:", + description=f"Prefix : **yui**\nLatency : **{round(self.latency * 1000, 2)} ms**\nInvite : [Click Here]({config.INVITE})", + color=config.NORMAL_COLOR, + ) + self.mention_embed.set_thumbnail( + url=self.user.avatar.url + if self.user is not None and self.user.avatar is not None + else "" + ) + async def on_ready(self): - logging.info("Logged in as {}".format(self.user)) - logging.info("Discord Version : {}".format(__version__)) - + logger.logger.info("Logged in as {}".format(self.user)) + logger.logger.info("Discord Version : {}".format(__version__)) + + async def on_message(self, message: Message) -> None: + """ + Override the base on_message to extend functionality + """ - async def on_message(self, message: Message): - if message.content.strip() in self.mentions: - embd = Embed( - title="Ya-Ho :wave:", - description=f"Prefix : **yui**\nLatency : **{round(self.latency * 1000, 2)} ms**\nInvite : [Click Here]({config.INVITE})", - color=config.NORMAL_COLOR, - ).set_thumbnail(url=self.user.avatar.url) + if self.user is None: + return - return await message.channel.send(embed=embd) + if message.content == self.user.mention: + await message.channel.send(embed=self.mention_embed) + return await self.process_commands(message) - SPOTIFY_TRACK_BASE = "https://open.spotify.com/track/" + SPOTIFY_TRACK_BASE = f"{self.user.mention} https://open.spotify.com/track/" if message.content.strip().startswith(SPOTIFY_TRACK_BASE): await process_spotify_links(message) diff --git a/utils/logger.py b/utils/logger.py new file mode 100644 index 0000000..bf6c9bd --- /dev/null +++ b/utils/logger.py @@ -0,0 +1,65 @@ +import logging +import os +from logging.handlers import TimedRotatingFileHandler + +logger = None + +os.makedirs("logs", exist_ok=True) + + +def setup_logger(test=False) -> logging.Logger: + global logger + + class TerminalFormatter(logging.Formatter): + grey = "\x1b[38;20m" + yellow = "\x1b[33;20m" + red = "\x1b[31;20m" + bold_red = "\x1b[31;1m" + reset = "\x1b[0m" + + custom_format = ( + "%(asctime)s - %(levelname)s - %(message)s (%(filename)s:%(lineno)d)" + ) + + FORMATS = { + logging.DEBUG: grey + custom_format + reset, + logging.INFO: grey + custom_format + reset, + logging.WARNING: yellow + custom_format + reset, + logging.ERROR: red + custom_format + reset, + logging.CRITICAL: bold_red + custom_format + reset, + } + + def format(self, record): + log_fmt = self.FORMATS.get(record.levelno) + formatter = logging.Formatter(log_fmt) + return formatter.format(record) + + class FileFormatter(logging.Formatter): + custom_format = ( + "%(asctime)s - %(levelname)-8s - %(message)s (%(filename)s:%(lineno)d)" + ) + + def format(self, record): + formatter = logging.Formatter( + self.custom_format, datefmt="%d-%m-%Y %H:%M:%S" + ) + return formatter.format(record) + + logger = logging.Logger("yuibotlogger") + logger.setLevel(logging.DEBUG) + + # stream handler for handling terminal logging only if we are in test mode + if test is True: + stream_handler = logging.StreamHandler() + stream_handler.setFormatter(TerminalFormatter()) + logger.addHandler(stream_handler) + + # file handler for logging to file + if test is not True: + file_handler = TimedRotatingFileHandler( + "logs/yuibot.log", when="midnight", interval=1, backupCount=7 + ) + file_handler.setFormatter(FileFormatter()) + logger.addHandler(file_handler) + + return logger diff --git a/uv.lock b/uv.lock index 4eab459..dcbc637 100644 --- a/uv.lock +++ b/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 2 +revision = 3 requires-python = ">=3.11" [[package]] @@ -908,27 +908,28 @@ wheels = [ [[package]] name = "uv" -version = "0.7.22" +version = "0.9.27" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/43/c5/d1defc44eea72552ee7667212359bded10ac8f7e39b62b042a3d4a9d4040/uv-0.7.22.tar.gz", hash = "sha256:f5cf159907d594e33433f14737d1ee843dc8799edfcf57b5b8c0f282d1117051", size = 3387947, upload-time = "2025-07-17T17:01:01.31Z" } +sdist = { url = "https://files.pythonhosted.org/packages/92/70/611bcee4385b7aa00cf7acf29bc51854c365ee09ddb2cdb14b07a36b4db8/uv-0.9.27.tar.gz", hash = "sha256:9147862902b5d40894f78339803225e39b0535c4c04537188de160eb7635e46b", size = 3830404, upload-time = "2026-01-26T23:27:43.442Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/76/10/55d46d79d61da0ae4ca50e126b369cbce3f2ffd478df61e7e280d3d13302/uv-0.7.22-py3-none-linux_armv6l.whl", hash = "sha256:995bdc2d8ec75620544bad1bea389334c740ff4aeeb42fbee93107b0780fa1d2", size = 17824193, upload-time = "2025-07-17T17:00:02.285Z" }, - { url = "https://files.pythonhosted.org/packages/46/31/163f836537bb3c41b7f7687e539096cc251dd7ebc7afb492cc8ef77a7243/uv-0.7.22-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:bc2d9d49b8bc83ef2a0cbfd39926e2112059b9ddddb7e3baa31726cce33c707b", size = 17908564, upload-time = "2025-07-17T17:00:06.768Z" }, - { url = "https://files.pythonhosted.org/packages/64/f5/0ee734f5e988fd8f26aad1f150703fe8c7d664029c9c677b989b69caf104/uv-0.7.22-py3-none-macosx_11_0_arm64.whl", hash = "sha256:573edda226dc26e6fea03aa89a45af2f2a367ad1f466af15c4eb54286dae042f", size = 16615938, upload-time = "2025-07-17T17:00:10.01Z" }, - { url = "https://files.pythonhosted.org/packages/d4/45/71eec20e6390e464885e0546037dcecf1f10afac884e701ce70cb990774f/uv-0.7.22-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:c366847fc6260c3fa101be0a99ed1ac8613537ea3ec4fa6e4aac5d1a173945ec", size = 17171889, upload-time = "2025-07-17T17:00:13.988Z" }, - { url = "https://files.pythonhosted.org/packages/d7/86/088894e7013116e5edcce29d9e7313046289be1c50056fdc0e9a336200e3/uv-0.7.22-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:706393788882cca2581d681fcf51bcbd5b04aa695e9dad41c048219f6a2a0b0a", size = 17535032, upload-time = "2025-07-17T17:00:17.472Z" }, - { url = "https://files.pythonhosted.org/packages/a2/0f/216225a343659a76162a6ccef43aeacca060f1a0a5c0b56042a30e3fb7d6/uv-0.7.22-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f61c122ef8d6679dab90a24c3a2c4c688b6b3112e6e2735ae1590784520fc82b", size = 18261859, upload-time = "2025-07-17T17:00:20.513Z" }, - { url = "https://files.pythonhosted.org/packages/35/88/a566e43a77713b0fb66e933f82752a089b5e27d820a2cc549431985259f3/uv-0.7.22-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:b4660613c1fd86f607856e21fd2700bc19ae39fedcfc5865434ddbd3b76b39ae", size = 19472386, upload-time = "2025-07-17T17:00:24.167Z" }, - { url = "https://files.pythonhosted.org/packages/30/5f/310f4d21dc10577cc4aff3dc12f21f7108840c2025be479551143a158b98/uv-0.7.22-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e3178dd8b118d61ffbf59297417ee0a5136b3fe34bca76105ce78a2854d9e6a2", size = 19224926, upload-time = "2025-07-17T17:00:28.279Z" }, - { url = "https://files.pythonhosted.org/packages/8e/21/eac566208accded98ff0a74d4995e7805715c5ac4bc71ff2ff9a16cf4d39/uv-0.7.22-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75d7a4dbf219bcabefc760498b0b137a85966673ed43580f9dc339ff2bdaae73", size = 18784319, upload-time = "2025-07-17T17:00:31.492Z" }, - { url = "https://files.pythonhosted.org/packages/88/12/11dfd036bf776a775f6972a3f9ccfa95b4817ec7a21a427f233a5d1d904b/uv-0.7.22-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:560b00403b9cf82e2ae7d6d680c6d54c3f5c709b3b6357e092059c5d4b682baa", size = 18676032, upload-time = "2025-07-17T17:00:34.656Z" }, - { url = "https://files.pythonhosted.org/packages/c8/55/8da57cdf08fa83228e78d0f882c56278021d370029a6f726d40d2fd1c17f/uv-0.7.22-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:996f50ba85a21da7bb3d6b08be952d3fe06bede3573a50fb576d2fa77d72b1ed", size = 17455515, upload-time = "2025-07-17T17:00:37.832Z" }, - { url = "https://files.pythonhosted.org/packages/ee/7b/00f98f7aaf540537ca5261dcd2b59a81f3bc93b148e1c1ef61243c1c1708/uv-0.7.22-py3-none-musllinux_1_1_armv7l.whl", hash = "sha256:b2abb6c638afcd5fd020284a2f36b71d41277675d23732691dc92dd4376db4b8", size = 17493955, upload-time = "2025-07-17T17:00:40.841Z" }, - { url = "https://files.pythonhosted.org/packages/3d/45/49b9dca3aac1c595a967cfb78b30675a0746c7bd15ad219f727950b7c893/uv-0.7.22-py3-none-musllinux_1_1_i686.whl", hash = "sha256:8a66db63e0220a0d05bf9836c1b35fd8562a8d28f6989b412c914dbc4be15e16", size = 17802203, upload-time = "2025-07-17T17:00:43.931Z" }, - { url = "https://files.pythonhosted.org/packages/77/f4/e3acf80651e3650d6606109e95c56292616c423557b8503200409005a0db/uv-0.7.22-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:49a78e2703d26de1b95730a1310505debb121a178732f8dc321ed99de8eca708", size = 18772914, upload-time = "2025-07-17T17:00:47.731Z" }, - { url = "https://files.pythonhosted.org/packages/2a/50/74062e3a7f468e7f9dafc953920a1781608a2933a1d14fa48493f8b5dca4/uv-0.7.22-py3-none-win32.whl", hash = "sha256:e6115997907151e28858c238f7af18782d3ed4c24e2c0572710e05f08895cb19", size = 17716379, upload-time = "2025-07-17T17:00:51.265Z" }, - { url = "https://files.pythonhosted.org/packages/16/1a/dd8390675bf572b472063af0c612b0875156ea5ff7d2660ea44f7e361709/uv-0.7.22-py3-none-win_amd64.whl", hash = "sha256:d476f10783d1a9d49fa14fd9447fc694c75d3a93a7ff237a12bbc69eb9d29c27", size = 19512351, upload-time = "2025-07-17T17:00:55.347Z" }, - { url = "https://files.pythonhosted.org/packages/f8/a1/51de664e7eeeb71b63cfe74c95cdbdc3abc20fa1443b60e78c1536ac8e64/uv-0.7.22-py3-none-win_arm64.whl", hash = "sha256:8c478034d422b99327c58914463c0841aed0bc1e8edf231ff861e191cdfea862", size = 18049309, upload-time = "2025-07-17T17:00:58.725Z" }, + { url = "https://files.pythonhosted.org/packages/53/f1/9e9afb9fadb73f7914a0fc63b6f9d182b6fe6b1e55deb7cbdc29ff31299f/uv-0.9.27-py3-none-linux_armv6l.whl", hash = "sha256:ce3f16e66a96dcdc63f6ada9f7747686930986d2df104a9dd2d09664b2d870c8", size = 22011502, upload-time = "2026-01-26T23:27:31.714Z" }, + { url = "https://files.pythonhosted.org/packages/ea/b2/c36a87f5c745d310b7d8a53df053d6a87864aa38e3a964b0845eb6de37cc/uv-0.9.27-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:a662a7df5cc781ae7baa65171b5d488d946ea93e61b7bbeda5a24d21a0cd9003", size = 21081065, upload-time = "2026-01-26T23:28:11.895Z" }, + { url = "https://files.pythonhosted.org/packages/44/1d/be2d80573c531389933059f6e5ef265ef7324c268f3ade80e500aa627f6b/uv-0.9.27-py3-none-macosx_11_0_arm64.whl", hash = "sha256:8f00158023e77600da602c5f1fa97cd8c2eef987d6aba34c16cf04a3e5a932f4", size = 19844905, upload-time = "2026-01-26T23:27:34.486Z" }, + { url = "https://files.pythonhosted.org/packages/3e/f7/59679af9f0446d8ffc1239e3356390c95925e0004549b64df3f189b1422b/uv-0.9.27-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:5f2393051ed2023cc7d6ff99e41184b7c7bb7da001bc727cd4bee6da96f4a966", size = 21592623, upload-time = "2026-01-26T23:27:51.132Z" }, + { url = "https://files.pythonhosted.org/packages/e2/31/0faaad82951fc6b14dfad8e187e43747a528aa50ee283385f903e86d67d1/uv-0.9.27-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.musllinux_1_1_armv7l.whl", hash = "sha256:3f8cf7a50a95ae5cb0366d24edf79d15b3ba380b462af49e3404f9f7b91101c7", size = 21636917, upload-time = "2026-01-26T23:27:46.252Z" }, + { url = "https://files.pythonhosted.org/packages/c2/8a/e181c32b7f5309fd987667d368fb944822c713e92a7eba3c73d2eddec6cd/uv-0.9.27-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c331e0445465ea6029e2dd0f5499024e552136c10069fac0ca21708f2aeb1ce6", size = 21633082, upload-time = "2026-01-26T23:28:09.417Z" }, + { url = "https://files.pythonhosted.org/packages/da/0e/1d44157bc8e5d1c382db087d9a30ab85fc7b5c2d610fb2e3d861c5a69d9b/uv-0.9.27-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a56e0c92da67060d4c86a3b92b2c69e8fb1d61933636764aba16531ddb13f6e3", size = 22843044, upload-time = "2026-01-26T23:27:29.091Z" }, + { url = "https://files.pythonhosted.org/packages/eb/76/7c1b13e4dc8237dd3721f4ec933bb2e5be400fd2812cf98dc2be645a0f7d/uv-0.9.27-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:0c9b2e874f5207a50b852726f3a0740eadf30baf2c7973380d697f4e3166d347", size = 24141329, upload-time = "2026-01-26T23:27:39.705Z" }, + { url = "https://files.pythonhosted.org/packages/6f/04/551749fd863cb43290c9a3f4348ccdd88ec0445c26a00ba840d776572867/uv-0.9.27-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:79841a2e4df4a32f22fbb0919c3e513226684572fba227b37467ba6404f3fafd", size = 23637517, upload-time = "2026-01-26T23:27:37.111Z" }, + { url = "https://files.pythonhosted.org/packages/d9/c6/78b619a51a6646af4633714b161f980ab764cc892e0f79228162fba51fe8/uv-0.9.27-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33902c95b561ac67d15c339fe1eaf39e068372c7464c79c3bd0e2bf9ee914dcb", size = 22864516, upload-time = "2026-01-26T23:27:56.35Z" }, + { url = "https://files.pythonhosted.org/packages/15/19/b35928e55307beb69b60b88446df3cb8d7ff3ba0993fc2214a43266c17d1/uv-0.9.27-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:79939f7e92d707fb84933509df747d1b88b00d94ebe41f3a1e30916cc33c7307", size = 22746151, upload-time = "2026-01-26T23:27:26.166Z" }, + { url = "https://files.pythonhosted.org/packages/9f/70/fbab20d40afe7ac9ec20011acec75f8bb3b9b83dfbe2cdb1405cad7a8cf2/uv-0.9.27-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:7e2d4183a0dca7596ea6385e9d5a0a87ada4f71a70aa110e2b22234370b8d8ef", size = 21661188, upload-time = "2026-01-26T23:27:53.79Z" }, + { url = "https://files.pythonhosted.org/packages/44/02/4d4cf298bd22e53d6c289404b093cf876e64ee1fb946cc32a6f965030629/uv-0.9.27-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:1555ab7bc8501144e8771e54a628eb02cb95f3612d54659bb7132576260feee5", size = 22397798, upload-time = "2026-01-26T23:28:07.102Z" }, + { url = "https://files.pythonhosted.org/packages/97/29/3acef6a0eea58afbf7f7a08e4258430e3c7394a6b1e28249450f4c0ddc60/uv-0.9.27-py3-none-musllinux_1_1_i686.whl", hash = "sha256:542731a6f53072e725959a9c839b195048715d840213d9834d36f74fa4249855", size = 22111665, upload-time = "2026-01-26T23:27:58.762Z" }, + { url = "https://files.pythonhosted.org/packages/13/15/1e7b34f02e8f53c9498311f991421e794ad57fa60a2d3e41b43485e914e4/uv-0.9.27-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:4f534ad701ca3fffac4a8e1df2a36930e6a0cbf4dad52aeabc2c3c9e2cbbe65e", size = 22951420, upload-time = "2026-01-26T23:27:48.818Z" }, + { url = "https://files.pythonhosted.org/packages/aa/c5/c3bb3a5885891ed8fe7dcc897db03366f3e19da8bf48ae8f5ea4da34545d/uv-0.9.27-py3-none-win32.whl", hash = "sha256:18aab0e19634997366907a9b8a1648e79b0fa34d1b86d8e8ee1e7ba5b9faa6ae", size = 20817398, upload-time = "2026-01-26T23:28:14.265Z" }, + { url = "https://files.pythonhosted.org/packages/db/19/22d2671928f6d2fef1edfdbf758abbb5a0f218b69fd23bd5fd52bbe5b078/uv-0.9.27-py3-none-win_amd64.whl", hash = "sha256:f961c53f83ae6a01e3ffacc584c91044958bc6db003e803c490e106a79981222", size = 23412228, upload-time = "2026-01-26T23:28:01.547Z" }, + { url = "https://files.pythonhosted.org/packages/17/eb/9a71112e2266b79da552a4b2ffb52331ca7171437b901705427f8e54e77c/uv-0.9.27-py3-none-win_arm64.whl", hash = "sha256:463327fb343c3085a3333389d6e5908cb48203b327707790c06bdc8f2ca57b95", size = 21836206, upload-time = "2026-01-26T23:28:04.411Z" }, ] [[package]]