From 29a74e093faeb317f947358bd86a48f83b169a5b Mon Sep 17 00:00:00 2001 From: Gustav Odinger Date: Mon, 21 Sep 2020 19:51:04 +0200 Subject: Add checks.py file - Necessary for extensions.py to work - Copied from the 'Python' bot, with minor tweaks to make it work with SeasonalBot --- bot/utils/checks.py | 164 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 164 insertions(+) create mode 100644 bot/utils/checks.py (limited to 'bot') diff --git a/bot/utils/checks.py b/bot/utils/checks.py new file mode 100644 index 00000000..3031a271 --- /dev/null +++ b/bot/utils/checks.py @@ -0,0 +1,164 @@ +import datetime +import logging +from typing import Callable, Container, Iterable, Optional + +from discord.ext.commands import ( + BucketType, + CheckFailure, + Cog, + Command, + CommandOnCooldown, + Context, + Cooldown, + CooldownMapping, +) + +from bot import constants + +log = logging.getLogger(__name__) + + +class InWhitelistCheckFailure(CheckFailure): + """Raised when the `in_whitelist` check fails.""" + + def __init__(self, redirect_channel: Optional[int]) -> None: + self.redirect_channel = redirect_channel + + if redirect_channel: + redirect_message = f" here. Please use the <#{redirect_channel}> channel instead" + else: + redirect_message = "" + + error_message = f"You are not allowed to use that command{redirect_message}." + + super().__init__(error_message) + + +def in_whitelist_check( + ctx: Context, + channels: Container[int] = (), + categories: Container[int] = (), + roles: Container[int] = (), + redirect: Optional[int] = constants.Channels.seasonalbot_commands, + fail_silently: bool = False, +) -> bool: + """ + Check if a command was issued in a whitelisted context. + + The whitelists that can be provided are: + + - `channels`: a container with channel ids for whitelisted channels + - `categories`: a container with category ids for whitelisted categories + - `roles`: a container with with role ids for whitelisted roles + + If the command was invoked in a context that was not whitelisted, the member is either + redirected to the `redirect` channel that was passed (default: #bot-commands) or simply + told that they're not allowed to use this particular command (if `None` was passed). + """ + if redirect and redirect not in channels: + # It does not make sense for the channel whitelist to not contain the redirection + # channel (if applicable). That's why we add the redirection channel to the `channels` + # container if it's not already in it. As we allow any container type to be passed, + # we first create a tuple in order to safely add the redirection channel. + # + # Note: It's possible for the redirect channel to be in a whitelisted category, but + # there's no easy way to check that and as a channel can easily be moved in and out of + # categories, it's probably not wise to rely on its category in any case. + channels = tuple(channels) + (redirect,) + + if channels and ctx.channel.id in channels: + log.trace(f"{ctx.author} may use the `{ctx.command.name}` command as they are in a whitelisted channel.") + return True + + # Only check the category id if we have a category whitelist and the channel has a `category_id` + if categories and hasattr(ctx.channel, "category_id") and ctx.channel.category_id in categories: + log.trace(f"{ctx.author} may use the `{ctx.command.name}` command as they are in a whitelisted category.") + return True + + # Only check the roles whitelist if we have one and ensure the author's roles attribute returns + # an iterable to prevent breakage in DM channels (for if we ever decide to enable commands there). + if roles and any(r.id in roles for r in getattr(ctx.author, "roles", ())): + log.trace(f"{ctx.author} may use the `{ctx.command.name}` command as they have a whitelisted role.") + return True + + log.trace(f"{ctx.author} may not use the `{ctx.command.name}` command within this context.") + + # Some commands are secret, and should produce no feedback at all. + if not fail_silently: + raise InWhitelistCheckFailure(redirect) + return False + + +def with_role_check(ctx: Context, *role_ids: int) -> bool: + """Returns True if the user has any one of the roles in role_ids.""" + if not ctx.guild: # Return False in a DM + log.trace(f"{ctx.author} tried to use the '{ctx.command.name}'command from a DM. " + "This command is restricted by the with_role decorator. Rejecting request.") + return False + + for role in ctx.author.roles: + if role.id in role_ids: + log.trace(f"{ctx.author} has the '{role.name}' role, and passes the check.") + return True + + log.trace(f"{ctx.author} does not have the required role to use " + f"the '{ctx.command.name}' command, so the request is rejected.") + return False + + +def without_role_check(ctx: Context, *role_ids: int) -> bool: + """Returns True if the user does not have any of the roles in role_ids.""" + if not ctx.guild: # Return False in a DM + log.trace(f"{ctx.author} tried to use the '{ctx.command.name}' command from a DM. " + "This command is restricted by the without_role decorator. Rejecting request.") + return False + + author_roles = [role.id for role in ctx.author.roles] + check = all(role not in author_roles for role in role_ids) + log.trace(f"{ctx.author} tried to call the '{ctx.command.name}' command. " + f"The result of the without_role check was {check}.") + return check + + +def cooldown_with_role_bypass(rate: int, per: float, type: BucketType = BucketType.default, *, + bypass_roles: Iterable[int]) -> Callable: + """ + Applies a cooldown to a command, but allows members with certain roles to be ignored. + + NOTE: this replaces the `Command.before_invoke` callback, which *might* introduce problems in the future. + """ + # Make it a set so lookup is hash based. + bypass = set(bypass_roles) + + # This handles the actual cooldown logic. + buckets = CooldownMapping(Cooldown(rate, per, type)) + + # Will be called after the command has been parse but before it has been invoked, ensures that + # the cooldown won't be updated if the user screws up their input to the command. + async def predicate(cog: Cog, ctx: Context) -> None: + nonlocal bypass, buckets + + if any(role.id in bypass for role in ctx.author.roles): + return + + # Cooldown logic, taken from discord.py internals. + current = ctx.message.created_at.replace(tzinfo=datetime.timezone.utc).timestamp() + bucket = buckets.get_bucket(ctx.message) + retry_after = bucket.update_rate_limit(current) + if retry_after: + raise CommandOnCooldown(bucket, retry_after) + + def wrapper(command: Command) -> Command: + # NOTE: this could be changed if a subclass of Command were to be used. I didn't see the need for it + # so I just made it raise an error when the decorator is applied before the actual command object exists. + # + # If the `before_invoke` detail is ever a problem then I can quickly just swap over. + if not isinstance(command, Command): + raise TypeError('Decorator `cooldown_with_role_bypass` must be applied after the command decorator. ' + 'This means it has to be above the command decorator in the code.') + + command._before_invoke = predicate + + return command + + return wrapper -- cgit v1.2.3 From b8672252bee458e7aa8c5ff9139a3f1f326501de Mon Sep 17 00:00:00 2001 From: Gustav Odinger Date: Mon, 21 Sep 2020 19:53:26 +0200 Subject: Add extensions.py file - Necessary for extensions.py to work - Fully copied over from the 'Python' bot --- bot/utils/extensions.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 bot/utils/extensions.py (limited to 'bot') diff --git a/bot/utils/extensions.py b/bot/utils/extensions.py new file mode 100644 index 00000000..50350ea8 --- /dev/null +++ b/bot/utils/extensions.py @@ -0,0 +1,34 @@ +import importlib +import inspect +import pkgutil +from typing import Iterator, NoReturn + +from bot import exts + + +def unqualify(name: str) -> str: + """Return an unqualified name given a qualified module/package `name`.""" + return name.rsplit(".", maxsplit=1)[-1] + + +def walk_extensions() -> Iterator[str]: + """Yield extension names from the bot.exts subpackage.""" + + def on_error(name: str) -> NoReturn: + raise ImportError(name=name) # pragma: no cover + + for module in pkgutil.walk_packages(exts.__path__, f"{exts.__name__}.", onerror=on_error): + if unqualify(module.name).startswith("_"): + # Ignore module/package names starting with an underscore. + continue + + if module.ispkg: + imported = importlib.import_module(module.name) + if not inspect.isfunction(getattr(imported, "setup", None)): + # If it lacks a setup function, it's not an extension. + continue + + yield module.name + + +EXTENSIONS = frozenset(walk_extensions()) -- cgit v1.2.3 From da13946c4d05b03d0e3df79befb855d6898ba83c Mon Sep 17 00:00:00 2001 From: Gustav Odinger Date: Mon, 21 Sep 2020 19:55:35 +0200 Subject: Update constants.py to include URLs - Includes bot_avatar and github_bot_repo URLs - Necessary for extensions.py cog to work --- bot/constants.py | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'bot') diff --git a/bot/constants.py b/bot/constants.py index fa428a61..07b040fc 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -24,6 +24,7 @@ __all__ = ( "ERROR_REPLIES", "NEGATIVE_REPLIES", "POSITIVE_REPLIES", + "URLs" ) log = logging.getLogger(__name__) @@ -177,6 +178,12 @@ class Roles(NamedTuple): verified = 352427296948486144 helpers = 267630620367257601 rockstars = 458226413825294336 + core_developers = 757650781385261197 # Change this value for local test servers. + + +class URLs: + bot_avatar = "https://raw.githubusercontent.com/discord-python/branding/master/logos/logo_circle/logo_circle.png" + github_bot_repo = "https://github.com/python-discord/bot" class Tokens(NamedTuple): -- cgit v1.2.3 From 15b05ab02d663e8efe40f818c76be0cc6a38680f Mon Sep 17 00:00:00 2001 From: Gustav Odinger Date: Mon, 21 Sep 2020 20:00:52 +0200 Subject: Add utils module in bot/exts/ --- bot/exts/utils/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 bot/exts/utils/__init__.py (limited to 'bot') diff --git a/bot/exts/utils/__init__.py b/bot/exts/utils/__init__.py new file mode 100644 index 00000000..e69de29b -- cgit v1.2.3 From 318200d8d340acbad79694a4f541adf421234a94 Mon Sep 17 00:00:00 2001 From: Gustav Odinger Date: Mon, 21 Sep 2020 20:08:46 +0200 Subject: Add status emojis in constants.py - Required for extensions.py to work properly --- bot/constants.py | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'bot') diff --git a/bot/constants.py b/bot/constants.py index 07b040fc..20d950cb 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -123,6 +123,11 @@ class Emojis: pull_request_closed = "<:PRClosed:629695470519713818>" merge = "<:PRMerged:629695470570176522>" + status_online = "<:status_online:470326272351010816>" + status_idle = "<:status_idle:470326266625785866>" + status_dnd = "<:status_dnd:470326272082313216>" + status_offline = "<:status_offline:470326266537705472>" + class Hacktoberfest(NamedTuple): voice_id = 514420006474219521 -- cgit v1.2.3 From 24c4af406b23589130dcfe03882d27bd8450dba5 Mon Sep 17 00:00:00 2001 From: Gustav Odinger Date: Mon, 21 Sep 2020 20:13:49 +0200 Subject: Add extensions managment command - Allows admins and core developes to manage loaded and unloaded extensions (mostly cogs) - Mostly copied from the 'Python' bot, with some tweaks to make it work for SeasonalBot --- bot/exts/utils/extensions.py | 265 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 265 insertions(+) create mode 100644 bot/exts/utils/extensions.py (limited to 'bot') diff --git a/bot/exts/utils/extensions.py b/bot/exts/utils/extensions.py new file mode 100644 index 00000000..c449a727 --- /dev/null +++ b/bot/exts/utils/extensions.py @@ -0,0 +1,265 @@ +import functools +import logging +import typing as t +from enum import Enum + +from discord import Colour, Embed +from discord.ext import commands +from discord.ext.commands import Context, group + +from bot import exts +from bot.bot import SeasonalBot as Bot +from bot.constants import Emojis, MODERATION_ROLES, Roles, URLs +from bot.utils.checks import with_role_check +from bot.utils.extensions import EXTENSIONS, unqualify +from bot.utils.pagination import LinePaginator + +log = logging.getLogger(__name__) + + +UNLOAD_BLACKLIST = {f"{exts.__name__}.utils.extensions", f"{exts.__name__}.moderation.modlog"} +BASE_PATH_LEN = len(exts.__name__.split(".")) + + +class Action(Enum): + """Represents an action to perform on an extension.""" + + # Need to be partial otherwise they are considered to be function definitions. + LOAD = functools.partial(Bot.load_extension) + UNLOAD = functools.partial(Bot.unload_extension) + RELOAD = functools.partial(Bot.reload_extension) + + +class Extension(commands.Converter): + """ + Fully qualify the name of an extension and ensure it exists. + + The * and ** values bypass this when used with the reload command. + """ + + async def convert(self, ctx: Context, argument: str) -> str: + """Fully qualify the name of an extension and ensure it exists.""" + # Special values to reload all extensions + if argument == "*" or argument == "**": + return argument + + argument = argument.lower() + + if argument in EXTENSIONS: + return argument + elif (qualified_arg := f"{exts.__name__}.{argument}") in EXTENSIONS: + return qualified_arg + + matches = [] + for ext in EXTENSIONS: + if argument == unqualify(ext): + matches.append(ext) + + if len(matches) > 1: + matches.sort() + names = "\n".join(matches) + raise commands.BadArgument( + f":x: `{argument}` is an ambiguous extension name. " + f"Please use one of the following fully-qualified names.```\n{names}```" + ) + elif matches: + return matches[0] + else: + raise commands.BadArgument(f":x: Could not find the extension `{argument}`.") + + +class Extensions(commands.Cog): + """Extension management commands.""" + + def __init__(self, bot: Bot): + self.bot = bot + + @group(name="extensions", aliases=("ext", "exts", "c", "cogs"), invoke_without_command=True) + async def extensions_group(self, ctx: Context) -> None: + """Load, unload, reload, and list loaded extensions.""" + await ctx.send_help(ctx.command) + + @extensions_group.command(name="load", aliases=("l",)) + async def load_command(self, ctx: Context, *extensions: Extension) -> None: + r""" + Load extensions given their fully qualified or unqualified names. + + If '\*' or '\*\*' is given as the name, all unloaded extensions will be loaded. + """ # noqa: W605 + if not extensions: + await ctx.send_help(ctx.command) + return + + if "*" in extensions or "**" in extensions: + extensions = set(EXTENSIONS) - set(self.bot.extensions.keys()) + + msg = self.batch_manage(Action.LOAD, *extensions) + await ctx.send(msg) + + @extensions_group.command(name="unload", aliases=("ul",)) + async def unload_command(self, ctx: Context, *extensions: Extension) -> None: + r""" + Unload currently loaded extensions given their fully qualified or unqualified names. + + If '\*' or '\*\*' is given as the name, all loaded extensions will be unloaded. + """ # noqa: W605 + if not extensions: + await ctx.send_help(ctx.command) + return + + blacklisted = "\n".join(UNLOAD_BLACKLIST & set(extensions)) + + if blacklisted: + msg = f":x: The following extension(s) may not be unloaded:```{blacklisted}```" + else: + if "*" in extensions or "**" in extensions: + extensions = set(self.bot.extensions.keys()) - UNLOAD_BLACKLIST + + msg = self.batch_manage(Action.UNLOAD, *extensions) + + await ctx.send(msg) + + @extensions_group.command(name="reload", aliases=("r",), root_aliases=("reload",)) + async def reload_command(self, ctx: Context, *extensions: Extension) -> None: + r""" + Reload extensions given their fully qualified or unqualified names. + + If an extension fails to be reloaded, it will be rolled-back to the prior working state. + + If '\*' is given as the name, all currently loaded extensions will be reloaded. + If '\*\*' is given as the name, all extensions, including unloaded ones, will be reloaded. + """ # noqa: W605 + if not extensions: + await ctx.send_help(ctx.command) + return + + if "**" in extensions: + extensions = EXTENSIONS + elif "*" in extensions: + extensions = set(self.bot.extensions.keys()) | set(extensions) + extensions.remove("*") + + msg = self.batch_manage(Action.RELOAD, *extensions) + + await ctx.send(msg) + + @extensions_group.command(name="list", aliases=("all",)) + async def list_command(self, ctx: Context) -> None: + """ + Get a list of all extensions, including their loaded status. + + Grey indicates that the extension is unloaded. + Green indicates that the extension is currently loaded. + """ + embed = Embed(colour=Colour.blurple()) + embed.set_author( + name="Extensions List", + url=URLs.github_bot_repo, + icon_url=URLs.bot_avatar + ) + + lines = [] + categories = self.group_extension_statuses() + for category, extensions in sorted(categories.items()): + # Treat each category as a single line by concatenating everything. + # This ensures the paginator will not cut off a page in the middle of a category. + category = category.replace("_", " ").title() + extensions = "\n".join(sorted(extensions)) + lines.append(f"**{category}**\n{extensions}\n") + + log.debug(f"{ctx.author} requested a list of all cogs. Returning a paginated list.") + await LinePaginator.paginate(lines, ctx, embed, max_size=700, empty=False) + + def group_extension_statuses(self) -> t.Mapping[str, str]: + """Return a mapping of extension names and statuses to their categories.""" + categories = {} + + for ext in EXTENSIONS: + if ext in self.bot.extensions: + status = Emojis.status_online + else: + status = Emojis.status_offline + + path = ext.split(".") + if len(path) > BASE_PATH_LEN + 1: + category = " - ".join(path[BASE_PATH_LEN:-1]) + else: + category = "uncategorised" + + categories.setdefault(category, []).append(f"{status} {path[-1]}") + + return categories + + def batch_manage(self, action: Action, *extensions: str) -> str: + """ + Apply an action to multiple extensions and return a message with the results. + + If only one extension is given, it is deferred to `manage()`. + """ + if len(extensions) == 1: + msg, _ = self.manage(action, extensions[0]) + return msg + + verb = action.name.lower() + failures = {} + + for extension in extensions: + _, error = self.manage(action, extension) + if error: + failures[extension] = error + + emoji = ":x:" if failures else ":ok_hand:" + msg = f"{emoji} {len(extensions) - len(failures)} / {len(extensions)} extensions {verb}ed." + + if failures: + failures = "\n".join(f"{ext}\n {err}" for ext, err in failures.items()) + msg += f"\nFailures:```{failures}```" + + log.debug(f"Batch {verb}ed extensions.") + + return msg + + def manage(self, action: Action, ext: str) -> t.Tuple[str, t.Optional[str]]: + """Apply an action to an extension and return the status message and any error message.""" + verb = action.name.lower() + error_msg = None + + try: + action.value(self.bot, ext) + except (commands.ExtensionAlreadyLoaded, commands.ExtensionNotLoaded): + if action is Action.RELOAD: + # When reloading, just load the extension if it was not loaded. + return self.manage(Action.LOAD, ext) + + msg = f":x: Extension `{ext}` is already {verb}ed." + log.debug(msg[4:]) + except Exception as e: + if hasattr(e, "original"): + e = e.original + + log.exception(f"Extension '{ext}' failed to {verb}.") + + error_msg = f"{e.__class__.__name__}: {e}" + msg = f":x: Failed to {verb} extension `{ext}`:\n```{error_msg}```" + else: + msg = f":ok_hand: Extension successfully {verb}ed: `{ext}`." + log.debug(msg[10:]) + + return msg, error_msg + + # This cannot be static (must have a __func__ attribute). + def cog_check(self, ctx: Context) -> bool: + """Only allow moderators and core developers to invoke the commands in this cog.""" + return with_role_check(ctx, *MODERATION_ROLES, Roles.core_developers) + + # This cannot be static (must have a __func__ attribute). + async def cog_command_error(self, ctx: Context, error: Exception) -> None: + """Handle BadArgument errors locally to prevent the help command from showing.""" + if isinstance(error, commands.BadArgument): + await ctx.send(str(error)) + error.handled = True + + +def setup(bot: Bot) -> None: + """Load the Extensions cog.""" + bot.add_cog(Extensions(bot)) -- cgit v1.2.3 From 65f044b975b18f5fd34f22a8b8e4567705ba68c6 Mon Sep 17 00:00:00 2001 From: Gustav Odinger Date: Mon, 21 Sep 2020 20:17:01 +0200 Subject: Fix core developers role id - Previous versions used the id from a local test server - This version uses the PyDis core developer role --- bot/constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/constants.py b/bot/constants.py index 20d950cb..e7d4265e 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -183,7 +183,7 @@ class Roles(NamedTuple): verified = 352427296948486144 helpers = 267630620367257601 rockstars = 458226413825294336 - core_developers = 757650781385261197 # Change this value for local test servers. + core_developers = 587606783669829632 class URLs: -- cgit v1.2.3 From 18c7e1091ac61e1bb9ef3dae96800a8793067f1a Mon Sep 17 00:00:00 2001 From: gustavwilliam <65498475+gustavwilliam@users.noreply.github.com> Date: Mon, 21 Sep 2020 23:21:52 +0200 Subject: Update max_size of paginator - Should prevent `RuntimeError: Line exceeds maximum page size 698` Co-authored-by: Dennis Pham --- bot/exts/utils/extensions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/exts/utils/extensions.py b/bot/exts/utils/extensions.py index c449a727..b9e58cb8 100644 --- a/bot/exts/utils/extensions.py +++ b/bot/exts/utils/extensions.py @@ -168,7 +168,7 @@ class Extensions(commands.Cog): lines.append(f"**{category}**\n{extensions}\n") log.debug(f"{ctx.author} requested a list of all cogs. Returning a paginated list.") - await LinePaginator.paginate(lines, ctx, embed, max_size=700, empty=False) + await LinePaginator.paginate(lines, ctx, embed, max_size=1200, empty=False) def group_extension_statuses(self) -> t.Mapping[str, str]: """Return a mapping of extension names and statuses to their categories.""" -- cgit v1.2.3 From de2c977653ecfe13d7e18bd579ede8e09847b571 Mon Sep 17 00:00:00 2001 From: gustavwilliam <65498475+gustavwilliam@users.noreply.github.com> Date: Mon, 21 Sep 2020 23:23:07 +0200 Subject: Remove modlog from unload blacklist - No modlog exists for SeasonalBot and is therefore redundant Co-authored-by: Dennis Pham --- bot/exts/utils/extensions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/exts/utils/extensions.py b/bot/exts/utils/extensions.py index b9e58cb8..834b7d4f 100644 --- a/bot/exts/utils/extensions.py +++ b/bot/exts/utils/extensions.py @@ -17,7 +17,7 @@ from bot.utils.pagination import LinePaginator log = logging.getLogger(__name__) -UNLOAD_BLACKLIST = {f"{exts.__name__}.utils.extensions", f"{exts.__name__}.moderation.modlog"} +UNLOAD_BLACKLIST = {f"{exts.__name__}.utils.extensions"} BASE_PATH_LEN = len(exts.__name__.split(".")) -- cgit v1.2.3 From 13ef604bd43a5eae4b1b3ade8670ceca1fc4592a Mon Sep 17 00:00:00 2001 From: Gustav Odinger Date: Mon, 21 Sep 2020 23:19:50 +0200 Subject: Move bot repo URL constant and delete avatar URL - Avatar URL can be accessed through bot.user.avatar_url and won't need to be a constant - Bot repo URL fits better under the Client named tuple - URLs class removed, since it was no longer used --- bot/constants.py | 6 +----- bot/exts/utils/extensions.py | 6 +++--- 2 files changed, 4 insertions(+), 8 deletions(-) (limited to 'bot') diff --git a/bot/constants.py b/bot/constants.py index e7d4265e..c69d5a83 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -85,6 +85,7 @@ class Client(NamedTuple): token = environ.get("SEASONALBOT_TOKEN") sentry_dsn = environ.get("SEASONALBOT_SENTRY_DSN") debug = environ.get("SEASONALBOT_DEBUG", "").lower() == "true" + github_bot_repo = "https://github.com/python-discord/bot" # Override seasonal locks: 1 (January) to 12 (December) month_override = int(environ["MONTH_OVERRIDE"]) if "MONTH_OVERRIDE" in environ else None @@ -186,11 +187,6 @@ class Roles(NamedTuple): core_developers = 587606783669829632 -class URLs: - bot_avatar = "https://raw.githubusercontent.com/discord-python/branding/master/logos/logo_circle/logo_circle.png" - github_bot_repo = "https://github.com/python-discord/bot" - - class Tokens(NamedTuple): giphy = environ.get("GIPHY_TOKEN") aoc_session_cookie = environ.get("AOC_SESSION_COOKIE") diff --git a/bot/exts/utils/extensions.py b/bot/exts/utils/extensions.py index 834b7d4f..65dfef84 100644 --- a/bot/exts/utils/extensions.py +++ b/bot/exts/utils/extensions.py @@ -9,7 +9,7 @@ from discord.ext.commands import Context, group from bot import exts from bot.bot import SeasonalBot as Bot -from bot.constants import Emojis, MODERATION_ROLES, Roles, URLs +from bot.constants import Client, Emojis, MODERATION_ROLES, Roles from bot.utils.checks import with_role_check from bot.utils.extensions import EXTENSIONS, unqualify from bot.utils.pagination import LinePaginator @@ -154,8 +154,8 @@ class Extensions(commands.Cog): embed = Embed(colour=Colour.blurple()) embed.set_author( name="Extensions List", - url=URLs.github_bot_repo, - icon_url=URLs.bot_avatar + url=Client.github_bot_repo, + icon_url=str(Bot.user.avatar_url) ) lines = [] -- cgit v1.2.3 From 61e04be50ccc8cb8f819aeb3eb5c6ead31960b4e Mon Sep 17 00:00:00 2001 From: Gustav Odinger Date: Mon, 21 Sep 2020 23:47:11 +0200 Subject: Update snake cog so ext command won't show files - Doing .c list would show the files inside the snakes module as individual cogs, which isn't the case --- bot/exts/evergreen/snakes/__init__.py | 2 +- bot/exts/evergreen/snakes/_converter.py | 85 +++ bot/exts/evergreen/snakes/_snakes_cog.py | 1151 ++++++++++++++++++++++++++++++ bot/exts/evergreen/snakes/_utils.py | 716 +++++++++++++++++++ bot/exts/evergreen/snakes/converter.py | 85 --- bot/exts/evergreen/snakes/snakes_cog.py | 1151 ------------------------------ bot/exts/evergreen/snakes/utils.py | 716 ------------------- 7 files changed, 1953 insertions(+), 1953 deletions(-) create mode 100644 bot/exts/evergreen/snakes/_converter.py create mode 100644 bot/exts/evergreen/snakes/_snakes_cog.py create mode 100644 bot/exts/evergreen/snakes/_utils.py delete mode 100644 bot/exts/evergreen/snakes/converter.py delete mode 100644 bot/exts/evergreen/snakes/snakes_cog.py delete mode 100644 bot/exts/evergreen/snakes/utils.py (limited to 'bot') diff --git a/bot/exts/evergreen/snakes/__init__.py b/bot/exts/evergreen/snakes/__init__.py index 2eae2751..bc42f0c2 100644 --- a/bot/exts/evergreen/snakes/__init__.py +++ b/bot/exts/evergreen/snakes/__init__.py @@ -2,7 +2,7 @@ import logging from discord.ext import commands -from bot.exts.evergreen.snakes.snakes_cog import Snakes +from bot.exts.evergreen.snakes._snakes_cog import Snakes log = logging.getLogger(__name__) diff --git a/bot/exts/evergreen/snakes/_converter.py b/bot/exts/evergreen/snakes/_converter.py new file mode 100644 index 00000000..eee248cf --- /dev/null +++ b/bot/exts/evergreen/snakes/_converter.py @@ -0,0 +1,85 @@ +import json +import logging +import random +from typing import Iterable, List + +import discord +from discord.ext.commands import Context, Converter +from fuzzywuzzy import fuzz + +from bot.exts.evergreen.snakes._utils import SNAKE_RESOURCES +from bot.utils import disambiguate + +log = logging.getLogger(__name__) + + +class Snake(Converter): + """Snake converter for the Snakes Cog.""" + + snakes = None + special_cases = None + + async def convert(self, ctx: Context, name: str) -> str: + """Convert the input snake name to the closest matching Snake object.""" + await self.build_list() + name = name.lower() + + if name == 'python': + return 'Python (programming language)' + + def get_potential(iterable: Iterable, *, threshold: int = 80) -> List[str]: + nonlocal name + potential = [] + + for item in iterable: + original, item = item, item.lower() + + if name == item: + return [original] + + a, b = fuzz.ratio(name, item), fuzz.partial_ratio(name, item) + if a >= threshold or b >= threshold: + potential.append(original) + + return potential + + # Handle special cases + if name.lower() in self.special_cases: + return self.special_cases.get(name.lower(), name.lower()) + + names = {snake['name']: snake['scientific'] for snake in self.snakes} + all_names = names.keys() | names.values() + timeout = len(all_names) * (3 / 4) + + embed = discord.Embed( + title='Found multiple choices. Please choose the correct one.', colour=0x59982F) + embed.set_author(name=ctx.author.display_name, icon_url=ctx.author.avatar_url) + + name = await disambiguate(ctx, get_potential(all_names), timeout=timeout, embed=embed) + return names.get(name, name) + + @classmethod + async def build_list(cls) -> None: + """Build list of snakes from the static snake resources.""" + # Get all the snakes + if cls.snakes is None: + with (SNAKE_RESOURCES / "snake_names.json").open(encoding="utf8") as snakefile: + cls.snakes = json.load(snakefile) + + # Get the special cases + if cls.special_cases is None: + with (SNAKE_RESOURCES / "special_snakes.json").open(encoding="utf8") as snakefile: + special_cases = json.load(snakefile) + cls.special_cases = {snake['name'].lower(): snake for snake in special_cases} + + @classmethod + async def random(cls) -> str: + """ + Get a random Snake from the loaded resources. + + This is stupid. We should find a way to somehow get the global session into a global context, + so I can get it from here. + """ + await cls.build_list() + names = [snake['scientific'] for snake in cls.snakes] + return random.choice(names) diff --git a/bot/exts/evergreen/snakes/_snakes_cog.py b/bot/exts/evergreen/snakes/_snakes_cog.py new file mode 100644 index 00000000..a846274b --- /dev/null +++ b/bot/exts/evergreen/snakes/_snakes_cog.py @@ -0,0 +1,1151 @@ +import asyncio +import colorsys +import logging +import os +import random +import re +import string +import textwrap +import urllib +from functools import partial +from io import BytesIO +from typing import Any, Dict, List + +import aiohttp +import async_timeout +from PIL import Image, ImageDraw, ImageFont +from discord import Colour, Embed, File, Member, Message, Reaction +from discord.ext.commands import BadArgument, Bot, Cog, CommandError, Context, bot_has_permissions, group + +from bot.constants import ERROR_REPLIES, Tokens +from bot.exts.evergreen.snakes import _utils as utils +from bot.exts.evergreen.snakes._converter import Snake +from bot.utils.decorators import locked + +log = logging.getLogger(__name__) + + +# region: Constants +# Color +SNAKE_COLOR = 0x399600 + +# Antidote constants +SYRINGE_EMOJI = "\U0001F489" # :syringe: +PILL_EMOJI = "\U0001F48A" # :pill: +HOURGLASS_EMOJI = "\u231B" # :hourglass: +CROSSBONES_EMOJI = "\u2620" # :skull_crossbones: +ALEMBIC_EMOJI = "\u2697" # :alembic: +TICK_EMOJI = "\u2705" # :white_check_mark: - Correct peg, correct hole +CROSS_EMOJI = "\u274C" # :x: - Wrong peg, wrong hole +BLANK_EMOJI = "\u26AA" # :white_circle: - Correct peg, wrong hole +HOLE_EMOJI = "\u2B1C" # :white_square: - Used in guesses +EMPTY_UNICODE = "\u200b" # literally just an empty space + +ANTIDOTE_EMOJI = ( + SYRINGE_EMOJI, + PILL_EMOJI, + HOURGLASS_EMOJI, + CROSSBONES_EMOJI, + ALEMBIC_EMOJI, +) + +# Quiz constants +ANSWERS_EMOJI = { + "a": "\U0001F1E6", # :regional_indicator_a: 🇦 + "b": "\U0001F1E7", # :regional_indicator_b: 🇧 + "c": "\U0001F1E8", # :regional_indicator_c: 🇨 + "d": "\U0001F1E9", # :regional_indicator_d: 🇩 +} + +ANSWERS_EMOJI_REVERSE = { + "\U0001F1E6": "A", # :regional_indicator_a: 🇦 + "\U0001F1E7": "B", # :regional_indicator_b: 🇧 + "\U0001F1E8": "C", # :regional_indicator_c: 🇨 + "\U0001F1E9": "D", # :regional_indicator_d: 🇩 +} + +# Zzzen of pythhhon constant +ZEN = """ +Beautiful is better than ugly. +Explicit is better than implicit. +Simple is better than complex. +Complex is better than complicated. +Flat is better than nested. +Sparse is better than dense. +Readability counts. +Special cases aren't special enough to break the rules. +Although practicality beats purity. +Errors should never pass silently. +Unless explicitly silenced. +In the face of ambiguity, refuse the temptation to guess. +There should be one-- and preferably only one --obvious way to do it. +Now is better than never. +Although never is often better than *right* now. +If the implementation is hard to explain, it's a bad idea. +If the implementation is easy to explain, it may be a good idea. +""" + +# Max messages to train snake_chat on +MSG_MAX = 100 + +# get_snek constants +URL = "https://en.wikipedia.org/w/api.php?" + +# snake guess responses +INCORRECT_GUESS = ( + "Nope, that's not what it is.", + "Not quite.", + "Not even close.", + "Terrible guess.", + "Nnnno.", + "Dude. No.", + "I thought everyone knew this one.", + "Guess you suck at snakes.", + "Bet you feel stupid now.", + "Hahahaha, no.", + "Did you hit the wrong key?" +) + +CORRECT_GUESS = ( + "**WRONG**. Wait, no, actually you're right.", + "Yeah, you got it!", + "Yep, that's exactly what it is.", + "Uh-huh. Yep yep yep.", + "Yeah that's right.", + "Yup. How did you know that?", + "Are you a herpetologist?", + "Sure, okay, but I bet you can't pronounce it.", + "Are you cheating?" +) + +# snake card consts +CARD = { + "top": Image.open("bot/resources/snakes/snake_cards/card_top.png"), + "frame": Image.open("bot/resources/snakes/snake_cards/card_frame.png"), + "bottom": Image.open("bot/resources/snakes/snake_cards/card_bottom.png"), + "backs": [ + Image.open(f"bot/resources/snakes/snake_cards/backs/{file}") + for file in os.listdir("bot/resources/snakes/snake_cards/backs") + ], + "font": ImageFont.truetype("bot/resources/snakes/snake_cards/expressway.ttf", 20) +} +# endregion + + +class Snakes(Cog): + """ + Commands related to snakes, created by our community during the first code jam. + + More information can be found in the code-jam-1 repo. + + https://github.com/python-discord/code-jam-1 + """ + + wiki_brief = re.compile(r'(.*?)(=+ (.*?) =+)', flags=re.DOTALL) + valid_image_extensions = ('gif', 'png', 'jpeg', 'jpg', 'webp') + + def __init__(self, bot: Bot): + self.active_sal = {} + self.bot = bot + self.snake_names = utils.get_resource("snake_names") + self.snake_idioms = utils.get_resource("snake_idioms") + self.snake_quizzes = utils.get_resource("snake_quiz") + self.snake_facts = utils.get_resource("snake_facts") + + # region: Helper methods + @staticmethod + def _beautiful_pastel(hue: float) -> int: + """Returns random bright pastels.""" + light = random.uniform(0.7, 0.85) + saturation = 1 + + rgb = colorsys.hls_to_rgb(hue, light, saturation) + hex_rgb = "" + + for part in rgb: + value = int(part * 0xFF) + hex_rgb += f"{value:02x}" + + return int(hex_rgb, 16) + + @staticmethod + def _generate_card(buffer: BytesIO, content: dict) -> BytesIO: + """ + Generate a card from snake information. + + Written by juan and Someone during the first code jam. + """ + snake = Image.open(buffer) + + # Get the size of the snake icon, configure the height of the image box (yes, it changes) + icon_width = 347 # Hardcoded, not much i can do about that + icon_height = int((icon_width / snake.width) * snake.height) + frame_copies = icon_height // CARD['frame'].height + 1 + snake.thumbnail((icon_width, icon_height)) + + # Get the dimensions of the final image + main_height = icon_height + CARD['top'].height + CARD['bottom'].height + main_width = CARD['frame'].width + + # Start creating the foreground + foreground = Image.new("RGBA", (main_width, main_height), (0, 0, 0, 0)) + foreground.paste(CARD['top'], (0, 0)) + + # Generate the frame borders to the correct height + for offset in range(frame_copies): + position = (0, CARD['top'].height + offset * CARD['frame'].height) + foreground.paste(CARD['frame'], position) + + # Add the image and bottom part of the image + foreground.paste(snake, (36, CARD['top'].height)) # Also hardcoded :( + foreground.paste(CARD['bottom'], (0, CARD['top'].height + icon_height)) + + # Setup the background + back = random.choice(CARD['backs']) + back_copies = main_height // back.height + 1 + full_image = Image.new("RGBA", (main_width, main_height), (0, 0, 0, 0)) + + # Generate the tiled background + for offset in range(back_copies): + full_image.paste(back, (16, 16 + offset * back.height)) + + # Place the foreground onto the final image + full_image.paste(foreground, (0, 0), foreground) + + # Get the first two sentences of the info + description = '.'.join(content['info'].split(".")[:2]) + '.' + + # Setup positioning variables + margin = 36 + offset = CARD['top'].height + icon_height + margin + + # Create blank rectangle image which will be behind the text + rectangle = Image.new( + "RGBA", + (main_width, main_height), + (0, 0, 0, 0) + ) + + # Draw a semi-transparent rectangle on it + rect = ImageDraw.Draw(rectangle) + rect.rectangle( + (margin, offset, main_width - margin, main_height - margin), + fill=(63, 63, 63, 128) + ) + + # Paste it onto the final image + full_image.paste(rectangle, (0, 0), mask=rectangle) + + # Draw the text onto the final image + draw = ImageDraw.Draw(full_image) + for line in textwrap.wrap(description, 36): + draw.text([margin + 4, offset], line, font=CARD['font']) + offset += CARD['font'].getsize(line)[1] + + # Get the image contents as a BufferIO object + buffer = BytesIO() + full_image.save(buffer, 'PNG') + buffer.seek(0) + + return buffer + + @staticmethod + def _snakify(message: str) -> str: + """Sssnakifffiesss a sstring.""" + # Replace fricatives with exaggerated snake fricatives. + simple_fricatives = [ + "f", "s", "z", "h", + "F", "S", "Z", "H", + ] + complex_fricatives = [ + "th", "sh", "Th", "Sh" + ] + + for letter in simple_fricatives: + if letter.islower(): + message = message.replace(letter, letter * random.randint(2, 4)) + else: + message = message.replace(letter, (letter * random.randint(2, 4)).title()) + + for fricative in complex_fricatives: + message = message.replace(fricative, fricative[0] + fricative[1] * random.randint(2, 4)) + + return message + + async def _fetch(self, session: aiohttp.ClientSession, url: str, params: dict = None) -> dict: + """Asynchronous web request helper method.""" + if params is None: + params = {} + + async with async_timeout.timeout(10): + async with session.get(url, params=params) as response: + return await response.json() + + def _get_random_long_message(self, messages: List[str], retries: int = 10) -> str: + """ + Fetch a message that's at least 3 words long, if possible to do so in retries attempts. + + Else, just return whatever the last message is. + """ + long_message = random.choice(messages) + if len(long_message.split()) < 3 and retries > 0: + return self._get_random_long_message( + messages, + retries=retries - 1 + ) + + return long_message + + async def _get_snek(self, name: str) -> Dict[str, Any]: + """ + Fetches all the data from a wikipedia article about a snake. + + Builds a dict that the .get() method can use. + + Created by Ava and eivl. + """ + snake_info = {} + + async with aiohttp.ClientSession() as session: + params = { + 'format': 'json', + 'action': 'query', + 'list': 'search', + 'srsearch': name, + 'utf8': '', + 'srlimit': '1', + } + + json = await self._fetch(session, URL, params=params) + + # Wikipedia does have a error page + try: + pageid = json["query"]["search"][0]["pageid"] + except KeyError: + # Wikipedia error page ID(?) + pageid = 41118 + except IndexError: + return None + + params = { + 'format': 'json', + 'action': 'query', + 'prop': 'extracts|images|info', + 'exlimit': 'max', + 'explaintext': '', + 'inprop': 'url', + 'pageids': pageid + } + + json = await self._fetch(session, URL, params=params) + + # Constructing dict - handle exceptions later + try: + snake_info["title"] = json["query"]["pages"][f"{pageid}"]["title"] + snake_info["extract"] = json["query"]["pages"][f"{pageid}"]["extract"] + snake_info["images"] = json["query"]["pages"][f"{pageid}"]["images"] + snake_info["fullurl"] = json["query"]["pages"][f"{pageid}"]["fullurl"] + snake_info["pageid"] = json["query"]["pages"][f"{pageid}"]["pageid"] + except KeyError: + snake_info["error"] = True + + if snake_info["images"]: + i_url = 'https://commons.wikimedia.org/wiki/Special:FilePath/' + image_list = [] + map_list = [] + thumb_list = [] + + # Wikipedia has arbitrary images that are not snakes + banned = [ + 'Commons-logo.svg', + 'Red%20Pencil%20Icon.png', + 'distribution', + 'The%20Death%20of%20Cleopatra%20arthur.jpg', + 'Head%20of%20holotype', + 'locator', + 'Woma.png', + '-map.', + '.svg', + 'ange.', + 'Adder%20(PSF).png' + ] + + for image in snake_info["images"]: + # Images come in the format of `File:filename.extension` + file, sep, filename = image["title"].partition(':') + filename = filename.replace(" ", "%20") # Wikipedia returns good data! + + if not filename.startswith('Map'): + if any(ban in filename for ban in banned): + pass + else: + image_list.append(f"{i_url}{filename}") + thumb_list.append(f"{i_url}{filename}?width=100") + else: + map_list.append(f"{i_url}{filename}") + + snake_info["image_list"] = image_list + snake_info["map_list"] = map_list + snake_info["thumb_list"] = thumb_list + snake_info["name"] = name + + match = self.wiki_brief.match(snake_info['extract']) + info = match.group(1) if match else None + + if info: + info = info.replace("\n", "\n\n") # Give us some proper paragraphs. + + snake_info["info"] = info + + return snake_info + + async def _get_snake_name(self) -> Dict[str, str]: + """Gets a random snake name.""" + return random.choice(self.snake_names) + + async def _validate_answer(self, ctx: Context, message: Message, answer: str, options: list) -> None: + """Validate the answer using a reaction event loop.""" + def predicate(reaction: Reaction, user: Member) -> bool: + """Test if the the answer is valid and can be evaluated.""" + return ( + reaction.message.id == message.id # The reaction is attached to the question we asked. + and user == ctx.author # It's the user who triggered the quiz. + and str(reaction.emoji) in ANSWERS_EMOJI.values() # The reaction is one of the options. + ) + + for emoji in ANSWERS_EMOJI.values(): + await message.add_reaction(emoji) + + # Validate the answer + try: + reaction, user = await ctx.bot.wait_for("reaction_add", timeout=45.0, check=predicate) + except asyncio.TimeoutError: + await ctx.channel.send(f"You took too long. The correct answer was **{options[answer]}**.") + await message.clear_reactions() + return + + if str(reaction.emoji) == ANSWERS_EMOJI[answer]: + await ctx.send(f"{random.choice(CORRECT_GUESS)} The correct answer was **{options[answer]}**.") + else: + await ctx.send( + f"{random.choice(INCORRECT_GUESS)} The correct answer was **{options[answer]}**." + ) + + await message.clear_reactions() + # endregion + + # region: Commands + @group(name='snakes', aliases=('snake',), invoke_without_command=True) + async def snakes_group(self, ctx: Context) -> None: + """Commands from our first code jam.""" + await ctx.send_help(ctx.command) + + @bot_has_permissions(manage_messages=True) + @snakes_group.command(name='antidote') + @locked() + async def antidote_command(self, ctx: Context) -> None: + """ + Antidote! Can you create the antivenom before the patient dies? + + Rules: You have 4 ingredients for each antidote, you only have 10 attempts + Once you synthesize the antidote, you will be presented with 4 markers + Tick: This means you have a CORRECT ingredient in the CORRECT position + Circle: This means you have a CORRECT ingredient in the WRONG position + Cross: This means you have a WRONG ingredient in the WRONG position + + Info: The game automatically ends after 5 minutes inactivity. + You should only use each ingredient once. + + This game was created by Lord Bisk and Runew0lf. + """ + def predicate(reaction_: Reaction, user_: Member) -> bool: + """Make sure that this reaction is what we want to operate on.""" + return ( + all(( + # Reaction is on this message + reaction_.message.id == board_id.id, + # Reaction is one of the pagination emotes + reaction_.emoji in ANTIDOTE_EMOJI, + # Reaction was not made by the Bot + user_.id != self.bot.user.id, + # Reaction was made by author + user_.id == ctx.author.id + )) + ) + + # Initialize variables + antidote_tries = 0 + antidote_guess_count = 0 + antidote_guess_list = [] + guess_result = [] + board = [] + page_guess_list = [] + page_result_list = [] + win = False + + antidote_embed = Embed(color=SNAKE_COLOR, title="Antidote") + antidote_embed.set_author(name=ctx.author.name, icon_url=ctx.author.avatar_url) + + # Generate answer + antidote_answer = list(ANTIDOTE_EMOJI) # Duplicate list, not reference it + random.shuffle(antidote_answer) + antidote_answer.pop() + + # Begin initial board building + for i in range(0, 10): + page_guess_list.append(f"{HOLE_EMOJI} {HOLE_EMOJI} {HOLE_EMOJI} {HOLE_EMOJI}") + page_result_list.append(f"{CROSS_EMOJI} {CROSS_EMOJI} {CROSS_EMOJI} {CROSS_EMOJI}") + board.append(f"`{i+1:02d}` " + f"{page_guess_list[i]} - " + f"{page_result_list[i]}") + board.append(EMPTY_UNICODE) + antidote_embed.add_field(name="10 guesses remaining", value="\n".join(board)) + board_id = await ctx.send(embed=antidote_embed) # Display board + + # Add our player reactions + for emoji in ANTIDOTE_EMOJI: + await board_id.add_reaction(emoji) + + # Begin main game loop + while not win and antidote_tries < 10: + try: + reaction, user = await ctx.bot.wait_for( + "reaction_add", timeout=300, check=predicate) + except asyncio.TimeoutError: + log.debug("Antidote timed out waiting for a reaction") + break # We're done, no reactions for the last 5 minutes + + if antidote_tries < 10: + if antidote_guess_count < 4: + if reaction.emoji in ANTIDOTE_EMOJI: + antidote_guess_list.append(reaction.emoji) + antidote_guess_count += 1 + + if antidote_guess_count == 4: # Guesses complete + antidote_guess_count = 0 + page_guess_list[antidote_tries] = " ".join(antidote_guess_list) + + # Now check guess + for i in range(0, len(antidote_answer)): + if antidote_guess_list[i] == antidote_answer[i]: + guess_result.append(TICK_EMOJI) + elif antidote_guess_list[i] in antidote_answer: + guess_result.append(BLANK_EMOJI) + else: + guess_result.append(CROSS_EMOJI) + guess_result.sort() + page_result_list[antidote_tries] = " ".join(guess_result) + + # Rebuild the board + board = [] + for i in range(0, 10): + board.append(f"`{i+1:02d}` " + f"{page_guess_list[i]} - " + f"{page_result_list[i]}") + board.append(EMPTY_UNICODE) + + # Remove Reactions + for emoji in antidote_guess_list: + await board_id.remove_reaction(emoji, user) + + if antidote_guess_list == antidote_answer: + win = True + + antidote_tries += 1 + guess_result = [] + antidote_guess_list = [] + + antidote_embed.clear_fields() + antidote_embed.add_field(name=f"{10 - antidote_tries} " + f"guesses remaining", + value="\n".join(board)) + # Redisplay the board + await board_id.edit(embed=antidote_embed) + + # Winning / Ending Screen + if win is True: + antidote_embed = Embed(color=SNAKE_COLOR, title="Antidote") + antidote_embed.set_author(name=ctx.author.name, icon_url=ctx.author.avatar_url) + antidote_embed.set_image(url="https://i.makeagif.com/media/7-12-2015/Cj1pts.gif") + antidote_embed.add_field(name="You have created the snake antidote!", + value=f"The solution was: {' '.join(antidote_answer)}\n" + f"You had {10 - antidote_tries} tries remaining.") + await board_id.edit(embed=antidote_embed) + else: + antidote_embed = Embed(color=SNAKE_COLOR, title="Antidote") + antidote_embed.set_author(name=ctx.author.name, icon_url=ctx.author.avatar_url) + antidote_embed.set_image(url="https://media.giphy.com/media/ceeN6U57leAhi/giphy.gif") + antidote_embed.add_field(name=EMPTY_UNICODE, + value=f"Sorry you didnt make the antidote in time.\n" + f"The formula was {' '.join(antidote_answer)}") + await board_id.edit(embed=antidote_embed) + + log.debug("Ending pagination and removing all reactions...") + await board_id.clear_reactions() + + @snakes_group.command(name='draw') + async def draw_command(self, ctx: Context) -> None: + """ + Draws a random snek using Perlin noise. + + Written by Momo and kel. + Modified by juan and lemon. + """ + with ctx.typing(): + + # Generate random snake attributes + width = random.randint(6, 10) + length = random.randint(15, 22) + random_hue = random.random() + snek_color = self._beautiful_pastel(random_hue) + text_color = self._beautiful_pastel((random_hue + 0.5) % 1) + bg_color = ( + random.randint(32, 50), + random.randint(32, 50), + random.randint(50, 70), + ) + + # Build and send the snek + text = random.choice(self.snake_idioms)["idiom"] + factory = utils.PerlinNoiseFactory(dimension=1, octaves=2) + image_frame = utils.create_snek_frame( + factory, + snake_width=width, + snake_length=length, + snake_color=snek_color, + text=text, + text_color=text_color, + bg_color=bg_color + ) + png_bytes = utils.frame_to_png_bytes(image_frame) + file = File(png_bytes, filename='snek.png') + await ctx.send(file=file) + + @snakes_group.command(name='get') + @bot_has_permissions(manage_messages=True) + @locked() + async def get_command(self, ctx: Context, *, name: Snake = None) -> None: + """ + Fetches information about a snake from Wikipedia. + + Created by Ava and eivl. + """ + with ctx.typing(): + if name is None: + name = await Snake.random() + + if isinstance(name, dict): + data = name + else: + data = await self._get_snek(name) + + if data.get('error'): + return await ctx.send('Could not fetch data from Wikipedia.') + + description = data["info"] + + # Shorten the description if needed + if len(description) > 1000: + description = description[:1000] + last_newline = description.rfind("\n") + if last_newline > 0: + description = description[:last_newline] + + # Strip and add the Wiki link. + if "fullurl" in data: + description = description.strip("\n") + description += f"\n\nRead more on [Wikipedia]({data['fullurl']})" + + # Build and send the embed. + embed = Embed( + title=data.get("title", data.get('name')), + description=description, + colour=0x59982F, + ) + + emoji = 'https://emojipedia-us.s3.amazonaws.com/thumbs/60/google/3/snake_1f40d.png' + image = next((url for url in data['image_list'] + if url.endswith(self.valid_image_extensions)), emoji) + embed.set_image(url=image) + + await ctx.send(embed=embed) + + @snakes_group.command(name='guess', aliases=('identify',)) + @locked() + async def guess_command(self, ctx: Context) -> None: + """ + Snake identifying game. + + Made by Ava and eivl. + Modified by lemon. + """ + with ctx.typing(): + + image = None + + while image is None: + snakes = [await Snake.random() for _ in range(4)] + snake = random.choice(snakes) + answer = "abcd"[snakes.index(snake)] + + data = await self._get_snek(snake) + + image = next((url for url in data['image_list'] + if url.endswith(self.valid_image_extensions)), None) + + embed = Embed( + title='Which of the following is the snake in the image?', + description="\n".join( + f"{'ABCD'[snakes.index(snake)]}: {snake}" for snake in snakes), + colour=SNAKE_COLOR + ) + embed.set_image(url=image) + + guess = await ctx.send(embed=embed) + options = {f"{'abcd'[snakes.index(snake)]}": snake for snake in snakes} + await self._validate_answer(ctx, guess, answer, options) + + @snakes_group.command(name='hatch') + async def hatch_command(self, ctx: Context) -> None: + """ + Hatches your personal snake. + + Written by Momo and kel. + """ + # Pick a random snake to hatch. + snake_name = random.choice(list(utils.snakes.keys())) + snake_image = utils.snakes[snake_name] + + # Hatch the snake + message = await ctx.channel.send(embed=Embed(description="Hatching your snake :snake:...")) + await asyncio.sleep(1) + + for stage in utils.stages: + hatch_embed = Embed(description=stage) + await message.edit(embed=hatch_embed) + await asyncio.sleep(1) + await asyncio.sleep(1) + await message.delete() + + # Build and send the embed. + my_snake_embed = Embed(description=":tada: Congrats! You hatched: **{0}**".format(snake_name)) + my_snake_embed.set_thumbnail(url=snake_image) + my_snake_embed.set_footer( + text=" Owner: {0}#{1}".format(ctx.message.author.name, ctx.message.author.discriminator) + ) + + await ctx.channel.send(embed=my_snake_embed) + + @snakes_group.command(name='movie') + async def movie_command(self, ctx: Context) -> None: + """ + Gets a random snake-related movie from OMDB. + + Written by Samuel. + Modified by gdude. + """ + url = "http://www.omdbapi.com/" + page = random.randint(1, 27) + + response = await self.bot.http_session.get( + url, + params={ + "s": "snake", + "page": page, + "type": "movie", + "apikey": Tokens.omdb + } + ) + data = await response.json() + movie = random.choice(data["Search"])["imdbID"] + + response = await self.bot.http_session.get( + url, + params={ + "i": movie, + "apikey": Tokens.omdb + } + ) + data = await response.json() + + embed = Embed( + title=data["Title"], + color=SNAKE_COLOR + ) + + del data["Response"], data["imdbID"], data["Title"] + + for key, value in data.items(): + if not value or value == "N/A" or key in ("Response", "imdbID", "Title", "Type"): + continue + + if key == "Ratings": # [{'Source': 'Internet Movie Database', 'Value': '7.6/10'}] + rating = random.choice(value) + + if rating["Source"] != "Internet Movie Database": + embed.add_field(name=f"Rating: {rating['Source']}", value=rating["Value"]) + + continue + + if key == "Poster": + embed.set_image(url=value) + continue + + elif key == "imdbRating": + key = "IMDB Rating" + + elif key == "imdbVotes": + key = "IMDB Votes" + + embed.add_field(name=key, value=value, inline=True) + + embed.set_footer(text="Data provided by the OMDB API") + + await ctx.channel.send( + embed=embed + ) + + @snakes_group.command(name='quiz') + @locked() + async def quiz_command(self, ctx: Context) -> None: + """ + Asks a snake-related question in the chat and validates the user's guess. + + This was created by Mushy and Cardium, + and modified by Urthas and lemon. + """ + # Prepare a question. + question = random.choice(self.snake_quizzes) + answer = question["answerkey"] + options = {key: question["options"][key] for key in ANSWERS_EMOJI.keys()} + + # Build and send the embed. + embed = Embed( + color=SNAKE_COLOR, + title=question["question"], + description="\n".join( + [f"**{key.upper()}**: {answer}" for key, answer in options.items()] + ) + ) + + quiz = await ctx.channel.send("", embed=embed) + await self._validate_answer(ctx, quiz, answer, options) + + @snakes_group.command(name='name', aliases=('name_gen',)) + async def name_command(self, ctx: Context, *, name: str = None) -> None: + """ + Snakifies a username. + + Slices the users name at the last vowel (or second last if the name + ends with a vowel), and then combines it with a random snake name, + which is sliced at the first vowel (or second if the name starts with + a vowel). + + If the name contains no vowels, it just appends the snakename + to the end of the name. + + Examples: + lemon + anaconda = lemoconda + krzsn + anaconda = krzsnconda + gdude + anaconda = gduconda + aperture + anaconda = apertuconda + lucy + python = luthon + joseph + taipan = joseipan + + This was written by Iceman, and modified for inclusion into the bot by lemon. + """ + snake_name = await self._get_snake_name() + snake_name = snake_name['name'] + snake_prefix = "" + + # Set aside every word in the snake name except the last. + if " " in snake_name: + snake_prefix = " ".join(snake_name.split()[:-1]) + snake_name = snake_name.split()[-1] + + # If no name is provided, use whoever called the command. + if name: + user_name = name + else: + user_name = ctx.author.display_name + + # Get the index of the vowel to slice the username at + user_slice_index = len(user_name) + for index, char in enumerate(reversed(user_name)): + if index == 0: + continue + if char.lower() in "aeiouy": + user_slice_index -= index + break + + # Now, get the index of the vowel to slice the snake_name at + snake_slice_index = 0 + for index, char in enumerate(snake_name): + if index == 0: + continue + if char.lower() in "aeiouy": + snake_slice_index = index + 1 + break + + # Combine! + snake_name = snake_name[snake_slice_index:] + user_name = user_name[:user_slice_index] + result = f"{snake_prefix} {user_name}{snake_name}" + result = string.capwords(result) + + # Embed and send + embed = Embed( + title="Snake name", + description=f"Your snake-name is **{result}**", + color=SNAKE_COLOR + ) + + return await ctx.send(embed=embed) + + @snakes_group.command(name='sal') + @locked() + async def sal_command(self, ctx: Context) -> None: + """ + Play a game of Snakes and Ladders. + + Written by Momo and kel. + Modified by lemon. + """ + # Check if there is already a game in this channel + if ctx.channel in self.active_sal: + await ctx.send(f"{ctx.author.mention} A game is already in progress in this channel.") + return + + game = utils.SnakeAndLaddersGame(snakes=self, context=ctx) + self.active_sal[ctx.channel] = game + + await game.open_game() + + @snakes_group.command(name='about') + async def about_command(self, ctx: Context) -> None: + """Show an embed with information about the event, its participants, and its winners.""" + contributors = [ + "<@!245270749919576066>", + "<@!396290259907903491>", + "<@!172395097705414656>", + "<@!361708843425726474>", + "<@!300302216663793665>", + "<@!210248051430916096>", + "<@!174588005745557505>", + "<@!87793066227822592>", + "<@!211619754039967744>", + "<@!97347867923976192>", + "<@!136081839474343936>", + "<@!263560579770220554>", + "<@!104749643715387392>", + "<@!303940835005825024>", + ] + + embed = Embed( + title="About the snake cog", + description=( + "The features in this cog were created by members of the community " + "during our first ever " + "[code jam event](https://pythondiscord.com/pages/code-jams/code-jam-1-snakes-bot/). \n\n" + "The event saw over 50 participants, who competed to write a discord bot cog with a snake theme over " + "48 hours. The staff then selected the best features from all the best teams, and made modifications " + "to ensure they would all work together before integrating them into the community bot.\n\n" + "It was a tight race, but in the end, <@!104749643715387392> and <@!303940835005825024> " + f"walked away as grand champions. Make sure you check out `{ctx.prefix}snakes sal`," + f"`{ctx.prefix}snakes draw` and `{ctx.prefix}snakes hatch` " + "to see what they came up with." + ) + ) + + embed.add_field( + name="Contributors", + value=( + ", ".join(contributors) + ) + ) + + await ctx.channel.send(embed=embed) + + @snakes_group.command(name='card') + async def card_command(self, ctx: Context, *, name: Snake = None) -> None: + """ + Create an interesting little card from a snake. + + Created by juan and Someone during the first code jam. + """ + # Get the snake data we need + if not name: + name_obj = await self._get_snake_name() + name = name_obj['scientific'] + content = await self._get_snek(name) + + elif isinstance(name, dict): + content = name + + else: + content = await self._get_snek(name) + + # Make the card + async with ctx.typing(): + + stream = BytesIO() + async with async_timeout.timeout(10): + async with self.bot.http_session.get(content['image_list'][0]) as response: + stream.write(await response.read()) + + stream.seek(0) + + func = partial(self._generate_card, stream, content) + final_buffer = await self.bot.loop.run_in_executor(None, func) + + # Send it! + await ctx.send( + f"A wild {content['name'].title()} appears!", + file=File(final_buffer, filename=content['name'].replace(" ", "") + ".png") + ) + + @snakes_group.command(name='fact') + async def fact_command(self, ctx: Context) -> None: + """ + Gets a snake-related fact. + + Written by Andrew and Prithaj. + Modified by lemon. + """ + question = random.choice(self.snake_facts)["fact"] + embed = Embed( + title="Snake fact", + color=SNAKE_COLOR, + description=question + ) + await ctx.channel.send(embed=embed) + + @snakes_group.command(name='snakify') + async def snakify_command(self, ctx: Context, *, message: str = None) -> None: + """ + How would I talk if I were a snake? + + If `message` is passed, the bot will snakify the message. + Otherwise, a random message from the user's history is snakified. + + Written by Momo and kel. + Modified by lemon. + """ + with ctx.typing(): + embed = Embed() + user = ctx.message.author + + if not message: + + # Get a random message from the users history + messages = [] + async for message in ctx.channel.history(limit=500).filter( + lambda msg: msg.author == ctx.message.author # Message was sent by author. + ): + messages.append(message.content) + + message = self._get_random_long_message(messages) + + # Set the avatar + if user.avatar is not None: + avatar = f"https://cdn.discordapp.com/avatars/{user.id}/{user.avatar}" + else: + avatar = ctx.author.default_avatar_url + + # Build and send the embed + embed.set_author( + name=f"{user.name}#{user.discriminator}", + icon_url=avatar, + ) + embed.description = f"*{self._snakify(message)}*" + + await ctx.channel.send(embed=embed) + + @snakes_group.command(name='video', aliases=('get_video',)) + async def video_command(self, ctx: Context, *, search: str = None) -> None: + """ + Gets a YouTube video about snakes. + + If `search` is given, a snake with that name will be searched on Youtube. + + Written by Andrew and Prithaj. + """ + # Are we searching for anything specific? + if search: + query = search + ' snake' + else: + snake = await self._get_snake_name() + query = snake['name'] + + # Build the URL and make the request + url = 'https://www.googleapis.com/youtube/v3/search' + response = await self.bot.http_session.get( + url, + params={ + "part": "snippet", + "q": urllib.parse.quote(query), + "type": "video", + "key": Tokens.youtube + } + ) + response = await response.json() + data = response['items'] + + # Send the user a video + if len(data) > 0: + num = random.randint(0, len(data) - 1) + youtube_base_url = 'https://www.youtube.com/watch?v=' + await ctx.channel.send( + content=f"{youtube_base_url}{data[num]['id']['videoId']}" + ) + else: + log.warning(f"YouTube API error. Full response looks like {response}") + + @snakes_group.command(name='zen') + async def zen_command(self, ctx: Context) -> None: + """ + Gets a random quote from the Zen of Python, except as if spoken by a snake. + + Written by Prithaj and Andrew. + Modified by lemon. + """ + embed = Embed( + title="Zzzen of Pythhon", + color=SNAKE_COLOR + ) + + # Get the zen quote and snakify it + zen_quote = random.choice(ZEN.splitlines()) + zen_quote = self._snakify(zen_quote) + + # Embed and send + embed.description = zen_quote + await ctx.channel.send( + embed=embed + ) + # endregion + + # region: Error handlers + @get_command.error + @card_command.error + @video_command.error + async def command_error(self, ctx: Context, error: CommandError) -> None: + """Local error handler for the Snake Cog.""" + embed = Embed() + embed.colour = Colour.red() + + if isinstance(error, BadArgument): + embed.description = str(error) + embed.title = random.choice(ERROR_REPLIES) + + elif isinstance(error, OSError): + log.error(f"snake_card encountered an OSError: {error} ({error.original})") + embed.description = "Could not generate the snake card! Please try again." + embed.title = random.choice(ERROR_REPLIES) + + else: + log.error(f"Unhandled tag command error: {error} ({error.original})") + return + + await ctx.send(embed=embed) + # endregion diff --git a/bot/exts/evergreen/snakes/_utils.py b/bot/exts/evergreen/snakes/_utils.py new file mode 100644 index 00000000..7d6caf04 --- /dev/null +++ b/bot/exts/evergreen/snakes/_utils.py @@ -0,0 +1,716 @@ +import asyncio +import io +import json +import logging +import math +import random +from itertools import product +from pathlib import Path +from typing import List, Tuple + +from PIL import Image +from PIL.ImageDraw import ImageDraw +from discord import File, Member, Reaction +from discord.ext.commands import Cog, Context + +from bot.constants import Roles + +SNAKE_RESOURCES = Path("bot/resources/snakes").absolute() + +h1 = r'''``` + ---- + ------ + /--------\ + |--------| + |--------| + \------/ + ----```''' +h2 = r'''``` + ---- + ------ + /---\-/--\ + |-----\--| + |--------| + \------/ + ----```''' +h3 = r'''``` + ---- + ------ + /---\-/--\ + |-----\--| + |-----/--| + \----\-/ + ----```''' +h4 = r'''``` + ----- + ----- \ + /--| /---\ + |--\ -\---| + |--\--/-- / + \------- / + ------```''' +stages = [h1, h2, h3, h4] +snakes = { + "Baby Python": "https://i.imgur.com/SYOcmSa.png", + "Baby Rattle Snake": "https://i.imgur.com/i5jYA8f.png", + "Baby Dragon Snake": "https://i.imgur.com/SuMKM4m.png", + "Baby Garden Snake": "https://i.imgur.com/5vYx3ah.png", + "Baby Cobra": "https://i.imgur.com/jk14ryt.png" +} + +BOARD_TILE_SIZE = 56 # the size of each board tile +BOARD_PLAYER_SIZE = 20 # the size of each player icon +BOARD_MARGIN = (10, 0) # margins, in pixels (for player icons) +# The size of the image to download +# Should a power of 2 and higher than BOARD_PLAYER_SIZE +PLAYER_ICON_IMAGE_SIZE = 32 +MAX_PLAYERS = 4 # depends on the board size/quality, 4 is for the default board + +# board definition (from, to) +BOARD = { + # ladders + 2: 38, + 7: 14, + 8: 31, + 15: 26, + 21: 42, + 28: 84, + 36: 44, + 51: 67, + 71: 91, + 78: 98, + 87: 94, + + # snakes + 99: 80, + 95: 75, + 92: 88, + 89: 68, + 74: 53, + 64: 60, + 62: 19, + 49: 11, + 46: 25, + 16: 6 +} + +DEFAULT_SNAKE_COLOR: int = 0x15c7ea +DEFAULT_BACKGROUND_COLOR: int = 0 +DEFAULT_IMAGE_DIMENSIONS: Tuple[int] = (200, 200) +DEFAULT_SNAKE_LENGTH: int = 22 +DEFAULT_SNAKE_WIDTH: int = 8 +DEFAULT_SEGMENT_LENGTH_RANGE: Tuple[int] = (7, 10) +DEFAULT_IMAGE_MARGINS: Tuple[int] = (50, 50) +DEFAULT_TEXT: str = "snek\nit\nup" +DEFAULT_TEXT_POSITION: Tuple[int] = ( + 10, + 10 +) +DEFAULT_TEXT_COLOR: int = 0xf2ea15 +X = 0 +Y = 1 +ANGLE_RANGE = math.pi * 2 + + +def get_resource(file: str) -> List[dict]: + """Load Snake resources JSON.""" + with (SNAKE_RESOURCES / f"{file}.json").open(encoding="utf-8") as snakefile: + return json.load(snakefile) + + +def smoothstep(t: float) -> float: + """Smooth curve with a zero derivative at 0 and 1, making it useful for interpolating.""" + return t * t * (3. - 2. * t) + + +def lerp(t: float, a: float, b: float) -> float: + """Linear interpolation between a and b, given a fraction t.""" + return a + t * (b - a) + + +class PerlinNoiseFactory(object): + """ + Callable that produces Perlin noise for an arbitrary point in an arbitrary number of dimensions. + + The underlying grid is aligned with the integers. + + There is no limit to the coordinates used; new gradients are generated on the fly as necessary. + + Taken from: https://gist.github.com/eevee/26f547457522755cb1fb8739d0ea89a1 + Licensed under ISC + """ + + def __init__(self, dimension: int, octaves: int = 1, tile: Tuple[int] = (), unbias: bool = False): + """ + Create a new Perlin noise factory in the given number of dimensions. + + dimension should be an integer and at least 1. + + More octaves create a foggier and more-detailed noise pattern. More than 4 octaves is rather excessive. + + ``tile`` can be used to make a seamlessly tiling pattern. + For example: + pnf = PerlinNoiseFactory(2, tile=(0, 3)) + + This will produce noise that tiles every 3 units vertically, but never tiles horizontally. + + If ``unbias`` is True, the smoothstep function will be applied to the output before returning + it, to counteract some of Perlin noise's significant bias towards the center of its output range. + """ + self.dimension = dimension + self.octaves = octaves + self.tile = tile + (0,) * dimension + self.unbias = unbias + + # For n dimensions, the range of Perlin noise is ±sqrt(n)/2; multiply + # by this to scale to ±1 + self.scale_factor = 2 * dimension ** -0.5 + + self.gradient = {} + + def _generate_gradient(self) -> Tuple[float, ...]: + """ + Generate a random unit vector at each grid point. + + This is the "gradient" vector, in that the grid tile slopes towards it + """ + # 1 dimension is special, since the only unit vector is trivial; + # instead, use a slope between -1 and 1 + if self.dimension == 1: + return (random.uniform(-1, 1),) + + # Generate a random point on the surface of the unit n-hypersphere; + # this is the same as a random unit vector in n dimensions. Thanks + # to: http://mathworld.wolfram.com/SpherePointPicking.html + # Pick n normal random variables with stddev 1 + random_point = [random.gauss(0, 1) for _ in range(self.dimension)] + # Then scale the result to a unit vector + scale = sum(n * n for n in random_point) ** -0.5 + return tuple(coord * scale for coord in random_point) + + def get_plain_noise(self, *point) -> float: + """Get plain noise for a single point, without taking into account either octaves or tiling.""" + if len(point) != self.dimension: + raise ValueError("Expected {0} values, got {1}".format( + self.dimension, len(point))) + + # Build a list of the (min, max) bounds in each dimension + grid_coords = [] + for coord in point: + min_coord = math.floor(coord) + max_coord = min_coord + 1 + grid_coords.append((min_coord, max_coord)) + + # Compute the dot product of each gradient vector and the point's + # distance from the corresponding grid point. This gives you each + # gradient's "influence" on the chosen point. + dots = [] + for grid_point in product(*grid_coords): + if grid_point not in self.gradient: + self.gradient[grid_point] = self._generate_gradient() + gradient = self.gradient[grid_point] + + dot = 0 + for i in range(self.dimension): + dot += gradient[i] * (point[i] - grid_point[i]) + dots.append(dot) + + # Interpolate all those dot products together. The interpolation is + # done with smoothstep to smooth out the slope as you pass from one + # grid cell into the next. + # Due to the way product() works, dot products are ordered such that + # the last dimension alternates: (..., min), (..., max), etc. So we + # can interpolate adjacent pairs to "collapse" that last dimension. Then + # the results will alternate in their second-to-last dimension, and so + # forth, until we only have a single value left. + dim = self.dimension + while len(dots) > 1: + dim -= 1 + s = smoothstep(point[dim] - grid_coords[dim][0]) + + next_dots = [] + while dots: + next_dots.append(lerp(s, dots.pop(0), dots.pop(0))) + + dots = next_dots + + return dots[0] * self.scale_factor + + def __call__(self, *point) -> float: + """ + Get the value of this Perlin noise function at the given point. + + The number of values given should match the number of dimensions. + """ + ret = 0 + for o in range(self.octaves): + o2 = 1 << o + new_point = [] + for i, coord in enumerate(point): + coord *= o2 + if self.tile[i]: + coord %= self.tile[i] * o2 + new_point.append(coord) + ret += self.get_plain_noise(*new_point) / o2 + + # Need to scale n back down since adding all those extra octaves has + # probably expanded it beyond ±1 + # 1 octave: ±1 + # 2 octaves: ±1½ + # 3 octaves: ±1¾ + ret /= 2 - 2 ** (1 - self.octaves) + + if self.unbias: + # The output of the plain Perlin noise algorithm has a fairly + # strong bias towards the center due to the central limit theorem + # -- in fact the top and bottom 1/8 virtually never happen. That's + # a quarter of our entire output range! If only we had a function + # in [0..1] that could introduce a bias towards the endpoints... + r = (ret + 1) / 2 + # Doing it this many times is a completely made-up heuristic. + for _ in range(int(self.octaves / 2 + 0.5)): + r = smoothstep(r) + ret = r * 2 - 1 + + return ret + + +def create_snek_frame( + perlin_factory: PerlinNoiseFactory, perlin_lookup_vertical_shift: float = 0, + image_dimensions: Tuple[int] = DEFAULT_IMAGE_DIMENSIONS, image_margins: Tuple[int] = DEFAULT_IMAGE_MARGINS, + snake_length: int = DEFAULT_SNAKE_LENGTH, + snake_color: int = DEFAULT_SNAKE_COLOR, bg_color: int = DEFAULT_BACKGROUND_COLOR, + segment_length_range: Tuple[int] = DEFAULT_SEGMENT_LENGTH_RANGE, snake_width: int = DEFAULT_SNAKE_WIDTH, + text: str = DEFAULT_TEXT, text_position: Tuple[int] = DEFAULT_TEXT_POSITION, + text_color: Tuple[int] = DEFAULT_TEXT_COLOR +) -> Image: + """ + Creates a single random snek frame using Perlin noise. + + `perlin_lookup_vertical_shift` represents the Perlin noise shift in the Y-dimension for this frame. + If `text` is given, display the given text with the snek. + """ + start_x = random.randint(image_margins[X], image_dimensions[X] - image_margins[X]) + start_y = random.randint(image_margins[Y], image_dimensions[Y] - image_margins[Y]) + points = [(start_x, start_y)] + + for index in range(0, snake_length): + angle = perlin_factory.get_plain_noise( + ((1 / (snake_length + 1)) * (index + 1)) + perlin_lookup_vertical_shift + ) * ANGLE_RANGE + current_point = points[index] + segment_length = random.randint(segment_length_range[0], segment_length_range[1]) + points.append(( + current_point[X] + segment_length * math.cos(angle), + current_point[Y] + segment_length * math.sin(angle) + )) + + # normalize bounds + min_dimensions = [start_x, start_y] + max_dimensions = [start_x, start_y] + for point in points: + min_dimensions[X] = min(point[X], min_dimensions[X]) + min_dimensions[Y] = min(point[Y], min_dimensions[Y]) + max_dimensions[X] = max(point[X], max_dimensions[X]) + max_dimensions[Y] = max(point[Y], max_dimensions[Y]) + + # shift towards middle + dimension_range = (max_dimensions[X] - min_dimensions[X], max_dimensions[Y] - min_dimensions[Y]) + shift = ( + image_dimensions[X] / 2 - (dimension_range[X] / 2 + min_dimensions[X]), + image_dimensions[Y] / 2 - (dimension_range[Y] / 2 + min_dimensions[Y]) + ) + + image = Image.new(mode='RGB', size=image_dimensions, color=bg_color) + draw = ImageDraw(image) + for index in range(1, len(points)): + point = points[index] + previous = points[index - 1] + draw.line( + ( + shift[X] + previous[X], + shift[Y] + previous[Y], + shift[X] + point[X], + shift[Y] + point[Y] + ), + width=snake_width, + fill=snake_color + ) + if text is not None: + draw.multiline_text(text_position, text, fill=text_color) + del draw + return image + + +def frame_to_png_bytes(image: Image) -> io.BytesIO: + """Convert image to byte stream.""" + stream = io.BytesIO() + image.save(stream, format='PNG') + stream.seek(0) + return stream + + +log = logging.getLogger(__name__) +START_EMOJI = "\u2611" # :ballot_box_with_check: - Start the game +CANCEL_EMOJI = "\u274C" # :x: - Cancel or leave the game +ROLL_EMOJI = "\U0001F3B2" # :game_die: - Roll the die! +JOIN_EMOJI = "\U0001F64B" # :raising_hand: - Join the game. +STARTUP_SCREEN_EMOJI = [ + JOIN_EMOJI, + START_EMOJI, + CANCEL_EMOJI +] +GAME_SCREEN_EMOJI = [ + ROLL_EMOJI, + CANCEL_EMOJI +] + + +class SnakeAndLaddersGame: + """Snakes and Ladders game Cog.""" + + def __init__(self, snakes: Cog, context: Context): + self.snakes = snakes + self.ctx = context + self.channel = self.ctx.channel + self.state = 'booting' + self.started = False + self.author = self.ctx.author + self.players = [] + self.player_tiles = {} + self.round_has_rolled = {} + self.avatar_images = {} + self.board = None + self.positions = None + self.rolls = [] + + async def open_game(self) -> None: + """ + Create a new Snakes and Ladders game. + + Listen for reactions until players have joined, and the game has been started. + """ + def startup_event_check(reaction_: Reaction, user_: Member) -> bool: + """Make sure that this reaction is what we want to operate on.""" + return ( + all(( + reaction_.message.id == startup.id, # Reaction is on startup message + reaction_.emoji in STARTUP_SCREEN_EMOJI, # Reaction is one of the startup emotes + user_.id != self.ctx.bot.user.id, # Reaction was not made by the bot + )) + ) + + # Check to see if the bot can remove reactions + if not self.channel.permissions_for(self.ctx.guild.me).manage_messages: + log.warning( + "Unable to start Snakes and Ladders - " + f"Missing manage_messages permissions in {self.channel}" + ) + return + + await self._add_player(self.author) + await self.channel.send( + "**Snakes and Ladders**: A new game is about to start!", + file=File( + str(SNAKE_RESOURCES / "snakes_and_ladders" / "banner.jpg"), + filename='Snakes and Ladders.jpg' + ) + ) + startup = await self.channel.send( + f"Press {JOIN_EMOJI} to participate, and press " + f"{START_EMOJI} to start the game" + ) + for emoji in STARTUP_SCREEN_EMOJI: + await startup.add_reaction(emoji) + + self.state = 'waiting' + + while not self.started: + try: + reaction, user = await self.ctx.bot.wait_for( + "reaction_add", + timeout=300, + check=startup_event_check + ) + if reaction.emoji == JOIN_EMOJI: + await self.player_join(user) + elif reaction.emoji == CANCEL_EMOJI: + if user == self.author or (self._is_moderator(user) and user not in self.players): + # Allow game author or non-playing moderation staff to cancel a waiting game + await self.cancel_game() + return + else: + await self.player_leave(user) + elif reaction.emoji == START_EMOJI: + if self.ctx.author == user: + self.started = True + await self.start_game(user) + await startup.delete() + break + + await startup.remove_reaction(reaction.emoji, user) + + except asyncio.TimeoutError: + log.debug("Snakes and Ladders timed out waiting for a reaction") + await self.cancel_game() + return # We're done, no reactions for the last 5 minutes + + async def _add_player(self, user: Member) -> None: + """Add player to game.""" + self.players.append(user) + self.player_tiles[user.id] = 1 + + avatar_bytes = await user.avatar_url_as(format='jpeg', size=PLAYER_ICON_IMAGE_SIZE).read() + im = Image.open(io.BytesIO(avatar_bytes)).resize((BOARD_PLAYER_SIZE, BOARD_PLAYER_SIZE)) + self.avatar_images[user.id] = im + + async def player_join(self, user: Member) -> None: + """ + Handle players joining the game. + + Prevent player joining if they have already joined, if the game is full, or if the game is + in a waiting state. + """ + for p in self.players: + if user == p: + await self.channel.send(user.mention + " You are already in the game.", delete_after=10) + return + if self.state != 'waiting': + await self.channel.send(user.mention + " You cannot join at this time.", delete_after=10) + return + if len(self.players) is MAX_PLAYERS: + await self.channel.send(user.mention + " The game is full!", delete_after=10) + return + + await self._add_player(user) + + await self.channel.send( + f"**Snakes and Ladders**: {user.mention} has joined the game.\n" + f"There are now {str(len(self.players))} players in the game.", + delete_after=10 + ) + + async def player_leave(self, user: Member) -> bool: + """ + Handle players leaving the game. + + Leaving is prevented if the user wasn't part of the game. + + If the number of players reaches 0, the game is terminated. In this case, a sentinel boolean + is returned True to prevent a game from continuing after it's destroyed. + """ + is_surrendered = False # Sentinel value to assist with stopping a surrendered game + for p in self.players: + if user == p: + self.players.remove(p) + self.player_tiles.pop(p.id, None) + self.round_has_rolled.pop(p.id, None) + await self.channel.send( + "**Snakes and Ladders**: " + user.mention + " has left the game.", + delete_after=10 + ) + + if self.state != 'waiting' and len(self.players) == 0: + await self.channel.send("**Snakes and Ladders**: The game has been surrendered!") + is_surrendered = True + self._destruct() + + return is_surrendered + else: + await self.channel.send(user.mention + " You are not in the match.", delete_after=10) + return is_surrendered + + async def cancel_game(self) -> None: + """Cancel the running game.""" + await self.channel.send("**Snakes and Ladders**: Game has been canceled.") + self._destruct() + + async def start_game(self, user: Member) -> None: + """ + Allow the game author to begin the game. + + The game cannot be started if the game is in a waiting state. + """ + if not user == self.author: + await self.channel.send(user.mention + " Only the author of the game can start it.", delete_after=10) + return + + if not self.state == 'waiting': + await self.channel.send(user.mention + " The game cannot be started at this time.", delete_after=10) + return + + self.state = 'starting' + player_list = ', '.join(user.mention for user in self.players) + await self.channel.send("**Snakes and Ladders**: The game is starting!\nPlayers: " + player_list) + await self.start_round() + + async def start_round(self) -> None: + """Begin the round.""" + def game_event_check(reaction_: Reaction, user_: Member) -> bool: + """Make sure that this reaction is what we want to operate on.""" + return ( + all(( + reaction_.message.id == self.positions.id, # Reaction is on positions message + reaction_.emoji in GAME_SCREEN_EMOJI, # Reaction is one of the game emotes + user_.id != self.ctx.bot.user.id, # Reaction was not made by the bot + )) + ) + + self.state = 'roll' + for user in self.players: + self.round_has_rolled[user.id] = False + board_img = Image.open(str(SNAKE_RESOURCES / "snakes_and_ladders" / "board.jpg")) + player_row_size = math.ceil(MAX_PLAYERS / 2) + + for i, player in enumerate(self.players): + tile = self.player_tiles[player.id] + tile_coordinates = self._board_coordinate_from_index(tile) + x_offset = BOARD_MARGIN[0] + tile_coordinates[0] * BOARD_TILE_SIZE + y_offset = \ + BOARD_MARGIN[1] + ( + (10 * BOARD_TILE_SIZE) - (9 - tile_coordinates[1]) * BOARD_TILE_SIZE - BOARD_PLAYER_SIZE) + x_offset += BOARD_PLAYER_SIZE * (i % player_row_size) + y_offset -= BOARD_PLAYER_SIZE * math.floor(i / player_row_size) + board_img.paste(self.avatar_images[player.id], + box=(x_offset, y_offset)) + + board_file = File(frame_to_png_bytes(board_img), filename='Board.jpg') + player_list = '\n'.join((user.mention + ": Tile " + str(self.player_tiles[user.id])) for user in self.players) + + # Store and send new messages + temp_board = await self.channel.send( + "**Snakes and Ladders**: A new round has started! Current board:", + file=board_file + ) + temp_positions = await self.channel.send( + f"**Current positions**:\n{player_list}\n\nUse {ROLL_EMOJI} to roll the dice!" + ) + + # Delete the previous messages + if self.board and self.positions: + await self.board.delete() + await self.positions.delete() + + # remove the roll messages + for roll in self.rolls: + await roll.delete() + self.rolls = [] + + # Save new messages + self.board = temp_board + self.positions = temp_positions + + # Wait for rolls + for emoji in GAME_SCREEN_EMOJI: + await self.positions.add_reaction(emoji) + + is_surrendered = False + while True: + try: + reaction, user = await self.ctx.bot.wait_for( + "reaction_add", + timeout=300, + check=game_event_check + ) + + if reaction.emoji == ROLL_EMOJI: + await self.player_roll(user) + elif reaction.emoji == CANCEL_EMOJI: + if self._is_moderator(user) and user not in self.players: + # Only allow non-playing moderation staff to cancel a running game + await self.cancel_game() + return + else: + is_surrendered = await self.player_leave(user) + + await self.positions.remove_reaction(reaction.emoji, user) + + if self._check_all_rolled(): + break + + except asyncio.TimeoutError: + log.debug("Snakes and Ladders timed out waiting for a reaction") + await self.cancel_game() + return # We're done, no reactions for the last 5 minutes + + # Round completed + # Check to see if the game was surrendered before completing the round, without this + # sentinel, the game object would be deleted but the next round still posted into purgatory + if not is_surrendered: + await self._complete_round() + + async def player_roll(self, user: Member) -> None: + """Handle the player's roll.""" + if user.id not in self.player_tiles: + await self.channel.send(user.mention + " You are not in the match.", delete_after=10) + return + if self.state != 'roll': + await self.channel.send(user.mention + " You may not roll at this time.", delete_after=10) + return + if self.round_has_rolled[user.id]: + return + roll = random.randint(1, 6) + self.rolls.append(await self.channel.send(f"{user.mention} rolled a **{roll}**!")) + next_tile = self.player_tiles[user.id] + roll + + # apply snakes and ladders + if next_tile in BOARD: + target = BOARD[next_tile] + if target < next_tile: + await self.channel.send( + f"{user.mention} slips on a snake and falls back to **{target}**", + delete_after=15 + ) + else: + await self.channel.send( + f"{user.mention} climbs a ladder to **{target}**", + delete_after=15 + ) + next_tile = target + + self.player_tiles[user.id] = min(100, next_tile) + self.round_has_rolled[user.id] = True + + async def _complete_round(self) -> None: + """At the conclusion of a round check to see if there's been a winner.""" + self.state = 'post_round' + + # check for winner + winner = self._check_winner() + if winner is None: + # there is no winner, start the next round + await self.start_round() + return + + # announce winner and exit + await self.channel.send("**Snakes and Ladders**: " + winner.mention + " has won the game! :tada:") + self._destruct() + + def _check_winner(self) -> Member: + """Return a winning member if we're in the post-round state and there's a winner.""" + if self.state != 'post_round': + return None + return next((player for player in self.players if self.player_tiles[player.id] == 100), + None) + + def _check_all_rolled(self) -> bool: + """Check if all members have made their roll.""" + return all(rolled for rolled in self.round_has_rolled.values()) + + def _destruct(self) -> None: + """Clean up the finished game object.""" + del self.snakes.active_sal[self.channel] + + def _board_coordinate_from_index(self, index: int) -> Tuple[int, int]: + """Convert the tile number to the x/y coordinates for graphical purposes.""" + y_level = 9 - math.floor((index - 1) / 10) + is_reversed = math.floor((index - 1) / 10) % 2 != 0 + x_level = (index - 1) % 10 + if is_reversed: + x_level = 9 - x_level + return x_level, y_level + + @staticmethod + def _is_moderator(user: Member) -> bool: + """Return True if the user is a Moderator.""" + return any(Roles.moderator == role.id for role in user.roles) diff --git a/bot/exts/evergreen/snakes/converter.py b/bot/exts/evergreen/snakes/converter.py deleted file mode 100644 index 55609b8e..00000000 --- a/bot/exts/evergreen/snakes/converter.py +++ /dev/null @@ -1,85 +0,0 @@ -import json -import logging -import random -from typing import Iterable, List - -import discord -from discord.ext.commands import Context, Converter -from fuzzywuzzy import fuzz - -from bot.exts.evergreen.snakes.utils import SNAKE_RESOURCES -from bot.utils import disambiguate - -log = logging.getLogger(__name__) - - -class Snake(Converter): - """Snake converter for the Snakes Cog.""" - - snakes = None - special_cases = None - - async def convert(self, ctx: Context, name: str) -> str: - """Convert the input snake name to the closest matching Snake object.""" - await self.build_list() - name = name.lower() - - if name == 'python': - return 'Python (programming language)' - - def get_potential(iterable: Iterable, *, threshold: int = 80) -> List[str]: - nonlocal name - potential = [] - - for item in iterable: - original, item = item, item.lower() - - if name == item: - return [original] - - a, b = fuzz.ratio(name, item), fuzz.partial_ratio(name, item) - if a >= threshold or b >= threshold: - potential.append(original) - - return potential - - # Handle special cases - if name.lower() in self.special_cases: - return self.special_cases.get(name.lower(), name.lower()) - - names = {snake['name']: snake['scientific'] for snake in self.snakes} - all_names = names.keys() | names.values() - timeout = len(all_names) * (3 / 4) - - embed = discord.Embed( - title='Found multiple choices. Please choose the correct one.', colour=0x59982F) - embed.set_author(name=ctx.author.display_name, icon_url=ctx.author.avatar_url) - - name = await disambiguate(ctx, get_potential(all_names), timeout=timeout, embed=embed) - return names.get(name, name) - - @classmethod - async def build_list(cls) -> None: - """Build list of snakes from the static snake resources.""" - # Get all the snakes - if cls.snakes is None: - with (SNAKE_RESOURCES / "snake_names.json").open(encoding="utf8") as snakefile: - cls.snakes = json.load(snakefile) - - # Get the special cases - if cls.special_cases is None: - with (SNAKE_RESOURCES / "special_snakes.json").open(encoding="utf8") as snakefile: - special_cases = json.load(snakefile) - cls.special_cases = {snake['name'].lower(): snake for snake in special_cases} - - @classmethod - async def random(cls) -> str: - """ - Get a random Snake from the loaded resources. - - This is stupid. We should find a way to somehow get the global session into a global context, - so I can get it from here. - """ - await cls.build_list() - names = [snake['scientific'] for snake in cls.snakes] - return random.choice(names) diff --git a/bot/exts/evergreen/snakes/snakes_cog.py b/bot/exts/evergreen/snakes/snakes_cog.py deleted file mode 100644 index 9bbad9fe..00000000 --- a/bot/exts/evergreen/snakes/snakes_cog.py +++ /dev/null @@ -1,1151 +0,0 @@ -import asyncio -import colorsys -import logging -import os -import random -import re -import string -import textwrap -import urllib -from functools import partial -from io import BytesIO -from typing import Any, Dict, List - -import aiohttp -import async_timeout -from PIL import Image, ImageDraw, ImageFont -from discord import Colour, Embed, File, Member, Message, Reaction -from discord.ext.commands import BadArgument, Bot, Cog, CommandError, Context, bot_has_permissions, group - -from bot.constants import ERROR_REPLIES, Tokens -from bot.exts.evergreen.snakes import utils -from bot.exts.evergreen.snakes.converter import Snake -from bot.utils.decorators import locked - -log = logging.getLogger(__name__) - - -# region: Constants -# Color -SNAKE_COLOR = 0x399600 - -# Antidote constants -SYRINGE_EMOJI = "\U0001F489" # :syringe: -PILL_EMOJI = "\U0001F48A" # :pill: -HOURGLASS_EMOJI = "\u231B" # :hourglass: -CROSSBONES_EMOJI = "\u2620" # :skull_crossbones: -ALEMBIC_EMOJI = "\u2697" # :alembic: -TICK_EMOJI = "\u2705" # :white_check_mark: - Correct peg, correct hole -CROSS_EMOJI = "\u274C" # :x: - Wrong peg, wrong hole -BLANK_EMOJI = "\u26AA" # :white_circle: - Correct peg, wrong hole -HOLE_EMOJI = "\u2B1C" # :white_square: - Used in guesses -EMPTY_UNICODE = "\u200b" # literally just an empty space - -ANTIDOTE_EMOJI = ( - SYRINGE_EMOJI, - PILL_EMOJI, - HOURGLASS_EMOJI, - CROSSBONES_EMOJI, - ALEMBIC_EMOJI, -) - -# Quiz constants -ANSWERS_EMOJI = { - "a": "\U0001F1E6", # :regional_indicator_a: 🇦 - "b": "\U0001F1E7", # :regional_indicator_b: 🇧 - "c": "\U0001F1E8", # :regional_indicator_c: 🇨 - "d": "\U0001F1E9", # :regional_indicator_d: 🇩 -} - -ANSWERS_EMOJI_REVERSE = { - "\U0001F1E6": "A", # :regional_indicator_a: 🇦 - "\U0001F1E7": "B", # :regional_indicator_b: 🇧 - "\U0001F1E8": "C", # :regional_indicator_c: 🇨 - "\U0001F1E9": "D", # :regional_indicator_d: 🇩 -} - -# Zzzen of pythhhon constant -ZEN = """ -Beautiful is better than ugly. -Explicit is better than implicit. -Simple is better than complex. -Complex is better than complicated. -Flat is better than nested. -Sparse is better than dense. -Readability counts. -Special cases aren't special enough to break the rules. -Although practicality beats purity. -Errors should never pass silently. -Unless explicitly silenced. -In the face of ambiguity, refuse the temptation to guess. -There should be one-- and preferably only one --obvious way to do it. -Now is better than never. -Although never is often better than *right* now. -If the implementation is hard to explain, it's a bad idea. -If the implementation is easy to explain, it may be a good idea. -""" - -# Max messages to train snake_chat on -MSG_MAX = 100 - -# get_snek constants -URL = "https://en.wikipedia.org/w/api.php?" - -# snake guess responses -INCORRECT_GUESS = ( - "Nope, that's not what it is.", - "Not quite.", - "Not even close.", - "Terrible guess.", - "Nnnno.", - "Dude. No.", - "I thought everyone knew this one.", - "Guess you suck at snakes.", - "Bet you feel stupid now.", - "Hahahaha, no.", - "Did you hit the wrong key?" -) - -CORRECT_GUESS = ( - "**WRONG**. Wait, no, actually you're right.", - "Yeah, you got it!", - "Yep, that's exactly what it is.", - "Uh-huh. Yep yep yep.", - "Yeah that's right.", - "Yup. How did you know that?", - "Are you a herpetologist?", - "Sure, okay, but I bet you can't pronounce it.", - "Are you cheating?" -) - -# snake card consts -CARD = { - "top": Image.open("bot/resources/snakes/snake_cards/card_top.png"), - "frame": Image.open("bot/resources/snakes/snake_cards/card_frame.png"), - "bottom": Image.open("bot/resources/snakes/snake_cards/card_bottom.png"), - "backs": [ - Image.open(f"bot/resources/snakes/snake_cards/backs/{file}") - for file in os.listdir("bot/resources/snakes/snake_cards/backs") - ], - "font": ImageFont.truetype("bot/resources/snakes/snake_cards/expressway.ttf", 20) -} -# endregion - - -class Snakes(Cog): - """ - Commands related to snakes, created by our community during the first code jam. - - More information can be found in the code-jam-1 repo. - - https://github.com/python-discord/code-jam-1 - """ - - wiki_brief = re.compile(r'(.*?)(=+ (.*?) =+)', flags=re.DOTALL) - valid_image_extensions = ('gif', 'png', 'jpeg', 'jpg', 'webp') - - def __init__(self, bot: Bot): - self.active_sal = {} - self.bot = bot - self.snake_names = utils.get_resource("snake_names") - self.snake_idioms = utils.get_resource("snake_idioms") - self.snake_quizzes = utils.get_resource("snake_quiz") - self.snake_facts = utils.get_resource("snake_facts") - - # region: Helper methods - @staticmethod - def _beautiful_pastel(hue: float) -> int: - """Returns random bright pastels.""" - light = random.uniform(0.7, 0.85) - saturation = 1 - - rgb = colorsys.hls_to_rgb(hue, light, saturation) - hex_rgb = "" - - for part in rgb: - value = int(part * 0xFF) - hex_rgb += f"{value:02x}" - - return int(hex_rgb, 16) - - @staticmethod - def _generate_card(buffer: BytesIO, content: dict) -> BytesIO: - """ - Generate a card from snake information. - - Written by juan and Someone during the first code jam. - """ - snake = Image.open(buffer) - - # Get the size of the snake icon, configure the height of the image box (yes, it changes) - icon_width = 347 # Hardcoded, not much i can do about that - icon_height = int((icon_width / snake.width) * snake.height) - frame_copies = icon_height // CARD['frame'].height + 1 - snake.thumbnail((icon_width, icon_height)) - - # Get the dimensions of the final image - main_height = icon_height + CARD['top'].height + CARD['bottom'].height - main_width = CARD['frame'].width - - # Start creating the foreground - foreground = Image.new("RGBA", (main_width, main_height), (0, 0, 0, 0)) - foreground.paste(CARD['top'], (0, 0)) - - # Generate the frame borders to the correct height - for offset in range(frame_copies): - position = (0, CARD['top'].height + offset * CARD['frame'].height) - foreground.paste(CARD['frame'], position) - - # Add the image and bottom part of the image - foreground.paste(snake, (36, CARD['top'].height)) # Also hardcoded :( - foreground.paste(CARD['bottom'], (0, CARD['top'].height + icon_height)) - - # Setup the background - back = random.choice(CARD['backs']) - back_copies = main_height // back.height + 1 - full_image = Image.new("RGBA", (main_width, main_height), (0, 0, 0, 0)) - - # Generate the tiled background - for offset in range(back_copies): - full_image.paste(back, (16, 16 + offset * back.height)) - - # Place the foreground onto the final image - full_image.paste(foreground, (0, 0), foreground) - - # Get the first two sentences of the info - description = '.'.join(content['info'].split(".")[:2]) + '.' - - # Setup positioning variables - margin = 36 - offset = CARD['top'].height + icon_height + margin - - # Create blank rectangle image which will be behind the text - rectangle = Image.new( - "RGBA", - (main_width, main_height), - (0, 0, 0, 0) - ) - - # Draw a semi-transparent rectangle on it - rect = ImageDraw.Draw(rectangle) - rect.rectangle( - (margin, offset, main_width - margin, main_height - margin), - fill=(63, 63, 63, 128) - ) - - # Paste it onto the final image - full_image.paste(rectangle, (0, 0), mask=rectangle) - - # Draw the text onto the final image - draw = ImageDraw.Draw(full_image) - for line in textwrap.wrap(description, 36): - draw.text([margin + 4, offset], line, font=CARD['font']) - offset += CARD['font'].getsize(line)[1] - - # Get the image contents as a BufferIO object - buffer = BytesIO() - full_image.save(buffer, 'PNG') - buffer.seek(0) - - return buffer - - @staticmethod - def _snakify(message: str) -> str: - """Sssnakifffiesss a sstring.""" - # Replace fricatives with exaggerated snake fricatives. - simple_fricatives = [ - "f", "s", "z", "h", - "F", "S", "Z", "H", - ] - complex_fricatives = [ - "th", "sh", "Th", "Sh" - ] - - for letter in simple_fricatives: - if letter.islower(): - message = message.replace(letter, letter * random.randint(2, 4)) - else: - message = message.replace(letter, (letter * random.randint(2, 4)).title()) - - for fricative in complex_fricatives: - message = message.replace(fricative, fricative[0] + fricative[1] * random.randint(2, 4)) - - return message - - async def _fetch(self, session: aiohttp.ClientSession, url: str, params: dict = None) -> dict: - """Asynchronous web request helper method.""" - if params is None: - params = {} - - async with async_timeout.timeout(10): - async with session.get(url, params=params) as response: - return await response.json() - - def _get_random_long_message(self, messages: List[str], retries: int = 10) -> str: - """ - Fetch a message that's at least 3 words long, if possible to do so in retries attempts. - - Else, just return whatever the last message is. - """ - long_message = random.choice(messages) - if len(long_message.split()) < 3 and retries > 0: - return self._get_random_long_message( - messages, - retries=retries - 1 - ) - - return long_message - - async def _get_snek(self, name: str) -> Dict[str, Any]: - """ - Fetches all the data from a wikipedia article about a snake. - - Builds a dict that the .get() method can use. - - Created by Ava and eivl. - """ - snake_info = {} - - async with aiohttp.ClientSession() as session: - params = { - 'format': 'json', - 'action': 'query', - 'list': 'search', - 'srsearch': name, - 'utf8': '', - 'srlimit': '1', - } - - json = await self._fetch(session, URL, params=params) - - # Wikipedia does have a error page - try: - pageid = json["query"]["search"][0]["pageid"] - except KeyError: - # Wikipedia error page ID(?) - pageid = 41118 - except IndexError: - return None - - params = { - 'format': 'json', - 'action': 'query', - 'prop': 'extracts|images|info', - 'exlimit': 'max', - 'explaintext': '', - 'inprop': 'url', - 'pageids': pageid - } - - json = await self._fetch(session, URL, params=params) - - # Constructing dict - handle exceptions later - try: - snake_info["title"] = json["query"]["pages"][f"{pageid}"]["title"] - snake_info["extract"] = json["query"]["pages"][f"{pageid}"]["extract"] - snake_info["images"] = json["query"]["pages"][f"{pageid}"]["images"] - snake_info["fullurl"] = json["query"]["pages"][f"{pageid}"]["fullurl"] - snake_info["pageid"] = json["query"]["pages"][f"{pageid}"]["pageid"] - except KeyError: - snake_info["error"] = True - - if snake_info["images"]: - i_url = 'https://commons.wikimedia.org/wiki/Special:FilePath/' - image_list = [] - map_list = [] - thumb_list = [] - - # Wikipedia has arbitrary images that are not snakes - banned = [ - 'Commons-logo.svg', - 'Red%20Pencil%20Icon.png', - 'distribution', - 'The%20Death%20of%20Cleopatra%20arthur.jpg', - 'Head%20of%20holotype', - 'locator', - 'Woma.png', - '-map.', - '.svg', - 'ange.', - 'Adder%20(PSF).png' - ] - - for image in snake_info["images"]: - # Images come in the format of `File:filename.extension` - file, sep, filename = image["title"].partition(':') - filename = filename.replace(" ", "%20") # Wikipedia returns good data! - - if not filename.startswith('Map'): - if any(ban in filename for ban in banned): - pass - else: - image_list.append(f"{i_url}{filename}") - thumb_list.append(f"{i_url}{filename}?width=100") - else: - map_list.append(f"{i_url}{filename}") - - snake_info["image_list"] = image_list - snake_info["map_list"] = map_list - snake_info["thumb_list"] = thumb_list - snake_info["name"] = name - - match = self.wiki_brief.match(snake_info['extract']) - info = match.group(1) if match else None - - if info: - info = info.replace("\n", "\n\n") # Give us some proper paragraphs. - - snake_info["info"] = info - - return snake_info - - async def _get_snake_name(self) -> Dict[str, str]: - """Gets a random snake name.""" - return random.choice(self.snake_names) - - async def _validate_answer(self, ctx: Context, message: Message, answer: str, options: list) -> None: - """Validate the answer using a reaction event loop.""" - def predicate(reaction: Reaction, user: Member) -> bool: - """Test if the the answer is valid and can be evaluated.""" - return ( - reaction.message.id == message.id # The reaction is attached to the question we asked. - and user == ctx.author # It's the user who triggered the quiz. - and str(reaction.emoji) in ANSWERS_EMOJI.values() # The reaction is one of the options. - ) - - for emoji in ANSWERS_EMOJI.values(): - await message.add_reaction(emoji) - - # Validate the answer - try: - reaction, user = await ctx.bot.wait_for("reaction_add", timeout=45.0, check=predicate) - except asyncio.TimeoutError: - await ctx.channel.send(f"You took too long. The correct answer was **{options[answer]}**.") - await message.clear_reactions() - return - - if str(reaction.emoji) == ANSWERS_EMOJI[answer]: - await ctx.send(f"{random.choice(CORRECT_GUESS)} The correct answer was **{options[answer]}**.") - else: - await ctx.send( - f"{random.choice(INCORRECT_GUESS)} The correct answer was **{options[answer]}**." - ) - - await message.clear_reactions() - # endregion - - # region: Commands - @group(name='snakes', aliases=('snake',), invoke_without_command=True) - async def snakes_group(self, ctx: Context) -> None: - """Commands from our first code jam.""" - await ctx.send_help(ctx.command) - - @bot_has_permissions(manage_messages=True) - @snakes_group.command(name='antidote') - @locked() - async def antidote_command(self, ctx: Context) -> None: - """ - Antidote! Can you create the antivenom before the patient dies? - - Rules: You have 4 ingredients for each antidote, you only have 10 attempts - Once you synthesize the antidote, you will be presented with 4 markers - Tick: This means you have a CORRECT ingredient in the CORRECT position - Circle: This means you have a CORRECT ingredient in the WRONG position - Cross: This means you have a WRONG ingredient in the WRONG position - - Info: The game automatically ends after 5 minutes inactivity. - You should only use each ingredient once. - - This game was created by Lord Bisk and Runew0lf. - """ - def predicate(reaction_: Reaction, user_: Member) -> bool: - """Make sure that this reaction is what we want to operate on.""" - return ( - all(( - # Reaction is on this message - reaction_.message.id == board_id.id, - # Reaction is one of the pagination emotes - reaction_.emoji in ANTIDOTE_EMOJI, - # Reaction was not made by the Bot - user_.id != self.bot.user.id, - # Reaction was made by author - user_.id == ctx.author.id - )) - ) - - # Initialize variables - antidote_tries = 0 - antidote_guess_count = 0 - antidote_guess_list = [] - guess_result = [] - board = [] - page_guess_list = [] - page_result_list = [] - win = False - - antidote_embed = Embed(color=SNAKE_COLOR, title="Antidote") - antidote_embed.set_author(name=ctx.author.name, icon_url=ctx.author.avatar_url) - - # Generate answer - antidote_answer = list(ANTIDOTE_EMOJI) # Duplicate list, not reference it - random.shuffle(antidote_answer) - antidote_answer.pop() - - # Begin initial board building - for i in range(0, 10): - page_guess_list.append(f"{HOLE_EMOJI} {HOLE_EMOJI} {HOLE_EMOJI} {HOLE_EMOJI}") - page_result_list.append(f"{CROSS_EMOJI} {CROSS_EMOJI} {CROSS_EMOJI} {CROSS_EMOJI}") - board.append(f"`{i+1:02d}` " - f"{page_guess_list[i]} - " - f"{page_result_list[i]}") - board.append(EMPTY_UNICODE) - antidote_embed.add_field(name="10 guesses remaining", value="\n".join(board)) - board_id = await ctx.send(embed=antidote_embed) # Display board - - # Add our player reactions - for emoji in ANTIDOTE_EMOJI: - await board_id.add_reaction(emoji) - - # Begin main game loop - while not win and antidote_tries < 10: - try: - reaction, user = await ctx.bot.wait_for( - "reaction_add", timeout=300, check=predicate) - except asyncio.TimeoutError: - log.debug("Antidote timed out waiting for a reaction") - break # We're done, no reactions for the last 5 minutes - - if antidote_tries < 10: - if antidote_guess_count < 4: - if reaction.emoji in ANTIDOTE_EMOJI: - antidote_guess_list.append(reaction.emoji) - antidote_guess_count += 1 - - if antidote_guess_count == 4: # Guesses complete - antidote_guess_count = 0 - page_guess_list[antidote_tries] = " ".join(antidote_guess_list) - - # Now check guess - for i in range(0, len(antidote_answer)): - if antidote_guess_list[i] == antidote_answer[i]: - guess_result.append(TICK_EMOJI) - elif antidote_guess_list[i] in antidote_answer: - guess_result.append(BLANK_EMOJI) - else: - guess_result.append(CROSS_EMOJI) - guess_result.sort() - page_result_list[antidote_tries] = " ".join(guess_result) - - # Rebuild the board - board = [] - for i in range(0, 10): - board.append(f"`{i+1:02d}` " - f"{page_guess_list[i]} - " - f"{page_result_list[i]}") - board.append(EMPTY_UNICODE) - - # Remove Reactions - for emoji in antidote_guess_list: - await board_id.remove_reaction(emoji, user) - - if antidote_guess_list == antidote_answer: - win = True - - antidote_tries += 1 - guess_result = [] - antidote_guess_list = [] - - antidote_embed.clear_fields() - antidote_embed.add_field(name=f"{10 - antidote_tries} " - f"guesses remaining", - value="\n".join(board)) - # Redisplay the board - await board_id.edit(embed=antidote_embed) - - # Winning / Ending Screen - if win is True: - antidote_embed = Embed(color=SNAKE_COLOR, title="Antidote") - antidote_embed.set_author(name=ctx.author.name, icon_url=ctx.author.avatar_url) - antidote_embed.set_image(url="https://i.makeagif.com/media/7-12-2015/Cj1pts.gif") - antidote_embed.add_field(name="You have created the snake antidote!", - value=f"The solution was: {' '.join(antidote_answer)}\n" - f"You had {10 - antidote_tries} tries remaining.") - await board_id.edit(embed=antidote_embed) - else: - antidote_embed = Embed(color=SNAKE_COLOR, title="Antidote") - antidote_embed.set_author(name=ctx.author.name, icon_url=ctx.author.avatar_url) - antidote_embed.set_image(url="https://media.giphy.com/media/ceeN6U57leAhi/giphy.gif") - antidote_embed.add_field(name=EMPTY_UNICODE, - value=f"Sorry you didnt make the antidote in time.\n" - f"The formula was {' '.join(antidote_answer)}") - await board_id.edit(embed=antidote_embed) - - log.debug("Ending pagination and removing all reactions...") - await board_id.clear_reactions() - - @snakes_group.command(name='draw') - async def draw_command(self, ctx: Context) -> None: - """ - Draws a random snek using Perlin noise. - - Written by Momo and kel. - Modified by juan and lemon. - """ - with ctx.typing(): - - # Generate random snake attributes - width = random.randint(6, 10) - length = random.randint(15, 22) - random_hue = random.random() - snek_color = self._beautiful_pastel(random_hue) - text_color = self._beautiful_pastel((random_hue + 0.5) % 1) - bg_color = ( - random.randint(32, 50), - random.randint(32, 50), - random.randint(50, 70), - ) - - # Build and send the snek - text = random.choice(self.snake_idioms)["idiom"] - factory = utils.PerlinNoiseFactory(dimension=1, octaves=2) - image_frame = utils.create_snek_frame( - factory, - snake_width=width, - snake_length=length, - snake_color=snek_color, - text=text, - text_color=text_color, - bg_color=bg_color - ) - png_bytes = utils.frame_to_png_bytes(image_frame) - file = File(png_bytes, filename='snek.png') - await ctx.send(file=file) - - @snakes_group.command(name='get') - @bot_has_permissions(manage_messages=True) - @locked() - async def get_command(self, ctx: Context, *, name: Snake = None) -> None: - """ - Fetches information about a snake from Wikipedia. - - Created by Ava and eivl. - """ - with ctx.typing(): - if name is None: - name = await Snake.random() - - if isinstance(name, dict): - data = name - else: - data = await self._get_snek(name) - - if data.get('error'): - return await ctx.send('Could not fetch data from Wikipedia.') - - description = data["info"] - - # Shorten the description if needed - if len(description) > 1000: - description = description[:1000] - last_newline = description.rfind("\n") - if last_newline > 0: - description = description[:last_newline] - - # Strip and add the Wiki link. - if "fullurl" in data: - description = description.strip("\n") - description += f"\n\nRead more on [Wikipedia]({data['fullurl']})" - - # Build and send the embed. - embed = Embed( - title=data.get("title", data.get('name')), - description=description, - colour=0x59982F, - ) - - emoji = 'https://emojipedia-us.s3.amazonaws.com/thumbs/60/google/3/snake_1f40d.png' - image = next((url for url in data['image_list'] - if url.endswith(self.valid_image_extensions)), emoji) - embed.set_image(url=image) - - await ctx.send(embed=embed) - - @snakes_group.command(name='guess', aliases=('identify',)) - @locked() - async def guess_command(self, ctx: Context) -> None: - """ - Snake identifying game. - - Made by Ava and eivl. - Modified by lemon. - """ - with ctx.typing(): - - image = None - - while image is None: - snakes = [await Snake.random() for _ in range(4)] - snake = random.choice(snakes) - answer = "abcd"[snakes.index(snake)] - - data = await self._get_snek(snake) - - image = next((url for url in data['image_list'] - if url.endswith(self.valid_image_extensions)), None) - - embed = Embed( - title='Which of the following is the snake in the image?', - description="\n".join( - f"{'ABCD'[snakes.index(snake)]}: {snake}" for snake in snakes), - colour=SNAKE_COLOR - ) - embed.set_image(url=image) - - guess = await ctx.send(embed=embed) - options = {f"{'abcd'[snakes.index(snake)]}": snake for snake in snakes} - await self._validate_answer(ctx, guess, answer, options) - - @snakes_group.command(name='hatch') - async def hatch_command(self, ctx: Context) -> None: - """ - Hatches your personal snake. - - Written by Momo and kel. - """ - # Pick a random snake to hatch. - snake_name = random.choice(list(utils.snakes.keys())) - snake_image = utils.snakes[snake_name] - - # Hatch the snake - message = await ctx.channel.send(embed=Embed(description="Hatching your snake :snake:...")) - await asyncio.sleep(1) - - for stage in utils.stages: - hatch_embed = Embed(description=stage) - await message.edit(embed=hatch_embed) - await asyncio.sleep(1) - await asyncio.sleep(1) - await message.delete() - - # Build and send the embed. - my_snake_embed = Embed(description=":tada: Congrats! You hatched: **{0}**".format(snake_name)) - my_snake_embed.set_thumbnail(url=snake_image) - my_snake_embed.set_footer( - text=" Owner: {0}#{1}".format(ctx.message.author.name, ctx.message.author.discriminator) - ) - - await ctx.channel.send(embed=my_snake_embed) - - @snakes_group.command(name='movie') - async def movie_command(self, ctx: Context) -> None: - """ - Gets a random snake-related movie from OMDB. - - Written by Samuel. - Modified by gdude. - """ - url = "http://www.omdbapi.com/" - page = random.randint(1, 27) - - response = await self.bot.http_session.get( - url, - params={ - "s": "snake", - "page": page, - "type": "movie", - "apikey": Tokens.omdb - } - ) - data = await response.json() - movie = random.choice(data["Search"])["imdbID"] - - response = await self.bot.http_session.get( - url, - params={ - "i": movie, - "apikey": Tokens.omdb - } - ) - data = await response.json() - - embed = Embed( - title=data["Title"], - color=SNAKE_COLOR - ) - - del data["Response"], data["imdbID"], data["Title"] - - for key, value in data.items(): - if not value or value == "N/A" or key in ("Response", "imdbID", "Title", "Type"): - continue - - if key == "Ratings": # [{'Source': 'Internet Movie Database', 'Value': '7.6/10'}] - rating = random.choice(value) - - if rating["Source"] != "Internet Movie Database": - embed.add_field(name=f"Rating: {rating['Source']}", value=rating["Value"]) - - continue - - if key == "Poster": - embed.set_image(url=value) - continue - - elif key == "imdbRating": - key = "IMDB Rating" - - elif key == "imdbVotes": - key = "IMDB Votes" - - embed.add_field(name=key, value=value, inline=True) - - embed.set_footer(text="Data provided by the OMDB API") - - await ctx.channel.send( - embed=embed - ) - - @snakes_group.command(name='quiz') - @locked() - async def quiz_command(self, ctx: Context) -> None: - """ - Asks a snake-related question in the chat and validates the user's guess. - - This was created by Mushy and Cardium, - and modified by Urthas and lemon. - """ - # Prepare a question. - question = random.choice(self.snake_quizzes) - answer = question["answerkey"] - options = {key: question["options"][key] for key in ANSWERS_EMOJI.keys()} - - # Build and send the embed. - embed = Embed( - color=SNAKE_COLOR, - title=question["question"], - description="\n".join( - [f"**{key.upper()}**: {answer}" for key, answer in options.items()] - ) - ) - - quiz = await ctx.channel.send("", embed=embed) - await self._validate_answer(ctx, quiz, answer, options) - - @snakes_group.command(name='name', aliases=('name_gen',)) - async def name_command(self, ctx: Context, *, name: str = None) -> None: - """ - Snakifies a username. - - Slices the users name at the last vowel (or second last if the name - ends with a vowel), and then combines it with a random snake name, - which is sliced at the first vowel (or second if the name starts with - a vowel). - - If the name contains no vowels, it just appends the snakename - to the end of the name. - - Examples: - lemon + anaconda = lemoconda - krzsn + anaconda = krzsnconda - gdude + anaconda = gduconda - aperture + anaconda = apertuconda - lucy + python = luthon - joseph + taipan = joseipan - - This was written by Iceman, and modified for inclusion into the bot by lemon. - """ - snake_name = await self._get_snake_name() - snake_name = snake_name['name'] - snake_prefix = "" - - # Set aside every word in the snake name except the last. - if " " in snake_name: - snake_prefix = " ".join(snake_name.split()[:-1]) - snake_name = snake_name.split()[-1] - - # If no name is provided, use whoever called the command. - if name: - user_name = name - else: - user_name = ctx.author.display_name - - # Get the index of the vowel to slice the username at - user_slice_index = len(user_name) - for index, char in enumerate(reversed(user_name)): - if index == 0: - continue - if char.lower() in "aeiouy": - user_slice_index -= index - break - - # Now, get the index of the vowel to slice the snake_name at - snake_slice_index = 0 - for index, char in enumerate(snake_name): - if index == 0: - continue - if char.lower() in "aeiouy": - snake_slice_index = index + 1 - break - - # Combine! - snake_name = snake_name[snake_slice_index:] - user_name = user_name[:user_slice_index] - result = f"{snake_prefix} {user_name}{snake_name}" - result = string.capwords(result) - - # Embed and send - embed = Embed( - title="Snake name", - description=f"Your snake-name is **{result}**", - color=SNAKE_COLOR - ) - - return await ctx.send(embed=embed) - - @snakes_group.command(name='sal') - @locked() - async def sal_command(self, ctx: Context) -> None: - """ - Play a game of Snakes and Ladders. - - Written by Momo and kel. - Modified by lemon. - """ - # Check if there is already a game in this channel - if ctx.channel in self.active_sal: - await ctx.send(f"{ctx.author.mention} A game is already in progress in this channel.") - return - - game = utils.SnakeAndLaddersGame(snakes=self, context=ctx) - self.active_sal[ctx.channel] = game - - await game.open_game() - - @snakes_group.command(name='about') - async def about_command(self, ctx: Context) -> None: - """Show an embed with information about the event, its participants, and its winners.""" - contributors = [ - "<@!245270749919576066>", - "<@!396290259907903491>", - "<@!172395097705414656>", - "<@!361708843425726474>", - "<@!300302216663793665>", - "<@!210248051430916096>", - "<@!174588005745557505>", - "<@!87793066227822592>", - "<@!211619754039967744>", - "<@!97347867923976192>", - "<@!136081839474343936>", - "<@!263560579770220554>", - "<@!104749643715387392>", - "<@!303940835005825024>", - ] - - embed = Embed( - title="About the snake cog", - description=( - "The features in this cog were created by members of the community " - "during our first ever " - "[code jam event](https://pythondiscord.com/pages/code-jams/code-jam-1-snakes-bot/). \n\n" - "The event saw over 50 participants, who competed to write a discord bot cog with a snake theme over " - "48 hours. The staff then selected the best features from all the best teams, and made modifications " - "to ensure they would all work together before integrating them into the community bot.\n\n" - "It was a tight race, but in the end, <@!104749643715387392> and <@!303940835005825024> " - f"walked away as grand champions. Make sure you check out `{ctx.prefix}snakes sal`," - f"`{ctx.prefix}snakes draw` and `{ctx.prefix}snakes hatch` " - "to see what they came up with." - ) - ) - - embed.add_field( - name="Contributors", - value=( - ", ".join(contributors) - ) - ) - - await ctx.channel.send(embed=embed) - - @snakes_group.command(name='card') - async def card_command(self, ctx: Context, *, name: Snake = None) -> None: - """ - Create an interesting little card from a snake. - - Created by juan and Someone during the first code jam. - """ - # Get the snake data we need - if not name: - name_obj = await self._get_snake_name() - name = name_obj['scientific'] - content = await self._get_snek(name) - - elif isinstance(name, dict): - content = name - - else: - content = await self._get_snek(name) - - # Make the card - async with ctx.typing(): - - stream = BytesIO() - async with async_timeout.timeout(10): - async with self.bot.http_session.get(content['image_list'][0]) as response: - stream.write(await response.read()) - - stream.seek(0) - - func = partial(self._generate_card, stream, content) - final_buffer = await self.bot.loop.run_in_executor(None, func) - - # Send it! - await ctx.send( - f"A wild {content['name'].title()} appears!", - file=File(final_buffer, filename=content['name'].replace(" ", "") + ".png") - ) - - @snakes_group.command(name='fact') - async def fact_command(self, ctx: Context) -> None: - """ - Gets a snake-related fact. - - Written by Andrew and Prithaj. - Modified by lemon. - """ - question = random.choice(self.snake_facts)["fact"] - embed = Embed( - title="Snake fact", - color=SNAKE_COLOR, - description=question - ) - await ctx.channel.send(embed=embed) - - @snakes_group.command(name='snakify') - async def snakify_command(self, ctx: Context, *, message: str = None) -> None: - """ - How would I talk if I were a snake? - - If `message` is passed, the bot will snakify the message. - Otherwise, a random message from the user's history is snakified. - - Written by Momo and kel. - Modified by lemon. - """ - with ctx.typing(): - embed = Embed() - user = ctx.message.author - - if not message: - - # Get a random message from the users history - messages = [] - async for message in ctx.channel.history(limit=500).filter( - lambda msg: msg.author == ctx.message.author # Message was sent by author. - ): - messages.append(message.content) - - message = self._get_random_long_message(messages) - - # Set the avatar - if user.avatar is not None: - avatar = f"https://cdn.discordapp.com/avatars/{user.id}/{user.avatar}" - else: - avatar = ctx.author.default_avatar_url - - # Build and send the embed - embed.set_author( - name=f"{user.name}#{user.discriminator}", - icon_url=avatar, - ) - embed.description = f"*{self._snakify(message)}*" - - await ctx.channel.send(embed=embed) - - @snakes_group.command(name='video', aliases=('get_video',)) - async def video_command(self, ctx: Context, *, search: str = None) -> None: - """ - Gets a YouTube video about snakes. - - If `search` is given, a snake with that name will be searched on Youtube. - - Written by Andrew and Prithaj. - """ - # Are we searching for anything specific? - if search: - query = search + ' snake' - else: - snake = await self._get_snake_name() - query = snake['name'] - - # Build the URL and make the request - url = 'https://www.googleapis.com/youtube/v3/search' - response = await self.bot.http_session.get( - url, - params={ - "part": "snippet", - "q": urllib.parse.quote(query), - "type": "video", - "key": Tokens.youtube - } - ) - response = await response.json() - data = response['items'] - - # Send the user a video - if len(data) > 0: - num = random.randint(0, len(data) - 1) - youtube_base_url = 'https://www.youtube.com/watch?v=' - await ctx.channel.send( - content=f"{youtube_base_url}{data[num]['id']['videoId']}" - ) - else: - log.warning(f"YouTube API error. Full response looks like {response}") - - @snakes_group.command(name='zen') - async def zen_command(self, ctx: Context) -> None: - """ - Gets a random quote from the Zen of Python, except as if spoken by a snake. - - Written by Prithaj and Andrew. - Modified by lemon. - """ - embed = Embed( - title="Zzzen of Pythhon", - color=SNAKE_COLOR - ) - - # Get the zen quote and snakify it - zen_quote = random.choice(ZEN.splitlines()) - zen_quote = self._snakify(zen_quote) - - # Embed and send - embed.description = zen_quote - await ctx.channel.send( - embed=embed - ) - # endregion - - # region: Error handlers - @get_command.error - @card_command.error - @video_command.error - async def command_error(self, ctx: Context, error: CommandError) -> None: - """Local error handler for the Snake Cog.""" - embed = Embed() - embed.colour = Colour.red() - - if isinstance(error, BadArgument): - embed.description = str(error) - embed.title = random.choice(ERROR_REPLIES) - - elif isinstance(error, OSError): - log.error(f"snake_card encountered an OSError: {error} ({error.original})") - embed.description = "Could not generate the snake card! Please try again." - embed.title = random.choice(ERROR_REPLIES) - - else: - log.error(f"Unhandled tag command error: {error} ({error.original})") - return - - await ctx.send(embed=embed) - # endregion diff --git a/bot/exts/evergreen/snakes/utils.py b/bot/exts/evergreen/snakes/utils.py deleted file mode 100644 index 7d6caf04..00000000 --- a/bot/exts/evergreen/snakes/utils.py +++ /dev/null @@ -1,716 +0,0 @@ -import asyncio -import io -import json -import logging -import math -import random -from itertools import product -from pathlib import Path -from typing import List, Tuple - -from PIL import Image -from PIL.ImageDraw import ImageDraw -from discord import File, Member, Reaction -from discord.ext.commands import Cog, Context - -from bot.constants import Roles - -SNAKE_RESOURCES = Path("bot/resources/snakes").absolute() - -h1 = r'''``` - ---- - ------ - /--------\ - |--------| - |--------| - \------/ - ----```''' -h2 = r'''``` - ---- - ------ - /---\-/--\ - |-----\--| - |--------| - \------/ - ----```''' -h3 = r'''``` - ---- - ------ - /---\-/--\ - |-----\--| - |-----/--| - \----\-/ - ----```''' -h4 = r'''``` - ----- - ----- \ - /--| /---\ - |--\ -\---| - |--\--/-- / - \------- / - ------```''' -stages = [h1, h2, h3, h4] -snakes = { - "Baby Python": "https://i.imgur.com/SYOcmSa.png", - "Baby Rattle Snake": "https://i.imgur.com/i5jYA8f.png", - "Baby Dragon Snake": "https://i.imgur.com/SuMKM4m.png", - "Baby Garden Snake": "https://i.imgur.com/5vYx3ah.png", - "Baby Cobra": "https://i.imgur.com/jk14ryt.png" -} - -BOARD_TILE_SIZE = 56 # the size of each board tile -BOARD_PLAYER_SIZE = 20 # the size of each player icon -BOARD_MARGIN = (10, 0) # margins, in pixels (for player icons) -# The size of the image to download -# Should a power of 2 and higher than BOARD_PLAYER_SIZE -PLAYER_ICON_IMAGE_SIZE = 32 -MAX_PLAYERS = 4 # depends on the board size/quality, 4 is for the default board - -# board definition (from, to) -BOARD = { - # ladders - 2: 38, - 7: 14, - 8: 31, - 15: 26, - 21: 42, - 28: 84, - 36: 44, - 51: 67, - 71: 91, - 78: 98, - 87: 94, - - # snakes - 99: 80, - 95: 75, - 92: 88, - 89: 68, - 74: 53, - 64: 60, - 62: 19, - 49: 11, - 46: 25, - 16: 6 -} - -DEFAULT_SNAKE_COLOR: int = 0x15c7ea -DEFAULT_BACKGROUND_COLOR: int = 0 -DEFAULT_IMAGE_DIMENSIONS: Tuple[int] = (200, 200) -DEFAULT_SNAKE_LENGTH: int = 22 -DEFAULT_SNAKE_WIDTH: int = 8 -DEFAULT_SEGMENT_LENGTH_RANGE: Tuple[int] = (7, 10) -DEFAULT_IMAGE_MARGINS: Tuple[int] = (50, 50) -DEFAULT_TEXT: str = "snek\nit\nup" -DEFAULT_TEXT_POSITION: Tuple[int] = ( - 10, - 10 -) -DEFAULT_TEXT_COLOR: int = 0xf2ea15 -X = 0 -Y = 1 -ANGLE_RANGE = math.pi * 2 - - -def get_resource(file: str) -> List[dict]: - """Load Snake resources JSON.""" - with (SNAKE_RESOURCES / f"{file}.json").open(encoding="utf-8") as snakefile: - return json.load(snakefile) - - -def smoothstep(t: float) -> float: - """Smooth curve with a zero derivative at 0 and 1, making it useful for interpolating.""" - return t * t * (3. - 2. * t) - - -def lerp(t: float, a: float, b: float) -> float: - """Linear interpolation between a and b, given a fraction t.""" - return a + t * (b - a) - - -class PerlinNoiseFactory(object): - """ - Callable that produces Perlin noise for an arbitrary point in an arbitrary number of dimensions. - - The underlying grid is aligned with the integers. - - There is no limit to the coordinates used; new gradients are generated on the fly as necessary. - - Taken from: https://gist.github.com/eevee/26f547457522755cb1fb8739d0ea89a1 - Licensed under ISC - """ - - def __init__(self, dimension: int, octaves: int = 1, tile: Tuple[int] = (), unbias: bool = False): - """ - Create a new Perlin noise factory in the given number of dimensions. - - dimension should be an integer and at least 1. - - More octaves create a foggier and more-detailed noise pattern. More than 4 octaves is rather excessive. - - ``tile`` can be used to make a seamlessly tiling pattern. - For example: - pnf = PerlinNoiseFactory(2, tile=(0, 3)) - - This will produce noise that tiles every 3 units vertically, but never tiles horizontally. - - If ``unbias`` is True, the smoothstep function will be applied to the output before returning - it, to counteract some of Perlin noise's significant bias towards the center of its output range. - """ - self.dimension = dimension - self.octaves = octaves - self.tile = tile + (0,) * dimension - self.unbias = unbias - - # For n dimensions, the range of Perlin noise is ±sqrt(n)/2; multiply - # by this to scale to ±1 - self.scale_factor = 2 * dimension ** -0.5 - - self.gradient = {} - - def _generate_gradient(self) -> Tuple[float, ...]: - """ - Generate a random unit vector at each grid point. - - This is the "gradient" vector, in that the grid tile slopes towards it - """ - # 1 dimension is special, since the only unit vector is trivial; - # instead, use a slope between -1 and 1 - if self.dimension == 1: - return (random.uniform(-1, 1),) - - # Generate a random point on the surface of the unit n-hypersphere; - # this is the same as a random unit vector in n dimensions. Thanks - # to: http://mathworld.wolfram.com/SpherePointPicking.html - # Pick n normal random variables with stddev 1 - random_point = [random.gauss(0, 1) for _ in range(self.dimension)] - # Then scale the result to a unit vector - scale = sum(n * n for n in random_point) ** -0.5 - return tuple(coord * scale for coord in random_point) - - def get_plain_noise(self, *point) -> float: - """Get plain noise for a single point, without taking into account either octaves or tiling.""" - if len(point) != self.dimension: - raise ValueError("Expected {0} values, got {1}".format( - self.dimension, len(point))) - - # Build a list of the (min, max) bounds in each dimension - grid_coords = [] - for coord in point: - min_coord = math.floor(coord) - max_coord = min_coord + 1 - grid_coords.append((min_coord, max_coord)) - - # Compute the dot product of each gradient vector and the point's - # distance from the corresponding grid point. This gives you each - # gradient's "influence" on the chosen point. - dots = [] - for grid_point in product(*grid_coords): - if grid_point not in self.gradient: - self.gradient[grid_point] = self._generate_gradient() - gradient = self.gradient[grid_point] - - dot = 0 - for i in range(self.dimension): - dot += gradient[i] * (point[i] - grid_point[i]) - dots.append(dot) - - # Interpolate all those dot products together. The interpolation is - # done with smoothstep to smooth out the slope as you pass from one - # grid cell into the next. - # Due to the way product() works, dot products are ordered such that - # the last dimension alternates: (..., min), (..., max), etc. So we - # can interpolate adjacent pairs to "collapse" that last dimension. Then - # the results will alternate in their second-to-last dimension, and so - # forth, until we only have a single value left. - dim = self.dimension - while len(dots) > 1: - dim -= 1 - s = smoothstep(point[dim] - grid_coords[dim][0]) - - next_dots = [] - while dots: - next_dots.append(lerp(s, dots.pop(0), dots.pop(0))) - - dots = next_dots - - return dots[0] * self.scale_factor - - def __call__(self, *point) -> float: - """ - Get the value of this Perlin noise function at the given point. - - The number of values given should match the number of dimensions. - """ - ret = 0 - for o in range(self.octaves): - o2 = 1 << o - new_point = [] - for i, coord in enumerate(point): - coord *= o2 - if self.tile[i]: - coord %= self.tile[i] * o2 - new_point.append(coord) - ret += self.get_plain_noise(*new_point) / o2 - - # Need to scale n back down since adding all those extra octaves has - # probably expanded it beyond ±1 - # 1 octave: ±1 - # 2 octaves: ±1½ - # 3 octaves: ±1¾ - ret /= 2 - 2 ** (1 - self.octaves) - - if self.unbias: - # The output of the plain Perlin noise algorithm has a fairly - # strong bias towards the center due to the central limit theorem - # -- in fact the top and bottom 1/8 virtually never happen. That's - # a quarter of our entire output range! If only we had a function - # in [0..1] that could introduce a bias towards the endpoints... - r = (ret + 1) / 2 - # Doing it this many times is a completely made-up heuristic. - for _ in range(int(self.octaves / 2 + 0.5)): - r = smoothstep(r) - ret = r * 2 - 1 - - return ret - - -def create_snek_frame( - perlin_factory: PerlinNoiseFactory, perlin_lookup_vertical_shift: float = 0, - image_dimensions: Tuple[int] = DEFAULT_IMAGE_DIMENSIONS, image_margins: Tuple[int] = DEFAULT_IMAGE_MARGINS, - snake_length: int = DEFAULT_SNAKE_LENGTH, - snake_color: int = DEFAULT_SNAKE_COLOR, bg_color: int = DEFAULT_BACKGROUND_COLOR, - segment_length_range: Tuple[int] = DEFAULT_SEGMENT_LENGTH_RANGE, snake_width: int = DEFAULT_SNAKE_WIDTH, - text: str = DEFAULT_TEXT, text_position: Tuple[int] = DEFAULT_TEXT_POSITION, - text_color: Tuple[int] = DEFAULT_TEXT_COLOR -) -> Image: - """ - Creates a single random snek frame using Perlin noise. - - `perlin_lookup_vertical_shift` represents the Perlin noise shift in the Y-dimension for this frame. - If `text` is given, display the given text with the snek. - """ - start_x = random.randint(image_margins[X], image_dimensions[X] - image_margins[X]) - start_y = random.randint(image_margins[Y], image_dimensions[Y] - image_margins[Y]) - points = [(start_x, start_y)] - - for index in range(0, snake_length): - angle = perlin_factory.get_plain_noise( - ((1 / (snake_length + 1)) * (index + 1)) + perlin_lookup_vertical_shift - ) * ANGLE_RANGE - current_point = points[index] - segment_length = random.randint(segment_length_range[0], segment_length_range[1]) - points.append(( - current_point[X] + segment_length * math.cos(angle), - current_point[Y] + segment_length * math.sin(angle) - )) - - # normalize bounds - min_dimensions = [start_x, start_y] - max_dimensions = [start_x, start_y] - for point in points: - min_dimensions[X] = min(point[X], min_dimensions[X]) - min_dimensions[Y] = min(point[Y], min_dimensions[Y]) - max_dimensions[X] = max(point[X], max_dimensions[X]) - max_dimensions[Y] = max(point[Y], max_dimensions[Y]) - - # shift towards middle - dimension_range = (max_dimensions[X] - min_dimensions[X], max_dimensions[Y] - min_dimensions[Y]) - shift = ( - image_dimensions[X] / 2 - (dimension_range[X] / 2 + min_dimensions[X]), - image_dimensions[Y] / 2 - (dimension_range[Y] / 2 + min_dimensions[Y]) - ) - - image = Image.new(mode='RGB', size=image_dimensions, color=bg_color) - draw = ImageDraw(image) - for index in range(1, len(points)): - point = points[index] - previous = points[index - 1] - draw.line( - ( - shift[X] + previous[X], - shift[Y] + previous[Y], - shift[X] + point[X], - shift[Y] + point[Y] - ), - width=snake_width, - fill=snake_color - ) - if text is not None: - draw.multiline_text(text_position, text, fill=text_color) - del draw - return image - - -def frame_to_png_bytes(image: Image) -> io.BytesIO: - """Convert image to byte stream.""" - stream = io.BytesIO() - image.save(stream, format='PNG') - stream.seek(0) - return stream - - -log = logging.getLogger(__name__) -START_EMOJI = "\u2611" # :ballot_box_with_check: - Start the game -CANCEL_EMOJI = "\u274C" # :x: - Cancel or leave the game -ROLL_EMOJI = "\U0001F3B2" # :game_die: - Roll the die! -JOIN_EMOJI = "\U0001F64B" # :raising_hand: - Join the game. -STARTUP_SCREEN_EMOJI = [ - JOIN_EMOJI, - START_EMOJI, - CANCEL_EMOJI -] -GAME_SCREEN_EMOJI = [ - ROLL_EMOJI, - CANCEL_EMOJI -] - - -class SnakeAndLaddersGame: - """Snakes and Ladders game Cog.""" - - def __init__(self, snakes: Cog, context: Context): - self.snakes = snakes - self.ctx = context - self.channel = self.ctx.channel - self.state = 'booting' - self.started = False - self.author = self.ctx.author - self.players = [] - self.player_tiles = {} - self.round_has_rolled = {} - self.avatar_images = {} - self.board = None - self.positions = None - self.rolls = [] - - async def open_game(self) -> None: - """ - Create a new Snakes and Ladders game. - - Listen for reactions until players have joined, and the game has been started. - """ - def startup_event_check(reaction_: Reaction, user_: Member) -> bool: - """Make sure that this reaction is what we want to operate on.""" - return ( - all(( - reaction_.message.id == startup.id, # Reaction is on startup message - reaction_.emoji in STARTUP_SCREEN_EMOJI, # Reaction is one of the startup emotes - user_.id != self.ctx.bot.user.id, # Reaction was not made by the bot - )) - ) - - # Check to see if the bot can remove reactions - if not self.channel.permissions_for(self.ctx.guild.me).manage_messages: - log.warning( - "Unable to start Snakes and Ladders - " - f"Missing manage_messages permissions in {self.channel}" - ) - return - - await self._add_player(self.author) - await self.channel.send( - "**Snakes and Ladders**: A new game is about to start!", - file=File( - str(SNAKE_RESOURCES / "snakes_and_ladders" / "banner.jpg"), - filename='Snakes and Ladders.jpg' - ) - ) - startup = await self.channel.send( - f"Press {JOIN_EMOJI} to participate, and press " - f"{START_EMOJI} to start the game" - ) - for emoji in STARTUP_SCREEN_EMOJI: - await startup.add_reaction(emoji) - - self.state = 'waiting' - - while not self.started: - try: - reaction, user = await self.ctx.bot.wait_for( - "reaction_add", - timeout=300, - check=startup_event_check - ) - if reaction.emoji == JOIN_EMOJI: - await self.player_join(user) - elif reaction.emoji == CANCEL_EMOJI: - if user == self.author or (self._is_moderator(user) and user not in self.players): - # Allow game author or non-playing moderation staff to cancel a waiting game - await self.cancel_game() - return - else: - await self.player_leave(user) - elif reaction.emoji == START_EMOJI: - if self.ctx.author == user: - self.started = True - await self.start_game(user) - await startup.delete() - break - - await startup.remove_reaction(reaction.emoji, user) - - except asyncio.TimeoutError: - log.debug("Snakes and Ladders timed out waiting for a reaction") - await self.cancel_game() - return # We're done, no reactions for the last 5 minutes - - async def _add_player(self, user: Member) -> None: - """Add player to game.""" - self.players.append(user) - self.player_tiles[user.id] = 1 - - avatar_bytes = await user.avatar_url_as(format='jpeg', size=PLAYER_ICON_IMAGE_SIZE).read() - im = Image.open(io.BytesIO(avatar_bytes)).resize((BOARD_PLAYER_SIZE, BOARD_PLAYER_SIZE)) - self.avatar_images[user.id] = im - - async def player_join(self, user: Member) -> None: - """ - Handle players joining the game. - - Prevent player joining if they have already joined, if the game is full, or if the game is - in a waiting state. - """ - for p in self.players: - if user == p: - await self.channel.send(user.mention + " You are already in the game.", delete_after=10) - return - if self.state != 'waiting': - await self.channel.send(user.mention + " You cannot join at this time.", delete_after=10) - return - if len(self.players) is MAX_PLAYERS: - await self.channel.send(user.mention + " The game is full!", delete_after=10) - return - - await self._add_player(user) - - await self.channel.send( - f"**Snakes and Ladders**: {user.mention} has joined the game.\n" - f"There are now {str(len(self.players))} players in the game.", - delete_after=10 - ) - - async def player_leave(self, user: Member) -> bool: - """ - Handle players leaving the game. - - Leaving is prevented if the user wasn't part of the game. - - If the number of players reaches 0, the game is terminated. In this case, a sentinel boolean - is returned True to prevent a game from continuing after it's destroyed. - """ - is_surrendered = False # Sentinel value to assist with stopping a surrendered game - for p in self.players: - if user == p: - self.players.remove(p) - self.player_tiles.pop(p.id, None) - self.round_has_rolled.pop(p.id, None) - await self.channel.send( - "**Snakes and Ladders**: " + user.mention + " has left the game.", - delete_after=10 - ) - - if self.state != 'waiting' and len(self.players) == 0: - await self.channel.send("**Snakes and Ladders**: The game has been surrendered!") - is_surrendered = True - self._destruct() - - return is_surrendered - else: - await self.channel.send(user.mention + " You are not in the match.", delete_after=10) - return is_surrendered - - async def cancel_game(self) -> None: - """Cancel the running game.""" - await self.channel.send("**Snakes and Ladders**: Game has been canceled.") - self._destruct() - - async def start_game(self, user: Member) -> None: - """ - Allow the game author to begin the game. - - The game cannot be started if the game is in a waiting state. - """ - if not user == self.author: - await self.channel.send(user.mention + " Only the author of the game can start it.", delete_after=10) - return - - if not self.state == 'waiting': - await self.channel.send(user.mention + " The game cannot be started at this time.", delete_after=10) - return - - self.state = 'starting' - player_list = ', '.join(user.mention for user in self.players) - await self.channel.send("**Snakes and Ladders**: The game is starting!\nPlayers: " + player_list) - await self.start_round() - - async def start_round(self) -> None: - """Begin the round.""" - def game_event_check(reaction_: Reaction, user_: Member) -> bool: - """Make sure that this reaction is what we want to operate on.""" - return ( - all(( - reaction_.message.id == self.positions.id, # Reaction is on positions message - reaction_.emoji in GAME_SCREEN_EMOJI, # Reaction is one of the game emotes - user_.id != self.ctx.bot.user.id, # Reaction was not made by the bot - )) - ) - - self.state = 'roll' - for user in self.players: - self.round_has_rolled[user.id] = False - board_img = Image.open(str(SNAKE_RESOURCES / "snakes_and_ladders" / "board.jpg")) - player_row_size = math.ceil(MAX_PLAYERS / 2) - - for i, player in enumerate(self.players): - tile = self.player_tiles[player.id] - tile_coordinates = self._board_coordinate_from_index(tile) - x_offset = BOARD_MARGIN[0] + tile_coordinates[0] * BOARD_TILE_SIZE - y_offset = \ - BOARD_MARGIN[1] + ( - (10 * BOARD_TILE_SIZE) - (9 - tile_coordinates[1]) * BOARD_TILE_SIZE - BOARD_PLAYER_SIZE) - x_offset += BOARD_PLAYER_SIZE * (i % player_row_size) - y_offset -= BOARD_PLAYER_SIZE * math.floor(i / player_row_size) - board_img.paste(self.avatar_images[player.id], - box=(x_offset, y_offset)) - - board_file = File(frame_to_png_bytes(board_img), filename='Board.jpg') - player_list = '\n'.join((user.mention + ": Tile " + str(self.player_tiles[user.id])) for user in self.players) - - # Store and send new messages - temp_board = await self.channel.send( - "**Snakes and Ladders**: A new round has started! Current board:", - file=board_file - ) - temp_positions = await self.channel.send( - f"**Current positions**:\n{player_list}\n\nUse {ROLL_EMOJI} to roll the dice!" - ) - - # Delete the previous messages - if self.board and self.positions: - await self.board.delete() - await self.positions.delete() - - # remove the roll messages - for roll in self.rolls: - await roll.delete() - self.rolls = [] - - # Save new messages - self.board = temp_board - self.positions = temp_positions - - # Wait for rolls - for emoji in GAME_SCREEN_EMOJI: - await self.positions.add_reaction(emoji) - - is_surrendered = False - while True: - try: - reaction, user = await self.ctx.bot.wait_for( - "reaction_add", - timeout=300, - check=game_event_check - ) - - if reaction.emoji == ROLL_EMOJI: - await self.player_roll(user) - elif reaction.emoji == CANCEL_EMOJI: - if self._is_moderator(user) and user not in self.players: - # Only allow non-playing moderation staff to cancel a running game - await self.cancel_game() - return - else: - is_surrendered = await self.player_leave(user) - - await self.positions.remove_reaction(reaction.emoji, user) - - if self._check_all_rolled(): - break - - except asyncio.TimeoutError: - log.debug("Snakes and Ladders timed out waiting for a reaction") - await self.cancel_game() - return # We're done, no reactions for the last 5 minutes - - # Round completed - # Check to see if the game was surrendered before completing the round, without this - # sentinel, the game object would be deleted but the next round still posted into purgatory - if not is_surrendered: - await self._complete_round() - - async def player_roll(self, user: Member) -> None: - """Handle the player's roll.""" - if user.id not in self.player_tiles: - await self.channel.send(user.mention + " You are not in the match.", delete_after=10) - return - if self.state != 'roll': - await self.channel.send(user.mention + " You may not roll at this time.", delete_after=10) - return - if self.round_has_rolled[user.id]: - return - roll = random.randint(1, 6) - self.rolls.append(await self.channel.send(f"{user.mention} rolled a **{roll}**!")) - next_tile = self.player_tiles[user.id] + roll - - # apply snakes and ladders - if next_tile in BOARD: - target = BOARD[next_tile] - if target < next_tile: - await self.channel.send( - f"{user.mention} slips on a snake and falls back to **{target}**", - delete_after=15 - ) - else: - await self.channel.send( - f"{user.mention} climbs a ladder to **{target}**", - delete_after=15 - ) - next_tile = target - - self.player_tiles[user.id] = min(100, next_tile) - self.round_has_rolled[user.id] = True - - async def _complete_round(self) -> None: - """At the conclusion of a round check to see if there's been a winner.""" - self.state = 'post_round' - - # check for winner - winner = self._check_winner() - if winner is None: - # there is no winner, start the next round - await self.start_round() - return - - # announce winner and exit - await self.channel.send("**Snakes and Ladders**: " + winner.mention + " has won the game! :tada:") - self._destruct() - - def _check_winner(self) -> Member: - """Return a winning member if we're in the post-round state and there's a winner.""" - if self.state != 'post_round': - return None - return next((player for player in self.players if self.player_tiles[player.id] == 100), - None) - - def _check_all_rolled(self) -> bool: - """Check if all members have made their roll.""" - return all(rolled for rolled in self.round_has_rolled.values()) - - def _destruct(self) -> None: - """Clean up the finished game object.""" - del self.snakes.active_sal[self.channel] - - def _board_coordinate_from_index(self, index: int) -> Tuple[int, int]: - """Convert the tile number to the x/y coordinates for graphical purposes.""" - y_level = 9 - math.floor((index - 1) / 10) - is_reversed = math.floor((index - 1) / 10) % 2 != 0 - x_level = (index - 1) % 10 - if is_reversed: - x_level = 9 - x_level - return x_level, y_level - - @staticmethod - def _is_moderator(user: Member) -> bool: - """Return True if the user is a Moderator.""" - return any(Roles.moderator == role.id for role in user.roles) -- cgit v1.2.3 From a8d6f6d729cd0dffc6edf4b41f6a909e868dcfb0 Mon Sep 17 00:00:00 2001 From: Gustav Odinger Date: Mon, 21 Sep 2020 23:52:20 +0200 Subject: Fix extensions command bot avatar url - Uses self.bot (the bot instance) instead of Bot (the bot object) to access avatar url --- bot/exts/utils/extensions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/exts/utils/extensions.py b/bot/exts/utils/extensions.py index 65dfef84..102a0416 100644 --- a/bot/exts/utils/extensions.py +++ b/bot/exts/utils/extensions.py @@ -155,7 +155,7 @@ class Extensions(commands.Cog): embed.set_author( name="Extensions List", url=Client.github_bot_repo, - icon_url=str(Bot.user.avatar_url) + icon_url=str(self.bot.user.avatar_url) ) lines = [] -- cgit v1.2.3 From 34b86c4227f5f8ecd088d782c2e10195a2dc2c21 Mon Sep 17 00:00:00 2001 From: Anubhav1603 Date: Tue, 22 Sep 2020 16:48:10 +0530 Subject: handled exceptiom of ContentTypeError at search_wikipedia --- bot/exts/evergreen/wikipedia.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) (limited to 'bot') diff --git a/bot/exts/evergreen/wikipedia.py b/bot/exts/evergreen/wikipedia.py index c1fff873..6cf8f592 100644 --- a/bot/exts/evergreen/wikipedia.py +++ b/bot/exts/evergreen/wikipedia.py @@ -3,6 +3,7 @@ import datetime import logging from typing import List +from aiohttp import client_exceptions from discord import Color, Embed, Message from discord.ext import commands @@ -28,18 +29,20 @@ class WikipediaCog(commands.Cog): async def search_wikipedia(self, search_term: str) -> List[str]: """Search wikipedia and return the first 10 pages found.""" - async with self.http_session.get(SEARCH_API.format(search_term=search_term)) as response: - data = await response.json() - pages = [] + async with self.http_session.get(SEARCH_API.format(search_term=search_term)) as response: + try: + data = await response.json() - search_results = data["query"]["search"] + search_results = data["query"]["search"] - # Ignore pages with "may refer to" - for search_result in search_results: - log.info("trying to append titles") - if "may refer to" not in search_result["snippet"]: - pages.append(search_result["title"]) + # Ignore pages with "may refer to" + for search_result in search_results: + log.info("trying to append titles") + if "may refer to" not in search_result["snippet"]: + pages.append(search_result["title"]) + except client_exceptions.ContentTypeError: + pages = None log.info("Finished appending titles") return pages -- cgit v1.2.3 From 37fa3c2af994d7e44d3733aa4e9575ae14bfd61d Mon Sep 17 00:00:00 2001 From: Anubhav1603 Date: Tue, 22 Sep 2020 17:14:32 +0530 Subject: added json file for zodiac explantion --- bot/resources/valentines/zodiac_explanation.json | 98 ++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 bot/resources/valentines/zodiac_explanation.json (limited to 'bot') diff --git a/bot/resources/valentines/zodiac_explanation.json b/bot/resources/valentines/zodiac_explanation.json new file mode 100644 index 00000000..ccca6fcd --- /dev/null +++ b/bot/resources/valentines/zodiac_explanation.json @@ -0,0 +1,98 @@ +{ + "Aries":{ + "About":"Amazing people born between **March 21** to **April 19**. Aries loves to be number one, so it’s no surprise that these audacious rams are the first sign of the zodiac. Bold and ambitious, Aries dives headfirst into even the most challenging situations.", + "Motto":"***“When you know yourself, you're empowered. When you accept yourself, you're invincible.”***", + "Strengths":"courageous, determined, confident, enthusiastic, optimistic, honest, passionate.", + "Weaknesses":"impatient, moody, short-tempered, impulsive, aggressive.", + "full_form":"**A** for assertive\n**R** for refreshing\n **I** for independent\n**E** for energetic\n**S** for sexy", + "url":"https://www.horoscope.com/images-US/signs/profile-aries.png" + }, + "Taurus": { + "About": "Amazing people born between **April 20** to **May 20**. Taurus is an earth sign represented by the bull. Like their celestial spirit animal, Taureans enjoy relaxing in serene, bucolic environments surrounded by soft sounds, soothing aromas, and succulent flavors", + "Motto": "***“Nothing worth having comes easy.”***", + "Strengths": "reliable, patient, practical, devoted, responsible, stable.", + "Weaknesses": "stubborn, possessive, uncompromising.", + "full_form": "***T*** for trailblazing\n***A*** for ambitious\n***U*** for unwavering\n***R*** for reliable\n***U*** for understanding\n***S*** for stable", + "url": "https://www.horoscope.com/images-US/signs/profile-taurus.png" + }, + "Gemini":{ + "About": "Amazing people born between **May 21** to **June 20**. Have you ever been so busy that you wished you could clone yourself just to get everything done? That’s the Gemini experience in a nutshell. Appropriately symbolized by the celestial twins, this air sign was interested in so many pursuits that it had to double itself.", + "Motto": "***“I manifest my reality.”***", + "Strengths": "gentle, affectionate, curious, adaptable, ability to learn quickly and exchange ideas.", + "Weaknesses": "nervous, inconsistent, indecisive.", + "full_form": "***G*** for generous\n***E*** for emotionally in tune\n***M*** for motivated\n***I*** for imaginative\n***N*** for nice\n***I*** for intelligent", + "url": "https://www.horoscope.com/images-US/signs/profile-gemini.png" + }, + "Cancer":{ + "About": "Amazing people born between **June 21 ** to **July 22**. Cancer is a cardinal water sign. Represented by the crab, this crustacean seamlessly weaves between the sea and shore representing Cancer’s ability to exist in both emotional and material realms. Cancers are highly intuitive and their psychic abilities manifest in tangible spaces: For instance, Cancers can effortlessly pick up the energies in a room.", + "Motto": "***“I feel, therefore I am.”***", + "Strengths": "tenacious, highly imaginative, loyal, emotional, sympathetic, persuasive.", + "Weaknesses": "moody, pessimistic, suspicious, manipulative, insecuremoody, pessimistic, suspicious, manipulative, insecure.", + "full_form": "***C*** for caring\n***A*** for ambitious\n***N*** for nourishing\n***C*** for creative\n***E*** for emotionally intelligent\n***R*** for resilient", + "url": "https://www.horoscope.com/images-US/signs/profile-cancer.png" + }, + "Leo":{ + "About": "Amazing people born between **July 23** to **August 22**. Roll out the red carpet because Leo has arrived. Leo is represented by the lion and these spirited fire signs are the kings and queens of the celestial jungle. They’re delighted to embrace their royal status: Vivacious, theatrical, and passionate, Leos love to bask in the spotlight and celebrate themselves.", + "Motto": "***“If you know the way, go the way and show the way—you're a leader.”***", + "Strengths": "creative, passionate, generous, warm-hearted, cheerful, humorous.", + "Weaknesses": "arrogant, stubborn, self-centered, lazy, inflexible.", + "full_form": "***L*** for leaders\n***E*** for energetic\n***O*** for optimistic", + "url": "https://www.horoscope.com/images-US/signs/profile-leo.png" + }, + "Virgo":{ + "About": "Amazing people born between **August 23** to **September 22**. Virgo is an earth sign historically represented by the goddess of wheat and agriculture, an association that speaks to Virgo’s deep-rooted presence in the material world. Virgos are logical, practical, and systematic in their approach to life. This earth sign is a perfectionist at heart and isn’t afraid to improve skills through diligent and consistent practice.", + "Motto": "***“My best can always be better.”***", + "Strengths": "loyal, analytical, kind, hardworking, practical.", + "Weaknesses": "shyness, worry, overly critical of self and others, all work and no play.", + "full_form": "***V*** for virtuous\n***I*** for intelligent\n***R*** for responsible\n***G*** for generous\n***O*** for optimistic", + "url": "https://www.horoscope.com/images-US/signs/profile-virgo.png" + }, + "Libra":{ + "About": "Amazing people born between **September 23** to **October 22**. Libra is an air sign represented by the scales (interestingly, the only inanimate object of the zodiac), an association that reflects Libra's fixation on balance and harmony. Libra is obsessed with symmetry and strives to create equilibrium in all areas of life.", + "Motto": "***“No person is an island.”***", + "Strengths": "cooperative, diplomatic, gracious, fair-minded, social.", + "Weaknesses": "indecisive, avoids confrontations, will carry a grudge, self-pity.", + "full_form": "***L*** for loyal\n***I*** for inquisitive\n***B*** for balanced\n***R*** for responsible\n***A*** for altruistic", + "url": "https://www.horoscope.com/images-US/signs/profile-libra.png" + }, + "Scorpio":{ + "About": "Amazing people born between **October 23** to **November 21**. Scorpio is one of the most misunderstood signs of the zodiac. Because of its incredible passion and power, Scorpio is often mistaken for a fire sign. In fact, Scorpio is a water sign that derives its strength from the psychic, emotional realm.", + "Motto": "***“You never know what you are capable of until you try.”***", + "Strengths": "resourceful, brave, passionate, stubborn, a true friend.", + "Weaknesses": "distrusting, jealous, secretive, violent.", + "full_form": "***S*** for seductive\n***C*** for cerebral\n***O*** for original\n***R*** for reactive\n***P*** for passionate\n***I*** for intuitive\n***O*** for outstanding", + "url": "https://www.horoscope.com/images-US/signs/profile-scorpio.png" + }, + "Sagittarius":{ + "About": "Amazing people born between **November 22** to **December 21**. Represented by the archer, Sagittarians are always on a quest for knowledge. The last fire sign of the zodiac, Sagittarius launches its many pursuits like blazing arrows, chasing after geographical, intellectual, and spiritual adventures.", + "Motto": "***“Towering genius disdains a beaten path.”***", + "Strengths": "generous, idealistic, great sense of humor.", + "Weaknesses": "promises more than can deliver, very impatient, will say anything no matter how undiplomatic.", + "full_form": "***S*** for seductive\n***A*** for adventurous\n***G*** for grateful\n***I*** for intelligent\n***T*** for trailblazing\n***T*** for tenacious adept\n***A*** for adept\n***R*** for responsible\n***I*** for idealistic\n***U*** for unparalled\n***S*** for sophisticated", + "url": "https://www.horoscope.com/images-US/signs/profile-sagittarius.png" + }, + "Capricorn":{ + "About": "Amazing people born between **December 22** to **January 19**. The last earth sign of the zodiac, Capricorn is represented by the sea goat, a mythological creature with the body of a goat and tail of a fish. Accordingly, Capricorns are skilled at navigating both the material and emotional realms.", + "Motto": "***“I can succeed at anything I put my mind to.”***", + "Strengths": "responsible, disciplined, self-control, good managers.", + "Weaknesses": "know-it-all, unforgiving, condescending, expecting the worst.", + "full_form": "***C*** for confident\n***A*** for analytical\n***P*** for practical\n***R*** for responsible\n***I*** for intelligent\n***C*** for caring\n***O*** for organized\n***R*** for realistic\n***N*** for neat", + "url": "https://www.horoscope.com/images-US/signs/profile-capricorn.png" + }, + "Aquarius":{ + "About": "Amazing people born between **January 20** to **February 18**. Despite the “aqua” in its name, Aquarius is actually the last air sign of the zodiac. Aquarius is represented by the water bearer, the mystical healer who bestows water, or life, upon the land. Accordingly, Aquarius is the most humanitarian astrological sign.", + "Motto": "***“There is no me, there is only we.”***", + "Strengths": "Progressive, original, independent, humanitarian.", + "Weaknesses": "Runs from emotional expression, temperamental, uncompromising, aloof.", + "full_form": "***A*** for analytical\n***Q*** for quirky\n***U*** for uncompromising\n***A*** for action-focused\n***R*** for respectful\n***I*** for intelligent\n***U*** for unique\n***S*** for sincere", + "url": "https://www.horoscope.com/images-US/signs/profile-aquarius.png" + }, + "Pisces":{ + "About": "Amazing people born between **February 19** to **March 20**. Pisces, a water sign, is the last constellation of the zodiac. It's symbolized by two fish swimming in opposite directions, representing the constant division of Pisces' attention between fantasy and reality. As the final sign, Pisces has absorbed every lesson — the joys and the pain, the hopes and the fears — learned by all of the other signs.", + "Motto": "***“I have a lot of love to give, it only takes a little patience and those worth giving it all to.”***", + "Strengths": "Compassionate, artistic, intuitive, gentle, wise, musical.", + "Weaknesses": "Fearful, overly trusting, sad, desire to escape reality, can be a victim or a martyr.", + "full_form": "***P*** for psychic\n***I*** for intelligent\n***S*** for surprising\n***C*** for creative\n***E*** for emotionally-driven\n***S*** for sensitive", + "url": "https://www.horoscope.com/images-US/signs/profile-pisces.png" + } +} -- cgit v1.2.3 From 3609a35d5e50f88d7189f0124eadcadd220768bc Mon Sep 17 00:00:00 2001 From: Anubhav1603 Date: Tue, 22 Sep 2020 17:15:43 +0530 Subject: added zodiac sign list --- bot/exts/valentines/valentine_zodiac.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'bot') diff --git a/bot/exts/valentines/valentine_zodiac.py b/bot/exts/valentines/valentine_zodiac.py index ef9ddc78..5d36314d 100644 --- a/bot/exts/valentines/valentine_zodiac.py +++ b/bot/exts/valentines/valentine_zodiac.py @@ -13,6 +13,9 @@ log = logging.getLogger(__name__) LETTER_EMOJI = ':love_letter:' HEART_EMOJIS = [":heart:", ":gift_heart:", ":revolving_hearts:", ":sparkling_heart:", ":two_hearts:"] +zodiac_signs = ["Aries", "Taurus", "Gemini", "Cancer", "Leo", "Virgo", "Libra", + "Scorpio", "Sagittarius", "Capricorn", "Aquarius", "Pisces"] + class ValentineZodiac(commands.Cog): """A Cog that returns a counter compatible zodiac sign to the given user's zodiac sign.""" -- cgit v1.2.3 From 3ec47214966f9547b104c416f5ff37e1415536b3 Mon Sep 17 00:00:00 2001 From: Anubhav1603 Date: Tue, 22 Sep 2020 17:24:11 +0530 Subject: changed List[str]->Optional[List[str]] --- bot/exts/evergreen/wikipedia.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'bot') diff --git a/bot/exts/evergreen/wikipedia.py b/bot/exts/evergreen/wikipedia.py index 6cf8f592..f8711f90 100644 --- a/bot/exts/evergreen/wikipedia.py +++ b/bot/exts/evergreen/wikipedia.py @@ -1,7 +1,7 @@ import asyncio import datetime import logging -from typing import List +from typing import List, Optional from aiohttp import client_exceptions from discord import Color, Embed, Message @@ -27,7 +27,7 @@ class WikipediaCog(commands.Cog): """Formating wikipedia link with index and title.""" return f'`{index}` [{title}]({WIKIPEDIA_URL.format(title=title.replace(" ", "_"))})' - async def search_wikipedia(self, search_term: str) -> List[str]: + async def search_wikipedia(self, search_term: str) -> Optional[List[str]]: """Search wikipedia and return the first 10 pages found.""" pages = [] async with self.http_session.get(SEARCH_API.format(search_term=search_term)) as response: -- cgit v1.2.3 From 69b17d575deadbcae3dbc07bd156824a263ebdc2 Mon Sep 17 00:00:00 2001 From: Anubhav1603 Date: Tue, 22 Sep 2020 17:44:52 +0530 Subject: added subcommand zodiac --- bot/exts/valentines/valentine_zodiac.py | 38 ++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) (limited to 'bot') diff --git a/bot/exts/valentines/valentine_zodiac.py b/bot/exts/valentines/valentine_zodiac.py index 5d36314d..f4e4ac34 100644 --- a/bot/exts/valentines/valentine_zodiac.py +++ b/bot/exts/valentines/valentine_zodiac.py @@ -17,6 +17,11 @@ zodiac_signs = ["Aries", "Taurus", "Gemini", "Cancer", "Leo", "Virgo", "Libra", "Scorpio", "Sagittarius", "Capricorn", "Aquarius", "Pisces"] +with open(Path("bot/resources/valentines/zodiac_explanation.json"), "r", encoding="utf8") as file: + """Load zodiac zodiac explanation from static JSON resource.""" + zodiac_fact = load(file) + + class ValentineZodiac(commands.Cog): """A Cog that returns a counter compatible zodiac sign to the given user's zodiac sign.""" @@ -32,13 +37,33 @@ class ValentineZodiac(commands.Cog): zodiacs = load(json_data) return zodiacs - @commands.command(name="partnerzodiac") - async def counter_zodiac(self, ctx: commands.Context, zodiac_sign: str) -> None: + def zodiac_sign_verify(self, zodiac: str) -> discord.Embed: + """Gives informative zodiac embed.""" + c_zodiac = zodiac.capitalize() + embed = discord.Embed() + embed.color = Colours.pink + if c_zodiac in zodiac_signs: + log.info("Making zodiac embed") + embed.title = f"__{c_zodiac}__" + embed.description = zodiac_fact[f"{c_zodiac}"]["About"] + embed.add_field(name='__Full form__', value=zodiac_fact[f"{c_zodiac}"]["full_form"], inline=False) + embed.add_field(name='__Motto__', value=zodiac_fact[f"{c_zodiac}"]["Motto"], inline=False) + embed.add_field(name='__Strengths__', value=zodiac_fact[f"{c_zodiac}"]["Strengths"], inline=False) + embed.add_field(name='__Weaknesses__', value=zodiac_fact[f"{c_zodiac}"]["Weaknesses"], inline=False) + embed.set_thumbnail(url=zodiac_fact[f"{c_zodiac}"]["url"]) + else: + embed.description = "Umm you gave wrong zodiac name so i aren't able to find any :sweat_smile:" + log.info("Wrong Zodiac name provided") + return embed + log.info("Zodiac embed ready") + + @commands.group(name="partnerzodiac", invoke_without_command=True) + async def partner_zodiac(self, ctx: commands.Context, zodiac_sign: str) -> None: """Provides a counter compatible zodiac sign to the given user's zodiac sign.""" try: compatible_zodiac = random.choice(self.zodiacs[zodiac_sign.lower()]) except KeyError: - return await ctx.send(zodiac_sign.capitalize() + " zodiac sign does not exist.") + return await ctx.send(f"`{zodiac_sign.capitalize()}` zodiac sign does not exist.") emoji1 = random.choice(HEART_EMOJIS) emoji2 = random.choice(HEART_EMOJIS) @@ -54,6 +79,13 @@ class ValentineZodiac(commands.Cog): ) await ctx.send(embed=embed) + @partner_zodiac.command(name='zodiac') + async def zodiac(self, ctx: commands.Context, zodiac_sign: str) -> None: + """Provides information about zodiac sign by taking zodiac sign name as input.""" + final_embed = self.zodiac_sign_verify(zodiac_sign) + log.info("Embed successfully sent") + await ctx.send(embed=final_embed) + def setup(bot: commands.Bot) -> None: """Valentine zodiac Cog load.""" -- cgit v1.2.3 From 873512a135e614409af8114c7856fcd13ccb3c79 Mon Sep 17 00:00:00 2001 From: Anubhav1603 Date: Tue, 22 Sep 2020 17:52:19 +0530 Subject: added subcommand date and removed a from doc string of date --- bot/exts/valentines/valentine_zodiac.py | 109 ++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) (limited to 'bot') diff --git a/bot/exts/valentines/valentine_zodiac.py b/bot/exts/valentines/valentine_zodiac.py index f4e4ac34..66762134 100644 --- a/bot/exts/valentines/valentine_zodiac.py +++ b/bot/exts/valentines/valentine_zodiac.py @@ -57,6 +57,100 @@ class ValentineZodiac(commands.Cog): return embed log.info("Zodiac embed ready") + def zodiac_date_verifer(self, month: str, date: int) -> str: + """Returns zodiac sign by checking month and date.""" + month = month.capitalize() + log.info("started searching zodaic sign based on month and date") + if month == "January" or month == "Jan": + if date >= 1 and date <= 19: + zodiac = "Capricorn" + elif date >= 20 and date <= 31: + zodiac = "Aquarius" + else: + zodiac = None + elif month == "Feburary" or month == "Feb": + if date >= 1 and date <= 18: + zodiac = "Aquarius" + elif date >= 19 and date <= 29: + zodiac = "Pisces" + else: + zodiac = None + elif month == "March" or month == "Mar": + if date >= 1 and date <= 20: + zodiac = "Pisces" + elif date >= 21 and date <= 31: + zodiac = "Aries" + else: + zodiac = None + elif month == "April" or month == "Apr": + if date >= 1 and date <= 19: + zodiac = "Aries" + elif date >= 20 and date <= 30: + zodiac = "Taurus" + else: + zodiac = None + elif month == "May": + if date >= 1 and date <= 20: + zodiac = "Taurus" + elif date >= 21 and date <= 31: + zodiac = "Gemini" + else: + zodiac = None + elif month == "June" or month == "Jun": + if date >= 1 and date <= 20: + zodiac = "Gemini" + elif date >= 21 and date <= 30: + zodiac = "Cancer" + else: + zodiac = None + elif month == "July" or month == "Jul": + if date >= 1 and date <= 22: + zodiac = "Cancer" + elif date >= 23 and date <= 31: + zodiac = "Leo" + else: + zodiac = None + elif month == "August" or month == "Aug": + if date >= 1 and date <= 22: + zodiac = "Leo" + elif date >= 23 and date <= 31: + zodiac = "Virgo" + else: + zodiac = None + elif month == "September" or month == "Sept": + if date >= 1 and date <= 22: + zodiac = "Virgo" + elif date >= 23 and date <= 30: + zodiac = "Libra" + else: + zodiac = None + elif month == "October" or month == "Oct": + if date >= 1 and date <= 22: + zodiac = "Libra" + elif date >= 23 and date <= 31: + zodiac = "Scorpio" + else: + zodiac = None + elif month == "November" or month == "Nov": + if date >= 1 and date <= 21: + zodiac = "Scorpio" + elif date >= 22 and date <= 30: + zodiac = "Sagittarius" + else: + zodiac = None + elif month == "December" or month == "Dec": + if date >= 1 and date <= 21: + zodiac = "Sagittarius" + elif date >= 22 and date <= 31: + zodiac = "Capricorn" + else: + zodiac = None + else: + zodiac = None + log.info("Wrong Zodiac date or month provided") + return zodiac + log.info("Zodiac name sent") + @commands.group(name="partnerzodiac", invoke_without_command=True) async def partner_zodiac(self, ctx: commands.Context, zodiac_sign: str) -> None: """Provides a counter compatible zodiac sign to the given user's zodiac sign.""" @@ -86,6 +180,21 @@ class ValentineZodiac(commands.Cog): log.info("Embed successfully sent") await ctx.send(embed=final_embed) + @partner_zodiac.command(name="date") + async def date_and_month(self, ctx: commands.Context, month: str, date: int) -> None: + """Provides information about zodiac sign by taking month and date as input.""" + zodiac_sign_based_on_month_and_date = self.zodiac_date_verifer(month, date) + log.info("zodiac sign based on month and date received") + if zodiac_sign_based_on_month_and_date is None: + log.info("zodiac sign based on month and date returned None") + final_embed = discord.Embed() + final_embed.color = Colours.pink + final_embed.description = "You provided wrong date or month so i aren't able to find any zodiac sign" + else: + final_embed = self.zodiac_sign_verify(zodiac_sign_based_on_month_and_date) + log.info("zodiac sign embed based on month and date is now sent") + await ctx.send(embed=final_embed) + def setup(bot: commands.Bot) -> None: """Valentine zodiac Cog load.""" -- cgit v1.2.3 From a9916fd972a961be21f8ea641df7834840647a2e Mon Sep 17 00:00:00 2001 From: Anubhav <57266248+Anubhav1603@users.noreply.github.com> Date: Tue, 22 Sep 2020 22:02:39 +0530 Subject: Changed WikipediaCog-> Wikipedia Change this becuase this causing issue in help command --- bot/exts/evergreen/wikipedia.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'bot') diff --git a/bot/exts/evergreen/wikipedia.py b/bot/exts/evergreen/wikipedia.py index f8711f90..f0fe494e 100644 --- a/bot/exts/evergreen/wikipedia.py +++ b/bot/exts/evergreen/wikipedia.py @@ -15,7 +15,7 @@ SEARCH_API = "https://en.wikipedia.org/w/api.php?action=query&list=search&srsear WIKIPEDIA_URL = "https://en.wikipedia.org/wiki/{title}" -class WikipediaCog(commands.Cog): +class Wikipedia(commands.Cog): """Get info from wikipedia.""" def __init__(self, bot: commands.Bot): @@ -111,4 +111,4 @@ class WikipediaCog(commands.Cog): def setup(bot: commands.Bot) -> None: """Wikipedia Cog load.""" - bot.add_cog(WikipediaCog(bot)) + bot.add_cog(Wikipedia(bot)) -- cgit v1.2.3 From fe899c1446a544adaa8078833f6dceecb6b11351 Mon Sep 17 00:00:00 2001 From: Anubhav <57266248+Anubhav1603@users.noreply.github.com> Date: Tue, 22 Sep 2020 22:10:25 +0530 Subject: Changed Wikipedia to WikipediaSearch --- bot/exts/evergreen/wikipedia.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'bot') diff --git a/bot/exts/evergreen/wikipedia.py b/bot/exts/evergreen/wikipedia.py index f0fe494e..be36e2c4 100644 --- a/bot/exts/evergreen/wikipedia.py +++ b/bot/exts/evergreen/wikipedia.py @@ -15,7 +15,7 @@ SEARCH_API = "https://en.wikipedia.org/w/api.php?action=query&list=search&srsear WIKIPEDIA_URL = "https://en.wikipedia.org/wiki/{title}" -class Wikipedia(commands.Cog): +class WikipediaSearch(commands.Cog): """Get info from wikipedia.""" def __init__(self, bot: commands.Bot): @@ -111,4 +111,4 @@ class Wikipedia(commands.Cog): def setup(bot: commands.Bot) -> None: """Wikipedia Cog load.""" - bot.add_cog(Wikipedia(bot)) + bot.add_cog(WikipediaSearch(bot)) -- cgit v1.2.3 From b94e4613a4e9e10eb8126f4c67c9ed73d1ecc32f Mon Sep 17 00:00:00 2001 From: gustavwilliam <65498475+gustavwilliam@users.noreply.github.com> Date: Tue, 22 Sep 2020 18:43:36 +0200 Subject: Fix GitHub repo link constant - Previous version was pointing to Python, not SeasonalBot Co-authored-by: Dennis Pham --- bot/constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/constants.py b/bot/constants.py index c69d5a83..2d6bfa1b 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -85,7 +85,7 @@ class Client(NamedTuple): token = environ.get("SEASONALBOT_TOKEN") sentry_dsn = environ.get("SEASONALBOT_SENTRY_DSN") debug = environ.get("SEASONALBOT_DEBUG", "").lower() == "true" - github_bot_repo = "https://github.com/python-discord/bot" + github_bot_repo = "https://github.com/python-discord/seasonalbot" # Override seasonal locks: 1 (January) to 12 (December) month_override = int(environ["MONTH_OVERRIDE"]) if "MONTH_OVERRIDE" in environ else None -- cgit v1.2.3 From 32b03f64acb1ea87d6206be014949ed2e756b196 Mon Sep 17 00:00:00 2001 From: Gustav Odinger Date: Tue, 22 Sep 2020 18:45:50 +0200 Subject: Remove non-existing class reference in constants - Since URLs no longer exists, it's now removed from __all__ --- bot/constants.py | 1 - 1 file changed, 1 deletion(-) (limited to 'bot') diff --git a/bot/constants.py b/bot/constants.py index 2d6bfa1b..f3424673 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -24,7 +24,6 @@ __all__ = ( "ERROR_REPLIES", "NEGATIVE_REPLIES", "POSITIVE_REPLIES", - "URLs" ) log = logging.getLogger(__name__) -- cgit v1.2.3 From 8f2b68044e327278d5af0a535c46d6724b1b7aa2 Mon Sep 17 00:00:00 2001 From: Anubhav1603 Date: Wed, 23 Sep 2020 16:01:09 +0530 Subject: modified zodiac_sign_verifer --- bot/exts/valentines/valentine_zodiac.py | 100 +++----------------------------- 1 file changed, 9 insertions(+), 91 deletions(-) (limited to 'bot') diff --git a/bot/exts/valentines/valentine_zodiac.py b/bot/exts/valentines/valentine_zodiac.py index 66762134..e020548c 100644 --- a/bot/exts/valentines/valentine_zodiac.py +++ b/bot/exts/valentines/valentine_zodiac.py @@ -1,5 +1,6 @@ import logging import random +from datetime import datetime from json import load from pathlib import Path @@ -20,6 +21,8 @@ zodiac_signs = ["Aries", "Taurus", "Gemini", "Cancer", "Leo", "Virgo", "Libra", with open(Path("bot/resources/valentines/zodiac_explanation.json"), "r", encoding="utf8") as file: """Load zodiac zodiac explanation from static JSON resource.""" zodiac_fact = load(file) +year = datetime.now().year +zodiac_date = {"Aries": (datetime(year, 1, 20), datetime(year, 2, 18))} class ValentineZodiac(commands.Cog): @@ -57,96 +60,11 @@ class ValentineZodiac(commands.Cog): return embed log.info("Zodiac embed ready") - def zodiac_date_verifer(self, month: str, date: int) -> str: + def zodiac_date_verifer(self, query_datetime: datetime) -> str: """Returns zodiac sign by checking month and date.""" - month = month.capitalize() - log.info("started searching zodaic sign based on month and date") - if month == "January" or month == "Jan": - if date >= 1 and date <= 19: - zodiac = "Capricorn" - elif date >= 20 and date <= 31: - zodiac = "Aquarius" - else: - zodiac = None - elif month == "Feburary" or month == "Feb": - if date >= 1 and date <= 18: - zodiac = "Aquarius" - elif date >= 19 and date <= 29: - zodiac = "Pisces" - else: - zodiac = None - elif month == "March" or month == "Mar": - if date >= 1 and date <= 20: - zodiac = "Pisces" - elif date >= 21 and date <= 31: - zodiac = "Aries" - else: - zodiac = None - elif month == "April" or month == "Apr": - if date >= 1 and date <= 19: - zodiac = "Aries" - elif date >= 20 and date <= 30: - zodiac = "Taurus" - else: - zodiac = None - elif month == "May": - if date >= 1 and date <= 20: - zodiac = "Taurus" - elif date >= 21 and date <= 31: - zodiac = "Gemini" - else: - zodiac = None - elif month == "June" or month == "Jun": - if date >= 1 and date <= 20: - zodiac = "Gemini" - elif date >= 21 and date <= 30: - zodiac = "Cancer" - else: - zodiac = None - elif month == "July" or month == "Jul": - if date >= 1 and date <= 22: - zodiac = "Cancer" - elif date >= 23 and date <= 31: - zodiac = "Leo" - else: - zodiac = None - elif month == "August" or month == "Aug": - if date >= 1 and date <= 22: - zodiac = "Leo" - elif date >= 23 and date <= 31: - zodiac = "Virgo" - else: - zodiac = None - elif month == "September" or month == "Sept": - if date >= 1 and date <= 22: - zodiac = "Virgo" - elif date >= 23 and date <= 30: - zodiac = "Libra" - else: - zodiac = None - elif month == "October" or month == "Oct": - if date >= 1 and date <= 22: - zodiac = "Libra" - elif date >= 23 and date <= 31: - zodiac = "Scorpio" - else: - zodiac = None - elif month == "November" or month == "Nov": - if date >= 1 and date <= 21: - zodiac = "Scorpio" - elif date >= 22 and date <= 30: - zodiac = "Sagittarius" - else: - zodiac = None - elif month == "December" or month == "Dec": - if date >= 1 and date <= 21: - zodiac = "Sagittarius" - elif date >= 22 and date <= 31: - zodiac = "Capricorn" - else: - zodiac = None - else: - zodiac = None + for zodiac_name, date_range in zodiac_date.items(): + if (date_range[0] <= query_datetime <= date_range[1]): + zodiac = zodiac_name log.info("Wrong Zodiac date or month provided") return zodiac log.info("Zodiac name sent") @@ -181,9 +99,9 @@ class ValentineZodiac(commands.Cog): await ctx.send(embed=final_embed) @partner_zodiac.command(name="date") - async def date_and_month(self, ctx: commands.Context, month: str, date: int) -> None: + async def date_and_month(self, ctx: commands.Context, month: int, date: int) -> None: """Provides information about zodiac sign by taking month and date as input.""" - zodiac_sign_based_on_month_and_date = self.zodiac_date_verifer(month, date) + zodiac_sign_based_on_month_and_date = self.zodiac_date_verifer(datetime(year, month, date)) log.info("zodiac sign based on month and date received") if zodiac_sign_based_on_month_and_date is None: log.info("zodiac sign based on month and date returned None") -- cgit v1.2.3 From 921f180f5310786b23712f82621ad3e0d8578efe Mon Sep 17 00:00:00 2001 From: Gustav Odinger Date: Wed, 23 Sep 2020 18:09:03 +0200 Subject: Remove walk_extensions from exts' init - Updates __main__.py to use the walk_extensions in utils/extensions.py --- bot/__main__.py | 3 ++- bot/exts/__init__.py | 23 +---------------------- 2 files changed, 3 insertions(+), 23 deletions(-) (limited to 'bot') diff --git a/bot/__main__.py b/bot/__main__.py index 0ffd6143..cd2d43a9 100644 --- a/bot/__main__.py +++ b/bot/__main__.py @@ -5,8 +5,9 @@ from sentry_sdk.integrations.logging import LoggingIntegration from bot.bot import bot from bot.constants import Client, STAFF_ROLES, WHITELISTED_CHANNELS -from bot.exts import walk_extensions from bot.utils.decorators import in_channel_check +from bot.utils.extensions import walk_extensions + sentry_logging = LoggingIntegration( level=logging.DEBUG, diff --git a/bot/exts/__init__.py b/bot/exts/__init__.py index 25deb9af..13f484ac 100644 --- a/bot/exts/__init__.py +++ b/bot/exts/__init__.py @@ -1,9 +1,8 @@ import logging import pkgutil -from pathlib import Path from typing import Iterator -__all__ = ("get_package_names", "walk_extensions") +__all__ = ("get_package_names",) log = logging.getLogger(__name__) @@ -13,23 +12,3 @@ def get_package_names() -> Iterator[str]: for package in pkgutil.iter_modules(__path__): if package.ispkg: yield package.name - - -def walk_extensions() -> Iterator[str]: - """ - Iterate dot-separated paths to all extensions. - - The strings are formatted in a way such that the bot's `load_extension` - method can take them. Use this to load all available extensions. - - This intentionally doesn't make use of pkgutil's `walk_packages`, as we only - want to build paths to extensions - not recursively all modules. For some - extensions, the `setup` function is in the package's __init__ file, while - modules nested under the package are only helpers. Constructing the paths - ourselves serves our purpose better. - """ - base_path = Path(__path__[0]) - - for package in get_package_names(): - for extension in pkgutil.iter_modules([base_path.joinpath(package)]): - yield f"bot.exts.{package}.{extension.name}" -- cgit v1.2.3 From 99f29fbb6a23da2e6a4a7fc15ce1d5c63988681d Mon Sep 17 00:00:00 2001 From: Matteo Bertucci Date: Thu, 24 Sep 2020 09:11:41 +0000 Subject: 8bitify: Use NEAREST to resize avatar. It used to be the default, but it got changed in Pillow 7.0 --- bot/exts/evergreen/8bitify.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/exts/evergreen/8bitify.py b/bot/exts/evergreen/8bitify.py index 60062fc1..c048d9bf 100644 --- a/bot/exts/evergreen/8bitify.py +++ b/bot/exts/evergreen/8bitify.py @@ -14,7 +14,7 @@ class EightBitify(commands.Cog): @staticmethod def pixelate(image: Image) -> Image: """Takes an image and pixelates it.""" - return image.resize((32, 32)).resize((1024, 1024)) + return image.resize((32, 32), resample=Image.NEAREST).resize((1024, 1024), resample=Image.NEAREST) @staticmethod def quantize(image: Image) -> Image: -- cgit v1.2.3 From ceaf27c9bcb5f4afe4c867d576ddfa5d2c79d46b Mon Sep 17 00:00:00 2001 From: Anubhav1603 Date: Thu, 24 Sep 2020 16:35:38 +0530 Subject: added starting date and ending date of zodiac sign in json --- bot/resources/valentines/zodiac_explanation.json | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) (limited to 'bot') diff --git a/bot/resources/valentines/zodiac_explanation.json b/bot/resources/valentines/zodiac_explanation.json index ccca6fcd..ce8dd5ec 100644 --- a/bot/resources/valentines/zodiac_explanation.json +++ b/bot/resources/valentines/zodiac_explanation.json @@ -1,5 +1,7 @@ { "Aries":{ + "start_at":[3, 21], + "end_at": [4, 19], "About":"Amazing people born between **March 21** to **April 19**. Aries loves to be number one, so it’s no surprise that these audacious rams are the first sign of the zodiac. Bold and ambitious, Aries dives headfirst into even the most challenging situations.", "Motto":"***“When you know yourself, you're empowered. When you accept yourself, you're invincible.”***", "Strengths":"courageous, determined, confident, enthusiastic, optimistic, honest, passionate.", @@ -8,6 +10,8 @@ "url":"https://www.horoscope.com/images-US/signs/profile-aries.png" }, "Taurus": { + "start_at": [4, 20], + "end_at": [5, 20], "About": "Amazing people born between **April 20** to **May 20**. Taurus is an earth sign represented by the bull. Like their celestial spirit animal, Taureans enjoy relaxing in serene, bucolic environments surrounded by soft sounds, soothing aromas, and succulent flavors", "Motto": "***“Nothing worth having comes easy.”***", "Strengths": "reliable, patient, practical, devoted, responsible, stable.", @@ -16,6 +20,8 @@ "url": "https://www.horoscope.com/images-US/signs/profile-taurus.png" }, "Gemini":{ + "start_at": [5, 21], + "end_at": [6, 20], "About": "Amazing people born between **May 21** to **June 20**. Have you ever been so busy that you wished you could clone yourself just to get everything done? That’s the Gemini experience in a nutshell. Appropriately symbolized by the celestial twins, this air sign was interested in so many pursuits that it had to double itself.", "Motto": "***“I manifest my reality.”***", "Strengths": "gentle, affectionate, curious, adaptable, ability to learn quickly and exchange ideas.", @@ -24,6 +30,8 @@ "url": "https://www.horoscope.com/images-US/signs/profile-gemini.png" }, "Cancer":{ + "start_at": [6, 21], + "end_at": [7, 22], "About": "Amazing people born between **June 21 ** to **July 22**. Cancer is a cardinal water sign. Represented by the crab, this crustacean seamlessly weaves between the sea and shore representing Cancer’s ability to exist in both emotional and material realms. Cancers are highly intuitive and their psychic abilities manifest in tangible spaces: For instance, Cancers can effortlessly pick up the energies in a room.", "Motto": "***“I feel, therefore I am.”***", "Strengths": "tenacious, highly imaginative, loyal, emotional, sympathetic, persuasive.", @@ -32,6 +40,8 @@ "url": "https://www.horoscope.com/images-US/signs/profile-cancer.png" }, "Leo":{ + "start_at": [7, 23], + "end_at": [8, 22], "About": "Amazing people born between **July 23** to **August 22**. Roll out the red carpet because Leo has arrived. Leo is represented by the lion and these spirited fire signs are the kings and queens of the celestial jungle. They’re delighted to embrace their royal status: Vivacious, theatrical, and passionate, Leos love to bask in the spotlight and celebrate themselves.", "Motto": "***“If you know the way, go the way and show the way—you're a leader.”***", "Strengths": "creative, passionate, generous, warm-hearted, cheerful, humorous.", @@ -40,6 +50,8 @@ "url": "https://www.horoscope.com/images-US/signs/profile-leo.png" }, "Virgo":{ + "start_at": [8, 23], + "end_at": [9, 22], "About": "Amazing people born between **August 23** to **September 22**. Virgo is an earth sign historically represented by the goddess of wheat and agriculture, an association that speaks to Virgo’s deep-rooted presence in the material world. Virgos are logical, practical, and systematic in their approach to life. This earth sign is a perfectionist at heart and isn’t afraid to improve skills through diligent and consistent practice.", "Motto": "***“My best can always be better.”***", "Strengths": "loyal, analytical, kind, hardworking, practical.", @@ -48,6 +60,8 @@ "url": "https://www.horoscope.com/images-US/signs/profile-virgo.png" }, "Libra":{ + "start_at": [9, 23], + "end_at": [10, 22], "About": "Amazing people born between **September 23** to **October 22**. Libra is an air sign represented by the scales (interestingly, the only inanimate object of the zodiac), an association that reflects Libra's fixation on balance and harmony. Libra is obsessed with symmetry and strives to create equilibrium in all areas of life.", "Motto": "***“No person is an island.”***", "Strengths": "cooperative, diplomatic, gracious, fair-minded, social.", @@ -56,6 +70,8 @@ "url": "https://www.horoscope.com/images-US/signs/profile-libra.png" }, "Scorpio":{ + "start_at": [10, 23], + "end_at": [11, 21], "About": "Amazing people born between **October 23** to **November 21**. Scorpio is one of the most misunderstood signs of the zodiac. Because of its incredible passion and power, Scorpio is often mistaken for a fire sign. In fact, Scorpio is a water sign that derives its strength from the psychic, emotional realm.", "Motto": "***“You never know what you are capable of until you try.”***", "Strengths": "resourceful, brave, passionate, stubborn, a true friend.", @@ -64,6 +80,8 @@ "url": "https://www.horoscope.com/images-US/signs/profile-scorpio.png" }, "Sagittarius":{ + "start_at": [11, 22], + "end_at": [12, 21], "About": "Amazing people born between **November 22** to **December 21**. Represented by the archer, Sagittarians are always on a quest for knowledge. The last fire sign of the zodiac, Sagittarius launches its many pursuits like blazing arrows, chasing after geographical, intellectual, and spiritual adventures.", "Motto": "***“Towering genius disdains a beaten path.”***", "Strengths": "generous, idealistic, great sense of humor.", @@ -72,6 +90,8 @@ "url": "https://www.horoscope.com/images-US/signs/profile-sagittarius.png" }, "Capricorn":{ + "start_at": [12, 22], + "end_at": [1, 19], "About": "Amazing people born between **December 22** to **January 19**. The last earth sign of the zodiac, Capricorn is represented by the sea goat, a mythological creature with the body of a goat and tail of a fish. Accordingly, Capricorns are skilled at navigating both the material and emotional realms.", "Motto": "***“I can succeed at anything I put my mind to.”***", "Strengths": "responsible, disciplined, self-control, good managers.", @@ -80,6 +100,8 @@ "url": "https://www.horoscope.com/images-US/signs/profile-capricorn.png" }, "Aquarius":{ + "start_at": [1, 20], + "end_at": [2,18], "About": "Amazing people born between **January 20** to **February 18**. Despite the “aqua” in its name, Aquarius is actually the last air sign of the zodiac. Aquarius is represented by the water bearer, the mystical healer who bestows water, or life, upon the land. Accordingly, Aquarius is the most humanitarian astrological sign.", "Motto": "***“There is no me, there is only we.”***", "Strengths": "Progressive, original, independent, humanitarian.", @@ -88,6 +110,8 @@ "url": "https://www.horoscope.com/images-US/signs/profile-aquarius.png" }, "Pisces":{ + "start_at": [2, 19], + "end_at": [3, 20], "About": "Amazing people born between **February 19** to **March 20**. Pisces, a water sign, is the last constellation of the zodiac. It's symbolized by two fish swimming in opposite directions, representing the constant division of Pisces' attention between fantasy and reality. As the final sign, Pisces has absorbed every lesson — the joys and the pain, the hopes and the fears — learned by all of the other signs.", "Motto": "***“I have a lot of love to give, it only takes a little patience and those worth giving it all to.”***", "Strengths": "Compassionate, artistic, intuitive, gentle, wise, musical.", -- cgit v1.2.3 From d83c3f058e1e6f0b249894331485daa27399ea0e Mon Sep 17 00:00:00 2001 From: Anubhav1603 Date: Thu, 24 Sep 2020 16:37:23 +0530 Subject: changed type of month and merged 2 static method into 1 --- bot/exts/valentines/valentine_zodiac.py | 39 +++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 17 deletions(-) (limited to 'bot') diff --git a/bot/exts/valentines/valentine_zodiac.py b/bot/exts/valentines/valentine_zodiac.py index e020548c..611d6e60 100644 --- a/bot/exts/valentines/valentine_zodiac.py +++ b/bot/exts/valentines/valentine_zodiac.py @@ -14,38 +14,35 @@ log = logging.getLogger(__name__) LETTER_EMOJI = ':love_letter:' HEART_EMOJIS = [":heart:", ":gift_heart:", ":revolving_hearts:", ":sparkling_heart:", ":two_hearts:"] -zodiac_signs = ["Aries", "Taurus", "Gemini", "Cancer", "Leo", "Virgo", "Libra", +ZODIAC_SIGNS = ["Aries", "Taurus", "Gemini", "Cancer", "Leo", "Virgo", "Libra", "Scorpio", "Sagittarius", "Capricorn", "Aquarius", "Pisces"] -with open(Path("bot/resources/valentines/zodiac_explanation.json"), "r", encoding="utf8") as file: - """Load zodiac zodiac explanation from static JSON resource.""" - zodiac_fact = load(file) -year = datetime.now().year -zodiac_date = {"Aries": (datetime(year, 1, 20), datetime(year, 2, 18))} - - class ValentineZodiac(commands.Cog): """A Cog that returns a counter compatible zodiac sign to the given user's zodiac sign.""" def __init__(self, bot: commands.Bot): self.bot = bot - self.zodiacs = self.load_json() + self.zodiacs, self.zodiac_fact = self.load_comp_json() @staticmethod - def load_json() -> dict: + def load_comp_json() -> dict: """Load zodiac compatibility from static JSON resource.""" + p1 = Path("bot/resources/valentines/zodiac_explanation.json") p = Path("bot/resources/valentines/zodiac_compatibility.json") + with p1.open(encoding="utf8") as json_data: + zodiac_fact = load(json_data) with p.open(encoding="utf8") as json_data: zodiacs = load(json_data) - return zodiacs + return zodiacs, zodiac_fact def zodiac_sign_verify(self, zodiac: str) -> discord.Embed: """Gives informative zodiac embed.""" c_zodiac = zodiac.capitalize() + zodiac_fact = self.zodiac_fact embed = discord.Embed() embed.color = Colours.pink - if c_zodiac in zodiac_signs: + if c_zodiac in ZODIAC_SIGNS: log.info("Making zodiac embed") embed.title = f"__{c_zodiac}__" embed.description = zodiac_fact[f"{c_zodiac}"]["About"] @@ -62,10 +59,15 @@ class ValentineZodiac(commands.Cog): def zodiac_date_verifer(self, query_datetime: datetime) -> str: """Returns zodiac sign by checking month and date.""" - for zodiac_name, date_range in zodiac_date.items(): - if (date_range[0] <= query_datetime <= date_range[1]): + for zodiac_name, date_range in self.zodiac_fact.items(): + value_start = datetime(2020, date_range["start_at"][0], date_range["start_at"][1]).date() + value_end = datetime(2020, date_range["end_at"][0], date_range["end_at"][1]).date() + if value_start <= query_datetime.date() <= value_end: zodiac = zodiac_name - log.info("Wrong Zodiac date or month provided") + break + else: + zodiac = None + log.info("Wrong Zodiac date or month provided") return zodiac log.info("Zodiac name sent") @@ -101,8 +103,11 @@ class ValentineZodiac(commands.Cog): @partner_zodiac.command(name="date") async def date_and_month(self, ctx: commands.Context, month: int, date: int) -> None: """Provides information about zodiac sign by taking month and date as input.""" - zodiac_sign_based_on_month_and_date = self.zodiac_date_verifer(datetime(year, month, date)) - log.info("zodiac sign based on month and date received") + try: + zodiac_sign_based_on_month_and_date = self.zodiac_date_verifer(datetime(2020, month, date)) + log.info("zodiac sign based on month and date received") + except ValueError as e: + await ctx.send(f'You cannot do that, {e}') if zodiac_sign_based_on_month_and_date is None: log.info("zodiac sign based on month and date returned None") final_embed = discord.Embed() -- cgit v1.2.3 From 28d5b05564f79f087d437e06fcf97b3ad9055451 Mon Sep 17 00:00:00 2001 From: Anubhav1603 Date: Thu, 24 Sep 2020 16:46:45 +0530 Subject: replaced c_zodiac with zodiac --- bot/exts/valentines/valentine_zodiac.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) (limited to 'bot') diff --git a/bot/exts/valentines/valentine_zodiac.py b/bot/exts/valentines/valentine_zodiac.py index 611d6e60..7d428878 100644 --- a/bot/exts/valentines/valentine_zodiac.py +++ b/bot/exts/valentines/valentine_zodiac.py @@ -38,19 +38,19 @@ class ValentineZodiac(commands.Cog): def zodiac_sign_verify(self, zodiac: str) -> discord.Embed: """Gives informative zodiac embed.""" - c_zodiac = zodiac.capitalize() + zodiac = zodiac.capitalize() zodiac_fact = self.zodiac_fact embed = discord.Embed() embed.color = Colours.pink - if c_zodiac in ZODIAC_SIGNS: + if zodiac in ZODIAC_SIGNS: log.info("Making zodiac embed") - embed.title = f"__{c_zodiac}__" - embed.description = zodiac_fact[f"{c_zodiac}"]["About"] - embed.add_field(name='__Full form__', value=zodiac_fact[f"{c_zodiac}"]["full_form"], inline=False) - embed.add_field(name='__Motto__', value=zodiac_fact[f"{c_zodiac}"]["Motto"], inline=False) - embed.add_field(name='__Strengths__', value=zodiac_fact[f"{c_zodiac}"]["Strengths"], inline=False) - embed.add_field(name='__Weaknesses__', value=zodiac_fact[f"{c_zodiac}"]["Weaknesses"], inline=False) - embed.set_thumbnail(url=zodiac_fact[f"{c_zodiac}"]["url"]) + embed.title = f"__{zodiac}__" + embed.description = zodiac_fact[f"{zodiac}"]["About"] + embed.add_field(name='__Full form__', value=zodiac_fact[f"{zodiac}"]["full_form"], inline=False) + embed.add_field(name='__Motto__', value=zodiac_fact[f"{zodiac}"]["Motto"], inline=False) + embed.add_field(name='__Strengths__', value=zodiac_fact[f"{zodiac}"]["Strengths"], inline=False) + embed.add_field(name='__Weaknesses__', value=zodiac_fact[f"{zodiac}"]["Weaknesses"], inline=False) + embed.set_thumbnail(url=zodiac_fact[f"{zodiac}"]["url"]) else: embed.description = "Umm you gave wrong zodiac name so i aren't able to find any :sweat_smile:" log.info("Wrong Zodiac name provided") @@ -77,7 +77,8 @@ class ValentineZodiac(commands.Cog): try: compatible_zodiac = random.choice(self.zodiacs[zodiac_sign.lower()]) except KeyError: - return await ctx.send(f"`{zodiac_sign.capitalize()}` zodiac sign does not exist.") + await ctx.send(f"`{zodiac_sign.capitalize()}` zodiac sign does not exist.") + return emoji1 = random.choice(HEART_EMOJIS) emoji2 = random.choice(HEART_EMOJIS) -- cgit v1.2.3 From abb493f3aa0fbf7c70f7be7a25943a0465ef18fc Mon Sep 17 00:00:00 2001 From: Anubhav1603 Date: Thu, 24 Sep 2020 16:48:25 +0530 Subject: corrected order of logging --- bot/exts/valentines/valentine_zodiac.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'bot') diff --git a/bot/exts/valentines/valentine_zodiac.py b/bot/exts/valentines/valentine_zodiac.py index 7d428878..0fd748f0 100644 --- a/bot/exts/valentines/valentine_zodiac.py +++ b/bot/exts/valentines/valentine_zodiac.py @@ -67,9 +67,9 @@ class ValentineZodiac(commands.Cog): break else: zodiac = None - log.info("Wrong Zodiac date or month provided") - return zodiac + log.info("Wrong Zodiac date or month provided") log.info("Zodiac name sent") + return zodiac @commands.group(name="partnerzodiac", invoke_without_command=True) async def partner_zodiac(self, ctx: commands.Context, zodiac_sign: str) -> None: -- cgit v1.2.3 From 1b8cbc444368bd9ac5130f5944156908f5050386 Mon Sep 17 00:00:00 2001 From: Anubhav1603 Date: Thu, 24 Sep 2020 16:50:34 +0530 Subject: corrected order of logging --- bot/exts/valentines/valentine_zodiac.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/exts/valentines/valentine_zodiac.py b/bot/exts/valentines/valentine_zodiac.py index 0fd748f0..12ecf2f8 100644 --- a/bot/exts/valentines/valentine_zodiac.py +++ b/bot/exts/valentines/valentine_zodiac.py @@ -54,8 +54,8 @@ class ValentineZodiac(commands.Cog): else: embed.description = "Umm you gave wrong zodiac name so i aren't able to find any :sweat_smile:" log.info("Wrong Zodiac name provided") - return embed log.info("Zodiac embed ready") + return embed def zodiac_date_verifer(self, query_datetime: datetime) -> str: """Returns zodiac sign by checking month and date.""" -- cgit v1.2.3 From ef6bc8a23a7f49e90e2317f53a4cdb89d8c838dc Mon Sep 17 00:00:00 2001 From: Anubhav1603 Date: Thu, 24 Sep 2020 17:04:15 +0530 Subject: chaanged partnerzodiac group to zodiac group --- bot/exts/valentines/valentine_zodiac.py | 50 ++++++++++++++++----------------- 1 file changed, 25 insertions(+), 25 deletions(-) (limited to 'bot') diff --git a/bot/exts/valentines/valentine_zodiac.py b/bot/exts/valentines/valentine_zodiac.py index 12ecf2f8..38ccdf10 100644 --- a/bot/exts/valentines/valentine_zodiac.py +++ b/bot/exts/valentines/valentine_zodiac.py @@ -71,37 +71,14 @@ class ValentineZodiac(commands.Cog): log.info("Zodiac name sent") return zodiac - @commands.group(name="partnerzodiac", invoke_without_command=True) - async def partner_zodiac(self, ctx: commands.Context, zodiac_sign: str) -> None: - """Provides a counter compatible zodiac sign to the given user's zodiac sign.""" - try: - compatible_zodiac = random.choice(self.zodiacs[zodiac_sign.lower()]) - except KeyError: - await ctx.send(f"`{zodiac_sign.capitalize()}` zodiac sign does not exist.") - return - - emoji1 = random.choice(HEART_EMOJIS) - emoji2 = random.choice(HEART_EMOJIS) - embed = discord.Embed( - title="Zodic Compatibility", - description=f'{zodiac_sign.capitalize()}{emoji1}{compatible_zodiac["Zodiac"]}\n' - f'{emoji2}Compatibility meter : {compatible_zodiac["compatibility_score"]}{emoji2}', - color=Colours.pink - ) - embed.add_field( - name=f'A letter from Dr.Zodiac {LETTER_EMOJI}', - value=compatible_zodiac['description'] - ) - await ctx.send(embed=embed) - - @partner_zodiac.command(name='zodiac') + @commands.group(name='zodiac', invoke_without_command=True) async def zodiac(self, ctx: commands.Context, zodiac_sign: str) -> None: """Provides information about zodiac sign by taking zodiac sign name as input.""" final_embed = self.zodiac_sign_verify(zodiac_sign) log.info("Embed successfully sent") await ctx.send(embed=final_embed) - @partner_zodiac.command(name="date") + @zodiac.command(name="date") async def date_and_month(self, ctx: commands.Context, month: int, date: int) -> None: """Provides information about zodiac sign by taking month and date as input.""" try: @@ -119,6 +96,29 @@ class ValentineZodiac(commands.Cog): log.info("zodiac sign embed based on month and date is now sent") await ctx.send(embed=final_embed) + @zodiac.command(name="partnerzodiac") + async def partner_zodiac(self, ctx: commands.Context, zodiac_sign: str) -> None: + """Provides a counter compatible zodiac sign to the given user's zodiac sign.""" + try: + compatible_zodiac = random.choice(self.zodiacs[zodiac_sign.lower()]) + except KeyError: + await ctx.send(f"`{zodiac_sign.capitalize()}` zodiac sign does not exist.") + return + + emoji1 = random.choice(HEART_EMOJIS) + emoji2 = random.choice(HEART_EMOJIS) + embed = discord.Embed( + title="Zodic Compatibility", + description=f'{zodiac_sign.capitalize()}{emoji1}{compatible_zodiac["Zodiac"]}\n' + f'{emoji2}Compatibility meter : {compatible_zodiac["compatibility_score"]}{emoji2}', + color=Colours.pink + ) + embed.add_field( + name=f'A letter from Dr.Zodiac {LETTER_EMOJI}', + value=compatible_zodiac['description'] + ) + await ctx.send(embed=embed) + def setup(bot: commands.Bot) -> None: """Valentine zodiac Cog load.""" -- cgit v1.2.3 From 606f5874cf5a5525db19ecad4dc1178b163f63f1 Mon Sep 17 00:00:00 2001 From: Anubhav1603 Date: Thu, 24 Sep 2020 17:33:30 +0530 Subject: added union to month now it take str and and int both --- bot/exts/valentines/valentine_zodiac.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/exts/valentines/valentine_zodiac.py b/bot/exts/valentines/valentine_zodiac.py index 38ccdf10..6fbce788 100644 --- a/bot/exts/valentines/valentine_zodiac.py +++ b/bot/exts/valentines/valentine_zodiac.py @@ -3,6 +3,7 @@ import random from datetime import datetime from json import load from pathlib import Path +from typing import Union import discord from discord.ext import commands @@ -17,6 +18,12 @@ HEART_EMOJIS = [":heart:", ":gift_heart:", ":revolving_hearts:", ":sparkling_hea ZODIAC_SIGNS = ["Aries", "Taurus", "Gemini", "Cancer", "Leo", "Virgo", "Libra", "Scorpio", "Sagittarius", "Capricorn", "Aquarius", "Pisces"] +MONTH_NAME = {"January": 1, "Jan": 1, "Feburary": 2, "Feb": 2, "March": 3, "Mar": 3, + "April": 4, "Apr": 4, "May": 5, "June": 6, "Jun": 6, "July": 7, "Jul": 7, + "August": 8, "Aug": 8, "September": 9, "Sept": 9, "October": 10, "Oct": 10, + "November": 11, "Nove": 11, "December": 12, "December": 12 + } + class ValentineZodiac(commands.Cog): """A Cog that returns a counter compatible zodiac sign to the given user's zodiac sign.""" @@ -79,8 +86,15 @@ class ValentineZodiac(commands.Cog): await ctx.send(embed=final_embed) @zodiac.command(name="date") - async def date_and_month(self, ctx: commands.Context, month: int, date: int) -> None: + async def date_and_month(self, ctx: commands.Context, date: int, month: Union[int, str]) -> None: """Provides information about zodiac sign by taking month and date as input.""" + if isinstance(month, str): + month = month.capitalize() + if month in MONTH_NAME.keys(): + month = MONTH_NAME[month] + else: + await ctx.send("Sorry, but you have given wrong Month name") + return try: zodiac_sign_based_on_month_and_date = self.zodiac_date_verifer(datetime(2020, month, date)) log.info("zodiac sign based on month and date received") -- cgit v1.2.3 From e88bae4a015d1373307610c0e0035bdd30beddd1 Mon Sep 17 00:00:00 2001 From: Anubhav1603 Date: Thu, 24 Sep 2020 18:07:48 +0530 Subject: handler exception of capricorn --- bot/exts/valentines/valentine_zodiac.py | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'bot') diff --git a/bot/exts/valentines/valentine_zodiac.py b/bot/exts/valentines/valentine_zodiac.py index 6fbce788..60510377 100644 --- a/bot/exts/valentines/valentine_zodiac.py +++ b/bot/exts/valentines/valentine_zodiac.py @@ -95,6 +95,12 @@ class ValentineZodiac(commands.Cog): else: await ctx.send("Sorry, but you have given wrong Month name") return + if month == 1 or month == 12: + if date >= 1 and date <= 19 or date >= 22 and date <= 31: + zodiac = "Capricorn" + final_embed = self.zodiac_sign_verify(zodiac) + await ctx.send(embed=final_embed) + return try: zodiac_sign_based_on_month_and_date = self.zodiac_date_verifer(datetime(2020, month, date)) log.info("zodiac sign based on month and date received") -- cgit v1.2.3 From 372f8982d2fa9c2c45b2a4c4eac107501261340f Mon Sep 17 00:00:00 2001 From: Anubhav1603 Date: Thu, 24 Sep 2020 19:15:07 +0530 Subject: corrected inconsistent tab --- bot/resources/valentines/zodiac_explanation.json | 176 +++++++++++------------ 1 file changed, 88 insertions(+), 88 deletions(-) (limited to 'bot') diff --git a/bot/resources/valentines/zodiac_explanation.json b/bot/resources/valentines/zodiac_explanation.json index ce8dd5ec..c23d474d 100644 --- a/bot/resources/valentines/zodiac_explanation.json +++ b/bot/resources/valentines/zodiac_explanation.json @@ -1,12 +1,12 @@ { - "Aries":{ + "Aries": { "start_at":[3, 21], "end_at": [4, 19], "About":"Amazing people born between **March 21** to **April 19**. Aries loves to be number one, so it’s no surprise that these audacious rams are the first sign of the zodiac. Bold and ambitious, Aries dives headfirst into even the most challenging situations.", "Motto":"***“When you know yourself, you're empowered. When you accept yourself, you're invincible.”***", "Strengths":"courageous, determined, confident, enthusiastic, optimistic, honest, passionate.", "Weaknesses":"impatient, moody, short-tempered, impulsive, aggressive.", - "full_form":"**A** for assertive\n**R** for refreshing\n **I** for independent\n**E** for energetic\n**S** for sexy", + "full_form":"__**A**__ssertive\n__**R**__efreshing\n__**I**__ndependent\n__**E**__nergetic\n__**S**__exy", "url":"https://www.horoscope.com/images-US/signs/profile-aries.png" }, "Taurus": { @@ -16,107 +16,107 @@ "Motto": "***“Nothing worth having comes easy.”***", "Strengths": "reliable, patient, practical, devoted, responsible, stable.", "Weaknesses": "stubborn, possessive, uncompromising.", - "full_form": "***T*** for trailblazing\n***A*** for ambitious\n***U*** for unwavering\n***R*** for reliable\n***U*** for understanding\n***S*** for stable", + "full_form": "__**T**__railblazing\n__**A**__mbitious\n__**U**__nwavering\n__**R**__eliable\n__**U**__nderstanding\n__**S**__table", "url": "https://www.horoscope.com/images-US/signs/profile-taurus.png" }, - "Gemini":{ + "Gemini": { "start_at": [5, 21], "end_at": [6, 20], "About": "Amazing people born between **May 21** to **June 20**. Have you ever been so busy that you wished you could clone yourself just to get everything done? That’s the Gemini experience in a nutshell. Appropriately symbolized by the celestial twins, this air sign was interested in so many pursuits that it had to double itself.", "Motto": "***“I manifest my reality.”***", "Strengths": "gentle, affectionate, curious, adaptable, ability to learn quickly and exchange ideas.", "Weaknesses": "nervous, inconsistent, indecisive.", - "full_form": "***G*** for generous\n***E*** for emotionally in tune\n***M*** for motivated\n***I*** for imaginative\n***N*** for nice\n***I*** for intelligent", + "full_form": "__**G**__enerous\n__**E**__motionally in tune\n__**M**__otivated\n__**I**__maginative\n__**N**__ice\n__**I**__ntelligent", "url": "https://www.horoscope.com/images-US/signs/profile-gemini.png" }, - "Cancer":{ - "start_at": [6, 21], - "end_at": [7, 22], - "About": "Amazing people born between **June 21 ** to **July 22**. Cancer is a cardinal water sign. Represented by the crab, this crustacean seamlessly weaves between the sea and shore representing Cancer’s ability to exist in both emotional and material realms. Cancers are highly intuitive and their psychic abilities manifest in tangible spaces: For instance, Cancers can effortlessly pick up the energies in a room.", - "Motto": "***“I feel, therefore I am.”***", - "Strengths": "tenacious, highly imaginative, loyal, emotional, sympathetic, persuasive.", - "Weaknesses": "moody, pessimistic, suspicious, manipulative, insecuremoody, pessimistic, suspicious, manipulative, insecure.", - "full_form": "***C*** for caring\n***A*** for ambitious\n***N*** for nourishing\n***C*** for creative\n***E*** for emotionally intelligent\n***R*** for resilient", - "url": "https://www.horoscope.com/images-US/signs/profile-cancer.png" + "Cancer": { + "start_at": [6, 21], + "end_at": [7, 22], + "About": "Amazing people born between **June 21 ** to **July 22**. Cancer is a cardinal water sign. Represented by the crab, this crustacean seamlessly weaves between the sea and shore representing Cancer’s ability to exist in both emotional and material realms. Cancers are highly intuitive and their psychic abilities manifest in tangible spaces: For instance, Cancers can effortlessly pick up the energies in a room.", + "Motto": "***“I feel, therefore I am.”***", + "Strengths": "tenacious, highly imaginative, loyal, emotional, sympathetic, persuasive.", + "Weaknesses": "moody, pessimistic, suspicious, manipulative, insecuremoody, pessimistic, suspicious, manipulative, insecure.", + "full_form": "__**C**__aring\n__**A**__mbitious\n__**N**__ourishing\n__**C**__reative\n__**E**__motionally intelligent\n__**R**__esilient", + "url": "https://www.horoscope.com/images-US/signs/profile-cancer.png" }, - "Leo":{ - "start_at": [7, 23], - "end_at": [8, 22], - "About": "Amazing people born between **July 23** to **August 22**. Roll out the red carpet because Leo has arrived. Leo is represented by the lion and these spirited fire signs are the kings and queens of the celestial jungle. They’re delighted to embrace their royal status: Vivacious, theatrical, and passionate, Leos love to bask in the spotlight and celebrate themselves.", - "Motto": "***“If you know the way, go the way and show the way—you're a leader.”***", - "Strengths": "creative, passionate, generous, warm-hearted, cheerful, humorous.", - "Weaknesses": "arrogant, stubborn, self-centered, lazy, inflexible.", - "full_form": "***L*** for leaders\n***E*** for energetic\n***O*** for optimistic", - "url": "https://www.horoscope.com/images-US/signs/profile-leo.png" + "Leo": { + "start_at": [7, 23], + "end_at": [8, 22], + "About": "Amazing people born between **July 23** to **August 22**. Roll out the red carpet because Leo has arrived. Leo is represented by the lion and these spirited fire signs are the kings and queens of the celestial jungle. They’re delighted to embrace their royal status: Vivacious, theatrical, and passionate, Leos love to bask in the spotlight and celebrate themselves.", + "Motto": "***“If you know the way, go the way and show the way—you're a leader.”***", + "Strengths": "creative, passionate, generous, warm-hearted, cheerful, humorous.", + "Weaknesses": "arrogant, stubborn, self-centered, lazy, inflexible.", + "full_form": "__**L**__eaders\n__**E**__nergetic\n__**O**__ptimistic", + "url": "https://www.horoscope.com/images-US/signs/profile-leo.png" }, - "Virgo":{ - "start_at": [8, 23], - "end_at": [9, 22], - "About": "Amazing people born between **August 23** to **September 22**. Virgo is an earth sign historically represented by the goddess of wheat and agriculture, an association that speaks to Virgo’s deep-rooted presence in the material world. Virgos are logical, practical, and systematic in their approach to life. This earth sign is a perfectionist at heart and isn’t afraid to improve skills through diligent and consistent practice.", - "Motto": "***“My best can always be better.”***", - "Strengths": "loyal, analytical, kind, hardworking, practical.", - "Weaknesses": "shyness, worry, overly critical of self and others, all work and no play.", - "full_form": "***V*** for virtuous\n***I*** for intelligent\n***R*** for responsible\n***G*** for generous\n***O*** for optimistic", - "url": "https://www.horoscope.com/images-US/signs/profile-virgo.png" + "Virgo": { + "start_at": [8, 23], + "end_at": [9, 22], + "About": "Amazing people born between **August 23** to **September 22**. Virgo is an earth sign historically represented by the goddess of wheat and agriculture, an association that speaks to Virgo’s deep-rooted presence in the material world. Virgos are logical, practical, and systematic in their approach to life. This earth sign is a perfectionist at heart and isn’t afraid to improve skills through diligent and consistent practice.", + "Motto": "***“My best can always be better.”***", + "Strengths": "loyal, analytical, kind, hardworking, practical.", + "Weaknesses": "shyness, worry, overly critical of self and others, all work and no play.", + "full_form": "__**V**__irtuous\n__**I**__ntelligent\n__**R**__esponsible\n__**G**__enerous\n__**O**__ptimistic", + "url": "https://www.horoscope.com/images-US/signs/profile-virgo.png" }, - "Libra":{ - "start_at": [9, 23], - "end_at": [10, 22], - "About": "Amazing people born between **September 23** to **October 22**. Libra is an air sign represented by the scales (interestingly, the only inanimate object of the zodiac), an association that reflects Libra's fixation on balance and harmony. Libra is obsessed with symmetry and strives to create equilibrium in all areas of life.", - "Motto": "***“No person is an island.”***", - "Strengths": "cooperative, diplomatic, gracious, fair-minded, social.", - "Weaknesses": "indecisive, avoids confrontations, will carry a grudge, self-pity.", - "full_form": "***L*** for loyal\n***I*** for inquisitive\n***B*** for balanced\n***R*** for responsible\n***A*** for altruistic", - "url": "https://www.horoscope.com/images-US/signs/profile-libra.png" + "Libra": { + "start_at": [9, 23], + "end_at": [10, 22], + "About": "Amazing people born between **September 23** to **October 22**. Libra is an air sign represented by the scales (interestingly, the only inanimate object of the zodiac), an association that reflects Libra's fixation on balance and harmony. Libra is obsessed with symmetry and strives to create equilibrium in all areas of life.", + "Motto": "***“No person is an island.”***", + "Strengths": "cooperative, diplomatic, gracious, fair-minded, social.", + "Weaknesses": "indecisive, avoids confrontations, will carry a grudge, self-pity.", + "full_form": "__**L**__oyal\n__**I**__nquisitive\n__**B**__alanced\n__**R**__esponsible\n__**A**__ltruistic", + "url": "https://www.horoscope.com/images-US/signs/profile-libra.png" + }, + "Scorpio": { + "start_at": [10, 23], + "end_at": [11, 21], + "About": "Amazing people born between **October 23** to **November 21**. Scorpio is one of the most misunderstood signs of the zodiac. Because of its incredible passion and power, Scorpio is often mistaken for a fire sign. In fact, Scorpio is a water sign that derives its strength from the psychic, emotional realm.", + "Motto": "***“You never know what you are capable of until you try.”***", + "Strengths": "resourceful, brave, passionate, stubborn, a true friend.", + "Weaknesses": "distrusting, jealous, secretive, violent.", + "full_form": "__**S**__eductive\n__**C**__erebral\n__**O**__riginal\n__**R**__eactive\n__**P**__assionate\n__**I**__ntuitive\n__**O**__utstanding", + "url": "https://www.horoscope.com/images-US/signs/profile-scorpio.png" }, - "Scorpio":{ - "start_at": [10, 23], - "end_at": [11, 21], - "About": "Amazing people born between **October 23** to **November 21**. Scorpio is one of the most misunderstood signs of the zodiac. Because of its incredible passion and power, Scorpio is often mistaken for a fire sign. In fact, Scorpio is a water sign that derives its strength from the psychic, emotional realm.", - "Motto": "***“You never know what you are capable of until you try.”***", - "Strengths": "resourceful, brave, passionate, stubborn, a true friend.", - "Weaknesses": "distrusting, jealous, secretive, violent.", - "full_form": "***S*** for seductive\n***C*** for cerebral\n***O*** for original\n***R*** for reactive\n***P*** for passionate\n***I*** for intuitive\n***O*** for outstanding", - "url": "https://www.horoscope.com/images-US/signs/profile-scorpio.png" + "Sagittarius": { + "start_at": [11, 22], + "end_at": [12, 21], + "About": "Amazing people born between **November 22** to **December 21**. Represented by the archer, Sagittarians are always on a quest for knowledge. The last fire sign of the zodiac, Sagittarius launches its many pursuits like blazing arrows, chasing after geographical, intellectual, and spiritual adventures.", + "Motto": "***“Towering genius disdains a beaten path.”***", + "Strengths": "generous, idealistic, great sense of humor.", + "Weaknesses": "promises more than can deliver, very impatient, will say anything no matter how undiplomatic.", + "full_form": "__**S**__eductive\n__**A**__dventurous\n__**G**__rateful\n__**I**__ntelligent\n__**T**__railblazing\n__**T**__enacious adept\n__**A**__dept\n__**R**__esponsible\n__**I**__dealistic\n__**U**__nparalled\n__**S**__ophisticated", + "url": "https://www.horoscope.com/images-US/signs/profile-sagittarius.png" }, - "Sagittarius":{ - "start_at": [11, 22], - "end_at": [12, 21], - "About": "Amazing people born between **November 22** to **December 21**. Represented by the archer, Sagittarians are always on a quest for knowledge. The last fire sign of the zodiac, Sagittarius launches its many pursuits like blazing arrows, chasing after geographical, intellectual, and spiritual adventures.", - "Motto": "***“Towering genius disdains a beaten path.”***", - "Strengths": "generous, idealistic, great sense of humor.", - "Weaknesses": "promises more than can deliver, very impatient, will say anything no matter how undiplomatic.", - "full_form": "***S*** for seductive\n***A*** for adventurous\n***G*** for grateful\n***I*** for intelligent\n***T*** for trailblazing\n***T*** for tenacious adept\n***A*** for adept\n***R*** for responsible\n***I*** for idealistic\n***U*** for unparalled\n***S*** for sophisticated", - "url": "https://www.horoscope.com/images-US/signs/profile-sagittarius.png" + "Capricorn": { + "start_at": [12, 22], + "end_at": [1, 19], + "About": "Amazing people born between **December 22** to **January 19**. The last earth sign of the zodiac, Capricorn is represented by the sea goat, a mythological creature with the body of a goat and tail of a fish. Accordingly, Capricorns are skilled at navigating both the material and emotional realms.", + "Motto": "***“I can succeed at anything I put my mind to.”***", + "Strengths": "responsible, disciplined, self-control, good managers.", + "Weaknesses": "know-it-all, unforgiving, condescending, expecting the worst.", + "full_form": "__**C**__onfident\n__**A**__nalytical\n__**P**__ractical\n__**R**__esponsible\n__**I**__ntelligent\n__**C**__aring\n__**O**__rganized\n__**R**__ealistic\n__**N**__eat", + "url": "https://www.horoscope.com/images-US/signs/profile-capricorn.png" }, - "Capricorn":{ - "start_at": [12, 22], - "end_at": [1, 19], - "About": "Amazing people born between **December 22** to **January 19**. The last earth sign of the zodiac, Capricorn is represented by the sea goat, a mythological creature with the body of a goat and tail of a fish. Accordingly, Capricorns are skilled at navigating both the material and emotional realms.", - "Motto": "***“I can succeed at anything I put my mind to.”***", - "Strengths": "responsible, disciplined, self-control, good managers.", - "Weaknesses": "know-it-all, unforgiving, condescending, expecting the worst.", - "full_form": "***C*** for confident\n***A*** for analytical\n***P*** for practical\n***R*** for responsible\n***I*** for intelligent\n***C*** for caring\n***O*** for organized\n***R*** for realistic\n***N*** for neat", - "url": "https://www.horoscope.com/images-US/signs/profile-capricorn.png" + "Aquarius": { + "start_at": [1, 20], + "end_at": [2,18], + "About": "Amazing people born between **January 20** to **February 18**. Despite the “aqua” in its name, Aquarius is actually the last air sign of the zodiac. Aquarius is represented by the water bearer, the mystical healer who bestows water, or life, upon the land. Accordingly, Aquarius is the most humanitarian astrological sign.", + "Motto": "***“There is no me, there is only we.”***", + "Strengths": "Progressive, original, independent, humanitarian.", + "Weaknesses": "Runs from emotional expression, temperamental, uncompromising, aloof.", + "full_form": "__**A**__nalytical\n__**Q**__uirky\n__**U**__ncompromising\n__**A**__ction-focused\n__**R**__espectful\n__**I**__ntelligent\n__**U**__nique\n__**S**__incere", + "url": "https://www.horoscope.com/images-US/signs/profile-aquarius.png" }, - "Aquarius":{ - "start_at": [1, 20], - "end_at": [2,18], - "About": "Amazing people born between **January 20** to **February 18**. Despite the “aqua” in its name, Aquarius is actually the last air sign of the zodiac. Aquarius is represented by the water bearer, the mystical healer who bestows water, or life, upon the land. Accordingly, Aquarius is the most humanitarian astrological sign.", - "Motto": "***“There is no me, there is only we.”***", - "Strengths": "Progressive, original, independent, humanitarian.", - "Weaknesses": "Runs from emotional expression, temperamental, uncompromising, aloof.", - "full_form": "***A*** for analytical\n***Q*** for quirky\n***U*** for uncompromising\n***A*** for action-focused\n***R*** for respectful\n***I*** for intelligent\n***U*** for unique\n***S*** for sincere", - "url": "https://www.horoscope.com/images-US/signs/profile-aquarius.png" - }, - "Pisces":{ - "start_at": [2, 19], - "end_at": [3, 20], - "About": "Amazing people born between **February 19** to **March 20**. Pisces, a water sign, is the last constellation of the zodiac. It's symbolized by two fish swimming in opposite directions, representing the constant division of Pisces' attention between fantasy and reality. As the final sign, Pisces has absorbed every lesson — the joys and the pain, the hopes and the fears — learned by all of the other signs.", - "Motto": "***“I have a lot of love to give, it only takes a little patience and those worth giving it all to.”***", - "Strengths": "Compassionate, artistic, intuitive, gentle, wise, musical.", - "Weaknesses": "Fearful, overly trusting, sad, desire to escape reality, can be a victim or a martyr.", - "full_form": "***P*** for psychic\n***I*** for intelligent\n***S*** for surprising\n***C*** for creative\n***E*** for emotionally-driven\n***S*** for sensitive", - "url": "https://www.horoscope.com/images-US/signs/profile-pisces.png" - } + "Pisces": { + "start_at": [2, 19], + "end_at": [3, 20], + "About": "Amazing people born between **February 19** to **March 20**. Pisces, a water sign, is the last constellation of the zodiac. It's symbolized by two fish swimming in opposite directions, representing the constant division of Pisces' attention between fantasy and reality. As the final sign, Pisces has absorbed every lesson — the joys and the pain, the hopes and the fears — learned by all of the other signs.", + "Motto": "***“I have a lot of love to give, it only takes a little patience and those worth giving it all to.”***", + "Strengths": "Compassionate, artistic, intuitive, gentle, wise, musical.", + "Weaknesses": "Fearful, overly trusting, sad, desire to escape reality, can be a victim or a martyr.", + "full_form": "__**P**__sychic\n__**I**__ntelligent\n__**S**__urprising\n__**C**__reative\n__**E**__motionally-driven\n__**S**__ensitive", + "url": "https://www.horoscope.com/images-US/signs/profile-pisces.png" + } } -- cgit v1.2.3 From d1a3aba9a7eff421ed9daf4694c6f2315c8b2af1 Mon Sep 17 00:00:00 2001 From: Anubhav <57266248+Anubhav1603@users.noreply.github.com> Date: Fri, 25 Sep 2020 18:17:27 +0530 Subject: Update bot/exts/valentines/valentine_zodiac.py Co-authored-by: Rohan Reddy Alleti --- bot/exts/valentines/valentine_zodiac.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'bot') diff --git a/bot/exts/valentines/valentine_zodiac.py b/bot/exts/valentines/valentine_zodiac.py index 60510377..8c051ebe 100644 --- a/bot/exts/valentines/valentine_zodiac.py +++ b/bot/exts/valentines/valentine_zodiac.py @@ -66,10 +66,8 @@ class ValentineZodiac(commands.Cog): def zodiac_date_verifer(self, query_datetime: datetime) -> str: """Returns zodiac sign by checking month and date.""" - for zodiac_name, date_range in self.zodiac_fact.items(): - value_start = datetime(2020, date_range["start_at"][0], date_range["start_at"][1]).date() - value_end = datetime(2020, date_range["end_at"][0], date_range["end_at"][1]).date() - if value_start <= query_datetime.date() <= value_end: + for zodiac_name, zodiac_data in self.zodiac_fact.items(): + if zodiac_data["start_at"] <= query_datetime.date() <= zodiac_data["end_at"]: zodiac = zodiac_name break else: -- cgit v1.2.3 From 9319667c648cb6c837ed4fb55af0ae375f246fa3 Mon Sep 17 00:00:00 2001 From: Anubhav <57266248+Anubhav1603@users.noreply.github.com> Date: Fri, 25 Sep 2020 18:18:09 +0530 Subject: changed month name to calender Co-authored-by: Rohan Reddy Alleti --- bot/exts/valentines/valentine_zodiac.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'bot') diff --git a/bot/exts/valentines/valentine_zodiac.py b/bot/exts/valentines/valentine_zodiac.py index 8c051ebe..7e38dcc0 100644 --- a/bot/exts/valentines/valentine_zodiac.py +++ b/bot/exts/valentines/valentine_zodiac.py @@ -88,10 +88,10 @@ class ValentineZodiac(commands.Cog): """Provides information about zodiac sign by taking month and date as input.""" if isinstance(month, str): month = month.capitalize() - if month in MONTH_NAME.keys(): - month = MONTH_NAME[month] - else: - await ctx.send("Sorry, but you have given wrong Month name") + try: + month = list(calendar.month_abbr).index(month[:3]) + except ValueError: + await ctx.send("Sorry, but you have given wrong month name.") return if month == 1 or month == 12: if date >= 1 and date <= 19 or date >= 22 and date <= 31: -- cgit v1.2.3 From 6ab5d835714117345582433218c09f295809038f Mon Sep 17 00:00:00 2001 From: Anubhav <57266248+Anubhav1603@users.noreply.github.com> Date: Fri, 25 Sep 2020 18:19:04 +0530 Subject: modified if else for checking capricon zodiac sign Co-authored-by: Rohan Reddy Alleti --- bot/exts/valentines/valentine_zodiac.py | 33 +++++++++++++++------------------ 1 file changed, 15 insertions(+), 18 deletions(-) (limited to 'bot') diff --git a/bot/exts/valentines/valentine_zodiac.py b/bot/exts/valentines/valentine_zodiac.py index 7e38dcc0..f15a2d8f 100644 --- a/bot/exts/valentines/valentine_zodiac.py +++ b/bot/exts/valentines/valentine_zodiac.py @@ -93,25 +93,22 @@ class ValentineZodiac(commands.Cog): except ValueError: await ctx.send("Sorry, but you have given wrong month name.") return - if month == 1 or month == 12: - if date >= 1 and date <= 19 or date >= 22 and date <= 31: - zodiac = "Capricorn" - final_embed = self.zodiac_sign_verify(zodiac) - await ctx.send(embed=final_embed) - return - try: - zodiac_sign_based_on_month_and_date = self.zodiac_date_verifer(datetime(2020, month, date)) - log.info("zodiac sign based on month and date received") - except ValueError as e: - await ctx.send(f'You cannot do that, {e}') - if zodiac_sign_based_on_month_and_date is None: - log.info("zodiac sign based on month and date returned None") - final_embed = discord.Embed() - final_embed.color = Colours.pink - final_embed.description = "You provided wrong date or month so i aren't able to find any zodiac sign" + if (month == 1 and (1 <= date <= 19)) or (month == 12 and (22 <= date <= 31)): + zodiac = "capricorn" + final_embed = self.zodiac_sign_verify(zodiac) else: - final_embed = self.zodiac_sign_verify(zodiac_sign_based_on_month_and_date) - log.info("zodiac sign embed based on month and date is now sent") + try: + zodiac_sign_based_on_month_and_date = self.zodiac_date_verifer(datetime(2020, month, date)) + log.info("zodiac sign based on month and date received") + except ValueError as e: + log.info("zodiac sign based on month and date returned None") + final_embed = discord.Embed() + final_embed.color = Colours.pink + final_embed.description = f"{e}, cannot find zodiac sign." + else: + final_embed = self.zodiac_sign_verify(zodiac_sign_based_on_month_and_date) + log.info("zodiac sign embed based on month and date is now sent.") + await ctx.send(embed=final_embed) @zodiac.command(name="partnerzodiac") -- cgit v1.2.3 From 7d98cdeadad5ad521312a3fc78830c837a4a479b Mon Sep 17 00:00:00 2001 From: Anubhav <57266248+Anubhav1603@users.noreply.github.com> Date: Fri, 25 Sep 2020 18:19:57 +0530 Subject: changed p1 to explanation file and p2 to compatible file Co-authored-by: PureFunctor --- bot/exts/valentines/valentine_zodiac.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'bot') diff --git a/bot/exts/valentines/valentine_zodiac.py b/bot/exts/valentines/valentine_zodiac.py index f15a2d8f..4344dbde 100644 --- a/bot/exts/valentines/valentine_zodiac.py +++ b/bot/exts/valentines/valentine_zodiac.py @@ -35,8 +35,8 @@ class ValentineZodiac(commands.Cog): @staticmethod def load_comp_json() -> dict: """Load zodiac compatibility from static JSON resource.""" - p1 = Path("bot/resources/valentines/zodiac_explanation.json") - p = Path("bot/resources/valentines/zodiac_compatibility.json") + explanation_file = Path("bot/resources/valentines/zodiac_explanation.json") + compatibility_file = Path("bot/resources/valentines/zodiac_compatibility.json") with p1.open(encoding="utf8") as json_data: zodiac_fact = load(json_data) with p.open(encoding="utf8") as json_data: -- cgit v1.2.3 From 01c4360a53fd4ce35344e83a9a7ea08c1a868d81 Mon Sep 17 00:00:00 2001 From: Anubhav <57266248+Anubhav1603@users.noreply.github.com> Date: Fri, 25 Sep 2020 18:20:32 +0530 Subject: changed p1 to explanation file and p2 to compatible file to work with code Co-authored-by: PureFunctor --- bot/exts/valentines/valentine_zodiac.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'bot') diff --git a/bot/exts/valentines/valentine_zodiac.py b/bot/exts/valentines/valentine_zodiac.py index 4344dbde..af1b3c0d 100644 --- a/bot/exts/valentines/valentine_zodiac.py +++ b/bot/exts/valentines/valentine_zodiac.py @@ -37,9 +37,9 @@ class ValentineZodiac(commands.Cog): """Load zodiac compatibility from static JSON resource.""" explanation_file = Path("bot/resources/valentines/zodiac_explanation.json") compatibility_file = Path("bot/resources/valentines/zodiac_compatibility.json") - with p1.open(encoding="utf8") as json_data: + with explanation_file.open(encoding="utf8") as json_data: zodiac_fact = load(json_data) - with p.open(encoding="utf8") as json_data: + with compatibility_file.open(encoding="utf8") as json_data: zodiacs = load(json_data) return zodiacs, zodiac_fact -- cgit v1.2.3 From f211cd27453578926f008f9a35735e23e7419e8f Mon Sep 17 00:00:00 2001 From: Anubhav <57266248+Anubhav1603@users.noreply.github.com> Date: Fri, 25 Sep 2020 18:21:36 +0530 Subject: changed zodiac sign to self.zodiac sign Co-authored-by: Rohan Reddy Alleti --- bot/exts/valentines/valentine_zodiac.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/exts/valentines/valentine_zodiac.py b/bot/exts/valentines/valentine_zodiac.py index af1b3c0d..5bdc39ec 100644 --- a/bot/exts/valentines/valentine_zodiac.py +++ b/bot/exts/valentines/valentine_zodiac.py @@ -49,7 +49,7 @@ class ValentineZodiac(commands.Cog): zodiac_fact = self.zodiac_fact embed = discord.Embed() embed.color = Colours.pink - if zodiac in ZODIAC_SIGNS: + if zodiac in self.zodiac_fact: log.info("Making zodiac embed") embed.title = f"__{zodiac}__" embed.description = zodiac_fact[f"{zodiac}"]["About"] -- cgit v1.2.3 From 8c17bb706eaae0ffc01fc499d812e9093ad5b433 Mon Sep 17 00:00:00 2001 From: Anubhav1603 Date: Fri, 25 Sep 2020 18:33:26 +0530 Subject: changed datetime to isoformat --- bot/resources/valentines/zodiac_explanation.json | 240 +++++++++++------------ 1 file changed, 120 insertions(+), 120 deletions(-) (limited to 'bot') diff --git a/bot/resources/valentines/zodiac_explanation.json b/bot/resources/valentines/zodiac_explanation.json index c23d474d..6be1a481 100644 --- a/bot/resources/valentines/zodiac_explanation.json +++ b/bot/resources/valentines/zodiac_explanation.json @@ -1,122 +1,122 @@ { - "Aries": { - "start_at":[3, 21], - "end_at": [4, 19], - "About":"Amazing people born between **March 21** to **April 19**. Aries loves to be number one, so it’s no surprise that these audacious rams are the first sign of the zodiac. Bold and ambitious, Aries dives headfirst into even the most challenging situations.", - "Motto":"***“When you know yourself, you're empowered. When you accept yourself, you're invincible.”***", - "Strengths":"courageous, determined, confident, enthusiastic, optimistic, honest, passionate.", - "Weaknesses":"impatient, moody, short-tempered, impulsive, aggressive.", - "full_form":"__**A**__ssertive\n__**R**__efreshing\n__**I**__ndependent\n__**E**__nergetic\n__**S**__exy", - "url":"https://www.horoscope.com/images-US/signs/profile-aries.png" - }, - "Taurus": { - "start_at": [4, 20], - "end_at": [5, 20], - "About": "Amazing people born between **April 20** to **May 20**. Taurus is an earth sign represented by the bull. Like their celestial spirit animal, Taureans enjoy relaxing in serene, bucolic environments surrounded by soft sounds, soothing aromas, and succulent flavors", - "Motto": "***“Nothing worth having comes easy.”***", - "Strengths": "reliable, patient, practical, devoted, responsible, stable.", - "Weaknesses": "stubborn, possessive, uncompromising.", - "full_form": "__**T**__railblazing\n__**A**__mbitious\n__**U**__nwavering\n__**R**__eliable\n__**U**__nderstanding\n__**S**__table", - "url": "https://www.horoscope.com/images-US/signs/profile-taurus.png" - }, - "Gemini": { - "start_at": [5, 21], - "end_at": [6, 20], - "About": "Amazing people born between **May 21** to **June 20**. Have you ever been so busy that you wished you could clone yourself just to get everything done? That’s the Gemini experience in a nutshell. Appropriately symbolized by the celestial twins, this air sign was interested in so many pursuits that it had to double itself.", - "Motto": "***“I manifest my reality.”***", - "Strengths": "gentle, affectionate, curious, adaptable, ability to learn quickly and exchange ideas.", - "Weaknesses": "nervous, inconsistent, indecisive.", - "full_form": "__**G**__enerous\n__**E**__motionally in tune\n__**M**__otivated\n__**I**__maginative\n__**N**__ice\n__**I**__ntelligent", - "url": "https://www.horoscope.com/images-US/signs/profile-gemini.png" - }, - "Cancer": { - "start_at": [6, 21], - "end_at": [7, 22], - "About": "Amazing people born between **June 21 ** to **July 22**. Cancer is a cardinal water sign. Represented by the crab, this crustacean seamlessly weaves between the sea and shore representing Cancer’s ability to exist in both emotional and material realms. Cancers are highly intuitive and their psychic abilities manifest in tangible spaces: For instance, Cancers can effortlessly pick up the energies in a room.", - "Motto": "***“I feel, therefore I am.”***", - "Strengths": "tenacious, highly imaginative, loyal, emotional, sympathetic, persuasive.", - "Weaknesses": "moody, pessimistic, suspicious, manipulative, insecuremoody, pessimistic, suspicious, manipulative, insecure.", - "full_form": "__**C**__aring\n__**A**__mbitious\n__**N**__ourishing\n__**C**__reative\n__**E**__motionally intelligent\n__**R**__esilient", - "url": "https://www.horoscope.com/images-US/signs/profile-cancer.png" - }, - "Leo": { - "start_at": [7, 23], - "end_at": [8, 22], - "About": "Amazing people born between **July 23** to **August 22**. Roll out the red carpet because Leo has arrived. Leo is represented by the lion and these spirited fire signs are the kings and queens of the celestial jungle. They’re delighted to embrace their royal status: Vivacious, theatrical, and passionate, Leos love to bask in the spotlight and celebrate themselves.", - "Motto": "***“If you know the way, go the way and show the way—you're a leader.”***", - "Strengths": "creative, passionate, generous, warm-hearted, cheerful, humorous.", - "Weaknesses": "arrogant, stubborn, self-centered, lazy, inflexible.", - "full_form": "__**L**__eaders\n__**E**__nergetic\n__**O**__ptimistic", - "url": "https://www.horoscope.com/images-US/signs/profile-leo.png" - }, - "Virgo": { - "start_at": [8, 23], - "end_at": [9, 22], - "About": "Amazing people born between **August 23** to **September 22**. Virgo is an earth sign historically represented by the goddess of wheat and agriculture, an association that speaks to Virgo’s deep-rooted presence in the material world. Virgos are logical, practical, and systematic in their approach to life. This earth sign is a perfectionist at heart and isn’t afraid to improve skills through diligent and consistent practice.", - "Motto": "***“My best can always be better.”***", - "Strengths": "loyal, analytical, kind, hardworking, practical.", - "Weaknesses": "shyness, worry, overly critical of self and others, all work and no play.", - "full_form": "__**V**__irtuous\n__**I**__ntelligent\n__**R**__esponsible\n__**G**__enerous\n__**O**__ptimistic", - "url": "https://www.horoscope.com/images-US/signs/profile-virgo.png" - }, - "Libra": { - "start_at": [9, 23], - "end_at": [10, 22], - "About": "Amazing people born between **September 23** to **October 22**. Libra is an air sign represented by the scales (interestingly, the only inanimate object of the zodiac), an association that reflects Libra's fixation on balance and harmony. Libra is obsessed with symmetry and strives to create equilibrium in all areas of life.", - "Motto": "***“No person is an island.”***", - "Strengths": "cooperative, diplomatic, gracious, fair-minded, social.", - "Weaknesses": "indecisive, avoids confrontations, will carry a grudge, self-pity.", - "full_form": "__**L**__oyal\n__**I**__nquisitive\n__**B**__alanced\n__**R**__esponsible\n__**A**__ltruistic", - "url": "https://www.horoscope.com/images-US/signs/profile-libra.png" - }, - "Scorpio": { - "start_at": [10, 23], - "end_at": [11, 21], - "About": "Amazing people born between **October 23** to **November 21**. Scorpio is one of the most misunderstood signs of the zodiac. Because of its incredible passion and power, Scorpio is often mistaken for a fire sign. In fact, Scorpio is a water sign that derives its strength from the psychic, emotional realm.", - "Motto": "***“You never know what you are capable of until you try.”***", - "Strengths": "resourceful, brave, passionate, stubborn, a true friend.", - "Weaknesses": "distrusting, jealous, secretive, violent.", - "full_form": "__**S**__eductive\n__**C**__erebral\n__**O**__riginal\n__**R**__eactive\n__**P**__assionate\n__**I**__ntuitive\n__**O**__utstanding", - "url": "https://www.horoscope.com/images-US/signs/profile-scorpio.png" - }, - "Sagittarius": { - "start_at": [11, 22], - "end_at": [12, 21], - "About": "Amazing people born between **November 22** to **December 21**. Represented by the archer, Sagittarians are always on a quest for knowledge. The last fire sign of the zodiac, Sagittarius launches its many pursuits like blazing arrows, chasing after geographical, intellectual, and spiritual adventures.", - "Motto": "***“Towering genius disdains a beaten path.”***", - "Strengths": "generous, idealistic, great sense of humor.", - "Weaknesses": "promises more than can deliver, very impatient, will say anything no matter how undiplomatic.", - "full_form": "__**S**__eductive\n__**A**__dventurous\n__**G**__rateful\n__**I**__ntelligent\n__**T**__railblazing\n__**T**__enacious adept\n__**A**__dept\n__**R**__esponsible\n__**I**__dealistic\n__**U**__nparalled\n__**S**__ophisticated", - "url": "https://www.horoscope.com/images-US/signs/profile-sagittarius.png" - }, - "Capricorn": { - "start_at": [12, 22], - "end_at": [1, 19], - "About": "Amazing people born between **December 22** to **January 19**. The last earth sign of the zodiac, Capricorn is represented by the sea goat, a mythological creature with the body of a goat and tail of a fish. Accordingly, Capricorns are skilled at navigating both the material and emotional realms.", - "Motto": "***“I can succeed at anything I put my mind to.”***", - "Strengths": "responsible, disciplined, self-control, good managers.", - "Weaknesses": "know-it-all, unforgiving, condescending, expecting the worst.", - "full_form": "__**C**__onfident\n__**A**__nalytical\n__**P**__ractical\n__**R**__esponsible\n__**I**__ntelligent\n__**C**__aring\n__**O**__rganized\n__**R**__ealistic\n__**N**__eat", - "url": "https://www.horoscope.com/images-US/signs/profile-capricorn.png" - }, - "Aquarius": { - "start_at": [1, 20], - "end_at": [2,18], - "About": "Amazing people born between **January 20** to **February 18**. Despite the “aqua” in its name, Aquarius is actually the last air sign of the zodiac. Aquarius is represented by the water bearer, the mystical healer who bestows water, or life, upon the land. Accordingly, Aquarius is the most humanitarian astrological sign.", - "Motto": "***“There is no me, there is only we.”***", - "Strengths": "Progressive, original, independent, humanitarian.", - "Weaknesses": "Runs from emotional expression, temperamental, uncompromising, aloof.", - "full_form": "__**A**__nalytical\n__**Q**__uirky\n__**U**__ncompromising\n__**A**__ction-focused\n__**R**__espectful\n__**I**__ntelligent\n__**U**__nique\n__**S**__incere", - "url": "https://www.horoscope.com/images-US/signs/profile-aquarius.png" - }, - "Pisces": { - "start_at": [2, 19], - "end_at": [3, 20], - "About": "Amazing people born between **February 19** to **March 20**. Pisces, a water sign, is the last constellation of the zodiac. It's symbolized by two fish swimming in opposite directions, representing the constant division of Pisces' attention between fantasy and reality. As the final sign, Pisces has absorbed every lesson — the joys and the pain, the hopes and the fears — learned by all of the other signs.", - "Motto": "***“I have a lot of love to give, it only takes a little patience and those worth giving it all to.”***", - "Strengths": "Compassionate, artistic, intuitive, gentle, wise, musical.", - "Weaknesses": "Fearful, overly trusting, sad, desire to escape reality, can be a victim or a martyr.", - "full_form": "__**P**__sychic\n__**I**__ntelligent\n__**S**__urprising\n__**C**__reative\n__**E**__motionally-driven\n__**S**__ensitive", - "url": "https://www.horoscope.com/images-US/signs/profile-pisces.png" - } + "Aries": { + "start_at": "2020-03-21", + "end_at": "2020-04-19", + "About": "Amazing people born between **March 21** to **April 19**. Aries loves to be number one, so it\u2019s no surprise that these audacious rams are the first sign of the zodiac. Bold and ambitious, Aries dives headfirst into even the most challenging situations.", + "Motto": "***\u201cWhen you know yourself, you're empowered. When you accept yourself, you're invincible.\u201d***", + "Strengths": "courageous, determined, confident, enthusiastic, optimistic, honest, passionate.", + "Weaknesses": "impatient, moody, short-tempered, impulsive, aggressive.", + "full_form": "__**A**__ssertive\n__**R**__efreshing\n__**I**__ndependent\n__**E**__nergetic\n__**S**__exy", + "url": "https://www.horoscope.com/images-US/signs/profile-aries.png" + }, + "Taurus": { + "start_at": "2020-04-20", + "end_at": "2020-05-20", + "About": "Amazing people born between **April 20** to **May 20**. Taurus is an earth sign represented by the bull. Like their celestial spirit animal, Taureans enjoy relaxing in serene, bucolic environments surrounded by soft sounds, soothing aromas, and succulent flavors", + "Motto": "***\u201cNothing worth having comes easy.\u201d***", + "Strengths": "reliable, patient, practical, devoted, responsible, stable.", + "Weaknesses": "stubborn, possessive, uncompromising.", + "full_form": "__**T**__railblazing\n__**A**__mbitious\n__**U**__nwavering\n__**R**__eliable\n__**U**__nderstanding\n__**S**__table", + "url": "https://www.horoscope.com/images-US/signs/profile-taurus.png" + }, + "Gemini": { + "start_at": "2020-05-21", + "end_at": "2020-06-20", + "About": "Amazing people born between **May 21** to **June 20**. Have you ever been so busy that you wished you could clone yourself just to get everything done? That\u2019s the Gemini experience in a nutshell. Appropriately symbolized by the celestial twins, this air sign was interested in so many pursuits that it had to double itself.", + "Motto": "***\u201cI manifest my reality.\u201d***", + "Strengths": "gentle, affectionate, curious, adaptable, ability to learn quickly and exchange ideas.", + "Weaknesses": "nervous, inconsistent, indecisive.", + "full_form": "__**G**__enerous\n__**E**__motionally in tune\n__**M**__otivated\n__**I**__maginative\n__**N**__ice\n__**I**__ntelligent", + "url": "https://www.horoscope.com/images-US/signs/profile-gemini.png" + }, + "Cancer": { + "start_at": "2020-06-21", + "end_at": "2020-07-22", + "About": "Amazing people born between **June 21 ** to **July 22**. Cancer is a cardinal water sign. Represented by the crab, this crustacean seamlessly weaves between the sea and shore representing Cancer\u2019s ability to exist in both emotional and material realms. Cancers are highly intuitive and their psychic abilities manifest in tangible spaces: For instance, Cancers can effortlessly pick up the energies in a room.", + "Motto": "***\u201cI feel, therefore I am.\u201d***", + "Strengths": "tenacious, highly imaginative, loyal, emotional, sympathetic, persuasive.", + "Weaknesses": "moody, pessimistic, suspicious, manipulative, insecuremoody, pessimistic, suspicious, manipulative, insecure.", + "full_form": "__**C**__aring\n__**A**__mbitious\n__**N**__ourishing\n__**C**__reative\n__**E**__motionally intelligent\n__**R**__esilient", + "url": "https://www.horoscope.com/images-US/signs/profile-cancer.png" + }, + "Leo": { + "start_at": "2020-07-23", + "end_at": "2020-08-22", + "About": "Amazing people born between **July 23** to **August 22**. Roll out the red carpet because Leo has arrived. Leo is represented by the lion and these spirited fire signs are the kings and queens of the celestial jungle. They\u2019re delighted to embrace their royal status: Vivacious, theatrical, and passionate, Leos love to bask in the spotlight and celebrate themselves.", + "Motto": "***\u201cIf you know the way, go the way and show the way\u2014you're a leader.\u201d***", + "Strengths": "creative, passionate, generous, warm-hearted, cheerful, humorous.", + "Weaknesses": "arrogant, stubborn, self-centered, lazy, inflexible.", + "full_form": "__**L**__eaders\n__**E**__nergetic\n__**O**__ptimistic", + "url": "https://www.horoscope.com/images-US/signs/profile-leo.png" + }, + "Virgo": { + "start_at": "2020-08-23", + "end_at": "2020-09-22", + "About": "Amazing people born between **August 23** to **September 22**. Virgo is an earth sign historically represented by the goddess of wheat and agriculture, an association that speaks to Virgo\u2019s deep-rooted presence in the material world. Virgos are logical, practical, and systematic in their approach to life. This earth sign is a perfectionist at heart and isn\u2019t afraid to improve skills through diligent and consistent practice.", + "Motto": "***\u201cMy best can always be better.\u201d***", + "Strengths": "loyal, analytical, kind, hardworking, practical.", + "Weaknesses": "shyness, worry, overly critical of self and others, all work and no play.", + "full_form": "__**V**__irtuous\n__**I**__ntelligent\n__**R**__esponsible\n__**G**__enerous\n__**O**__ptimistic", + "url": "https://www.horoscope.com/images-US/signs/profile-virgo.png" + }, + "Libra": { + "start_at": "2020-09-23", + "end_at": "2020-10-22", + "About": "Amazing people born between **September 23** to **October 22**. Libra is an air sign represented by the scales (interestingly, the only inanimate object of the zodiac), an association that reflects Libra's fixation on balance and harmony. Libra is obsessed with symmetry and strives to create equilibrium in all areas of life.", + "Motto": "***\u201cNo person is an island.\u201d***", + "Strengths": "cooperative, diplomatic, gracious, fair-minded, social.", + "Weaknesses": "indecisive, avoids confrontations, will carry a grudge, self-pity.", + "full_form": "__**L**__oyal\n__**I**__nquisitive\n__**B**__alanced\n__**R**__esponsible\n__**A**__ltruistic", + "url": "https://www.horoscope.com/images-US/signs/profile-libra.png" + }, + "Scorpio": { + "start_at": "2020-10-23", + "end_at": "2020-11-21", + "About": "Amazing people born between **October 23** to **November 21**. Scorpio is one of the most misunderstood signs of the zodiac. Because of its incredible passion and power, Scorpio is often mistaken for a fire sign. In fact, Scorpio is a water sign that derives its strength from the psychic, emotional realm.", + "Motto": "***\u201cYou never know what you are capable of until you try.\u201d***", + "Strengths": "resourceful, brave, passionate, stubborn, a true friend.", + "Weaknesses": "distrusting, jealous, secretive, violent.", + "full_form": "__**S**__eductive\n__**C**__erebral\n__**O**__riginal\n__**R**__eactive\n__**P**__assionate\n__**I**__ntuitive\n__**O**__utstanding", + "url": "https://www.horoscope.com/images-US/signs/profile-scorpio.png" + }, + "Sagittarius": { + "start_at": "2020-11-22", + "end_at": "2020-12-21", + "About": "Amazing people born between **November 22** to **December 21**. Represented by the archer, Sagittarians are always on a quest for knowledge. The last fire sign of the zodiac, Sagittarius launches its many pursuits like blazing arrows, chasing after geographical, intellectual, and spiritual adventures.", + "Motto": "***\u201cTowering genius disdains a beaten path.\u201d***", + "Strengths": "generous, idealistic, great sense of humor.", + "Weaknesses": "promises more than can deliver, very impatient, will say anything no matter how undiplomatic.", + "full_form": "__**S**__eductive\n__**A**__dventurous\n__**G**__rateful\n__**I**__ntelligent\n__**T**__railblazing\n__**T**__enacious adept\n__**A**__dept\n__**R**__esponsible\n__**I**__dealistic\n__**U**__nparalled\n__**S**__ophisticated", + "url": "https://www.horoscope.com/images-US/signs/profile-sagittarius.png" + }, + "Capricorn": { + "start_at": "2020-12-22", + "end_at": "2020-01-19", + "About": "Amazing people born between **December 22** to **January 19**. The last earth sign of the zodiac, Capricorn is represented by the sea goat, a mythological creature with the body of a goat and tail of a fish. Accordingly, Capricorns are skilled at navigating both the material and emotional realms.", + "Motto": "***\u201cI can succeed at anything I put my mind to.\u201d***", + "Strengths": "responsible, disciplined, self-control, good managers.", + "Weaknesses": "know-it-all, unforgiving, condescending, expecting the worst.", + "full_form": "__**C**__onfident\n__**A**__nalytical\n__**P**__ractical\n__**R**__esponsible\n__**I**__ntelligent\n__**C**__aring\n__**O**__rganized\n__**R**__ealistic\n__**N**__eat", + "url": "https://www.horoscope.com/images-US/signs/profile-capricorn.png" + }, + "Aquarius": { + "start_at": "2020-01-20", + "end_at": "2020-02-18", + "About": "Amazing people born between **January 20** to **February 18**. Despite the \u201caqua\u201d in its name, Aquarius is actually the last air sign of the zodiac. Aquarius is represented by the water bearer, the mystical healer who bestows water, or life, upon the land. Accordingly, Aquarius is the most humanitarian astrological sign.", + "Motto": "***\u201cThere is no me, there is only we.\u201d***", + "Strengths": "Progressive, original, independent, humanitarian.", + "Weaknesses": "Runs from emotional expression, temperamental, uncompromising, aloof.", + "full_form": "__**A**__nalytical\n__**Q**__uirky\n__**U**__ncompromising\n__**A**__ction-focused\n__**R**__espectful\n__**I**__ntelligent\n__**U**__nique\n__**S**__incere", + "url": "https://www.horoscope.com/images-US/signs/profile-aquarius.png" + }, + "Pisces": { + "start_at": "2020-02-19", + "end_at": "2020-03-20", + "About": "Amazing people born between **February 19** to **March 20**. Pisces, a water sign, is the last constellation of the zodiac. It's symbolized by two fish swimming in opposite directions, representing the constant division of Pisces' attention between fantasy and reality. As the final sign, Pisces has absorbed every lesson \u2014 the joys and the pain, the hopes and the fears \u2014 learned by all of the other signs.", + "Motto": "***\u201cI have a lot of love to give, it only takes a little patience and those worth giving it all to.\u201d***", + "Strengths": "Compassionate, artistic, intuitive, gentle, wise, musical.", + "Weaknesses": "Fearful, overly trusting, sad, desire to escape reality, can be a victim or a martyr.", + "full_form": "__**P**__sychic\n__**I**__ntelligent\n__**S**__urprising\n__**C**__reative\n__**E**__motionally-driven\n__**S**__ensitive", + "url": "https://www.horoscope.com/images-US/signs/profile-pisces.png" + } } -- cgit v1.2.3 From 652d6878c98d5c338f6e50657d7f53fd546f1d04 Mon Sep 17 00:00:00 2001 From: Anubhav1603 Date: Fri, 25 Sep 2020 18:35:13 +0530 Subject: fixed typo --- bot/exts/valentines/valentine_zodiac.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'bot') diff --git a/bot/exts/valentines/valentine_zodiac.py b/bot/exts/valentines/valentine_zodiac.py index 60510377..c2085d8e 100644 --- a/bot/exts/valentines/valentine_zodiac.py +++ b/bot/exts/valentines/valentine_zodiac.py @@ -18,10 +18,10 @@ HEART_EMOJIS = [":heart:", ":gift_heart:", ":revolving_hearts:", ":sparkling_hea ZODIAC_SIGNS = ["Aries", "Taurus", "Gemini", "Cancer", "Leo", "Virgo", "Libra", "Scorpio", "Sagittarius", "Capricorn", "Aquarius", "Pisces"] -MONTH_NAME = {"January": 1, "Jan": 1, "Feburary": 2, "Feb": 2, "March": 3, "Mar": 3, +MONTH_NAME = {"January": 1, "Jan": 1, "February": 2, "Feb": 2, "March": 3, "Mar": 3, "April": 4, "Apr": 4, "May": 5, "June": 6, "Jun": 6, "July": 7, "Jul": 7, "August": 8, "Aug": 8, "September": 9, "Sept": 9, "October": 10, "Oct": 10, - "November": 11, "Nove": 11, "December": 12, "December": 12 + "November": 11, "Nov": 11, "December": 12, "Dec": 12 } -- cgit v1.2.3 From 73ce191c9e8c30cc4ecc677414cd3d826a336f28 Mon Sep 17 00:00:00 2001 From: Anubhav1603 Date: Fri, 25 Sep 2020 19:39:45 +0530 Subject: added fromisoformat and bugfix --- bot/exts/valentines/valentine_zodiac.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) (limited to 'bot') diff --git a/bot/exts/valentines/valentine_zodiac.py b/bot/exts/valentines/valentine_zodiac.py index 970fa0f2..6e33e5a5 100644 --- a/bot/exts/valentines/valentine_zodiac.py +++ b/bot/exts/valentines/valentine_zodiac.py @@ -1,9 +1,10 @@ +import calendar import logging import random from datetime import datetime from json import load from pathlib import Path -from typing import Union +from typing import Tuple, Union import discord from discord.ext import commands @@ -33,12 +34,15 @@ class ValentineZodiac(commands.Cog): self.zodiacs, self.zodiac_fact = self.load_comp_json() @staticmethod - def load_comp_json() -> dict: + def load_comp_json() -> Tuple[dict, dict]: """Load zodiac compatibility from static JSON resource.""" explanation_file = Path("bot/resources/valentines/zodiac_explanation.json") compatibility_file = Path("bot/resources/valentines/zodiac_compatibility.json") with explanation_file.open(encoding="utf8") as json_data: zodiac_fact = load(json_data) + for _, zodiac_data in zodiac_fact.items(): + zodiac_data['start_at'] = datetime.fromisoformat(zodiac_data['start_at']) + zodiac_data['end_at'] = datetime.fromisoformat(zodiac_data['end_at']) with compatibility_file.open(encoding="utf8") as json_data: zodiacs = load(json_data) return zodiacs, zodiac_fact @@ -67,7 +71,7 @@ class ValentineZodiac(commands.Cog): def zodiac_date_verifer(self, query_datetime: datetime) -> str: """Returns zodiac sign by checking month and date.""" for zodiac_name, zodiac_data in self.zodiac_fact.items(): - if zodiac_data["start_at"] <= query_datetime.date() <= zodiac_data["end_at"]: + if zodiac_data["start_at"].date() <= query_datetime.date() <= zodiac_data["end_at"].date(): zodiac = zodiac_name break else: @@ -87,8 +91,8 @@ class ValentineZodiac(commands.Cog): async def date_and_month(self, ctx: commands.Context, date: int, month: Union[int, str]) -> None: """Provides information about zodiac sign by taking month and date as input.""" if isinstance(month, str): - month = month.capitalize() try: + month = month.capitalize() month = list(calendar.month_abbr).index(month[:3]) except ValueError: await ctx.send("Sorry, but you have given wrong month name.") -- cgit v1.2.3 From 8d343e3dbcc5678a074a8d304ea17ad58cfee035 Mon Sep 17 00:00:00 2001 From: Anubhav1603 Date: Fri, 25 Sep 2020 19:54:04 +0530 Subject: corrected typo --- bot/exts/valentines/valentine_zodiac.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'bot') diff --git a/bot/exts/valentines/valentine_zodiac.py b/bot/exts/valentines/valentine_zodiac.py index 6e33e5a5..fa3a353c 100644 --- a/bot/exts/valentines/valentine_zodiac.py +++ b/bot/exts/valentines/valentine_zodiac.py @@ -68,7 +68,7 @@ class ValentineZodiac(commands.Cog): log.info("Zodiac embed ready") return embed - def zodiac_date_verifer(self, query_datetime: datetime) -> str: + def zodiac_date_verifier(self, query_datetime: datetime) -> str: """Returns zodiac sign by checking month and date.""" for zodiac_name, zodiac_data in self.zodiac_fact.items(): if zodiac_data["start_at"].date() <= query_datetime.date() <= zodiac_data["end_at"].date(): @@ -102,7 +102,7 @@ class ValentineZodiac(commands.Cog): final_embed = self.zodiac_sign_verify(zodiac) else: try: - zodiac_sign_based_on_month_and_date = self.zodiac_date_verifer(datetime(2020, month, date)) + zodiac_sign_based_on_month_and_date = self.zodiac_date_verifier(datetime(2020, month, date)) log.info("zodiac sign based on month and date received") except ValueError as e: log.info("zodiac sign based on month and date returned None") @@ -127,7 +127,7 @@ class ValentineZodiac(commands.Cog): emoji1 = random.choice(HEART_EMOJIS) emoji2 = random.choice(HEART_EMOJIS) embed = discord.Embed( - title="Zodic Compatibility", + title="Zodiac Compatibility", description=f'{zodiac_sign.capitalize()}{emoji1}{compatible_zodiac["Zodiac"]}\n' f'{emoji2}Compatibility meter : {compatible_zodiac["compatibility_score"]}{emoji2}', color=Colours.pink -- cgit v1.2.3 From cc55da602cf16cd8c1018ee10c14de320c4943e5 Mon Sep 17 00:00:00 2001 From: Anubhav1603 Date: Fri, 25 Sep 2020 22:53:18 +0530 Subject: handled error for and corrected error for invalid input, removed unncessary list --- bot/exts/valentines/valentine_zodiac.py | 76 ++++++++++++++++----------------- 1 file changed, 36 insertions(+), 40 deletions(-) (limited to 'bot') diff --git a/bot/exts/valentines/valentine_zodiac.py b/bot/exts/valentines/valentine_zodiac.py index fa3a353c..b182390b 100644 --- a/bot/exts/valentines/valentine_zodiac.py +++ b/bot/exts/valentines/valentine_zodiac.py @@ -16,15 +16,6 @@ log = logging.getLogger(__name__) LETTER_EMOJI = ':love_letter:' HEART_EMOJIS = [":heart:", ":gift_heart:", ":revolving_hearts:", ":sparkling_heart:", ":two_hearts:"] -ZODIAC_SIGNS = ["Aries", "Taurus", "Gemini", "Cancer", "Leo", "Virgo", "Libra", - "Scorpio", "Sagittarius", "Capricorn", "Aquarius", "Pisces"] - -MONTH_NAME = {"January": 1, "Jan": 1, "February": 2, "Feb": 2, "March": 3, "Mar": 3, - "April": 4, "Apr": 4, "May": 5, "June": 6, "Jun": 6, "July": 7, "Jul": 7, - "August": 8, "Aug": 8, "September": 9, "Sept": 9, "October": 10, "Oct": 10, - "November": 11, "Nov": 11, "December": 12, "Dec": 12 - } - class ValentineZodiac(commands.Cog): """A Cog that returns a counter compatible zodiac sign to the given user's zodiac sign.""" @@ -45,25 +36,27 @@ class ValentineZodiac(commands.Cog): zodiac_data['end_at'] = datetime.fromisoformat(zodiac_data['end_at']) with compatibility_file.open(encoding="utf8") as json_data: zodiacs = load(json_data) - return zodiacs, zodiac_fact + return zodiacs, zodiac_fact def zodiac_sign_verify(self, zodiac: str) -> discord.Embed: """Gives informative zodiac embed.""" zodiac = zodiac.capitalize() - zodiac_fact = self.zodiac_fact embed = discord.Embed() embed.color = Colours.pink - if zodiac in self.zodiac_fact: + if zodiac.capitalize() in self.zodiac_fact: log.info("Making zodiac embed") embed.title = f"__{zodiac}__" - embed.description = zodiac_fact[f"{zodiac}"]["About"] - embed.add_field(name='__Full form__', value=zodiac_fact[f"{zodiac}"]["full_form"], inline=False) - embed.add_field(name='__Motto__', value=zodiac_fact[f"{zodiac}"]["Motto"], inline=False) - embed.add_field(name='__Strengths__', value=zodiac_fact[f"{zodiac}"]["Strengths"], inline=False) - embed.add_field(name='__Weaknesses__', value=zodiac_fact[f"{zodiac}"]["Weaknesses"], inline=False) - embed.set_thumbnail(url=zodiac_fact[f"{zodiac}"]["url"]) + embed.description = self.zodiac_fact[zodiac]["About"] + embed.add_field(name='__Full form__', value=self.zodiac_fact[zodiac]["full_form"], inline=False) + embed.add_field(name='__Motto__', value=self.zodiac_fact[zodiac]["Motto"], inline=False) + embed.add_field(name='__Strengths__', value=self.zodiac_fact[zodiac]["Strengths"], inline=False) + embed.add_field(name='__Weaknesses__', value=self.zodiac_fact[zodiac]["Weaknesses"], inline=False) + embed.set_thumbnail(url=self.zodiac_fact[zodiac]["url"]) else: - embed.description = "Umm you gave wrong zodiac name so i aren't able to find any :sweat_smile:" + err_comp = [f"`{i}` {zod_name}" for i, zod_name in enumerate(self.zodiac_fact.keys(), start=1)] + error = ("\n").join(err_comp) + error_msg = f"`{zodiac}` is not a valid zodiac sign, here is the list of valid zodiac signs." + embed.description = f"{error_msg}\n{error}" log.info("Wrong Zodiac name provided") log.info("Zodiac embed ready") return embed @@ -73,16 +66,15 @@ class ValentineZodiac(commands.Cog): for zodiac_name, zodiac_data in self.zodiac_fact.items(): if zodiac_data["start_at"].date() <= query_datetime.date() <= zodiac_data["end_at"].date(): zodiac = zodiac_name - break - else: - zodiac = None - log.info("Wrong Zodiac date or month provided") log.info("Zodiac name sent") return zodiac @commands.group(name='zodiac', invoke_without_command=True) async def zodiac(self, ctx: commands.Context, zodiac_sign: str) -> None: """Provides information about zodiac sign by taking zodiac sign name as input.""" + if zodiac_sign.startswith("`"): + await ctx.send("Please don't include `") + return final_embed = self.zodiac_sign_verify(zodiac_sign) log.info("Embed successfully sent") await ctx.send(embed=final_embed) @@ -95,7 +87,7 @@ class ValentineZodiac(commands.Cog): month = month.capitalize() month = list(calendar.month_abbr).index(month[:3]) except ValueError: - await ctx.send("Sorry, but you have given wrong month name.") + await ctx.send(f"Sorry, but `{month}` is wrong month name.") return if (month == 1 and (1 <= date <= 19)) or (month == 12 and (22 <= date <= 31)): zodiac = "capricorn" @@ -108,7 +100,8 @@ class ValentineZodiac(commands.Cog): log.info("zodiac sign based on month and date returned None") final_embed = discord.Embed() final_embed.color = Colours.pink - final_embed.description = f"{e}, cannot find zodiac sign." + final_embed.description = f"Zodiac sign is not found because, {e}" + log.info(e) else: final_embed = self.zodiac_sign_verify(zodiac_sign_based_on_month_and_date) log.info("zodiac sign embed based on month and date is now sent.") @@ -118,24 +111,27 @@ class ValentineZodiac(commands.Cog): @zodiac.command(name="partnerzodiac") async def partner_zodiac(self, ctx: commands.Context, zodiac_sign: str) -> None: """Provides a counter compatible zodiac sign to the given user's zodiac sign.""" + embed = discord.Embed() + embed.color = Colours.pink + if zodiac_sign.startswith("`"): + await ctx.send("Please don't include `") + return try: compatible_zodiac = random.choice(self.zodiacs[zodiac_sign.lower()]) + emoji1 = random.choice(HEART_EMOJIS) + emoji2 = random.choice(HEART_EMOJIS) + embed.title = "Zodiac Compatibility" + embed.description = f"""{zodiac_sign.capitalize()}{emoji1}{compatible_zodiac["Zodiac"]} + {emoji2}Compatibility meter : {compatible_zodiac["compatibility_score"]}{emoji2}""" + embed.add_field( + name=f'A letter from Dr.Zodiac {LETTER_EMOJI}', + value=compatible_zodiac['description'] + ) except KeyError: - await ctx.send(f"`{zodiac_sign.capitalize()}` zodiac sign does not exist.") - return - - emoji1 = random.choice(HEART_EMOJIS) - emoji2 = random.choice(HEART_EMOJIS) - embed = discord.Embed( - title="Zodiac Compatibility", - description=f'{zodiac_sign.capitalize()}{emoji1}{compatible_zodiac["Zodiac"]}\n' - f'{emoji2}Compatibility meter : {compatible_zodiac["compatibility_score"]}{emoji2}', - color=Colours.pink - ) - embed.add_field( - name=f'A letter from Dr.Zodiac {LETTER_EMOJI}', - value=compatible_zodiac['description'] - ) + err_comp = [f"`{i}` {zod_name}" for i, zod_name in enumerate(self.zodiac_fact.keys(), start=1)] + error = ("\n").join(err_comp) + error_msg = f"`{zodiac_sign}` is not a valid zodiac sign, here is the list of valid zodiac signs." + embed.description = f"{error_msg}\n{error}" await ctx.send(embed=embed) -- cgit v1.2.3 From 67bc58c1cc50fdf479274f4eb3e9f394363a7e6d Mon Sep 17 00:00:00 2001 From: bast Date: Fri, 25 Sep 2020 12:16:12 -0700 Subject: Make .bm handle embed-suppression syntax for message links [link] and [] are also supported --- bot/exts/evergreen/bookmark.py | 4 +++- bot/utils/converters.py | 15 +++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 bot/utils/converters.py (limited to 'bot') diff --git a/bot/exts/evergreen/bookmark.py b/bot/exts/evergreen/bookmark.py index 73908702..0adf1e68 100644 --- a/bot/exts/evergreen/bookmark.py +++ b/bot/exts/evergreen/bookmark.py @@ -6,6 +6,8 @@ from discord.ext import commands from bot.constants import Colours, ERROR_REPLIES, Emojis, Icons +from bot.utils.converters import BetterMessageConverter + log = logging.getLogger(__name__) @@ -19,7 +21,7 @@ class Bookmark(commands.Cog): async def bookmark( self, ctx: commands.Context, - target_message: discord.Message, + target_message: BetterMessageConverter, *, title: str = "Bookmark" ) -> None: diff --git a/bot/utils/converters.py b/bot/utils/converters.py new file mode 100644 index 00000000..74a0b5b7 --- /dev/null +++ b/bot/utils/converters.py @@ -0,0 +1,15 @@ +import discord + +from discord.ext.commands.converter import MessageConverter + + +class BetterMessageConverter(MessageConverter): + """A converter that handles embed-suppressed links like """ + async def convert(self, ctx, argument: str) -> discord.Message: + # It's possible to wrap a message in [<>] as well, and it's supported because its easy + if argument.startswith("[") and argument.endswith("]"): + argument = argument[1:-1] + if argument.startswith("<") and argument.endswith(">"): + argument = argument[1:-1] + + return await super().convert(ctx, argument) -- cgit v1.2.3 From 5707d6fce9eccf9aba1bede459b7cf8daa04e33e Mon Sep 17 00:00:00 2001 From: bast Date: Fri, 25 Sep 2020 14:25:29 -0700 Subject: Add more docstrings and flake8 cleanup --- bot/exts/evergreen/bookmark.py | 1 - bot/utils/converters.py | 7 ++++--- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'bot') diff --git a/bot/exts/evergreen/bookmark.py b/bot/exts/evergreen/bookmark.py index 0adf1e68..bb86e18d 100644 --- a/bot/exts/evergreen/bookmark.py +++ b/bot/exts/evergreen/bookmark.py @@ -5,7 +5,6 @@ import discord from discord.ext import commands from bot.constants import Colours, ERROR_REPLIES, Emojis, Icons - from bot.utils.converters import BetterMessageConverter log = logging.getLogger(__name__) diff --git a/bot/utils/converters.py b/bot/utils/converters.py index 74a0b5b7..f36fa03a 100644 --- a/bot/utils/converters.py +++ b/bot/utils/converters.py @@ -1,11 +1,12 @@ import discord - from discord.ext.commands.converter import MessageConverter class BetterMessageConverter(MessageConverter): - """A converter that handles embed-suppressed links like """ - async def convert(self, ctx, argument: str) -> discord.Message: + """A converter that handles embed-suppressed links like .""" + + async def convert(self, ctx: discord.ext.commands.Context, argument: str) -> discord.Message: + """Wrap the commands.MessageConverter to handle <> delimited message links.""" # It's possible to wrap a message in [<>] as well, and it's supported because its easy if argument.startswith("[") and argument.endswith("]"): argument = argument[1:-1] -- cgit v1.2.3 From 626f2baa5ce5f3a551bb5f1a5406ce1d3afd5dbc Mon Sep 17 00:00:00 2001 From: Anubhav <57266248+Anubhav1603@users.noreply.github.com> Date: Sat, 26 Sep 2020 11:54:45 +0530 Subject: Changed item to values Co-authored-by: Rohan Reddy Alleti --- bot/exts/valentines/valentine_zodiac.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/exts/valentines/valentine_zodiac.py b/bot/exts/valentines/valentine_zodiac.py index b182390b..96d04947 100644 --- a/bot/exts/valentines/valentine_zodiac.py +++ b/bot/exts/valentines/valentine_zodiac.py @@ -31,7 +31,7 @@ class ValentineZodiac(commands.Cog): compatibility_file = Path("bot/resources/valentines/zodiac_compatibility.json") with explanation_file.open(encoding="utf8") as json_data: zodiac_fact = load(json_data) - for _, zodiac_data in zodiac_fact.items(): + for zodiac_data in zodiac_fact.values(): zodiac_data['start_at'] = datetime.fromisoformat(zodiac_data['start_at']) zodiac_data['end_at'] = datetime.fromisoformat(zodiac_data['end_at']) with compatibility_file.open(encoding="utf8") as json_data: -- cgit v1.2.3 From 0dc7cd1f4cd6f4b181dc9b9429d07de6413eb456 Mon Sep 17 00:00:00 2001 From: Anubhav1603 Date: Sat, 26 Sep 2020 15:29:17 +0530 Subject: changed capricorn end_at from 2020 to 2021 --- bot/resources/valentines/zodiac_explanation.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/resources/valentines/zodiac_explanation.json b/bot/resources/valentines/zodiac_explanation.json index 6be1a481..786392a3 100644 --- a/bot/resources/valentines/zodiac_explanation.json +++ b/bot/resources/valentines/zodiac_explanation.json @@ -91,7 +91,7 @@ }, "Capricorn": { "start_at": "2020-12-22", - "end_at": "2020-01-19", + "end_at": "2021-01-19", "About": "Amazing people born between **December 22** to **January 19**. The last earth sign of the zodiac, Capricorn is represented by the sea goat, a mythological creature with the body of a goat and tail of a fish. Accordingly, Capricorns are skilled at navigating both the material and emotional realms.", "Motto": "***\u201cI can succeed at anything I put my mind to.\u201d***", "Strengths": "responsible, disciplined, self-control, good managers.", -- cgit v1.2.3 From 905178cab28e1c4fa2a642cfc7e345cea43bc828 Mon Sep 17 00:00:00 2001 From: Anubhav1603 Date: Sat, 26 Sep 2020 15:30:24 +0530 Subject: changed error msg --- bot/exts/valentines/valentine_zodiac.py | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) (limited to 'bot') diff --git a/bot/exts/valentines/valentine_zodiac.py b/bot/exts/valentines/valentine_zodiac.py index b182390b..90819688 100644 --- a/bot/exts/valentines/valentine_zodiac.py +++ b/bot/exts/valentines/valentine_zodiac.py @@ -1,7 +1,7 @@ import calendar import logging import random -from datetime import datetime +from datetime import date from json import load from pathlib import Path from typing import Tuple, Union @@ -32,8 +32,8 @@ class ValentineZodiac(commands.Cog): with explanation_file.open(encoding="utf8") as json_data: zodiac_fact = load(json_data) for _, zodiac_data in zodiac_fact.items(): - zodiac_data['start_at'] = datetime.fromisoformat(zodiac_data['start_at']) - zodiac_data['end_at'] = datetime.fromisoformat(zodiac_data['end_at']) + zodiac_data['start_at'] = date.fromisoformat(zodiac_data['start_at']) + zodiac_data['end_at'] = date.fromisoformat(zodiac_data['end_at']) with compatibility_file.open(encoding="utf8") as json_data: zodiacs = load(json_data) return zodiacs, zodiac_fact @@ -55,16 +55,16 @@ class ValentineZodiac(commands.Cog): else: err_comp = [f"`{i}` {zod_name}" for i, zod_name in enumerate(self.zodiac_fact.keys(), start=1)] error = ("\n").join(err_comp) - error_msg = f"`{zodiac}` is not a valid zodiac sign, here is the list of valid zodiac signs." + error_msg = f"**{zodiac}** is not a valid zodiac sign, here is the list of valid zodiac signs." embed.description = f"{error_msg}\n{error}" log.info("Wrong Zodiac name provided") log.info("Zodiac embed ready") return embed - def zodiac_date_verifier(self, query_datetime: datetime) -> str: + def zodiac_date_verifier(self, query_datetime: date) -> str: """Returns zodiac sign by checking month and date.""" for zodiac_name, zodiac_data in self.zodiac_fact.items(): - if zodiac_data["start_at"].date() <= query_datetime.date() <= zodiac_data["end_at"].date(): + if zodiac_data["start_at"] <= query_datetime <= zodiac_data["end_at"]: zodiac = zodiac_name log.info("Zodiac name sent") return zodiac @@ -72,15 +72,12 @@ class ValentineZodiac(commands.Cog): @commands.group(name='zodiac', invoke_without_command=True) async def zodiac(self, ctx: commands.Context, zodiac_sign: str) -> None: """Provides information about zodiac sign by taking zodiac sign name as input.""" - if zodiac_sign.startswith("`"): - await ctx.send("Please don't include `") - return final_embed = self.zodiac_sign_verify(zodiac_sign) log.info("Embed successfully sent") await ctx.send(embed=final_embed) @zodiac.command(name="date") - async def date_and_month(self, ctx: commands.Context, date: int, month: Union[int, str]) -> None: + async def date_and_month(self, ctx: commands.Context, query_date: int, month: Union[int, str]) -> None: """Provides information about zodiac sign by taking month and date as input.""" if isinstance(month, str): try: @@ -94,10 +91,10 @@ class ValentineZodiac(commands.Cog): final_embed = self.zodiac_sign_verify(zodiac) else: try: - zodiac_sign_based_on_month_and_date = self.zodiac_date_verifier(datetime(2020, month, date)) + zodiac_sign_based_on_month_and_date = self.zodiac_date_verifier(date(2020, month, query_date)) log.info("zodiac sign based on month and date received") except ValueError as e: - log.info("zodiac sign based on month and date returned None") + log.info("invalid date or month given") final_embed = discord.Embed() final_embed.color = Colours.pink final_embed.description = f"Zodiac sign is not found because, {e}" @@ -113,9 +110,6 @@ class ValentineZodiac(commands.Cog): """Provides a counter compatible zodiac sign to the given user's zodiac sign.""" embed = discord.Embed() embed.color = Colours.pink - if zodiac_sign.startswith("`"): - await ctx.send("Please don't include `") - return try: compatible_zodiac = random.choice(self.zodiacs[zodiac_sign.lower()]) emoji1 = random.choice(HEART_EMOJIS) @@ -130,7 +124,7 @@ class ValentineZodiac(commands.Cog): except KeyError: err_comp = [f"`{i}` {zod_name}" for i, zod_name in enumerate(self.zodiac_fact.keys(), start=1)] error = ("\n").join(err_comp) - error_msg = f"`{zodiac_sign}` is not a valid zodiac sign, here is the list of valid zodiac signs." + error_msg = f"**{zodiac_sign}** is not a valid zodiac sign, here is the list of valid zodiac signs." embed.description = f"{error_msg}\n{error}" await ctx.send(embed=embed) -- cgit v1.2.3 From a1ce0df145e7a49762a9363f92aeae2e716463b1 Mon Sep 17 00:00:00 2001 From: Anubhav1603 Date: Sat, 26 Sep 2020 15:46:51 +0530 Subject: fixed var name --- bot/exts/valentines/valentine_zodiac.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/exts/valentines/valentine_zodiac.py b/bot/exts/valentines/valentine_zodiac.py index 1e746ed7..963d130d 100644 --- a/bot/exts/valentines/valentine_zodiac.py +++ b/bot/exts/valentines/valentine_zodiac.py @@ -86,7 +86,7 @@ class ValentineZodiac(commands.Cog): except ValueError: await ctx.send(f"Sorry, but `{month}` is wrong month name.") return - if (month == 1 and (1 <= date <= 19)) or (month == 12 and (22 <= date <= 31)): + if (month == 1 and (1 <= query_date <= 19)) or (month == 12 and (22 <= query_date <= 31)): zodiac = "capricorn" final_embed = self.zodiac_sign_verify(zodiac) else: -- cgit v1.2.3 From d1e8c589b99aa8441a0bd911852870680de6d9b8 Mon Sep 17 00:00:00 2001 From: Anubhav1603 Date: Sat, 26 Sep 2020 17:53:57 +0530 Subject: added break and made separate error function --- bot/exts/valentines/valentine_zodiac.py | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) (limited to 'bot') diff --git a/bot/exts/valentines/valentine_zodiac.py b/bot/exts/valentines/valentine_zodiac.py index 963d130d..c1c1aff8 100644 --- a/bot/exts/valentines/valentine_zodiac.py +++ b/bot/exts/valentines/valentine_zodiac.py @@ -38,6 +38,17 @@ class ValentineZodiac(commands.Cog): zodiacs = load(json_data) return zodiacs, zodiac_fact + def error(self, zodiac: str) -> discord.Embed: + """Returns error embed.""" + embed = discord.Embed() + embed.color = Colours.pink + err_comp = [f"`{i}` {zod_name}" for i, zod_name in enumerate(self.zodiac_fact.keys(), start=1)] + err = ("\n").join(err_comp) + error_msg = f"**{zodiac}** is not a valid zodiac sign, here is the list of valid zodiac signs." + embed.description = f"{error_msg}\n{err}" + log.info("Wrong Zodiac name provided") + return embed + def zodiac_sign_verify(self, zodiac: str) -> discord.Embed: """Gives informative zodiac embed.""" zodiac = zodiac.capitalize() @@ -53,11 +64,7 @@ class ValentineZodiac(commands.Cog): embed.add_field(name='__Weaknesses__', value=self.zodiac_fact[zodiac]["Weaknesses"], inline=False) embed.set_thumbnail(url=self.zodiac_fact[zodiac]["url"]) else: - err_comp = [f"`{i}` {zod_name}" for i, zod_name in enumerate(self.zodiac_fact.keys(), start=1)] - error = ("\n").join(err_comp) - error_msg = f"**{zodiac}** is not a valid zodiac sign, here is the list of valid zodiac signs." - embed.description = f"{error_msg}\n{error}" - log.info("Wrong Zodiac name provided") + embed = self.error(zodiac) log.info("Zodiac embed ready") return embed @@ -66,6 +73,7 @@ class ValentineZodiac(commands.Cog): for zodiac_name, zodiac_data in self.zodiac_fact.items(): if zodiac_data["start_at"] <= query_datetime <= zodiac_data["end_at"]: zodiac = zodiac_name + break log.info("Zodiac name sent") return zodiac @@ -122,10 +130,7 @@ class ValentineZodiac(commands.Cog): value=compatible_zodiac['description'] ) except KeyError: - err_comp = [f"`{i}` {zod_name}" for i, zod_name in enumerate(self.zodiac_fact.keys(), start=1)] - error = ("\n").join(err_comp) - error_msg = f"**{zodiac_sign}** is not a valid zodiac sign, here is the list of valid zodiac signs." - embed.description = f"{error_msg}\n{error}" + embed = self.error(zodiac_sign) await ctx.send(embed=embed) -- cgit v1.2.3 From e0443640e6711909c5e28419622c235b8da6b82b Mon Sep 17 00:00:00 2001 From: Anubhav <57266248+Anubhav1603@users.noreply.github.com> Date: Sun, 27 Sep 2020 13:53:06 +0530 Subject: Changed zodiac_sign_verify to zodiac_build_embed Co-authored-by: PureFunctor --- bot/exts/valentines/valentine_zodiac.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/exts/valentines/valentine_zodiac.py b/bot/exts/valentines/valentine_zodiac.py index c1c1aff8..71e5bae9 100644 --- a/bot/exts/valentines/valentine_zodiac.py +++ b/bot/exts/valentines/valentine_zodiac.py @@ -49,7 +49,7 @@ class ValentineZodiac(commands.Cog): log.info("Wrong Zodiac name provided") return embed - def zodiac_sign_verify(self, zodiac: str) -> discord.Embed: + def zodiac_build_embed(self, zodiac: str) -> discord.Embed: """Gives informative zodiac embed.""" zodiac = zodiac.capitalize() embed = discord.Embed() -- cgit v1.2.3 From e5f0eae51dc479e9f6fb5496ed20f672b9c2cdff Mon Sep 17 00:00:00 2001 From: Anubhav1603 Date: Sun, 27 Sep 2020 14:23:14 +0530 Subject: removed extra var from error method --- bot/exts/valentines/valentine_zodiac.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'bot') diff --git a/bot/exts/valentines/valentine_zodiac.py b/bot/exts/valentines/valentine_zodiac.py index 71e5bae9..9315f304 100644 --- a/bot/exts/valentines/valentine_zodiac.py +++ b/bot/exts/valentines/valentine_zodiac.py @@ -42,10 +42,10 @@ class ValentineZodiac(commands.Cog): """Returns error embed.""" embed = discord.Embed() embed.color = Colours.pink - err_comp = [f"`{i}` {zod_name}" for i, zod_name in enumerate(self.zodiac_fact.keys(), start=1)] - err = ("\n").join(err_comp) - error_msg = f"**{zodiac}** is not a valid zodiac sign, here is the list of valid zodiac signs." - embed.description = f"{error_msg}\n{err}" + error_comp = ("\n").join([f"`{i}` {zod_name}" + for i, zod_name in enumerate(self.zodiac_fact.keys(), start=1)]) + embed.description = f"""**{zodiac}** is not a valid zodiac sign, here is the list of valid zodiac signs. + {error_comp}""" log.info("Wrong Zodiac name provided") return embed @@ -80,7 +80,7 @@ class ValentineZodiac(commands.Cog): @commands.group(name='zodiac', invoke_without_command=True) async def zodiac(self, ctx: commands.Context, zodiac_sign: str) -> None: """Provides information about zodiac sign by taking zodiac sign name as input.""" - final_embed = self.zodiac_sign_verify(zodiac_sign) + final_embed = self.zodiac_build_embed(zodiac_sign) log.info("Embed successfully sent") await ctx.send(embed=final_embed) @@ -96,7 +96,7 @@ class ValentineZodiac(commands.Cog): return if (month == 1 and (1 <= query_date <= 19)) or (month == 12 and (22 <= query_date <= 31)): zodiac = "capricorn" - final_embed = self.zodiac_sign_verify(zodiac) + final_embed = self.zodiac_build_embed(zodiac) else: try: zodiac_sign_based_on_month_and_date = self.zodiac_date_verifier(date(2020, month, query_date)) @@ -108,7 +108,7 @@ class ValentineZodiac(commands.Cog): final_embed.description = f"Zodiac sign is not found because, {e}" log.info(e) else: - final_embed = self.zodiac_sign_verify(zodiac_sign_based_on_month_and_date) + final_embed = self.zodiac_build_embed(zodiac_sign_based_on_month_and_date) log.info("zodiac sign embed based on month and date is now sent.") await ctx.send(embed=final_embed) -- cgit v1.2.3 From 5db5100290139fc66e85aaba2aa8314f336c8b0a Mon Sep 17 00:00:00 2001 From: Anubhav1603 Date: Sun, 27 Sep 2020 14:37:13 +0530 Subject: rewritten code of error method in better manner --- bot/exts/valentines/valentine_zodiac.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) (limited to 'bot') diff --git a/bot/exts/valentines/valentine_zodiac.py b/bot/exts/valentines/valentine_zodiac.py index 9315f304..5a03a9e4 100644 --- a/bot/exts/valentines/valentine_zodiac.py +++ b/bot/exts/valentines/valentine_zodiac.py @@ -42,10 +42,13 @@ class ValentineZodiac(commands.Cog): """Returns error embed.""" embed = discord.Embed() embed.color = Colours.pink - error_comp = ("\n").join([f"`{i}` {zod_name}" - for i, zod_name in enumerate(self.zodiac_fact.keys(), start=1)]) - embed.description = f"""**{zodiac}** is not a valid zodiac sign, here is the list of valid zodiac signs. - {error_comp}""" + error_comp = "\n".join( + [f"`{i}` {zod_name}"for i, zod_name in enumerate(self.zodiac_fact.keys(), start=1)] + ) + embed.description = ( + f"**{zodiac}** is not a valid zodiac sign, here is the list of valid zodiac signs.\n" + f"{error_comp}" + ) log.info("Wrong Zodiac name provided") return embed -- cgit v1.2.3 From e1cb20edd802299e643e3a0a59269acf9cd1d6ee Mon Sep 17 00:00:00 2001 From: Anubhav1603 Date: Sun, 27 Sep 2020 14:41:36 +0530 Subject: removed triple quotes and made code readable --- bot/exts/valentines/valentine_zodiac.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'bot') diff --git a/bot/exts/valentines/valentine_zodiac.py b/bot/exts/valentines/valentine_zodiac.py index 5a03a9e4..d50a6288 100644 --- a/bot/exts/valentines/valentine_zodiac.py +++ b/bot/exts/valentines/valentine_zodiac.py @@ -126,8 +126,10 @@ class ValentineZodiac(commands.Cog): emoji1 = random.choice(HEART_EMOJIS) emoji2 = random.choice(HEART_EMOJIS) embed.title = "Zodiac Compatibility" - embed.description = f"""{zodiac_sign.capitalize()}{emoji1}{compatible_zodiac["Zodiac"]} - {emoji2}Compatibility meter : {compatible_zodiac["compatibility_score"]}{emoji2}""" + embed.description = ( + f'{zodiac_sign.capitalize()}{emoji1}{compatible_zodiac["Zodiac"]}' + f'{emoji2}Compatibility meter : {compatible_zodiac["compatibility_score"]}{emoji2}' + ) embed.add_field( name=f'A letter from Dr.Zodiac {LETTER_EMOJI}', value=compatible_zodiac['description'] -- cgit v1.2.3 From aa9d633b7a2442620b2622c493030a57aa57b117 Mon Sep 17 00:00:00 2001 From: Anubhav1603 Date: Sun, 27 Sep 2020 14:44:44 +0530 Subject: added \n --- bot/exts/valentines/valentine_zodiac.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/exts/valentines/valentine_zodiac.py b/bot/exts/valentines/valentine_zodiac.py index d50a6288..29f9ce76 100644 --- a/bot/exts/valentines/valentine_zodiac.py +++ b/bot/exts/valentines/valentine_zodiac.py @@ -127,7 +127,7 @@ class ValentineZodiac(commands.Cog): emoji2 = random.choice(HEART_EMOJIS) embed.title = "Zodiac Compatibility" embed.description = ( - f'{zodiac_sign.capitalize()}{emoji1}{compatible_zodiac["Zodiac"]}' + f'{zodiac_sign.capitalize()}{emoji1}{compatible_zodiac["Zodiac"]}\n' f'{emoji2}Compatibility meter : {compatible_zodiac["compatibility_score"]}{emoji2}' ) embed.add_field( -- cgit v1.2.3 From a98460aac0ffa3a9fd79a2dd8c09cdf00a0ce237 Mon Sep 17 00:00:00 2001 From: Anubhav1603 Date: Sun, 27 Sep 2020 15:14:06 +0530 Subject: added period in log statement and removed [] from list comp at error method --- bot/exts/valentines/valentine_zodiac.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) (limited to 'bot') diff --git a/bot/exts/valentines/valentine_zodiac.py b/bot/exts/valentines/valentine_zodiac.py index 29f9ce76..4acfe59b 100644 --- a/bot/exts/valentines/valentine_zodiac.py +++ b/bot/exts/valentines/valentine_zodiac.py @@ -43,13 +43,13 @@ class ValentineZodiac(commands.Cog): embed = discord.Embed() embed.color = Colours.pink error_comp = "\n".join( - [f"`{i}` {zod_name}"for i, zod_name in enumerate(self.zodiac_fact.keys(), start=1)] + f"`{i}` {zod_name}"for i, zod_name in enumerate(self.zodiac_fact.keys(), start=1) ) embed.description = ( f"**{zodiac}** is not a valid zodiac sign, here is the list of valid zodiac signs.\n" f"{error_comp}" ) - log.info("Wrong Zodiac name provided") + log.info("Wrong Zodiac name provided.") return embed def zodiac_build_embed(self, zodiac: str) -> discord.Embed: @@ -58,7 +58,7 @@ class ValentineZodiac(commands.Cog): embed = discord.Embed() embed.color = Colours.pink if zodiac.capitalize() in self.zodiac_fact: - log.info("Making zodiac embed") + log.info("Making zodiac embed.") embed.title = f"__{zodiac}__" embed.description = self.zodiac_fact[zodiac]["About"] embed.add_field(name='__Full form__', value=self.zodiac_fact[zodiac]["full_form"], inline=False) @@ -68,7 +68,7 @@ class ValentineZodiac(commands.Cog): embed.set_thumbnail(url=self.zodiac_fact[zodiac]["url"]) else: embed = self.error(zodiac) - log.info("Zodiac embed ready") + log.info("Zodiac embed ready.") return embed def zodiac_date_verifier(self, query_datetime: date) -> str: @@ -77,14 +77,14 @@ class ValentineZodiac(commands.Cog): if zodiac_data["start_at"] <= query_datetime <= zodiac_data["end_at"]: zodiac = zodiac_name break - log.info("Zodiac name sent") + log.info("Zodiac name sent.") return zodiac @commands.group(name='zodiac', invoke_without_command=True) async def zodiac(self, ctx: commands.Context, zodiac_sign: str) -> None: """Provides information about zodiac sign by taking zodiac sign name as input.""" final_embed = self.zodiac_build_embed(zodiac_sign) - log.info("Embed successfully sent") + log.info("Embed successfully sent.") await ctx.send(embed=final_embed) @zodiac.command(name="date") @@ -103,13 +103,13 @@ class ValentineZodiac(commands.Cog): else: try: zodiac_sign_based_on_month_and_date = self.zodiac_date_verifier(date(2020, month, query_date)) - log.info("zodiac sign based on month and date received") + log.info("zodiac sign based on month and date received.") except ValueError as e: - log.info("invalid date or month given") + log.info("invalid date or month given.") final_embed = discord.Embed() final_embed.color = Colours.pink final_embed.description = f"Zodiac sign is not found because, {e}" - log.info(e) + log.info(f"error caused due to: {e}.") else: final_embed = self.zodiac_build_embed(zodiac_sign_based_on_month_and_date) log.info("zodiac sign embed based on month and date is now sent.") -- cgit v1.2.3 From 619eeab71047c8493be4d1f2c023bbbb1f3049aa Mon Sep 17 00:00:00 2001 From: Anubhav1603 Date: Sun, 27 Sep 2020 16:23:16 +0530 Subject: corrected grammer of logging statement ,imported json and add [] back --- bot/exts/valentines/valentine_zodiac.py | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) (limited to 'bot') diff --git a/bot/exts/valentines/valentine_zodiac.py b/bot/exts/valentines/valentine_zodiac.py index 4acfe59b..69c3770c 100644 --- a/bot/exts/valentines/valentine_zodiac.py +++ b/bot/exts/valentines/valentine_zodiac.py @@ -1,8 +1,8 @@ import calendar +import json import logging import random from datetime import date -from json import load from pathlib import Path from typing import Tuple, Union @@ -30,12 +30,12 @@ class ValentineZodiac(commands.Cog): explanation_file = Path("bot/resources/valentines/zodiac_explanation.json") compatibility_file = Path("bot/resources/valentines/zodiac_compatibility.json") with explanation_file.open(encoding="utf8") as json_data: - zodiac_fact = load(json_data) + zodiac_fact = json.load(json_data) for zodiac_data in zodiac_fact.values(): zodiac_data['start_at'] = date.fromisoformat(zodiac_data['start_at']) zodiac_data['end_at'] = date.fromisoformat(zodiac_data['end_at']) with compatibility_file.open(encoding="utf8") as json_data: - zodiacs = load(json_data) + zodiacs = json.load(json_data) return zodiacs, zodiac_fact def error(self, zodiac: str) -> discord.Embed: @@ -43,13 +43,13 @@ class ValentineZodiac(commands.Cog): embed = discord.Embed() embed.color = Colours.pink error_comp = "\n".join( - f"`{i}` {zod_name}"for i, zod_name in enumerate(self.zodiac_fact.keys(), start=1) + [f"`{i}` {zod_name}" for i, zod_name in enumerate(self.zodiac_fact.keys(), start=1)] ) embed.description = ( f"**{zodiac}** is not a valid zodiac sign, here is the list of valid zodiac signs.\n" f"{error_comp}" ) - log.info("Wrong Zodiac name provided.") + log.info("Invalid zodiac name provided.") return embed def zodiac_build_embed(self, zodiac: str) -> discord.Embed: @@ -57,7 +57,7 @@ class ValentineZodiac(commands.Cog): zodiac = zodiac.capitalize() embed = discord.Embed() embed.color = Colours.pink - if zodiac.capitalize() in self.zodiac_fact: + if zodiac in self.zodiac_fact: log.info("Making zodiac embed.") embed.title = f"__{zodiac}__" embed.description = self.zodiac_fact[zodiac]["About"] @@ -75,27 +75,25 @@ class ValentineZodiac(commands.Cog): """Returns zodiac sign by checking month and date.""" for zodiac_name, zodiac_data in self.zodiac_fact.items(): if zodiac_data["start_at"] <= query_datetime <= zodiac_data["end_at"]: - zodiac = zodiac_name - break - log.info("Zodiac name sent.") - return zodiac + log.info("Zodiac name sent.") + return zodiac_name @commands.group(name='zodiac', invoke_without_command=True) async def zodiac(self, ctx: commands.Context, zodiac_sign: str) -> None: """Provides information about zodiac sign by taking zodiac sign name as input.""" final_embed = self.zodiac_build_embed(zodiac_sign) - log.info("Embed successfully sent.") await ctx.send(embed=final_embed) + log.info("Embed successfully sent.") @zodiac.command(name="date") async def date_and_month(self, ctx: commands.Context, query_date: int, month: Union[int, str]) -> None: """Provides information about zodiac sign by taking month and date as input.""" if isinstance(month, str): + month = month.capitalize() try: - month = month.capitalize() month = list(calendar.month_abbr).index(month[:3]) except ValueError: - await ctx.send(f"Sorry, but `{month}` is wrong month name.") + await ctx.send(f"Sorry, but `{month}` is not a valid month name.") return if (month == 1 and (1 <= query_date <= 19)) or (month == 12 and (22 <= query_date <= 31)): zodiac = "capricorn" @@ -105,16 +103,15 @@ class ValentineZodiac(commands.Cog): zodiac_sign_based_on_month_and_date = self.zodiac_date_verifier(date(2020, month, query_date)) log.info("zodiac sign based on month and date received.") except ValueError as e: - log.info("invalid date or month given.") final_embed = discord.Embed() final_embed.color = Colours.pink final_embed.description = f"Zodiac sign is not found because, {e}" log.info(f"error caused due to: {e}.") else: final_embed = self.zodiac_build_embed(zodiac_sign_based_on_month_and_date) - log.info("zodiac sign embed based on month and date is now sent.") await ctx.send(embed=final_embed) + log.info("Zodiac sign embed based on month and date is now sent.") @zodiac.command(name="partnerzodiac") async def partner_zodiac(self, ctx: commands.Context, zodiac_sign: str) -> None: -- cgit v1.2.3 From e56b8b9cd6713a30b1349a28d761293f2357d684 Mon Sep 17 00:00:00 2001 From: Anubhav <57266248+Anubhav1603@users.noreply.github.com> Date: Sun, 27 Sep 2020 20:07:22 +0530 Subject: Update bot/exts/valentines/valentine_zodiac.py Co-authored-by: wookie184 --- bot/exts/valentines/valentine_zodiac.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/exts/valentines/valentine_zodiac.py b/bot/exts/valentines/valentine_zodiac.py index 69c3770c..6e6ad2f6 100644 --- a/bot/exts/valentines/valentine_zodiac.py +++ b/bot/exts/valentines/valentine_zodiac.py @@ -43,7 +43,7 @@ class ValentineZodiac(commands.Cog): embed = discord.Embed() embed.color = Colours.pink error_comp = "\n".join( - [f"`{i}` {zod_name}" for i, zod_name in enumerate(self.zodiac_fact.keys(), start=1)] + f"`{i}` {zod_name}" for i, zod_name in enumerate(self.zodiac_fact.keys(), start=1) ) embed.description = ( f"**{zodiac}** is not a valid zodiac sign, here is the list of valid zodiac signs.\n" -- cgit v1.2.3 From 87d30073e42750f143239f47ed4a125fb6e7486a Mon Sep 17 00:00:00 2001 From: Anubhav1603 Date: Mon, 28 Sep 2020 11:45:57 +0530 Subject: changed date -> datetime and added aliases partner --- bot/exts/valentines/valentine_zodiac.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) (limited to 'bot') diff --git a/bot/exts/valentines/valentine_zodiac.py b/bot/exts/valentines/valentine_zodiac.py index 6e6ad2f6..a495cbe1 100644 --- a/bot/exts/valentines/valentine_zodiac.py +++ b/bot/exts/valentines/valentine_zodiac.py @@ -2,7 +2,7 @@ import calendar import json import logging import random -from datetime import date +from datetime import datetime from pathlib import Path from typing import Tuple, Union @@ -32,8 +32,8 @@ class ValentineZodiac(commands.Cog): with explanation_file.open(encoding="utf8") as json_data: zodiac_fact = json.load(json_data) for zodiac_data in zodiac_fact.values(): - zodiac_data['start_at'] = date.fromisoformat(zodiac_data['start_at']) - zodiac_data['end_at'] = date.fromisoformat(zodiac_data['end_at']) + zodiac_data['start_at'] = datetime.fromisoformat(zodiac_data['start_at']) + zodiac_data['end_at'] = datetime.fromisoformat(zodiac_data['end_at']) with compatibility_file.open(encoding="utf8") as json_data: zodiacs = json.load(json_data) return zodiacs, zodiac_fact @@ -71,10 +71,10 @@ class ValentineZodiac(commands.Cog): log.info("Zodiac embed ready.") return embed - def zodiac_date_verifier(self, query_datetime: date) -> str: + def zodiac_date_verifier(self, query_datetime: datetime) -> str: """Returns zodiac sign by checking month and date.""" for zodiac_name, zodiac_data in self.zodiac_fact.items(): - if zodiac_data["start_at"] <= query_datetime <= zodiac_data["end_at"]: + if zodiac_data["start_at"].date() <= query_datetime.date() <= zodiac_data["end_at"].date(): log.info("Zodiac name sent.") return zodiac_name @@ -86,7 +86,7 @@ class ValentineZodiac(commands.Cog): log.info("Embed successfully sent.") @zodiac.command(name="date") - async def date_and_month(self, ctx: commands.Context, query_date: int, month: Union[int, str]) -> None: + async def date_and_month(self, ctx: commands.Context, date: int, month: Union[int, str]) -> None: """Provides information about zodiac sign by taking month and date as input.""" if isinstance(month, str): month = month.capitalize() @@ -95,12 +95,12 @@ class ValentineZodiac(commands.Cog): except ValueError: await ctx.send(f"Sorry, but `{month}` is not a valid month name.") return - if (month == 1 and (1 <= query_date <= 19)) or (month == 12 and (22 <= query_date <= 31)): + if (month == 1 and (1 <= date <= 19)) or (month == 12 and (22 <= date <= 31)): zodiac = "capricorn" final_embed = self.zodiac_build_embed(zodiac) else: try: - zodiac_sign_based_on_month_and_date = self.zodiac_date_verifier(date(2020, month, query_date)) + zodiac_sign_based_on_month_and_date = self.zodiac_date_verifier(datetime(2020, month, date)) log.info("zodiac sign based on month and date received.") except ValueError as e: final_embed = discord.Embed() @@ -113,7 +113,7 @@ class ValentineZodiac(commands.Cog): await ctx.send(embed=final_embed) log.info("Zodiac sign embed based on month and date is now sent.") - @zodiac.command(name="partnerzodiac") + @zodiac.command(name="partnerzodiac", aliases=['partner']) async def partner_zodiac(self, ctx: commands.Context, zodiac_sign: str) -> None: """Provides a counter compatible zodiac sign to the given user's zodiac sign.""" embed = discord.Embed() -- cgit v1.2.3 From d9be29e006d9b381b1563b344a2c212574ce62ae Mon Sep 17 00:00:00 2001 From: Anubhav <57266248+Anubhav1603@users.noreply.github.com> Date: Mon, 28 Sep 2020 13:31:22 +0530 Subject: Update bot/exts/valentines/valentine_zodiac.py Co-authored-by: Thomas Petersson <61778143+thomaspet@users.noreply.github.com> --- bot/exts/valentines/valentine_zodiac.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/exts/valentines/valentine_zodiac.py b/bot/exts/valentines/valentine_zodiac.py index a495cbe1..1874856a 100644 --- a/bot/exts/valentines/valentine_zodiac.py +++ b/bot/exts/valentines/valentine_zodiac.py @@ -111,7 +111,7 @@ class ValentineZodiac(commands.Cog): final_embed = self.zodiac_build_embed(zodiac_sign_based_on_month_and_date) await ctx.send(embed=final_embed) - log.info("Zodiac sign embed based on month and date is now sent.") + log.info("Zodiac sign embed based on date is now sent.") @zodiac.command(name="partnerzodiac", aliases=['partner']) async def partner_zodiac(self, ctx: commands.Context, zodiac_sign: str) -> None: -- cgit v1.2.3 From bf981acadeaf19bf1845f3b7969de5a23d4098e5 Mon Sep 17 00:00:00 2001 From: Anubhav <57266248+Anubhav1603@users.noreply.github.com> Date: Mon, 28 Sep 2020 13:34:26 +0530 Subject: Update bot/exts/valentines/valentine_zodiac.py Co-authored-by: Thomas Petersson <61778143+thomaspet@users.noreply.github.com> --- bot/exts/valentines/valentine_zodiac.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/exts/valentines/valentine_zodiac.py b/bot/exts/valentines/valentine_zodiac.py index 1874856a..e56fbebd 100644 --- a/bot/exts/valentines/valentine_zodiac.py +++ b/bot/exts/valentines/valentine_zodiac.py @@ -106,7 +106,7 @@ class ValentineZodiac(commands.Cog): final_embed = discord.Embed() final_embed.color = Colours.pink final_embed.description = f"Zodiac sign is not found because, {e}" - log.info(f"error caused due to: {e}.") + log.info(f"Error in "zodiac date" command:\n{e}.") else: final_embed = self.zodiac_build_embed(zodiac_sign_based_on_month_and_date) -- cgit v1.2.3 From c661081c09faf6d2628a55c051e15910d0384e96 Mon Sep 17 00:00:00 2001 From: Anubhav1603 Date: Tue, 29 Sep 2020 11:28:35 +0530 Subject: solved the invalid syntax issue --- bot/exts/valentines/valentine_zodiac.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/exts/valentines/valentine_zodiac.py b/bot/exts/valentines/valentine_zodiac.py index e56fbebd..55e9085d 100644 --- a/bot/exts/valentines/valentine_zodiac.py +++ b/bot/exts/valentines/valentine_zodiac.py @@ -106,7 +106,7 @@ class ValentineZodiac(commands.Cog): final_embed = discord.Embed() final_embed.color = Colours.pink final_embed.description = f"Zodiac sign is not found because, {e}" - log.info(f"Error in "zodiac date" command:\n{e}.") + log.info(f'Error in "zodiac date" command:\n{e}.') else: final_embed = self.zodiac_build_embed(zodiac_sign_based_on_month_and_date) -- cgit v1.2.3 From 242fb70a3c76db5593cf596a3d7a234792b134f9 Mon Sep 17 00:00:00 2001 From: Anubhav1603 Date: Tue, 29 Sep 2020 11:44:24 +0530 Subject: changed errore method to genrate_error method,changed error embed colour to red and minor tweaks --- bot/exts/valentines/valentine_zodiac.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'bot') diff --git a/bot/exts/valentines/valentine_zodiac.py b/bot/exts/valentines/valentine_zodiac.py index 55e9085d..9da49447 100644 --- a/bot/exts/valentines/valentine_zodiac.py +++ b/bot/exts/valentines/valentine_zodiac.py @@ -38,10 +38,10 @@ class ValentineZodiac(commands.Cog): zodiacs = json.load(json_data) return zodiacs, zodiac_fact - def error(self, zodiac: str) -> discord.Embed: + def generate_invalidname_embed(self, zodiac: str) -> discord.Embed: """Returns error embed.""" embed = discord.Embed() - embed.color = Colours.pink + embed.color = Colours.soft_red error_comp = "\n".join( f"`{i}` {zod_name}" for i, zod_name in enumerate(self.zodiac_fact.keys(), start=1) ) @@ -67,7 +67,7 @@ class ValentineZodiac(commands.Cog): embed.add_field(name='__Weaknesses__', value=self.zodiac_fact[zodiac]["Weaknesses"], inline=False) embed.set_thumbnail(url=self.zodiac_fact[zodiac]["url"]) else: - embed = self.error(zodiac) + embed = self.generate_invalidname_embed(zodiac) log.info("Zodiac embed ready.") return embed @@ -95,12 +95,12 @@ class ValentineZodiac(commands.Cog): except ValueError: await ctx.send(f"Sorry, but `{month}` is not a valid month name.") return - if (month == 1 and (1 <= date <= 19)) or (month == 12 and (22 <= date <= 31)): + if (month == 1 and 1 <= date <= 19) or (month == 12 and 22 <= date <= 31): zodiac = "capricorn" final_embed = self.zodiac_build_embed(zodiac) else: try: - zodiac_sign_based_on_month_and_date = self.zodiac_date_verifier(datetime(2020, month, date)) + zodiac_sign_based_on_date = self.zodiac_date_verifier(datetime(2020, month, date)) log.info("zodiac sign based on month and date received.") except ValueError as e: final_embed = discord.Embed() @@ -108,7 +108,7 @@ class ValentineZodiac(commands.Cog): final_embed.description = f"Zodiac sign is not found because, {e}" log.info(f'Error in "zodiac date" command:\n{e}.') else: - final_embed = self.zodiac_build_embed(zodiac_sign_based_on_month_and_date) + final_embed = self.zodiac_build_embed(zodiac_sign_based_on_date) await ctx.send(embed=final_embed) log.info("Zodiac sign embed based on date is now sent.") @@ -132,7 +132,7 @@ class ValentineZodiac(commands.Cog): value=compatible_zodiac['description'] ) except KeyError: - embed = self.error(zodiac_sign) + embed = self.generate_invalidname_embed(zodiac_sign) await ctx.send(embed=embed) -- cgit v1.2.3 From 10aa93109dfe2550f89d86e89cf3b35fa2371f92 Mon Sep 17 00:00:00 2001 From: Anubhav1603 Date: Tue, 29 Sep 2020 13:43:38 +0530 Subject: changed pink colour embed to soft_red --- bot/exts/valentines/valentine_zodiac.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/exts/valentines/valentine_zodiac.py b/bot/exts/valentines/valentine_zodiac.py index 9da49447..58b6c8d3 100644 --- a/bot/exts/valentines/valentine_zodiac.py +++ b/bot/exts/valentines/valentine_zodiac.py @@ -104,7 +104,7 @@ class ValentineZodiac(commands.Cog): log.info("zodiac sign based on month and date received.") except ValueError as e: final_embed = discord.Embed() - final_embed.color = Colours.pink + final_embed.color = Colours.soft_red final_embed.description = f"Zodiac sign is not found because, {e}" log.info(f'Error in "zodiac date" command:\n{e}.') else: -- cgit v1.2.3 From b4fba778deb7c6832dc75e856600e9367c47dd8d Mon Sep 17 00:00:00 2001 From: Anubhav <57266248+Anubhav1603@users.noreply.github.com> Date: Tue, 29 Sep 2020 14:18:53 +0530 Subject: cleaned code Co-authored-by: Thomas Petersson <61778143+thomaspet@users.noreply.github.com> --- bot/exts/valentines/valentine_zodiac.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'bot') diff --git a/bot/exts/valentines/valentine_zodiac.py b/bot/exts/valentines/valentine_zodiac.py index 58b6c8d3..f48acde4 100644 --- a/bot/exts/valentines/valentine_zodiac.py +++ b/bot/exts/valentines/valentine_zodiac.py @@ -34,8 +34,10 @@ class ValentineZodiac(commands.Cog): for zodiac_data in zodiac_fact.values(): zodiac_data['start_at'] = datetime.fromisoformat(zodiac_data['start_at']) zodiac_data['end_at'] = datetime.fromisoformat(zodiac_data['end_at']) + with compatibility_file.open(encoding="utf8") as json_data: zodiacs = json.load(json_data) + return zodiacs, zodiac_fact def generate_invalidname_embed(self, zodiac: str) -> discord.Embed: -- cgit v1.2.3 From 3713b1f1eea335bfdd16aadd25829039fe372fe0 Mon Sep 17 00:00:00 2001 From: bast Date: Tue, 29 Sep 2020 19:39:24 -0700 Subject: Rename BetterMessageConverter -> WrappedMessageConverter --- bot/exts/evergreen/bookmark.py | 4 ++-- bot/utils/converters.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'bot') diff --git a/bot/exts/evergreen/bookmark.py b/bot/exts/evergreen/bookmark.py index bb86e18d..5fa05d2e 100644 --- a/bot/exts/evergreen/bookmark.py +++ b/bot/exts/evergreen/bookmark.py @@ -5,7 +5,7 @@ import discord from discord.ext import commands from bot.constants import Colours, ERROR_REPLIES, Emojis, Icons -from bot.utils.converters import BetterMessageConverter +from bot.utils.converters import WrappedMessageConverter log = logging.getLogger(__name__) @@ -20,7 +20,7 @@ class Bookmark(commands.Cog): async def bookmark( self, ctx: commands.Context, - target_message: BetterMessageConverter, + target_message: WrappedMessageConverter, *, title: str = "Bookmark" ) -> None: diff --git a/bot/utils/converters.py b/bot/utils/converters.py index f36fa03a..228714c9 100644 --- a/bot/utils/converters.py +++ b/bot/utils/converters.py @@ -2,7 +2,7 @@ import discord from discord.ext.commands.converter import MessageConverter -class BetterMessageConverter(MessageConverter): +class WrappedMessageConverter(MessageConverter): """A converter that handles embed-suppressed links like .""" async def convert(self, ctx: discord.ext.commands.Context, argument: str) -> discord.Message: -- cgit v1.2.3 From 5f8b67244562317ffc5648eff53bf35fb0d5faa9 Mon Sep 17 00:00:00 2001 From: Sebastiaan Zeeff Date: Wed, 30 Sep 2020 21:27:20 +0200 Subject: Remove SpookySound Cog that played sounds in voice We had an old Cog that would allow our members to run a command to make Seasonal Bot join a voice channel to play a spooky sound. However, as our voice channel use has changed over the past year, we don't think that it's still a good idea to do this. That's why I removed the Cog and the constants related to it. --- bot/constants.py | 5 ---- bot/exts/halloween/spookysound.py | 48 --------------------------------------- 2 files changed, 53 deletions(-) delete mode 100644 bot/exts/halloween/spookysound.py (limited to 'bot') diff --git a/bot/constants.py b/bot/constants.py index 935b90e0..0c376344 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -11,7 +11,6 @@ __all__ = ( "Client", "Colours", "Emojis", - "Hacktoberfest", "Icons", "Lovefest", "Month", @@ -129,10 +128,6 @@ class Emojis: status_offline = "<:status_offline:470326266537705472>" -class Hacktoberfest(NamedTuple): - voice_id = 514420006474219521 - - class Icons: questionmark = "https://cdn.discordapp.com/emojis/512367613339369475.png" bookmark = ( diff --git a/bot/exts/halloween/spookysound.py b/bot/exts/halloween/spookysound.py deleted file mode 100644 index 569a9153..00000000 --- a/bot/exts/halloween/spookysound.py +++ /dev/null @@ -1,48 +0,0 @@ -import logging -import random -from pathlib import Path - -import discord -from discord.ext import commands - -from bot.bot import SeasonalBot -from bot.constants import Hacktoberfest - -log = logging.getLogger(__name__) - - -class SpookySound(commands.Cog): - """A cog that plays a spooky sound in a voice channel on command.""" - - def __init__(self, bot: SeasonalBot): - self.bot = bot - self.sound_files = list(Path("bot/resources/halloween/spookysounds").glob("*.mp3")) - self.channel = None - - @commands.cooldown(rate=1, per=1) - @commands.command(brief="Play a spooky sound, restricted to once per 2 mins") - async def spookysound(self, ctx: commands.Context) -> None: - """ - Connect to the Hacktoberbot voice channel, play a random spooky sound, then disconnect. - - Cannot be used more than once in 2 minutes. - """ - if not self.channel: - await self.bot.wait_until_guild_available() - self.channel = self.bot.get_channel(Hacktoberfest.voice_id) - - await ctx.send("Initiating spooky sound...") - file_path = random.choice(self.sound_files) - src = discord.FFmpegPCMAudio(str(file_path.resolve())) - voice = await self.channel.connect() - voice.play(src, after=lambda e: self.bot.loop.create_task(self.disconnect(voice))) - - @staticmethod - async def disconnect(voice: discord.VoiceClient) -> None: - """Helper method to disconnect a given voice client.""" - await voice.disconnect() - - -def setup(bot: SeasonalBot) -> None: - """Spooky sound Cog load.""" - bot.add_cog(SpookySound(bot)) -- cgit v1.2.3 From 15324b3428e80d2e7a37ba84443983684e834ffc Mon Sep 17 00:00:00 2001 From: Sebastiaan Zeeff Date: Wed, 30 Sep 2020 21:29:11 +0200 Subject: Update the Hacktoberfest channel constant I've updated the Hacktoberfest channel ID to the ID of the new channel just created for the 2020 edition of the event. --- bot/constants.py | 2 +- bot/exts/halloween/hacktoberstats.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'bot') diff --git a/bot/constants.py b/bot/constants.py index 0c376344..7ec8ac27 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -74,7 +74,7 @@ class Channels(NamedTuple): python_discussion = 267624335836053506 show_your_projects = int(environ.get("CHANNEL_SHOW_YOUR_PROJECTS", 303934982764625920)) show_your_projects_discussion = 360148304664723466 - hacktoberfest_2019 = 628184417646411776 + hacktoberfest_2020 = 760857070781071431 class Client(NamedTuple): diff --git a/bot/exts/halloween/hacktoberstats.py b/bot/exts/halloween/hacktoberstats.py index db5e37f2..92429c1b 100644 --- a/bot/exts/halloween/hacktoberstats.py +++ b/bot/exts/halloween/hacktoberstats.py @@ -18,7 +18,7 @@ log = logging.getLogger(__name__) CURRENT_YEAR = datetime.now().year # Used to construct GH API query PRS_FOR_SHIRT = 4 # Minimum number of PRs before a shirt is awarded -HACKTOBER_WHITELIST = WHITELISTED_CHANNELS + (Channels.hacktoberfest_2019,) +HACKTOBER_WHITELIST = WHITELISTED_CHANNELS + (Channels.hacktoberfest_2020,) class HacktoberStats(commands.Cog): -- cgit v1.2.3 From 45c204ada302e4c5f7f9866ff26b607391d94ff3 Mon Sep 17 00:00:00 2001 From: Joe Banks Date: Wed, 30 Sep 2020 22:28:03 +0100 Subject: Allow hacktoberfest commands in September and November --- bot/exts/halloween/hacktoberstats.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'bot') diff --git a/bot/exts/halloween/hacktoberstats.py b/bot/exts/halloween/hacktoberstats.py index 92429c1b..42754f4b 100644 --- a/bot/exts/halloween/hacktoberstats.py +++ b/bot/exts/halloween/hacktoberstats.py @@ -29,7 +29,7 @@ class HacktoberStats(commands.Cog): self.link_json = make_persistent(Path("bot", "resources", "halloween", "github_links.json")) self.linked_accounts = self.load_linked_users() - @in_month(Month.OCTOBER) + @in_month(Month.SEPTEMBER, Month.OCTOBER, Month.NOVEMBER) @commands.group(name="hacktoberstats", aliases=("hackstats",), invoke_without_command=True) @override_in_channel(HACKTOBER_WHITELIST) async def hacktoberstats_group(self, ctx: commands.Context, github_username: str = None) -> None: @@ -57,7 +57,7 @@ class HacktoberStats(commands.Cog): await self.get_stats(ctx, github_username) - @in_month(Month.OCTOBER) + @in_month(Month.SEPTEMBER, Month.OCTOBER, Month.NOVEMBER) @hacktoberstats_group.command(name="link") @override_in_channel(HACKTOBER_WHITELIST) async def link_user(self, ctx: commands.Context, github_username: str = None) -> None: @@ -92,7 +92,7 @@ class HacktoberStats(commands.Cog): logging.info(f"{author_id} tried to link a GitHub account but didn't provide a username") await ctx.send(f"{author_mention}, a GitHub username is required to link your account") - @in_month(Month.OCTOBER) + @in_month(Month.SEPTEMBER, Month.OCTOBER, Month.NOVEMBER) @hacktoberstats_group.command(name="unlink") @override_in_channel(HACKTOBER_WHITELIST) async def unlink_user(self, ctx: commands.Context) -> None: -- cgit v1.2.3 From a279a11e85a46b4731dc97d177415fbe35e8384f Mon Sep 17 00:00:00 2001 From: Joe Banks Date: Wed, 30 Sep 2020 22:28:26 +0100 Subject: Update Hacktoberfest image URL --- bot/exts/halloween/hacktoberstats.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/exts/halloween/hacktoberstats.py b/bot/exts/halloween/hacktoberstats.py index 42754f4b..08a83a55 100644 --- a/bot/exts/halloween/hacktoberstats.py +++ b/bot/exts/halloween/hacktoberstats.py @@ -196,7 +196,7 @@ class HacktoberStats(commands.Cog): stats_embed.set_author( name="Hacktoberfest", url="https://hacktoberfest.digitalocean.com", - icon_url="https://hacktoberfest.digitalocean.com/pretty_logo.png" + icon_url="https://avatars1.githubusercontent.com/u/35706162?s=200&v=4" ) stats_embed.add_field( name="Top 5 Repositories:", -- cgit v1.2.3 From 047902d599bb87b7227a259fe63fa3a3f34b1d71 Mon Sep 17 00:00:00 2001 From: Joe Banks Date: Wed, 30 Sep 2020 22:34:22 +0100 Subject: Update t-shirt references to t-shirts and trees! --- bot/exts/halloween/hacktoberstats.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'bot') diff --git a/bot/exts/halloween/hacktoberstats.py b/bot/exts/halloween/hacktoberstats.py index 08a83a55..9dd0e1a4 100644 --- a/bot/exts/halloween/hacktoberstats.py +++ b/bot/exts/halloween/hacktoberstats.py @@ -175,11 +175,11 @@ class HacktoberStats(commands.Cog): n = pr_stats['n_prs'] if n >= PRS_FOR_SHIRT: - shirtstr = f"**{github_username} has earned a tshirt!**" + shirtstr = f"**{github_username} has earned a T-shirt or a tree!**" elif n == PRS_FOR_SHIRT - 1: - shirtstr = f"**{github_username} is 1 PR away from a tshirt!**" + shirtstr = f"**{github_username} is 1 PR away from a T-shirt or a tree!**" else: - shirtstr = f"**{github_username} is {PRS_FOR_SHIRT - n} PRs away from a tshirt!**" + shirtstr = f"**{github_username} is {PRS_FOR_SHIRT - n} PRs away from a T-shirt or a tree!**" stats_embed = discord.Embed( title=f"{github_username}'s Hacktoberfest", -- cgit v1.2.3 From e2062ccde0d566503980496e4098a218bc1c111b Mon Sep 17 00:00:00 2001 From: Joe Banks Date: Wed, 30 Sep 2020 22:51:22 +0100 Subject: Update timeleft to not be locked to October --- bot/exts/halloween/timeleft.py | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) (limited to 'bot') diff --git a/bot/exts/halloween/timeleft.py b/bot/exts/halloween/timeleft.py index 295acc89..ef436057 100644 --- a/bot/exts/halloween/timeleft.py +++ b/bot/exts/halloween/timeleft.py @@ -13,20 +13,23 @@ class TimeLeft(commands.Cog): def __init__(self, bot: commands.Bot): self.bot = bot - @staticmethod - def in_october() -> bool: - """Return True if the current month is October.""" - return datetime.utcnow().month == 10 + def in_hacktober(self) -> bool: + """Return True if the current time is within Hacktoberfest.""" + _, end, start = self.load_date() + + now = datetime.utcnow() + + return start <= now <= end @staticmethod - def load_date() -> Tuple[int, datetime, datetime]: + def load_date() -> Tuple[datetime, datetime, datetime]: """Return of a tuple of the current time and the end and start times of the next October.""" now = datetime.utcnow() year = now.year if now.month > 10: year += 1 - end = datetime(year, 11, 1, 11, 59, 59) - start = datetime(year, 10, 1) + end = datetime(year, 11, 1, 12) # November 1st 12:00 (UTC-12:00) + start = datetime(year, 9, 30, 10) # September 30th 10:00 (UTC+14:00) return now, end, start @commands.command() @@ -35,16 +38,20 @@ class TimeLeft(commands.Cog): Calculates the time left until the end of Hacktober. Whilst in October, displays the days, hours and minutes left. - Only displays the days left until the beginning and end whilst in a different month + Only displays the days left until the beginning and end whilst in a different month. + + This factors in that Hacktoberfest starts when it is October anywhere in the world + and ends with the same rules. It treats the start as UTC+14:00 and the end as + UTC-12. """ now, end, start = self.load_date() diff = end - now days, seconds = diff.days, diff.seconds - if self.in_october(): + if self.in_hacktober(): minutes = seconds // 60 hours, minutes = divmod(minutes, 60) - await ctx.send(f"There is currently only {days} days, {hours} hours and {minutes}" - "minutes left until the end of Hacktober.") + await ctx.send(f"There are {days} days, {hours} hours and {minutes}" + " minutes left until the end of Hacktober.") else: start_diff = start - now start_days = start_diff.days -- cgit v1.2.3 From 60a024c818c405822f13046ff93805837bfc591e Mon Sep 17 00:00:00 2001 From: Joe Banks Date: Thu, 1 Oct 2020 00:02:15 +0100 Subject: Improve formatting in timeleft --- bot/exts/halloween/timeleft.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'bot') diff --git a/bot/exts/halloween/timeleft.py b/bot/exts/halloween/timeleft.py index ef436057..47adb09b 100644 --- a/bot/exts/halloween/timeleft.py +++ b/bot/exts/halloween/timeleft.py @@ -50,8 +50,11 @@ class TimeLeft(commands.Cog): if self.in_hacktober(): minutes = seconds // 60 hours, minutes = divmod(minutes, 60) - await ctx.send(f"There are {days} days, {hours} hours and {minutes}" - " minutes left until the end of Hacktober.") + + await ctx.send( + f"There are {days} days, {hours} hours and {minutes}" + f" minutes left until the end of Hacktober." + ) else: start_diff = start - now start_days = start_diff.days -- cgit v1.2.3 From ba12301b69aa347b705ed61e8250d948b514d23b Mon Sep 17 00:00:00 2001 From: Den4200 Date: Wed, 30 Sep 2020 23:33:03 -0400 Subject: Authenticate GitHub API requests for Hacktoberfest stats. Also changed the user agent from `Discord Python Hacktoberbot` to `Python Discord Hacktoberbot`. --- bot/exts/halloween/hacktoberstats.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'bot') diff --git a/bot/exts/halloween/hacktoberstats.py b/bot/exts/halloween/hacktoberstats.py index 9dd0e1a4..3347dd8f 100644 --- a/bot/exts/halloween/hacktoberstats.py +++ b/bot/exts/halloween/hacktoberstats.py @@ -10,7 +10,7 @@ import aiohttp import discord from discord.ext import commands -from bot.constants import Channels, Month, WHITELISTED_CHANNELS +from bot.constants import Channels, Month, Tokens, WHITELISTED_CHANNELS from bot.utils.decorators import in_month, override_in_channel from bot.utils.persist import make_persistent @@ -20,6 +20,10 @@ CURRENT_YEAR = datetime.now().year # Used to construct GH API query PRS_FOR_SHIRT = 4 # Minimum number of PRs before a shirt is awarded HACKTOBER_WHITELIST = WHITELISTED_CHANNELS + (Channels.hacktoberfest_2020,) +REQUEST_HEADERS = {"User-Agent": "Python Discord Hacktoberbot"} +if GITHUB_TOKEN := Tokens.github: + REQUEST_HEADERS["Authorization"] = f"token {GITHUB_TOKEN}" + class HacktoberStats(commands.Cog): """Hacktoberfest statistics Cog.""" @@ -242,9 +246,8 @@ class HacktoberStats(commands.Cog): f"&per_page={per_page}" ) - headers = {"user-agent": "Discord Python Hacktoberbot"} async with aiohttp.ClientSession() as session: - async with session.get(query_url, headers=headers) as resp: + async with session.get(query_url, headers=REQUEST_HEADERS) as resp: jsonresp = await resp.json() if "message" in jsonresp.keys(): -- cgit v1.2.3 From b0fe81808b32650e7dd6a7a8a86d5fc36c27c2fe Mon Sep 17 00:00:00 2001 From: Den4200 Date: Wed, 30 Sep 2020 23:34:45 -0400 Subject: Check the GitHub user exists before searching for their PRs. Ensures an error does not occur when a GitHub user does not exist. --- bot/exts/halloween/hacktoberstats.py | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'bot') diff --git a/bot/exts/halloween/hacktoberstats.py b/bot/exts/halloween/hacktoberstats.py index 3347dd8f..a2261caf 100644 --- a/bot/exts/halloween/hacktoberstats.py +++ b/bot/exts/halloween/hacktoberstats.py @@ -247,6 +247,11 @@ class HacktoberStats(commands.Cog): ) async with aiohttp.ClientSession() as session: + async with session.get(f"https://api.github.com/users/{github_username}", headers=REQUEST_HEADERS) as resp: + if resp.status == 404: + logging.debug(f"No GitHub user found named '{github_username}'") + return + async with session.get(query_url, headers=REQUEST_HEADERS) as resp: jsonresp = await resp.json() -- cgit v1.2.3 From deee8cfebc69e63ddc0060bc888e7f00b90efe6e Mon Sep 17 00:00:00 2001 From: Den4200 Date: Thu, 1 Oct 2020 00:15:14 -0400 Subject: Removed unnecessary GitHub API request and checked response message instead. This check is to see if a GitHub user is non-existent. We do not want to log this as an error. --- bot/exts/halloween/hacktoberstats.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) (limited to 'bot') diff --git a/bot/exts/halloween/hacktoberstats.py b/bot/exts/halloween/hacktoberstats.py index a2261caf..ed1755e3 100644 --- a/bot/exts/halloween/hacktoberstats.py +++ b/bot/exts/halloween/hacktoberstats.py @@ -24,6 +24,11 @@ REQUEST_HEADERS = {"User-Agent": "Python Discord Hacktoberbot"} if GITHUB_TOKEN := Tokens.github: REQUEST_HEADERS["Authorization"] = f"token {GITHUB_TOKEN}" +GITHUB_NONEXISTENT_USER_MESSAGE = ( + "The listed users cannot be searched either because the users do not exist " + "or you do not have permission to view the users." +) + class HacktoberStats(commands.Cog): """Hacktoberfest statistics Cog.""" @@ -247,19 +252,21 @@ class HacktoberStats(commands.Cog): ) async with aiohttp.ClientSession() as session: - async with session.get(f"https://api.github.com/users/{github_username}", headers=REQUEST_HEADERS) as resp: - if resp.status == 404: - logging.debug(f"No GitHub user found named '{github_username}'") - return - async with session.get(query_url, headers=REQUEST_HEADERS) as resp: jsonresp = await resp.json() if "message" in jsonresp.keys(): # One of the parameters is invalid, short circuit for now api_message = jsonresp["errors"][0]["message"] - logging.error(f"GitHub API request for '{github_username}' failed with message: {api_message}") + + # Ignore logging non-existent users or users we do not have permission to see + if api_message == GITHUB_NONEXISTENT_USER_MESSAGE: + logging.debug(f"No GitHub user found named '{github_username}'") + else: + logging.error(f"GitHub API request for '{github_username}' failed with message: {api_message}") + return + else: if jsonresp["total_count"] == 0: # Short circuit if there aren't any PRs -- cgit v1.2.3 From e34e3eb2667be82c7a69ced10684b0e0d02357b8 Mon Sep 17 00:00:00 2001 From: Anubhav1603 Date: Thu, 1 Oct 2020 10:33:50 +0530 Subject: adjusted logging level,corrected grammer and small bugfix --- bot/exts/valentines/valentine_zodiac.py | 46 +++++++++++----------- bot/resources/valentines/zodiac_compatibility.json | 24 +++++------ 2 files changed, 35 insertions(+), 35 deletions(-) (limited to 'bot') diff --git a/bot/exts/valentines/valentine_zodiac.py b/bot/exts/valentines/valentine_zodiac.py index 58b6c8d3..044951b4 100644 --- a/bot/exts/valentines/valentine_zodiac.py +++ b/bot/exts/valentines/valentine_zodiac.py @@ -68,14 +68,14 @@ class ValentineZodiac(commands.Cog): embed.set_thumbnail(url=self.zodiac_fact[zodiac]["url"]) else: embed = self.generate_invalidname_embed(zodiac) - log.info("Zodiac embed ready.") + log.trace("Successfully created zodiac information embed.") return embed - def zodiac_date_verifier(self, query_datetime: datetime) -> str: + def zodiac_date_verifier(self, date: datetime) -> str: """Returns zodiac sign by checking month and date.""" for zodiac_name, zodiac_data in self.zodiac_fact.items(): - if zodiac_data["start_at"].date() <= query_datetime.date() <= zodiac_data["end_at"].date(): - log.info("Zodiac name sent.") + if zodiac_data["start_at"].date() <= date.date() <= zodiac_data["end_at"].date(): + log.trace("Zodiac name sent.") return zodiac_name @commands.group(name='zodiac', invoke_without_command=True) @@ -83,7 +83,7 @@ class ValentineZodiac(commands.Cog): """Provides information about zodiac sign by taking zodiac sign name as input.""" final_embed = self.zodiac_build_embed(zodiac_sign) await ctx.send(embed=final_embed) - log.info("Embed successfully sent.") + log.trace("Embed successfully sent.") @zodiac.command(name="date") async def date_and_month(self, ctx: commands.Context, date: int, month: Union[int, str]) -> None: @@ -92,7 +92,9 @@ class ValentineZodiac(commands.Cog): month = month.capitalize() try: month = list(calendar.month_abbr).index(month[:3]) + log.info('Valid month name entered by user') except ValueError: + log.info('Invalid month name entered by user') await ctx.send(f"Sorry, but `{month}` is not a valid month name.") return if (month == 1 and 1 <= date <= 19) or (month == 12 and 22 <= date <= 31): @@ -105,35 +107,33 @@ class ValentineZodiac(commands.Cog): except ValueError as e: final_embed = discord.Embed() final_embed.color = Colours.soft_red - final_embed.description = f"Zodiac sign is not found because, {e}" + final_embed.description = f"Zodiac sign could not be found because.\n`{e}`" log.info(f'Error in "zodiac date" command:\n{e}.') else: final_embed = self.zodiac_build_embed(zodiac_sign_based_on_date) await ctx.send(embed=final_embed) - log.info("Zodiac sign embed based on date is now sent.") + log.trace("Embed from date successfully sent.") @zodiac.command(name="partnerzodiac", aliases=['partner']) async def partner_zodiac(self, ctx: commands.Context, zodiac_sign: str) -> None: - """Provides a counter compatible zodiac sign to the given user's zodiac sign.""" + """Provides a random counter compatible zodiac sign to the given user's zodiac sign.""" embed = discord.Embed() embed.color = Colours.pink - try: - compatible_zodiac = random.choice(self.zodiacs[zodiac_sign.lower()]) - emoji1 = random.choice(HEART_EMOJIS) - emoji2 = random.choice(HEART_EMOJIS) - embed.title = "Zodiac Compatibility" - embed.description = ( - f'{zodiac_sign.capitalize()}{emoji1}{compatible_zodiac["Zodiac"]}\n' - f'{emoji2}Compatibility meter : {compatible_zodiac["compatibility_score"]}{emoji2}' - ) - embed.add_field( - name=f'A letter from Dr.Zodiac {LETTER_EMOJI}', - value=compatible_zodiac['description'] - ) - except KeyError: - embed = self.generate_invalidname_embed(zodiac_sign) + compatible_zodiac = random.choice(self.zodiacs[zodiac_sign.capitalize()]) + emoji1 = random.choice(HEART_EMOJIS) + emoji2 = random.choice(HEART_EMOJIS) + embed.title = "Zodiac Compatibility" + embed.description = ( + f'{zodiac_sign.capitalize()}{emoji1}{compatible_zodiac["Zodiac"]}\n' + f'{emoji2}Compatibility meter : {compatible_zodiac["compatibility_score"]}{emoji2}' + ) + embed.add_field( + name=f'A letter from Dr.Zodiac {LETTER_EMOJI}', + value=compatible_zodiac['description'] + ) await ctx.send(embed=embed) + log.trace("Embed from date successfully sent.") def setup(bot: commands.Bot) -> None: diff --git a/bot/resources/valentines/zodiac_compatibility.json b/bot/resources/valentines/zodiac_compatibility.json index 3971d40d..ea9a7b37 100644 --- a/bot/resources/valentines/zodiac_compatibility.json +++ b/bot/resources/valentines/zodiac_compatibility.json @@ -1,5 +1,5 @@ { - "aries":[ + "Aries":[ { "Zodiac" : "Sagittarius", "description" : "The Archer is one of the most compatible signs Aries should consider when searching out relationships that will bear fruit. Sagittarians share a certain love of freedom with Aries that will help the two of them conquer new territory together.", @@ -21,7 +21,7 @@ "compatibility_score" : "74%" } ], - "taurus":[ + "Taurus":[ { "Zodiac" : "Virgo", "description" : "Although these signs have their set of differences, the Virgo Taurus compatibility is usually pretty strong. This is because both the signs want the same thing ultimately and have generally synchronous ways of reaching those points. This helps them complement each other and create a healthy relationship between them.", @@ -43,7 +43,7 @@ "compatibility_score" : "91%" } ], - "gemini":[ + "Gemini":[ { "Zodiac" : "Aries", "description" : "The theorem of astrology says that Aries and Gemini have a zero tolerance for boredom and will at once get rid of anything dull. An Arian will let a Geminian enjoy his personal freedom and the Gemini will respect his individuality.", @@ -65,7 +65,7 @@ "compatibility_score" : "91%" } ], - "cancer":[ + "Cancer":[ { "Zodiac" : "Taurus", "description" : "The Cancer Taurus zodiac relationship compatibility is strong because of their mutual love for safety, stability, and comfort. Their mutual understanding will always be powerful, which will be the pillar of strength of their relationship.", @@ -82,7 +82,7 @@ "compatibility_score" : "77%" } ], - "leo":[ + "Leo":[ { "Zodiac" : "Aries", "description" : "A Leo is generous and an Arian is open to life. Sharing the same likes and dislikes, they both crave for fun, romance and excitement. A Leo respects an Arian's need for freedom because an Arian does not interfere much in the life of a Leo. Aries will love the charisma and ideas of the Leo.", @@ -104,7 +104,7 @@ "compatibility_score" : "75%" } ], - "virgo":[ + "Virgo":[ { "Zodiac" : "Taurus", "description" : "Although these signs have their set of differences, the Virgo Taurus compatibility is usually pretty strong. This is because both the signs want the same thing ultimately and have generally synchronous ways of reaching those points. This helps them complement each other and create a healthy relationship between them.", @@ -126,7 +126,7 @@ "compatibility_score" : "77%" } ], - "libra":[ + "Libra":[ { "Zodiac" : "Leo", "description" : "Libra and Leo love match can work well for both the partners and truly help them learn from each other and grow individually, as well as together. Libra and Leo, when in the right frame of mind, form a formidable couple that attracts admiration and respect everywhere it goes.", @@ -148,7 +148,7 @@ "compatibility_score" : "71%" } ], - "scorpio":[ + "Scorpio":[ { "Zodiac" : "Cancer", "description" : "This union is not unusual, but will take a fair share of work in the start. A strong foundation of clear cut communication is mandatory to make this a loving and stress free relationship!", @@ -170,7 +170,7 @@ "compatibility_score" : "81%" } ], - "sagittarius":[ + "Sagittarius":[ { "Zodiac" : "Aries", "description" : "Sagittarius and Aries can make a very compatible pair. Their relationship will have a lot of passion, enthusiasm, and energy. These are very good traits to make their relationship deeper and stronger. Both Aries and Sagittarius will enjoy each other's company and their energy level rises as the relationship grows. Both will support and help in fighting hardships and failures.", @@ -192,7 +192,7 @@ "compatibility_score" : "83%" } ], - "capricorn":[ + "Capricorn":[ { "Zodiac" : "Taurus", "description" : "This is one of the most grounded and reliable bonds of the zodiac chart. If Capricorn and Taurus do find a way to handle their minor issues, they have a good chance of making it together and that too, in a happy, peaceful, and healthy relationship.", @@ -214,7 +214,7 @@ "compatibility_score" : "76%" } ], - "aquarius":[ + "Aquarius":[ { "Zodiac" : "Aries", "description" : "The relationship of Aries and Aquarius is very exciting, adventurous and interesting. They will enjoy each other's company as both of them love fun and freedom.This is a couple that lacks tenderness. They are not two brutes who let their relationship fade as soon as their passion does.", @@ -236,7 +236,7 @@ "compatibility_score" : "83%" } ], - "pisces":[ + "Pisces":[ { "Zodiac" : "Taurus", "description" : "This relationship will survive the test of time if both parties involved have unbreakable trust in each other and nurture that connection they have painstakingly built over the years. They must remember to be honest and committed to their partner through all times.If natural communication flows between them like clockwork, this will be a beautiful love story with a prominent tag of ‘happily-ever-after’ pinned right to it!", -- cgit v1.2.3 From 8f5494727d81be7190537f130ef9b384c9e72285 Mon Sep 17 00:00:00 2001 From: Anubhav1603 Date: Thu, 1 Oct 2020 13:44:09 +0530 Subject: inital commit --- bot/exts/evergreen/emoji_count.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 bot/exts/evergreen/emoji_count.py (limited to 'bot') diff --git a/bot/exts/evergreen/emoji_count.py b/bot/exts/evergreen/emoji_count.py new file mode 100644 index 00000000..b619f349 --- /dev/null +++ b/bot/exts/evergreen/emoji_count.py @@ -0,0 +1,29 @@ +import logging + +from discord.ext import commands + +log = logging.getLogger(__name__) + + +class EmojiCount(commands.Cog): + """Command that give random emoji based on category.""" + + def __init__(self, bot: commands.Bot): + self.bot = bot + + @commands.command(name="ec") + async def ec(self, ctx, emoj: str): + """Returns embed with emoji category and info given by user.""" + emoji = [] + for a in ctx.guild.emojis: + for n in a.name.split('_'): + if len(n) == 1: + pass + elif n.name[0] == emoji.lower(): + emoji.append(a) + await ctx.send(emoji) + + +def setup(bot: commands.Bot) -> None: + """Emoji Count Cog load.""" + bot.add_cog(EmojiCount(bot)) -- cgit v1.2.3 From 345ceba65872eab79bc28f34ef805cca91ce4cae Mon Sep 17 00:00:00 2001 From: Anubhav1603 Date: Thu, 1 Oct 2020 13:47:14 +0530 Subject: changed trace to info in except block --- bot/exts/valentines/valentine_zodiac.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'bot') diff --git a/bot/exts/valentines/valentine_zodiac.py b/bot/exts/valentines/valentine_zodiac.py index 19cb10fc..433947a1 100644 --- a/bot/exts/valentines/valentine_zodiac.py +++ b/bot/exts/valentines/valentine_zodiac.py @@ -94,7 +94,7 @@ class ValentineZodiac(commands.Cog): month = month.capitalize() try: month = list(calendar.month_abbr).index(month[:3]) - log.info('Valid month name entered by user') + log.trace('Valid month name entered by user') except ValueError: log.info('Invalid month name entered by user') await ctx.send(f"Sorry, but `{month}` is not a valid month name.") @@ -105,7 +105,7 @@ class ValentineZodiac(commands.Cog): else: try: zodiac_sign_based_on_date = self.zodiac_date_verifier(datetime(2020, month, date)) - log.info("zodiac sign based on month and date received.") + log.trace("zodiac sign based on month and date received.") except ValueError as e: final_embed = discord.Embed() final_embed.color = Colours.soft_red -- cgit v1.2.3 From d446cee6614ee333a65e671109012648a3e56628 Mon Sep 17 00:00:00 2001 From: Anubhav1603 Date: Thu, 1 Oct 2020 13:50:37 +0530 Subject: changed ` to codeblock --- bot/exts/valentines/valentine_zodiac.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/exts/valentines/valentine_zodiac.py b/bot/exts/valentines/valentine_zodiac.py index 433947a1..0925bf06 100644 --- a/bot/exts/valentines/valentine_zodiac.py +++ b/bot/exts/valentines/valentine_zodiac.py @@ -109,7 +109,7 @@ class ValentineZodiac(commands.Cog): except ValueError as e: final_embed = discord.Embed() final_embed.color = Colours.soft_red - final_embed.description = f"Zodiac sign could not be found because.\n`{e}`" + final_embed.description = f"Zodiac sign could not be found because.\n```{e}```" log.info(f'Error in "zodiac date" command:\n{e}.') else: final_embed = self.zodiac_build_embed(zodiac_sign_based_on_date) -- cgit v1.2.3 From ca463a739fd132489f44ed96fb8a8ffdde6e547e Mon Sep 17 00:00:00 2001 From: Anubhav1603 Date: Thu, 1 Oct 2020 13:53:51 +0530 Subject: corrected doc string and changed date to query_date --- bot/exts/valentines/valentine_zodiac.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'bot') diff --git a/bot/exts/valentines/valentine_zodiac.py b/bot/exts/valentines/valentine_zodiac.py index 0925bf06..67f57f94 100644 --- a/bot/exts/valentines/valentine_zodiac.py +++ b/bot/exts/valentines/valentine_zodiac.py @@ -73,10 +73,10 @@ class ValentineZodiac(commands.Cog): log.trace("Successfully created zodiac information embed.") return embed - def zodiac_date_verifier(self, date: datetime) -> str: - """Returns zodiac sign by checking month and date.""" + def zodiac_date_verifier(self, query_date: datetime) -> str: + """Returns zodiac sign by checking date.""" for zodiac_name, zodiac_data in self.zodiac_fact.items(): - if zodiac_data["start_at"].date() <= date.date() <= zodiac_data["end_at"].date(): + if zodiac_data["start_at"].date() <= query_date.date() <= zodiac_data["end_at"].date(): log.trace("Zodiac name sent.") return zodiac_name -- cgit v1.2.3 From a2943dac69237a754b7efc4e972462dbc969dc15 Mon Sep 17 00:00:00 2001 From: Anubhav1603 Date: Thu, 1 Oct 2020 14:17:49 +0530 Subject: added check for partnerzodiac to validate --- bot/exts/valentines/valentine_zodiac.py | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) (limited to 'bot') diff --git a/bot/exts/valentines/valentine_zodiac.py b/bot/exts/valentines/valentine_zodiac.py index 67f57f94..3f1a6337 100644 --- a/bot/exts/valentines/valentine_zodiac.py +++ b/bot/exts/valentines/valentine_zodiac.py @@ -122,18 +122,22 @@ class ValentineZodiac(commands.Cog): """Provides a random counter compatible zodiac sign to the given user's zodiac sign.""" embed = discord.Embed() embed.color = Colours.pink - compatible_zodiac = random.choice(self.zodiacs[zodiac_sign.capitalize()]) - emoji1 = random.choice(HEART_EMOJIS) - emoji2 = random.choice(HEART_EMOJIS) - embed.title = "Zodiac Compatibility" - embed.description = ( - f'{zodiac_sign.capitalize()}{emoji1}{compatible_zodiac["Zodiac"]}\n' - f'{emoji2}Compatibility meter : {compatible_zodiac["compatibility_score"]}{emoji2}' - ) - embed.add_field( - name=f'A letter from Dr.Zodiac {LETTER_EMOJI}', - value=compatible_zodiac['description'] - ) + zodiac_check = self.zodiacs.get(zodiac_sign.capitalize()) + if zodiac_check: + compatible_zodiac = random.choice(self.zodiacs[zodiac_sign.capitalize()]) + emoji1 = random.choice(HEART_EMOJIS) + emoji2 = random.choice(HEART_EMOJIS) + embed.title = "Zodiac Compatibility" + embed.description = ( + f'{zodiac_sign.capitalize()}{emoji1}{compatible_zodiac["Zodiac"]}\n' + f'{emoji2}Compatibility meter : {compatible_zodiac["compatibility_score"]}{emoji2}' + ) + embed.add_field( + name=f'A letter from Dr.Zodiac {LETTER_EMOJI}', + value=compatible_zodiac['description'] + ) + else: + embed = self.generate_invalidname_embed(zodiac_sign) await ctx.send(embed=embed) log.trace("Embed from date successfully sent.") -- cgit v1.2.3 From 5418dd70fd3a75ff161970b5abd2c4b39f9275b7 Mon Sep 17 00:00:00 2001 From: Anubhav1603 Date: Thu, 1 Oct 2020 14:28:04 +0530 Subject: removed \n and added , --- bot/resources/valentines/zodiac_explanation.json | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) (limited to 'bot') diff --git a/bot/resources/valentines/zodiac_explanation.json b/bot/resources/valentines/zodiac_explanation.json index 786392a3..9bb1d99d 100644 --- a/bot/resources/valentines/zodiac_explanation.json +++ b/bot/resources/valentines/zodiac_explanation.json @@ -6,7 +6,7 @@ "Motto": "***\u201cWhen you know yourself, you're empowered. When you accept yourself, you're invincible.\u201d***", "Strengths": "courageous, determined, confident, enthusiastic, optimistic, honest, passionate.", "Weaknesses": "impatient, moody, short-tempered, impulsive, aggressive.", - "full_form": "__**A**__ssertive\n__**R**__efreshing\n__**I**__ndependent\n__**E**__nergetic\n__**S**__exy", + "full_form": "__**A**__ssertive, __**R**__efreshing, __**I**__ndependent, __**E**__nergetic, __**S**__exy", "url": "https://www.horoscope.com/images-US/signs/profile-aries.png" }, "Taurus": { @@ -16,7 +16,7 @@ "Motto": "***\u201cNothing worth having comes easy.\u201d***", "Strengths": "reliable, patient, practical, devoted, responsible, stable.", "Weaknesses": "stubborn, possessive, uncompromising.", - "full_form": "__**T**__railblazing\n__**A**__mbitious\n__**U**__nwavering\n__**R**__eliable\n__**U**__nderstanding\n__**S**__table", + "full_form": "__**T**__railblazing, __**A**__mbitious, __**U**__nwavering, __**R**__eliable, __**U**__nderstanding, __**S**__table", "url": "https://www.horoscope.com/images-US/signs/profile-taurus.png" }, "Gemini": { @@ -26,7 +26,7 @@ "Motto": "***\u201cI manifest my reality.\u201d***", "Strengths": "gentle, affectionate, curious, adaptable, ability to learn quickly and exchange ideas.", "Weaknesses": "nervous, inconsistent, indecisive.", - "full_form": "__**G**__enerous\n__**E**__motionally in tune\n__**M**__otivated\n__**I**__maginative\n__**N**__ice\n__**I**__ntelligent", + "full_form": "__**G**__enerous, __**E**__motionally in tune, __**M**__otivated, __**I**__maginative, __**N**__ice, __**I**__ntelligent", "url": "https://www.horoscope.com/images-US/signs/profile-gemini.png" }, "Cancer": { @@ -36,7 +36,7 @@ "Motto": "***\u201cI feel, therefore I am.\u201d***", "Strengths": "tenacious, highly imaginative, loyal, emotional, sympathetic, persuasive.", "Weaknesses": "moody, pessimistic, suspicious, manipulative, insecuremoody, pessimistic, suspicious, manipulative, insecure.", - "full_form": "__**C**__aring\n__**A**__mbitious\n__**N**__ourishing\n__**C**__reative\n__**E**__motionally intelligent\n__**R**__esilient", + "full_form": "__**C**__aring, __**A**__mbitious, __**N**__ourishing, __**C**__reative, __**E**__motionally intelligent, __**R**__esilient", "url": "https://www.horoscope.com/images-US/signs/profile-cancer.png" }, "Leo": { @@ -46,7 +46,7 @@ "Motto": "***\u201cIf you know the way, go the way and show the way\u2014you're a leader.\u201d***", "Strengths": "creative, passionate, generous, warm-hearted, cheerful, humorous.", "Weaknesses": "arrogant, stubborn, self-centered, lazy, inflexible.", - "full_form": "__**L**__eaders\n__**E**__nergetic\n__**O**__ptimistic", + "full_form": "__**L**__eaders, __**E**__nergetic, __**O**__ptimistic", "url": "https://www.horoscope.com/images-US/signs/profile-leo.png" }, "Virgo": { @@ -56,7 +56,7 @@ "Motto": "***\u201cMy best can always be better.\u201d***", "Strengths": "loyal, analytical, kind, hardworking, practical.", "Weaknesses": "shyness, worry, overly critical of self and others, all work and no play.", - "full_form": "__**V**__irtuous\n__**I**__ntelligent\n__**R**__esponsible\n__**G**__enerous\n__**O**__ptimistic", + "full_form": "__**V**__irtuous, __**I**__ntelligent, __**R**__esponsible, __**G**__enerous, __**O**__ptimistic", "url": "https://www.horoscope.com/images-US/signs/profile-virgo.png" }, "Libra": { @@ -66,7 +66,7 @@ "Motto": "***\u201cNo person is an island.\u201d***", "Strengths": "cooperative, diplomatic, gracious, fair-minded, social.", "Weaknesses": "indecisive, avoids confrontations, will carry a grudge, self-pity.", - "full_form": "__**L**__oyal\n__**I**__nquisitive\n__**B**__alanced\n__**R**__esponsible\n__**A**__ltruistic", + "full_form": "__**L**__oyal, __**I**__nquisitive, __**B**__alanced, __**R**__esponsible, __**A**__ltruistic", "url": "https://www.horoscope.com/images-US/signs/profile-libra.png" }, "Scorpio": { @@ -76,7 +76,7 @@ "Motto": "***\u201cYou never know what you are capable of until you try.\u201d***", "Strengths": "resourceful, brave, passionate, stubborn, a true friend.", "Weaknesses": "distrusting, jealous, secretive, violent.", - "full_form": "__**S**__eductive\n__**C**__erebral\n__**O**__riginal\n__**R**__eactive\n__**P**__assionate\n__**I**__ntuitive\n__**O**__utstanding", + "full_form": "__**S**__eductive, __**C**__erebral, __**O**__riginal, __**R**__eactive, __**P**__assionate, __**I**__ntuitive, __**O**__utstanding", "url": "https://www.horoscope.com/images-US/signs/profile-scorpio.png" }, "Sagittarius": { @@ -86,7 +86,7 @@ "Motto": "***\u201cTowering genius disdains a beaten path.\u201d***", "Strengths": "generous, idealistic, great sense of humor.", "Weaknesses": "promises more than can deliver, very impatient, will say anything no matter how undiplomatic.", - "full_form": "__**S**__eductive\n__**A**__dventurous\n__**G**__rateful\n__**I**__ntelligent\n__**T**__railblazing\n__**T**__enacious adept\n__**A**__dept\n__**R**__esponsible\n__**I**__dealistic\n__**U**__nparalled\n__**S**__ophisticated", + "full_form": "__**S**__eductive, __**A**__dventurous, __**G**__rateful, __**I**__ntelligent, __**T**__railblazing, __**T**__enacious adept, __**A**__dept, __**R**__esponsible, __**I**__dealistic, __**U**__nparalled, __**S**__ophisticated", "url": "https://www.horoscope.com/images-US/signs/profile-sagittarius.png" }, "Capricorn": { @@ -96,7 +96,7 @@ "Motto": "***\u201cI can succeed at anything I put my mind to.\u201d***", "Strengths": "responsible, disciplined, self-control, good managers.", "Weaknesses": "know-it-all, unforgiving, condescending, expecting the worst.", - "full_form": "__**C**__onfident\n__**A**__nalytical\n__**P**__ractical\n__**R**__esponsible\n__**I**__ntelligent\n__**C**__aring\n__**O**__rganized\n__**R**__ealistic\n__**N**__eat", + "full_form": "__**C**__onfident, __**A**__nalytical, __**P**__ractical, __**R**__esponsible, __**I**__ntelligent, __**C**__aring, __**O**__rganized, __**R**__ealistic, __**N**__eat", "url": "https://www.horoscope.com/images-US/signs/profile-capricorn.png" }, "Aquarius": { @@ -106,7 +106,7 @@ "Motto": "***\u201cThere is no me, there is only we.\u201d***", "Strengths": "Progressive, original, independent, humanitarian.", "Weaknesses": "Runs from emotional expression, temperamental, uncompromising, aloof.", - "full_form": "__**A**__nalytical\n__**Q**__uirky\n__**U**__ncompromising\n__**A**__ction-focused\n__**R**__espectful\n__**I**__ntelligent\n__**U**__nique\n__**S**__incere", + "full_form": "__**A**__nalytical, __**Q**__uirky, __**U**__ncompromising, __**A**__ction-focused, __**R**__espectful, __**I**__ntelligent, __**U**__nique, __**S**__incere", "url": "https://www.horoscope.com/images-US/signs/profile-aquarius.png" }, "Pisces": { @@ -116,7 +116,7 @@ "Motto": "***\u201cI have a lot of love to give, it only takes a little patience and those worth giving it all to.\u201d***", "Strengths": "Compassionate, artistic, intuitive, gentle, wise, musical.", "Weaknesses": "Fearful, overly trusting, sad, desire to escape reality, can be a victim or a martyr.", - "full_form": "__**P**__sychic\n__**I**__ntelligent\n__**S**__urprising\n__**C**__reative\n__**E**__motionally-driven\n__**S**__ensitive", + "full_form": "__**P**__sychic, __**I**__ntelligent, __**S**__urprising, __**C**__reative, __**E**__motionally-driven, __**S**__ensitive", "url": "https://www.horoscope.com/images-US/signs/profile-pisces.png" } } -- cgit v1.2.3 From 2224f08b88be2681faaba55ca56cac418a5a219c Mon Sep 17 00:00:00 2001 From: Anubhav1603 Date: Thu, 1 Oct 2020 15:04:12 +0530 Subject: rearranged order of embed --- bot/exts/valentines/valentine_zodiac.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/exts/valentines/valentine_zodiac.py b/bot/exts/valentines/valentine_zodiac.py index 3f1a6337..56831060 100644 --- a/bot/exts/valentines/valentine_zodiac.py +++ b/bot/exts/valentines/valentine_zodiac.py @@ -63,10 +63,10 @@ class ValentineZodiac(commands.Cog): log.info("Making zodiac embed.") embed.title = f"__{zodiac}__" embed.description = self.zodiac_fact[zodiac]["About"] - embed.add_field(name='__Full form__', value=self.zodiac_fact[zodiac]["full_form"], inline=False) embed.add_field(name='__Motto__', value=self.zodiac_fact[zodiac]["Motto"], inline=False) embed.add_field(name='__Strengths__', value=self.zodiac_fact[zodiac]["Strengths"], inline=False) embed.add_field(name='__Weaknesses__', value=self.zodiac_fact[zodiac]["Weaknesses"], inline=False) + embed.add_field(name='__Full form__', value=self.zodiac_fact[zodiac]["full_form"], inline=False) embed.set_thumbnail(url=self.zodiac_fact[zodiac]["url"]) else: embed = self.generate_invalidname_embed(zodiac) -- cgit v1.2.3 From ea554e53f6218d70ea932c7445edd7a3e442a977 Mon Sep 17 00:00:00 2001 From: Anubhav1603 Date: Thu, 1 Oct 2020 15:08:49 +0530 Subject: capitalised first letter from weaknesses and strengths --- bot/resources/valentines/zodiac_explanation.json | 40 ++++++++++++------------ 1 file changed, 20 insertions(+), 20 deletions(-) (limited to 'bot') diff --git a/bot/resources/valentines/zodiac_explanation.json b/bot/resources/valentines/zodiac_explanation.json index 9bb1d99d..33864ea5 100644 --- a/bot/resources/valentines/zodiac_explanation.json +++ b/bot/resources/valentines/zodiac_explanation.json @@ -4,8 +4,8 @@ "end_at": "2020-04-19", "About": "Amazing people born between **March 21** to **April 19**. Aries loves to be number one, so it\u2019s no surprise that these audacious rams are the first sign of the zodiac. Bold and ambitious, Aries dives headfirst into even the most challenging situations.", "Motto": "***\u201cWhen you know yourself, you're empowered. When you accept yourself, you're invincible.\u201d***", - "Strengths": "courageous, determined, confident, enthusiastic, optimistic, honest, passionate.", - "Weaknesses": "impatient, moody, short-tempered, impulsive, aggressive.", + "Strengths": "Courageous, determined, confident, enthusiastic, optimistic, honest, passionate.", + "Weaknesses": "Impatient, moody, short-tempered, impulsive, aggressive.", "full_form": "__**A**__ssertive, __**R**__efreshing, __**I**__ndependent, __**E**__nergetic, __**S**__exy", "url": "https://www.horoscope.com/images-US/signs/profile-aries.png" }, @@ -14,8 +14,8 @@ "end_at": "2020-05-20", "About": "Amazing people born between **April 20** to **May 20**. Taurus is an earth sign represented by the bull. Like their celestial spirit animal, Taureans enjoy relaxing in serene, bucolic environments surrounded by soft sounds, soothing aromas, and succulent flavors", "Motto": "***\u201cNothing worth having comes easy.\u201d***", - "Strengths": "reliable, patient, practical, devoted, responsible, stable.", - "Weaknesses": "stubborn, possessive, uncompromising.", + "Strengths": "Reliable, patient, practical, devoted, responsible, stable.", + "Weaknesses": "Stubborn, possessive, uncompromising.", "full_form": "__**T**__railblazing, __**A**__mbitious, __**U**__nwavering, __**R**__eliable, __**U**__nderstanding, __**S**__table", "url": "https://www.horoscope.com/images-US/signs/profile-taurus.png" }, @@ -24,8 +24,8 @@ "end_at": "2020-06-20", "About": "Amazing people born between **May 21** to **June 20**. Have you ever been so busy that you wished you could clone yourself just to get everything done? That\u2019s the Gemini experience in a nutshell. Appropriately symbolized by the celestial twins, this air sign was interested in so many pursuits that it had to double itself.", "Motto": "***\u201cI manifest my reality.\u201d***", - "Strengths": "gentle, affectionate, curious, adaptable, ability to learn quickly and exchange ideas.", - "Weaknesses": "nervous, inconsistent, indecisive.", + "Strengths": "Gentle, affectionate, curious, adaptable, ability to learn quickly and exchange ideas.", + "Weaknesses": "Nervous, inconsistent, indecisive.", "full_form": "__**G**__enerous, __**E**__motionally in tune, __**M**__otivated, __**I**__maginative, __**N**__ice, __**I**__ntelligent", "url": "https://www.horoscope.com/images-US/signs/profile-gemini.png" }, @@ -34,8 +34,8 @@ "end_at": "2020-07-22", "About": "Amazing people born between **June 21 ** to **July 22**. Cancer is a cardinal water sign. Represented by the crab, this crustacean seamlessly weaves between the sea and shore representing Cancer\u2019s ability to exist in both emotional and material realms. Cancers are highly intuitive and their psychic abilities manifest in tangible spaces: For instance, Cancers can effortlessly pick up the energies in a room.", "Motto": "***\u201cI feel, therefore I am.\u201d***", - "Strengths": "tenacious, highly imaginative, loyal, emotional, sympathetic, persuasive.", - "Weaknesses": "moody, pessimistic, suspicious, manipulative, insecuremoody, pessimistic, suspicious, manipulative, insecure.", + "Strengths": "Tenacious, highly imaginative, loyal, emotional, sympathetic, persuasive.", + "Weaknesses": "Moody, pessimistic, suspicious, manipulative, insecuremoody, pessimistic, suspicious, manipulative, insecure.", "full_form": "__**C**__aring, __**A**__mbitious, __**N**__ourishing, __**C**__reative, __**E**__motionally intelligent, __**R**__esilient", "url": "https://www.horoscope.com/images-US/signs/profile-cancer.png" }, @@ -44,8 +44,8 @@ "end_at": "2020-08-22", "About": "Amazing people born between **July 23** to **August 22**. Roll out the red carpet because Leo has arrived. Leo is represented by the lion and these spirited fire signs are the kings and queens of the celestial jungle. They\u2019re delighted to embrace their royal status: Vivacious, theatrical, and passionate, Leos love to bask in the spotlight and celebrate themselves.", "Motto": "***\u201cIf you know the way, go the way and show the way\u2014you're a leader.\u201d***", - "Strengths": "creative, passionate, generous, warm-hearted, cheerful, humorous.", - "Weaknesses": "arrogant, stubborn, self-centered, lazy, inflexible.", + "Strengths": "Creative, passionate, generous, warm-hearted, cheerful, humorous.", + "Weaknesses": "Arrogant, stubborn, self-centered, lazy, inflexible.", "full_form": "__**L**__eaders, __**E**__nergetic, __**O**__ptimistic", "url": "https://www.horoscope.com/images-US/signs/profile-leo.png" }, @@ -54,8 +54,8 @@ "end_at": "2020-09-22", "About": "Amazing people born between **August 23** to **September 22**. Virgo is an earth sign historically represented by the goddess of wheat and agriculture, an association that speaks to Virgo\u2019s deep-rooted presence in the material world. Virgos are logical, practical, and systematic in their approach to life. This earth sign is a perfectionist at heart and isn\u2019t afraid to improve skills through diligent and consistent practice.", "Motto": "***\u201cMy best can always be better.\u201d***", - "Strengths": "loyal, analytical, kind, hardworking, practical.", - "Weaknesses": "shyness, worry, overly critical of self and others, all work and no play.", + "Strengths": "Loyal, analytical, kind, hardworking, practical.", + "Weaknesses": "Shyness, worry, overly critical of self and others, all work and no play.", "full_form": "__**V**__irtuous, __**I**__ntelligent, __**R**__esponsible, __**G**__enerous, __**O**__ptimistic", "url": "https://www.horoscope.com/images-US/signs/profile-virgo.png" }, @@ -64,8 +64,8 @@ "end_at": "2020-10-22", "About": "Amazing people born between **September 23** to **October 22**. Libra is an air sign represented by the scales (interestingly, the only inanimate object of the zodiac), an association that reflects Libra's fixation on balance and harmony. Libra is obsessed with symmetry and strives to create equilibrium in all areas of life.", "Motto": "***\u201cNo person is an island.\u201d***", - "Strengths": "cooperative, diplomatic, gracious, fair-minded, social.", - "Weaknesses": "indecisive, avoids confrontations, will carry a grudge, self-pity.", + "Strengths": "Cooperative, diplomatic, gracious, fair-minded, social.", + "Weaknesses": "Indecisive, avoids confrontations, will carry a grudge, self-pity.", "full_form": "__**L**__oyal, __**I**__nquisitive, __**B**__alanced, __**R**__esponsible, __**A**__ltruistic", "url": "https://www.horoscope.com/images-US/signs/profile-libra.png" }, @@ -74,8 +74,8 @@ "end_at": "2020-11-21", "About": "Amazing people born between **October 23** to **November 21**. Scorpio is one of the most misunderstood signs of the zodiac. Because of its incredible passion and power, Scorpio is often mistaken for a fire sign. In fact, Scorpio is a water sign that derives its strength from the psychic, emotional realm.", "Motto": "***\u201cYou never know what you are capable of until you try.\u201d***", - "Strengths": "resourceful, brave, passionate, stubborn, a true friend.", - "Weaknesses": "distrusting, jealous, secretive, violent.", + "Strengths": "Resourceful, brave, passionate, stubborn, a true friend.", + "Weaknesses": "Distrusting, jealous, secretive, violent.", "full_form": "__**S**__eductive, __**C**__erebral, __**O**__riginal, __**R**__eactive, __**P**__assionate, __**I**__ntuitive, __**O**__utstanding", "url": "https://www.horoscope.com/images-US/signs/profile-scorpio.png" }, @@ -84,8 +84,8 @@ "end_at": "2020-12-21", "About": "Amazing people born between **November 22** to **December 21**. Represented by the archer, Sagittarians are always on a quest for knowledge. The last fire sign of the zodiac, Sagittarius launches its many pursuits like blazing arrows, chasing after geographical, intellectual, and spiritual adventures.", "Motto": "***\u201cTowering genius disdains a beaten path.\u201d***", - "Strengths": "generous, idealistic, great sense of humor.", - "Weaknesses": "promises more than can deliver, very impatient, will say anything no matter how undiplomatic.", + "Strengths": "Generous, idealistic, great sense of humor.", + "Weaknesses": "Promises more than can deliver, very impatient, will say anything no matter how undiplomatic.", "full_form": "__**S**__eductive, __**A**__dventurous, __**G**__rateful, __**I**__ntelligent, __**T**__railblazing, __**T**__enacious adept, __**A**__dept, __**R**__esponsible, __**I**__dealistic, __**U**__nparalled, __**S**__ophisticated", "url": "https://www.horoscope.com/images-US/signs/profile-sagittarius.png" }, @@ -94,8 +94,8 @@ "end_at": "2021-01-19", "About": "Amazing people born between **December 22** to **January 19**. The last earth sign of the zodiac, Capricorn is represented by the sea goat, a mythological creature with the body of a goat and tail of a fish. Accordingly, Capricorns are skilled at navigating both the material and emotional realms.", "Motto": "***\u201cI can succeed at anything I put my mind to.\u201d***", - "Strengths": "responsible, disciplined, self-control, good managers.", - "Weaknesses": "know-it-all, unforgiving, condescending, expecting the worst.", + "Strengths": "Responsible, disciplined, self-control, good managers.", + "Weaknesses": "Know-it-all, unforgiving, condescending, expecting the worst.", "full_form": "__**C**__onfident, __**A**__nalytical, __**P**__ractical, __**R**__esponsible, __**I**__ntelligent, __**C**__aring, __**O**__rganized, __**R**__ealistic, __**N**__eat", "url": "https://www.horoscope.com/images-US/signs/profile-capricorn.png" }, -- cgit v1.2.3 From 2e8a0396586dfdb9eaff8a308ef39736ccdaffb3 Mon Sep 17 00:00:00 2001 From: Anubhav1603 Date: Thu, 1 Oct 2020 19:00:56 +0530 Subject: removed --- bot/exts/evergreen/emoji_count.py | 29 ----------------------------- 1 file changed, 29 deletions(-) delete mode 100644 bot/exts/evergreen/emoji_count.py (limited to 'bot') diff --git a/bot/exts/evergreen/emoji_count.py b/bot/exts/evergreen/emoji_count.py deleted file mode 100644 index b619f349..00000000 --- a/bot/exts/evergreen/emoji_count.py +++ /dev/null @@ -1,29 +0,0 @@ -import logging - -from discord.ext import commands - -log = logging.getLogger(__name__) - - -class EmojiCount(commands.Cog): - """Command that give random emoji based on category.""" - - def __init__(self, bot: commands.Bot): - self.bot = bot - - @commands.command(name="ec") - async def ec(self, ctx, emoj: str): - """Returns embed with emoji category and info given by user.""" - emoji = [] - for a in ctx.guild.emojis: - for n in a.name.split('_'): - if len(n) == 1: - pass - elif n.name[0] == emoji.lower(): - emoji.append(a) - await ctx.send(emoji) - - -def setup(bot: commands.Bot) -> None: - """Emoji Count Cog load.""" - bot.add_cog(EmojiCount(bot)) -- cgit v1.2.3 From f5d572f45b507537c10217b9c4f25b2fc733bd20 Mon Sep 17 00:00:00 2001 From: Anubhav1603 Date: Thu, 1 Oct 2020 19:20:02 +0530 Subject: changed line 64 logging to trace and in error description added date also --- bot/exts/valentines/valentine_zodiac.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'bot') diff --git a/bot/exts/valentines/valentine_zodiac.py b/bot/exts/valentines/valentine_zodiac.py index 56831060..29888bcf 100644 --- a/bot/exts/valentines/valentine_zodiac.py +++ b/bot/exts/valentines/valentine_zodiac.py @@ -45,7 +45,8 @@ class ValentineZodiac(commands.Cog): embed = discord.Embed() embed.color = Colours.soft_red error_comp = "\n".join( - f"`{i}` {zod_name}" for i, zod_name in enumerate(self.zodiac_fact.keys(), start=1) + f"`{i}` {name}: {zodiac['start_at'].date()} - {zodiac['end_at'].date()}" + for i, (name, zodiac) in enumerate(sorted(self.zodiac_fact.items()), start=1) ) embed.description = ( f"**{zodiac}** is not a valid zodiac sign, here is the list of valid zodiac signs.\n" @@ -60,7 +61,7 @@ class ValentineZodiac(commands.Cog): embed = discord.Embed() embed.color = Colours.pink if zodiac in self.zodiac_fact: - log.info("Making zodiac embed.") + log.trace("Making zodiac embed.") embed.title = f"__{zodiac}__" embed.description = self.zodiac_fact[zodiac]["About"] embed.add_field(name='__Motto__', value=self.zodiac_fact[zodiac]["Motto"], inline=False) -- cgit v1.2.3 From f052fb9c629f080729bfdf9942b08983bb1fa613 Mon Sep 17 00:00:00 2001 From: Anubhav1603 Date: Thu, 1 Oct 2020 19:52:46 +0530 Subject: formated date and month of error in better way --- bot/exts/valentines/valentine_zodiac.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/exts/valentines/valentine_zodiac.py b/bot/exts/valentines/valentine_zodiac.py index 29888bcf..36572729 100644 --- a/bot/exts/valentines/valentine_zodiac.py +++ b/bot/exts/valentines/valentine_zodiac.py @@ -45,7 +45,7 @@ class ValentineZodiac(commands.Cog): embed = discord.Embed() embed.color = Colours.soft_red error_comp = "\n".join( - f"`{i}` {name}: {zodiac['start_at'].date()} - {zodiac['end_at'].date()}" + f"`{i}` {name}: {zodiac['start_at'].strftime('%B `%d`')} - {zodiac['end_at'].strftime('%B `%d`')}" for i, (name, zodiac) in enumerate(sorted(self.zodiac_fact.items()), start=1) ) embed.description = ( -- cgit v1.2.3 From c350c1c252d53e17c9ecd527760a345741a8ef46 Mon Sep 17 00:00:00 2001 From: Anubhav1603 Date: Thu, 1 Oct 2020 21:12:12 +0530 Subject: modified error msg --- bot/exts/valentines/valentine_zodiac.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) (limited to 'bot') diff --git a/bot/exts/valentines/valentine_zodiac.py b/bot/exts/valentines/valentine_zodiac.py index 36572729..660bc092 100644 --- a/bot/exts/valentines/valentine_zodiac.py +++ b/bot/exts/valentines/valentine_zodiac.py @@ -44,14 +44,16 @@ class ValentineZodiac(commands.Cog): """Returns error embed.""" embed = discord.Embed() embed.color = Colours.soft_red - error_comp = "\n".join( - f"`{i}` {name}: {zodiac['start_at'].strftime('%B `%d`')} - {zodiac['end_at'].strftime('%B `%d`')}" - for i, (name, zodiac) in enumerate(sorted(self.zodiac_fact.items()), start=1) - ) - embed.description = ( - f"**{zodiac}** is not a valid zodiac sign, here is the list of valid zodiac signs.\n" - f"{error_comp}" - ) + error_msg = f"**{zodiac}** is not a valid zodiac sign, here is the list of valid zodiac signs.\n" + valid_zodiac_name = '' + for name in sorted(self.zodiac_fact.keys()): + if name == "Leo": + valid_zodiac_name += f'{name}\n ' + elif name == 'Virgo': + valid_zodiac_name += name + else: + valid_zodiac_name += f'{name}, ' + embed.description = error_msg + valid_zodiac_name log.info("Invalid zodiac name provided.") return embed -- cgit v1.2.3 From 27bd1a3fdf3f183829cd274b16bdeaf7adfc15d2 Mon Sep 17 00:00:00 2001 From: Den4200 Date: Thu, 1 Oct 2020 11:49:09 -0400 Subject: Authenticate GitHub API requests for the Hacktoberfest issue finder. --- bot/exts/halloween/hacktober-issue-finder.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) (limited to 'bot') diff --git a/bot/exts/halloween/hacktober-issue-finder.py b/bot/exts/halloween/hacktober-issue-finder.py index b5ad1c4f..78acf391 100644 --- a/bot/exts/halloween/hacktober-issue-finder.py +++ b/bot/exts/halloween/hacktober-issue-finder.py @@ -7,13 +7,19 @@ import aiohttp import discord from discord.ext import commands -from bot.constants import Month +from bot.constants import Month, Tokens from bot.utils.decorators import in_month log = logging.getLogger(__name__) URL = "https://api.github.com/search/issues?per_page=100&q=is:issue+label:hacktoberfest+language:python+state:open" -HEADERS = {"Accept": "application / vnd.github.v3 + json"} + +REQUEST_HEADERS = { + "User-Agent": "Python Discord Hacktoberbot", + "Accept": "application / vnd.github.v3 + json" +} +if GITHUB_TOKEN := Tokens.github: + REQUEST_HEADERS["Authorization"] = f"token {GITHUB_TOKEN}" class HacktoberIssues(commands.Cog): @@ -66,7 +72,7 @@ class HacktoberIssues(commands.Cog): url += f"&page={page}" log.debug(f"making api request to url: {url}") - async with session.get(url, headers=HEADERS) as response: + async with session.get(url, headers=REQUEST_HEADERS) as response: if response.status != 200: log.error(f"expected 200 status (got {response.status}) from the GitHub api.") await ctx.send(f"ERROR: expected 200 status (got {response.status}) from the GitHub api.") -- cgit v1.2.3 From db66d4f79fc0ffacec0d36b3868afed8776f1a7c Mon Sep 17 00:00:00 2001 From: Anubhav <57266248+Anubhav1603@users.noreply.github.com> Date: Thu, 1 Oct 2020 21:54:35 +0530 Subject: Update bot/exts/valentines/valentine_zodiac.py Co-authored-by: Thomas Petersson <61778143+thomaspet@users.noreply.github.com> --- bot/exts/valentines/valentine_zodiac.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) (limited to 'bot') diff --git a/bot/exts/valentines/valentine_zodiac.py b/bot/exts/valentines/valentine_zodiac.py index 660bc092..688bb8eb 100644 --- a/bot/exts/valentines/valentine_zodiac.py +++ b/bot/exts/valentines/valentine_zodiac.py @@ -45,14 +45,7 @@ class ValentineZodiac(commands.Cog): embed = discord.Embed() embed.color = Colours.soft_red error_msg = f"**{zodiac}** is not a valid zodiac sign, here is the list of valid zodiac signs.\n" - valid_zodiac_name = '' - for name in sorted(self.zodiac_fact.keys()): - if name == "Leo": - valid_zodiac_name += f'{name}\n ' - elif name == 'Virgo': - valid_zodiac_name += name - else: - valid_zodiac_name += f'{name}, ' + valid_zodiac_name = ", ".join(self.zodiac_fact).replace("Leo, ", "Leo,\n") embed.description = error_msg + valid_zodiac_name log.info("Invalid zodiac name provided.") return embed -- cgit v1.2.3 From 9be31c8d5aa5c5aba06340b4f646b82855635a54 Mon Sep 17 00:00:00 2001 From: Anubhav1603 Date: Thu, 1 Oct 2020 22:43:05 +0530 Subject: modified error message --- bot/exts/valentines/valentine_zodiac.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'bot') diff --git a/bot/exts/valentines/valentine_zodiac.py b/bot/exts/valentines/valentine_zodiac.py index 688bb8eb..3049540d 100644 --- a/bot/exts/valentines/valentine_zodiac.py +++ b/bot/exts/valentines/valentine_zodiac.py @@ -45,8 +45,9 @@ class ValentineZodiac(commands.Cog): embed = discord.Embed() embed.color = Colours.soft_red error_msg = f"**{zodiac}** is not a valid zodiac sign, here is the list of valid zodiac signs.\n" - valid_zodiac_name = ", ".join(self.zodiac_fact).replace("Leo, ", "Leo,\n") - embed.description = error_msg + valid_zodiac_name + first_part_of_valid_name_list = ", ".join(name for name in list(self.zodiac_fact.keys())[:7]) + end_part_of_valid_name_list = ", ".join(name for name in list(self.zodiac_fact.keys())[7:]) + embed.description = error_msg + first_part_of_valid_name_list + ",\n" + end_part_of_valid_name_list log.info("Invalid zodiac name provided.") return embed -- cgit v1.2.3 From 1f2d9a09b4aabbae0f5859ea0d909e80aaef194a Mon Sep 17 00:00:00 2001 From: Anubhav1603 Date: Fri, 2 Oct 2020 16:53:59 +0530 Subject: added embed_builder_method,error_embed_method --- bot/exts/evergreen/emoji_count.py | 80 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 bot/exts/evergreen/emoji_count.py (limited to 'bot') diff --git a/bot/exts/evergreen/emoji_count.py b/bot/exts/evergreen/emoji_count.py new file mode 100644 index 00000000..2f20dbed --- /dev/null +++ b/bot/exts/evergreen/emoji_count.py @@ -0,0 +1,80 @@ +import datetime +import logging +import random +from typing import Dict, Optional + +import discord +from discord.ext import commands + +from bot.constants import Colours + +log = logging.getLogger(__name__) + + +class EmojiCount(commands.Cog): + """Command that give random emoji based on category.""" + + def __init__(self, bot: commands.Bot): + self.bot = bot + + def embed_builder(self, emoji: dict) -> discord.Embed: + """Genrates embed with emoji name and count.""" + embed = discord.Embed() + embed.color = Colours.orange + embed.title = "Emoji Count" + embed.timestamp = datetime.datetime.utcnow() + if len(emoji) == 1: + for key, value in emoji.items(): + print(key) + embed.description = f"There are **{len(value)}** emojis in the **{key}** category" + embed.set_thumbnail(url=random.choice(value).url) + else: + msg = '' + for key, value in emoji.items(): + emoji_choice = random.choice(value) + error_msg = f'There are **{len(value)}** emojis in the **{key}** category\n' + msg += f'<:{emoji_choice.name}:{emoji_choice.id}> {error_msg}' + embed.description = msg + return embed + + @staticmethod + def generate_invalid_embed(ctx) -> discord.Embed: + """Genrates error embed.""" + embed = discord.Embed() + embed.color = Colours.soft_red + embed.title = "Invalid Input" + emoji_dict = {} + for emoji in ctx.guild.emojis: + emoji_dict.update({emoji.name.split("_")[0]: []}) + error_comp = ', '.join(key for key in emoji_dict.keys()) + embed.description = f"These are the valid categories\n```{error_comp}```" + return embed + + def emoji_list(self, ctx, emojis: dict) -> Dict: + """Genrates dictionary of emojis given by the user.""" + for emoji in ctx.guild.emojis: + for key, value in emojis.items(): + if emoji.name.split("_")[0] == key: + value.append(emoji) + return emojis + + @commands.command(name="ec") + async def ec(self, ctx, *, emoji: str = None) -> Optional[str]: + """Returns embed with emoji category and info given by user.""" + emoji_dict = {} + for a in ctx.guild.emojis: + if emoji is None: + emoji_dict.update({a.name.split("_")[0]: []}) + elif a.name.split("_")[0] in emoji: + emoji_dict.update({a.name.split("_")[0]: []}) + emoji_dict = self.emoji_list(ctx, emoji_dict) + if len(emoji_dict) == 0: + embed = self.generate_invalid_embed(ctx) + else: + embed = self.embed_builder(emoji_dict) + await ctx.send(embed=embed) + + +def setup(bot: commands.Bot) -> None: + """Emoji Count Cog load.""" + bot.add_cog(EmojiCount(bot)) -- cgit v1.2.3 From c9cdb2b5005bd20f881b5b7d2ed5fcef0d66b0c9 Mon Sep 17 00:00:00 2001 From: Anubhav1603 Date: Fri, 2 Oct 2020 17:03:07 +0530 Subject: added logging --- bot/exts/evergreen/emoji_count.py | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'bot') diff --git a/bot/exts/evergreen/emoji_count.py b/bot/exts/evergreen/emoji_count.py index 2f20dbed..a90c1b7d 100644 --- a/bot/exts/evergreen/emoji_count.py +++ b/bot/exts/evergreen/emoji_count.py @@ -35,6 +35,7 @@ class EmojiCount(commands.Cog): error_msg = f'There are **{len(value)}** emojis in the **{key}** category\n' msg += f'<:{emoji_choice.name}:{emoji_choice.id}> {error_msg}' embed.description = msg + log.trace('Emoji embed sent') return embed @staticmethod @@ -48,6 +49,7 @@ class EmojiCount(commands.Cog): emoji_dict.update({emoji.name.split("_")[0]: []}) error_comp = ', '.join(key for key in emoji_dict.keys()) embed.description = f"These are the valid categories\n```{error_comp}```" + log.trace("Error embed sent") return embed def emoji_list(self, ctx, emojis: dict) -> Dict: @@ -56,6 +58,7 @@ class EmojiCount(commands.Cog): for key, value in emojis.items(): if emoji.name.split("_")[0] == key: value.append(emoji) + log.trace("Emoji dict sent") return emojis @commands.command(name="ec") @@ -64,15 +67,20 @@ class EmojiCount(commands.Cog): emoji_dict = {} for a in ctx.guild.emojis: if emoji is None: + log.trace("Emoji Category doesn't provided by the user") emoji_dict.update({a.name.split("_")[0]: []}) elif a.name.split("_")[0] in emoji: + log.trace("Emoji Category provided by the user") emoji_dict.update({a.name.split("_")[0]: []}) emoji_dict = self.emoji_list(ctx, emoji_dict) if len(emoji_dict) == 0: embed = self.generate_invalid_embed(ctx) + log.trace("Error embed received") else: embed = self.embed_builder(emoji_dict) + log.trace("Emoji embed received") await ctx.send(embed=embed) + log.trace("Embed sent") def setup(bot: commands.Bot) -> None: -- cgit v1.2.3 From e9de05b77f886dffacca9cdee53dd1a424913fb9 Mon Sep 17 00:00:00 2001 From: Anubhav1603 Date: Fri, 2 Oct 2020 17:25:49 +0530 Subject: added ctx annotation --- bot/exts/evergreen/emoji_count.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'bot') diff --git a/bot/exts/evergreen/emoji_count.py b/bot/exts/evergreen/emoji_count.py index a90c1b7d..fa0879ae 100644 --- a/bot/exts/evergreen/emoji_count.py +++ b/bot/exts/evergreen/emoji_count.py @@ -39,7 +39,7 @@ class EmojiCount(commands.Cog): return embed @staticmethod - def generate_invalid_embed(ctx) -> discord.Embed: + def generate_invalid_embed(ctx: commands.Context) -> discord.Embed: """Genrates error embed.""" embed = discord.Embed() embed.color = Colours.soft_red @@ -52,7 +52,7 @@ class EmojiCount(commands.Cog): log.trace("Error embed sent") return embed - def emoji_list(self, ctx, emojis: dict) -> Dict: + def emoji_list(self, ctx: commands.Context, emojis: dict) -> Dict: """Genrates dictionary of emojis given by the user.""" for emoji in ctx.guild.emojis: for key, value in emojis.items(): @@ -62,7 +62,7 @@ class EmojiCount(commands.Cog): return emojis @commands.command(name="ec") - async def ec(self, ctx, *, emoji: str = None) -> Optional[str]: + async def ec(self, ctx: commands.Context, *, emoji: str = None) -> Optional[str]: """Returns embed with emoji category and info given by user.""" emoji_dict = {} for a in ctx.guild.emojis: -- cgit v1.2.3 From e949e54f0e0d3a3d5233799e837b12505d38ad5e Mon Sep 17 00:00:00 2001 From: Anubhav1603 Date: Fri, 2 Oct 2020 18:02:34 +0530 Subject: modified error message --- bot/exts/valentines/valentine_zodiac.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'bot') diff --git a/bot/exts/valentines/valentine_zodiac.py b/bot/exts/valentines/valentine_zodiac.py index 3049540d..2696999f 100644 --- a/bot/exts/valentines/valentine_zodiac.py +++ b/bot/exts/valentines/valentine_zodiac.py @@ -45,9 +45,11 @@ class ValentineZodiac(commands.Cog): embed = discord.Embed() embed.color = Colours.soft_red error_msg = f"**{zodiac}** is not a valid zodiac sign, here is the list of valid zodiac signs.\n" - first_part_of_valid_name_list = ", ".join(name for name in list(self.zodiac_fact.keys())[:7]) - end_part_of_valid_name_list = ", ".join(name for name in list(self.zodiac_fact.keys())[7:]) - embed.description = error_msg + first_part_of_valid_name_list + ",\n" + end_part_of_valid_name_list + names = list(self.zodiac_fact) + middle_index = len(names) // 2 + first_half_names = ", ".join(names[:middle_index]) + second_half_names = ", ".join(names[middle_index:]) + embed.description = error_msg + first_half_names + ",\n" + second_half_names log.info("Invalid zodiac name provided.") return embed -- cgit v1.2.3 From 8cc34b61f59f685344654d35791d0a722dd3d735 Mon Sep 17 00:00:00 2001 From: Anubhav <57266248+Anubhav1603@users.noreply.github.com> Date: Fri, 2 Oct 2020 18:06:06 +0530 Subject: Update bot/exts/evergreen/emoji_count.py Co-authored-by: Matteo Bertucci --- bot/exts/evergreen/emoji_count.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/exts/evergreen/emoji_count.py b/bot/exts/evergreen/emoji_count.py index fa0879ae..2e6c5638 100644 --- a/bot/exts/evergreen/emoji_count.py +++ b/bot/exts/evergreen/emoji_count.py @@ -18,7 +18,7 @@ class EmojiCount(commands.Cog): self.bot = bot def embed_builder(self, emoji: dict) -> discord.Embed: - """Genrates embed with emoji name and count.""" + """Generates an embed with the emoji names and count.""" embed = discord.Embed() embed.color = Colours.orange embed.title = "Emoji Count" -- cgit v1.2.3 From 7cf5afb3ac29b9752e4b89da7b0c8dacd4e6313a Mon Sep 17 00:00:00 2001 From: Anubhav <57266248+Anubhav1603@users.noreply.github.com> Date: Fri, 2 Oct 2020 18:07:43 +0530 Subject: Update bot/exts/evergreen/emoji_count.py Co-authored-by: Matteo Bertucci --- bot/exts/evergreen/emoji_count.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/exts/evergreen/emoji_count.py b/bot/exts/evergreen/emoji_count.py index 2e6c5638..4b35c619 100644 --- a/bot/exts/evergreen/emoji_count.py +++ b/bot/exts/evergreen/emoji_count.py @@ -67,7 +67,7 @@ class EmojiCount(commands.Cog): emoji_dict = {} for a in ctx.guild.emojis: if emoji is None: - log.trace("Emoji Category doesn't provided by the user") + log.trace("Emoji Category not provided by the user") emoji_dict.update({a.name.split("_")[0]: []}) elif a.name.split("_")[0] in emoji: log.trace("Emoji Category provided by the user") -- cgit v1.2.3 From 40f0c0e5b5c361ca3acf5e39b55470d2b9ea0bc4 Mon Sep 17 00:00:00 2001 From: Anubhav <57266248+Anubhav1603@users.noreply.github.com> Date: Fri, 2 Oct 2020 18:10:04 +0530 Subject: removed update and added data to dict directly Co-authored-by: Matteo Bertucci --- bot/exts/evergreen/emoji_count.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/exts/evergreen/emoji_count.py b/bot/exts/evergreen/emoji_count.py index 4b35c619..e9bd8c35 100644 --- a/bot/exts/evergreen/emoji_count.py +++ b/bot/exts/evergreen/emoji_count.py @@ -46,7 +46,7 @@ class EmojiCount(commands.Cog): embed.title = "Invalid Input" emoji_dict = {} for emoji in ctx.guild.emojis: - emoji_dict.update({emoji.name.split("_")[0]: []}) + emoji_dict[emoji.name.split("_")[0]] = [] error_comp = ', '.join(key for key in emoji_dict.keys()) embed.description = f"These are the valid categories\n```{error_comp}```" log.trace("Error embed sent") -- cgit v1.2.3 From 1c5f5837714b71ef93ee9eeac32e74df09a585d9 Mon Sep 17 00:00:00 2001 From: Anubhav1603 Date: Fri, 2 Oct 2020 18:15:41 +0530 Subject: changed error_msg to emoji_info --- bot/exts/evergreen/emoji_count.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'bot') diff --git a/bot/exts/evergreen/emoji_count.py b/bot/exts/evergreen/emoji_count.py index fa0879ae..95e4c927 100644 --- a/bot/exts/evergreen/emoji_count.py +++ b/bot/exts/evergreen/emoji_count.py @@ -32,8 +32,8 @@ class EmojiCount(commands.Cog): msg = '' for key, value in emoji.items(): emoji_choice = random.choice(value) - error_msg = f'There are **{len(value)}** emojis in the **{key}** category\n' - msg += f'<:{emoji_choice.name}:{emoji_choice.id}> {error_msg}' + emoji_info = f'There are **{len(value)}** emojis in the **{key}** category\n' + msg += f'<:{emoji_choice.name}:{emoji_choice.id}> {emoji_info}' embed.description = msg log.trace('Emoji embed sent') return embed @@ -61,7 +61,7 @@ class EmojiCount(commands.Cog): log.trace("Emoji dict sent") return emojis - @commands.command(name="ec") + @commands.command(name="emoji_count", aliases=["ec"]) async def ec(self, ctx: commands.Context, *, emoji: str = None) -> Optional[str]: """Returns embed with emoji category and info given by user.""" emoji_dict = {} -- cgit v1.2.3 From 480cc1b56077c76b42c9102ae6320c4801b1719b Mon Sep 17 00:00:00 2001 From: Anubhav1603 Date: Fri, 2 Oct 2020 18:31:40 +0530 Subject: removed print statement,changed logging msg and changed emoji list --- bot/exts/evergreen/emoji_count.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) (limited to 'bot') diff --git a/bot/exts/evergreen/emoji_count.py b/bot/exts/evergreen/emoji_count.py index 95e4c927..ecdb12fa 100644 --- a/bot/exts/evergreen/emoji_count.py +++ b/bot/exts/evergreen/emoji_count.py @@ -6,7 +6,7 @@ from typing import Dict, Optional import discord from discord.ext import commands -from bot.constants import Colours +from bot.constants import Colours, ERROR_REPLIES log = logging.getLogger(__name__) @@ -25,7 +25,6 @@ class EmojiCount(commands.Cog): embed.timestamp = datetime.datetime.utcnow() if len(emoji) == 1: for key, value in emoji.items(): - print(key) embed.description = f"There are **{len(value)}** emojis in the **{key}** category" embed.set_thumbnail(url=random.choice(value).url) else: @@ -35,7 +34,7 @@ class EmojiCount(commands.Cog): emoji_info = f'There are **{len(value)}** emojis in the **{key}** category\n' msg += f'<:{emoji_choice.name}:{emoji_choice.id}> {emoji_info}' embed.description = msg - log.trace('Emoji embed sent') + log.trace('Emoji embed built') return embed @staticmethod @@ -43,23 +42,25 @@ class EmojiCount(commands.Cog): """Genrates error embed.""" embed = discord.Embed() embed.color = Colours.soft_red - embed.title = "Invalid Input" + embed.title = random.choice(ERROR_REPLIES) emoji_dict = {} for emoji in ctx.guild.emojis: emoji_dict.update({emoji.name.split("_")[0]: []}) error_comp = ', '.join(key for key in emoji_dict.keys()) embed.description = f"These are the valid categories\n```{error_comp}```" - log.trace("Error embed sent") + log.trace("Error embed built") return embed - def emoji_list(self, ctx: commands.Context, emojis: dict) -> Dict: - """Genrates dictionary of emojis given by the user.""" + def emoji_list(self, ctx, categories) -> Dict: + """Generates an embed with the emoji names and count.""" + out = {category: [] for category in categories} + for emoji in ctx.guild.emojis: - for key, value in emojis.items(): - if emoji.name.split("_")[0] == key: - value.append(emoji) - log.trace("Emoji dict sent") - return emojis + category = emoji.name.split('_')[0] + if category in out: + out[category].append(emoji) + log.trace("Emoji dict built") + return out @commands.command(name="emoji_count", aliases=["ec"]) async def ec(self, ctx: commands.Context, *, emoji: str = None) -> Optional[str]: -- cgit v1.2.3 From d4b4885751a0830f684ac8cd95636abe2439b432 Mon Sep 17 00:00:00 2001 From: Anubhav1603 Date: Fri, 2 Oct 2020 18:34:41 +0530 Subject: added annotation --- bot/exts/evergreen/emoji_count.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'bot') diff --git a/bot/exts/evergreen/emoji_count.py b/bot/exts/evergreen/emoji_count.py index ecdb12fa..c303cb7e 100644 --- a/bot/exts/evergreen/emoji_count.py +++ b/bot/exts/evergreen/emoji_count.py @@ -51,7 +51,7 @@ class EmojiCount(commands.Cog): log.trace("Error embed built") return embed - def emoji_list(self, ctx, categories) -> Dict: + def emoji_list(self, ctx: commands.Context, categories: dict) -> Dict: """Generates an embed with the emoji names and count.""" out = {category: [] for category in categories} @@ -64,7 +64,7 @@ class EmojiCount(commands.Cog): @commands.command(name="emoji_count", aliases=["ec"]) async def ec(self, ctx: commands.Context, *, emoji: str = None) -> Optional[str]: - """Returns embed with emoji category and info given by user.""" + """Returns embed with emoji category and info given by the user.""" emoji_dict = {} for a in ctx.guild.emojis: if emoji is None: -- cgit v1.2.3 From 38968a545f3f237542448e4853d18e274c63e86d Mon Sep 17 00:00:00 2001 From: Anubhav <57266248+Anubhav1603@users.noreply.github.com> Date: Sat, 3 Oct 2020 12:44:19 +0530 Subject: Update bot/exts/evergreen/emoji_count.py Co-authored-by: ks129 <45097959+ks129@users.noreply.github.com> --- bot/exts/evergreen/emoji_count.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'bot') diff --git a/bot/exts/evergreen/emoji_count.py b/bot/exts/evergreen/emoji_count.py index 3ad0d61b..b32fd0ed 100644 --- a/bot/exts/evergreen/emoji_count.py +++ b/bot/exts/evergreen/emoji_count.py @@ -40,9 +40,10 @@ class EmojiCount(commands.Cog): @staticmethod def generate_invalid_embed(ctx: commands.Context) -> discord.Embed: """Genrates error embed.""" - embed = discord.Embed() - embed.color = Colours.soft_red - embed.title = random.choice(ERROR_REPLIES) + embed = discord.Embed( + color=Colours.soft_red, + title=random.choice(ERROR_REPLIES) + ) emoji_dict = {} for emoji in ctx.guild.emojis: emoji_dict[emoji.name.split("_")[0]] = [] -- cgit v1.2.3 From 33b2c6bb8de14d277709af3a24072d2b2e6e61f9 Mon Sep 17 00:00:00 2001 From: Anubhav <57266248+Anubhav1603@users.noreply.github.com> Date: Sat, 3 Oct 2020 12:44:32 +0530 Subject: Update bot/exts/evergreen/emoji_count.py Co-authored-by: ks129 <45097959+ks129@users.noreply.github.com> --- bot/exts/evergreen/emoji_count.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'bot') diff --git a/bot/exts/evergreen/emoji_count.py b/bot/exts/evergreen/emoji_count.py index b32fd0ed..c8076922 100644 --- a/bot/exts/evergreen/emoji_count.py +++ b/bot/exts/evergreen/emoji_count.py @@ -19,10 +19,11 @@ class EmojiCount(commands.Cog): def embed_builder(self, emoji: dict) -> discord.Embed: """Generates an embed with the emoji names and count.""" - embed = discord.Embed() - embed.color = Colours.orange - embed.title = "Emoji Count" - embed.timestamp = datetime.datetime.utcnow() + embed = discord.Embed( + color=Colours.orange, + title="Emoji Count", + timestamp=datetime.datetime.utcnow() + ) if len(emoji) == 1: for key, value in emoji.items(): embed.description = f"There are **{len(value)}** emojis in the **{key}** category" -- cgit v1.2.3 From 7c76f01c8dacdcd954758bcb5078672a86286f2c Mon Sep 17 00:00:00 2001 From: Anubhav1603 Date: Sat, 3 Oct 2020 13:23:58 +0530 Subject: removed unnecessary logging and added one space --- bot/exts/evergreen/emoji_count.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) (limited to 'bot') diff --git a/bot/exts/evergreen/emoji_count.py b/bot/exts/evergreen/emoji_count.py index c8076922..70c5ff53 100644 --- a/bot/exts/evergreen/emoji_count.py +++ b/bot/exts/evergreen/emoji_count.py @@ -35,7 +35,6 @@ class EmojiCount(commands.Cog): emoji_info = f'There are **{len(value)}** emojis in the **{key}** category\n' msg += f'<:{emoji_choice.name}:{emoji_choice.id}> {emoji_info}' embed.description = msg - log.trace('Emoji embed built') return embed @staticmethod @@ -45,12 +44,13 @@ class EmojiCount(commands.Cog): color=Colours.soft_red, title=random.choice(ERROR_REPLIES) ) + emoji_dict = {} for emoji in ctx.guild.emojis: emoji_dict[emoji.name.split("_")[0]] = [] + error_comp = ', '.join(key for key in emoji_dict.keys()) embed.description = f"These are the valid categories\n```{error_comp}```" - log.trace("Error embed built") return embed def emoji_list(self, ctx: commands.Context, categories: dict) -> Dict: @@ -61,7 +61,6 @@ class EmojiCount(commands.Cog): category = emoji.name.split('_')[0] if category in out: out[category].append(emoji) - log.trace("Emoji dict built") return out @commands.command(name="emoji_count", aliases=["ec"]) @@ -78,12 +77,9 @@ class EmojiCount(commands.Cog): emoji_dict = self.emoji_list(ctx, emoji_dict) if len(emoji_dict) == 0: embed = self.generate_invalid_embed(ctx) - log.trace("Error embed received") else: embed = self.embed_builder(emoji_dict) - log.trace("Emoji embed received") await ctx.send(embed=embed) - log.trace("Embed sent") def setup(bot: commands.Bot) -> None: -- cgit v1.2.3 From fcc345ae802cfe7815e10bc13f972c6925f3c60f Mon Sep 17 00:00:00 2001 From: Anubhav1603 Date: Sun, 4 Oct 2020 12:24:31 +0530 Subject: cleaned the code --- bot/exts/evergreen/emoji_count.py | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'bot') diff --git a/bot/exts/evergreen/emoji_count.py b/bot/exts/evergreen/emoji_count.py index 70c5ff53..ef900199 100644 --- a/bot/exts/evergreen/emoji_count.py +++ b/bot/exts/evergreen/emoji_count.py @@ -24,6 +24,7 @@ class EmojiCount(commands.Cog): title="Emoji Count", timestamp=datetime.datetime.utcnow() ) + if len(emoji) == 1: for key, value in emoji.items(): embed.description = f"There are **{len(value)}** emojis in the **{key}** category" @@ -67,6 +68,7 @@ class EmojiCount(commands.Cog): async def ec(self, ctx: commands.Context, *, emoji: str = None) -> Optional[str]: """Returns embed with emoji category and info given by the user.""" emoji_dict = {} + for a in ctx.guild.emojis: if emoji is None: log.trace("Emoji Category not provided by the user") @@ -74,7 +76,9 @@ class EmojiCount(commands.Cog): elif a.name.split("_")[0] in emoji: log.trace("Emoji Category provided by the user") emoji_dict.update({a.name.split("_")[0]: []}) + emoji_dict = self.emoji_list(ctx, emoji_dict) + if len(emoji_dict) == 0: embed = self.generate_invalid_embed(ctx) else: -- cgit v1.2.3