aboutsummaryrefslogtreecommitdiffstats
path: root/bot/exts/holidays
diff options
context:
space:
mode:
Diffstat (limited to 'bot/exts/holidays')
-rw-r--r--bot/exts/holidays/valentines/__init__.py0
-rw-r--r--bot/exts/holidays/valentines/be_my_valentine.py192
-rw-r--r--bot/exts/holidays/valentines/lovecalculator.py99
-rw-r--r--bot/exts/holidays/valentines/movie_generator.py67
-rw-r--r--bot/exts/holidays/valentines/myvalenstate.py82
-rw-r--r--bot/exts/holidays/valentines/pickuplines.py41
-rw-r--r--bot/exts/holidays/valentines/savethedate.py38
-rw-r--r--bot/exts/holidays/valentines/valentine_zodiac.py146
-rw-r--r--bot/exts/holidays/valentines/whoisvalentine.py49
9 files changed, 714 insertions, 0 deletions
diff --git a/bot/exts/holidays/valentines/__init__.py b/bot/exts/holidays/valentines/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/bot/exts/holidays/valentines/__init__.py
diff --git a/bot/exts/holidays/valentines/be_my_valentine.py b/bot/exts/holidays/valentines/be_my_valentine.py
new file mode 100644
index 00000000..4d454c3a
--- /dev/null
+++ b/bot/exts/holidays/valentines/be_my_valentine.py
@@ -0,0 +1,192 @@
+import logging
+import random
+from json import loads
+from pathlib import Path
+
+import discord
+from discord.ext import commands
+
+from bot.bot import Bot
+from bot.constants import Channels, Colours, Lovefest, Month
+from bot.utils.decorators import in_month
+from bot.utils.extensions import invoke_help_command
+
+log = logging.getLogger(__name__)
+
+HEART_EMOJIS = [":heart:", ":gift_heart:", ":revolving_hearts:", ":sparkling_heart:", ":two_hearts:"]
+
+
+class BeMyValentine(commands.Cog):
+ """A cog that sends Valentines to other users!"""
+
+ def __init__(self, bot: Bot):
+ self.bot = bot
+ self.valentines = self.load_json()
+
+ @staticmethod
+ def load_json() -> dict:
+ """Load Valentines messages from the static resources."""
+ p = Path("bot/resources/holidays/valentines/bemyvalentine_valentines.json")
+ return loads(p.read_text("utf8"))
+
+ @in_month(Month.FEBRUARY)
+ @commands.group(name="lovefest")
+ async def lovefest_role(self, ctx: commands.Context) -> None:
+ """
+ Subscribe or unsubscribe from the lovefest role.
+
+ The lovefest role makes you eligible to receive anonymous valentines from other users.
+
+ 1) use the command \".lovefest sub\" to get the lovefest role.
+ 2) use the command \".lovefest unsub\" to get rid of the lovefest role.
+ """
+ if not ctx.invoked_subcommand:
+ await invoke_help_command(ctx)
+
+ @lovefest_role.command(name="sub")
+ async def add_role(self, ctx: commands.Context) -> None:
+ """Adds the lovefest role."""
+ user = ctx.author
+ role = ctx.guild.get_role(Lovefest.role_id)
+ if role not in ctx.author.roles:
+ await user.add_roles(role)
+ await ctx.send("The Lovefest role has been added !")
+ else:
+ await ctx.send("You already have the role !")
+
+ @lovefest_role.command(name="unsub")
+ async def remove_role(self, ctx: commands.Context) -> None:
+ """Removes the lovefest role."""
+ user = ctx.author
+ role = ctx.guild.get_role(Lovefest.role_id)
+ if role not in ctx.author.roles:
+ await ctx.send("You dont have the lovefest role.")
+ else:
+ await user.remove_roles(role)
+ await ctx.send("The lovefest role has been successfully removed!")
+
+ @commands.cooldown(1, 1800, commands.BucketType.user)
+ @commands.group(name="bemyvalentine", invoke_without_command=True)
+ async def send_valentine(
+ self, ctx: commands.Context, user: discord.Member, *, valentine_type: str = None
+ ) -> None:
+ """
+ Send a valentine to a specified user with the lovefest role.
+
+ syntax: .bemyvalentine [user] [p/poem/c/compliment/or you can type your own valentine message]
+ (optional)
+
+ example: .bemyvalentine Iceman#6508 p (sends a poem to Iceman)
+ example: .bemyvalentine Iceman Hey I love you, wanna hang around ? (sends the custom message to Iceman)
+ NOTE : AVOID TAGGING THE USER MOST OF THE TIMES.JUST TRIM THE '@' when using this command.
+ """
+ if ctx.guild is None:
+ # This command should only be used in the server
+ raise commands.UserInputError("You are supposed to use this command in the server.")
+
+ if Lovefest.role_id not in [role.id for role in user.roles]:
+ raise commands.UserInputError(
+ f"You cannot send a valentine to {user} as they do not have the lovefest role!"
+ )
+
+ if user == ctx.author:
+ # Well a user can't valentine himself/herself.
+ raise commands.UserInputError("Come on, you can't send a valentine to yourself :expressionless:")
+
+ emoji_1, emoji_2 = self.random_emoji()
+ channel = self.bot.get_channel(Channels.community_bot_commands)
+ valentine, title = self.valentine_check(valentine_type)
+
+ embed = discord.Embed(
+ title=f"{emoji_1} {title} {user.display_name} {emoji_2}",
+ description=f"{valentine} \n **{emoji_2}From {ctx.author}{emoji_1}**",
+ color=Colours.pink
+ )
+ await channel.send(user.mention, embed=embed)
+
+ @commands.cooldown(1, 1800, commands.BucketType.user)
+ @send_valentine.command(name="secret")
+ async def anonymous(
+ self, ctx: commands.Context, user: discord.Member, *, valentine_type: str = None
+ ) -> None:
+ """
+ Send an anonymous Valentine via DM to to a specified user with the lovefest role.
+
+ syntax : .bemyvalentine secret [user] [p/poem/c/compliment/or you can type your own valentine message]
+ (optional)
+
+ example : .bemyvalentine secret Iceman#6508 p (sends a poem to Iceman in DM making you anonymous)
+ example : .bemyvalentine secret Iceman#6508 Hey I love you, wanna hang around ? (sends the custom message to
+ Iceman in DM making you anonymous)
+ """
+ if Lovefest.role_id not in [role.id for role in user.roles]:
+ await ctx.message.delete()
+ raise commands.UserInputError(
+ f"You cannot send a valentine to {user} as they do not have the lovefest role!"
+ )
+
+ if user == ctx.author:
+ # Well a user cant valentine himself/herself.
+ raise commands.UserInputError("Come on, you can't send a valentine to yourself :expressionless:")
+
+ emoji_1, emoji_2 = self.random_emoji()
+ valentine, title = self.valentine_check(valentine_type)
+
+ embed = discord.Embed(
+ title=f"{emoji_1}{title} {user.display_name}{emoji_2}",
+ description=f"{valentine} \n **{emoji_2}From anonymous{emoji_1}**",
+ color=Colours.pink
+ )
+ await ctx.message.delete()
+ try:
+ await user.send(embed=embed)
+ except discord.Forbidden:
+ raise commands.UserInputError(f"{user} has DMs disabled, so I couldn't send the message. Sorry!")
+ else:
+ await ctx.author.send(f"Your message has been sent to {user}")
+
+ def valentine_check(self, valentine_type: str) -> tuple[str, str]:
+ """Return the appropriate Valentine type & title based on the invoking user's input."""
+ if valentine_type is None:
+ return self.random_valentine()
+
+ elif valentine_type.lower() in ["p", "poem"]:
+ return self.valentine_poem(), "A poem dedicated to"
+
+ elif valentine_type.lower() in ["c", "compliment"]:
+ return self.valentine_compliment(), "A compliment for"
+
+ else:
+ # in this case, the user decides to type his own valentine.
+ return valentine_type, "A message for"
+
+ @staticmethod
+ def random_emoji() -> tuple[str, str]:
+ """Return two random emoji from the module-defined constants."""
+ emoji_1 = random.choice(HEART_EMOJIS)
+ emoji_2 = random.choice(HEART_EMOJIS)
+ return emoji_1, emoji_2
+
+ def random_valentine(self) -> tuple[str, str]:
+ """Grabs a random poem or a compliment (any message)."""
+ valentine_poem = random.choice(self.valentines["valentine_poems"])
+ valentine_compliment = random.choice(self.valentines["valentine_compliments"])
+ random_valentine = random.choice([valentine_compliment, valentine_poem])
+ if random_valentine == valentine_poem:
+ title = "A poem dedicated to"
+ else:
+ title = "A compliment for "
+ return random_valentine, title
+
+ def valentine_poem(self) -> str:
+ """Grabs a random poem."""
+ return random.choice(self.valentines["valentine_poems"])
+
+ def valentine_compliment(self) -> str:
+ """Grabs a random compliment."""
+ return random.choice(self.valentines["valentine_compliments"])
+
+
+def setup(bot: Bot) -> None:
+ """Load the Be my Valentine Cog."""
+ bot.add_cog(BeMyValentine(bot))
diff --git a/bot/exts/holidays/valentines/lovecalculator.py b/bot/exts/holidays/valentines/lovecalculator.py
new file mode 100644
index 00000000..3999db2b
--- /dev/null
+++ b/bot/exts/holidays/valentines/lovecalculator.py
@@ -0,0 +1,99 @@
+import bisect
+import hashlib
+import json
+import logging
+import random
+from pathlib import Path
+from typing import Coroutine, Optional
+
+import discord
+from discord import Member
+from discord.ext import commands
+from discord.ext.commands import BadArgument, Cog, clean_content
+
+from bot.bot import Bot
+from bot.constants import Channels, Client, Lovefest, Month
+from bot.utils.decorators import in_month
+
+log = logging.getLogger(__name__)
+
+LOVE_DATA = json.loads(Path("bot/resources/holidays/valentines/love_matches.json").read_text("utf8"))
+LOVE_DATA = sorted((int(key), value) for key, value in LOVE_DATA.items())
+
+
+class LoveCalculator(Cog):
+ """A cog for calculating the love between two people."""
+
+ @in_month(Month.FEBRUARY)
+ @commands.command(aliases=("love_calculator", "love_calc"))
+ @commands.cooldown(rate=1, per=5, type=commands.BucketType.user)
+ async def love(self, ctx: commands.Context, who: Member, whom: Optional[Member] = None) -> None:
+ """
+ Tells you how much the two love each other.
+
+ This command requires at least one member as input, if two are given love will be calculated between
+ those two users, if only one is given, the second member is asusmed to be the invoker.
+ Members are converted from:
+ - User ID
+ - Mention
+ - name#discrim
+ - name
+ - nickname
+
+ Any two arguments will always yield the same result, regardless of the order of arguments:
+ Running .love @joe#6000 @chrisjl#2655 will always yield the same result.
+ Running .love @chrisjl#2655 @joe#6000 will yield the same result as before.
+ """
+ if (
+ Lovefest.role_id not in [role.id for role in who.roles]
+ or (whom is not None and Lovefest.role_id not in [role.id for role in whom.roles])
+ ):
+ raise BadArgument(
+ "This command can only be ran against members with the lovefest role! "
+ "This role be can assigned by running "
+ f"`{Client.prefix}lovefest sub` in <#{Channels.community_bot_commands}>."
+ )
+
+ if whom is None:
+ whom = ctx.author
+
+ def normalize(arg: Member) -> Coroutine:
+ # This has to be done manually to be applied to usernames
+ return clean_content(escape_markdown=True).convert(ctx, str(arg))
+
+ # Sort to ensure same result for same input, regardless of order
+ who, whom = sorted([await normalize(arg) for arg in (who, whom)])
+
+ # Hash inputs to guarantee consistent results (hashing algorithm choice arbitrary)
+ #
+ # hashlib is used over the builtin hash() to guarantee same result over multiple runtimes
+ m = hashlib.sha256(who.encode() + whom.encode())
+ # Mod 101 for [0, 100]
+ love_percent = sum(m.digest()) % 101
+
+ # We need the -1 due to how bisect returns the point
+ # see the documentation for further detail
+ # https://docs.python.org/3/library/bisect.html#bisect.bisect
+ index = bisect.bisect(LOVE_DATA, (love_percent,)) - 1
+ # We already have the nearest "fit" love level
+ # We only need the dict, so we can ditch the first element
+ _, data = LOVE_DATA[index]
+
+ status = random.choice(data["titles"])
+ embed = discord.Embed(
+ title=status,
+ description=f"{who} \N{HEAVY BLACK HEART} {whom} scored {love_percent}%!\n\u200b",
+ color=discord.Color.dark_magenta()
+ )
+ embed.add_field(
+ name="A letter from Dr. Love:",
+ value=data["text"]
+ )
+ embed.set_footer(text=f"You can unsubscribe from lovefest by using {Client.prefix}lovefest unsub")
+
+ await ctx.send(embed=embed)
+
+
+def setup(bot: Bot) -> None:
+ """Load the Love calculator Cog."""
+ bot.add_cog(LoveCalculator())
diff --git a/bot/exts/holidays/valentines/movie_generator.py b/bot/exts/holidays/valentines/movie_generator.py
new file mode 100644
index 00000000..d2dc8213
--- /dev/null
+++ b/bot/exts/holidays/valentines/movie_generator.py
@@ -0,0 +1,67 @@
+import logging
+import random
+from os import environ
+
+import discord
+from discord.ext import commands
+
+from bot.bot import Bot
+
+TMDB_API_KEY = environ.get("TMDB_API_KEY")
+
+log = logging.getLogger(__name__)
+
+
+class RomanceMovieFinder(commands.Cog):
+ """A Cog that returns a random romance movie suggestion to a user."""
+
+ def __init__(self, bot: Bot):
+ self.bot = bot
+
+ @commands.command(name="romancemovie")
+ async def romance_movie(self, ctx: commands.Context) -> None:
+ """Randomly selects a romance movie and displays information about it."""
+ # Selecting a random int to parse it to the page parameter
+ random_page = random.randint(0, 20)
+ # TMDB api params
+ params = {
+ "api_key": TMDB_API_KEY,
+ "language": "en-US",
+ "sort_by": "popularity.desc",
+ "include_adult": "false",
+ "include_video": "false",
+ "page": random_page,
+ "with_genres": "10749"
+ }
+ # The api request url
+ request_url = "https://api.themoviedb.org/3/discover/movie"
+ async with self.bot.http_session.get(request_url, params=params) as resp:
+ # Trying to load the json file returned from the api
+ try:
+ data = await resp.json()
+ # Selecting random result from results object in the json file
+ selected_movie = random.choice(data["results"])
+
+ embed = discord.Embed(
+ title=f":sparkling_heart: {selected_movie['title']} :sparkling_heart:",
+ description=selected_movie["overview"],
+ )
+ embed.set_image(url=f"http://image.tmdb.org/t/p/w200/{selected_movie['poster_path']}")
+ embed.add_field(name="Release date :clock1:", value=selected_movie["release_date"])
+ embed.add_field(name="Rating :star2:", value=selected_movie["vote_average"])
+ embed.set_footer(text="This product uses the TMDb API but is not endorsed or certified by TMDb.")
+ embed.set_thumbnail(url="https://i.imgur.com/LtFtC8H.png")
+ await ctx.send(embed=embed)
+ except KeyError:
+ warning_message = (
+ "A KeyError was raised while fetching information on the movie. The API service"
+ " could be unavailable or the API key could be set incorrectly."
+ )
+ embed = discord.Embed(title=warning_message)
+ log.warning(warning_message)
+ await ctx.send(embed=embed)
+
+
+def setup(bot: Bot) -> None:
+ """Load the Romance movie Cog."""
+ bot.add_cog(RomanceMovieFinder(bot))
diff --git a/bot/exts/holidays/valentines/myvalenstate.py b/bot/exts/holidays/valentines/myvalenstate.py
new file mode 100644
index 00000000..4b547d9b
--- /dev/null
+++ b/bot/exts/holidays/valentines/myvalenstate.py
@@ -0,0 +1,82 @@
+import collections
+import json
+import logging
+from pathlib import Path
+from random import choice
+
+import discord
+from discord.ext import commands
+
+from bot.bot import Bot
+from bot.constants import Colours
+
+log = logging.getLogger(__name__)
+
+STATES = json.loads(Path("bot/resources/holidays/valentines/valenstates.json").read_text("utf8"))
+
+
+class MyValenstate(commands.Cog):
+ """A Cog to find your most likely Valentine's vacation destination."""
+
+ def levenshtein(self, source: str, goal: str) -> int:
+ """Calculates the Levenshtein Distance between source and goal."""
+ if len(source) < len(goal):
+ return self.levenshtein(goal, source)
+ if len(source) == 0:
+ return len(goal)
+ if len(goal) == 0:
+ return len(source)
+
+ pre_row = list(range(0, len(source) + 1))
+ for i, source_c in enumerate(source):
+ cur_row = [i + 1]
+ for j, goal_c in enumerate(goal):
+ if source_c != goal_c:
+ cur_row.append(min(pre_row[j], pre_row[j + 1], cur_row[j]) + 1)
+ else:
+ cur_row.append(min(pre_row[j], pre_row[j + 1], cur_row[j]))
+ pre_row = cur_row
+ return pre_row[-1]
+
+ @commands.command()
+ async def myvalenstate(self, ctx: commands.Context, *, name: str = None) -> None:
+ """Find the vacation spot(s) with the most matching characters to the invoking user."""
+ eq_chars = collections.defaultdict(int)
+ if name is None:
+ author = ctx.author.name.lower().replace(" ", "")
+ else:
+ author = name.lower().replace(" ", "")
+
+ for state in STATES.keys():
+ lower_state = state.lower().replace(" ", "")
+ eq_chars[state] = self.levenshtein(author, lower_state)
+
+ matches = [x for x, y in eq_chars.items() if y == min(eq_chars.values())]
+ valenstate = choice(matches)
+ matches.remove(valenstate)
+
+ embed_title = "But there are more!"
+ if len(matches) > 1:
+ leftovers = f"{', '.join(matches[:-2])}, and {matches[-1]}"
+ embed_text = f"You have {len(matches)} more matches, these being {leftovers}."
+ elif len(matches) == 1:
+ embed_title = "But there's another one!"
+ embed_text = f"You have another match, this being {matches[0]}."
+ else:
+ embed_title = "You have a true match!"
+ embed_text = "This state is your true Valenstate! There are no states that would suit" \
+ " you better"
+
+ embed = discord.Embed(
+ title=f"Your Valenstate is {valenstate} \u2764",
+ description=STATES[valenstate]["text"],
+ colour=Colours.pink
+ )
+ embed.add_field(name=embed_title, value=embed_text)
+ embed.set_image(url=STATES[valenstate]["flag"])
+ await ctx.send(embed=embed)
+
+
+def setup(bot: Bot) -> None:
+ """Load the Valenstate Cog."""
+ bot.add_cog(MyValenstate())
diff --git a/bot/exts/holidays/valentines/pickuplines.py b/bot/exts/holidays/valentines/pickuplines.py
new file mode 100644
index 00000000..bc4b88c6
--- /dev/null
+++ b/bot/exts/holidays/valentines/pickuplines.py
@@ -0,0 +1,41 @@
+import logging
+import random
+from json import loads
+from pathlib import Path
+
+import discord
+from discord.ext import commands
+
+from bot.bot import Bot
+from bot.constants import Colours
+
+log = logging.getLogger(__name__)
+
+PICKUP_LINES = loads(Path("bot/resources/holidays/valentines/pickup_lines.json").read_text("utf8"))
+
+
+class PickupLine(commands.Cog):
+ """A cog that gives random cheesy pickup lines."""
+
+ @commands.command()
+ async def pickupline(self, ctx: commands.Context) -> None:
+ """
+ Gives you a random pickup line.
+
+ Note that most of them are very cheesy.
+ """
+ random_line = random.choice(PICKUP_LINES["lines"])
+ embed = discord.Embed(
+ title=":cheese: Your pickup line :cheese:",
+ description=random_line["line"],
+ color=Colours.pink
+ )
+ embed.set_thumbnail(
+ url=random_line.get("image", PICKUP_LINES["placeholder"])
+ )
+ await ctx.send(embed=embed)
+
+
+def setup(bot: Bot) -> None:
+ """Load the Pickup lines Cog."""
+ bot.add_cog(PickupLine())
diff --git a/bot/exts/holidays/valentines/savethedate.py b/bot/exts/holidays/valentines/savethedate.py
new file mode 100644
index 00000000..3638c1ef
--- /dev/null
+++ b/bot/exts/holidays/valentines/savethedate.py
@@ -0,0 +1,38 @@
+import logging
+import random
+from json import loads
+from pathlib import Path
+
+import discord
+from discord.ext import commands
+
+from bot.bot import Bot
+from bot.constants import Colours
+
+log = logging.getLogger(__name__)
+
+HEART_EMOJIS = [":heart:", ":gift_heart:", ":revolving_hearts:", ":sparkling_heart:", ":two_hearts:"]
+
+VALENTINES_DATES = loads(Path("bot/resources/holidays/valentines/date_ideas.json").read_text("utf8"))
+
+
+class SaveTheDate(commands.Cog):
+ """A cog that gives random suggestion for a Valentine's date."""
+
+ @commands.command()
+ async def savethedate(self, ctx: commands.Context) -> None:
+ """Gives you ideas for what to do on a date with your valentine."""
+ random_date = random.choice(VALENTINES_DATES["ideas"])
+ emoji_1 = random.choice(HEART_EMOJIS)
+ emoji_2 = random.choice(HEART_EMOJIS)
+ embed = discord.Embed(
+ title=f"{emoji_1}{random_date['name']}{emoji_2}",
+ description=f"{random_date['description']}",
+ colour=Colours.pink
+ )
+ await ctx.send(embed=embed)
+
+
+def setup(bot: Bot) -> None:
+ """Load the Save the date Cog."""
+ bot.add_cog(SaveTheDate())
diff --git a/bot/exts/holidays/valentines/valentine_zodiac.py b/bot/exts/holidays/valentines/valentine_zodiac.py
new file mode 100644
index 00000000..d1b3a630
--- /dev/null
+++ b/bot/exts/holidays/valentines/valentine_zodiac.py
@@ -0,0 +1,146 @@
+import calendar
+import json
+import logging
+import random
+from datetime import datetime
+from pathlib import Path
+from typing import Union
+
+import discord
+from discord.ext import commands
+
+from bot.bot import Bot
+from bot.constants import Colours
+
+log = logging.getLogger(__name__)
+
+LETTER_EMOJI = ":love_letter:"
+HEART_EMOJIS = [":heart:", ":gift_heart:", ":revolving_hearts:", ":sparkling_heart:", ":two_hearts:"]
+
+
+class ValentineZodiac(commands.Cog):
+ """A Cog that returns a counter compatible zodiac sign to the given user's zodiac sign."""
+
+ def __init__(self):
+ self.zodiacs, self.zodiac_fact = self.load_comp_json()
+
+ @staticmethod
+ def load_comp_json() -> tuple[dict, dict]:
+ """Load zodiac compatibility from static JSON resource."""
+ explanation_file = Path("bot/resources/holidays/valentines/zodiac_explanation.json")
+ compatibility_file = Path("bot/resources/holidays/valentines/zodiac_compatibility.json")
+
+ zodiac_fact = json.loads(explanation_file.read_text("utf8"))
+
+ 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"])
+
+ zodiacs = json.loads(compatibility_file.read_text("utf8"))
+
+ return zodiacs, zodiac_fact
+
+ def generate_invalidname_embed(self, zodiac: str) -> discord.Embed:
+ """Returns error embed."""
+ 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"
+ 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
+
+ def zodiac_build_embed(self, zodiac: str) -> discord.Embed:
+ """Gives informative zodiac embed."""
+ zodiac = zodiac.capitalize()
+ embed = discord.Embed()
+ embed.color = Colours.pink
+ if zodiac in self.zodiac_fact:
+ 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)
+ 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)
+ log.trace("Successfully created zodiac information embed.")
+ return embed
+
+ 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() <= query_date.date() <= zodiac_data["end_at"].date():
+ log.trace("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)
+ await ctx.send(embed=final_embed)
+ 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:
+ """Provides information about zodiac sign by taking month and date as input."""
+ if isinstance(month, str):
+ month = month.capitalize()
+ try:
+ month = list(calendar.month_abbr).index(month[:3])
+ 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.")
+ return
+ 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_date = self.zodiac_date_verifier(datetime(2020, month, date))
+ log.trace("zodiac sign based on month and date received.")
+ 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```\n{e}\n```"
+ 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.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 random counter compatible zodiac sign to the given user's zodiac sign."""
+ embed = discord.Embed()
+ embed.color = Colours.pink
+ 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.")
+
+
+def setup(bot: Bot) -> None:
+ """Load the Valentine zodiac Cog."""
+ bot.add_cog(ValentineZodiac())
diff --git a/bot/exts/holidays/valentines/whoisvalentine.py b/bot/exts/holidays/valentines/whoisvalentine.py
new file mode 100644
index 00000000..67e46aa4
--- /dev/null
+++ b/bot/exts/holidays/valentines/whoisvalentine.py
@@ -0,0 +1,49 @@
+import json
+import logging
+from pathlib import Path
+from random import choice
+
+import discord
+from discord.ext import commands
+
+from bot.bot import Bot
+from bot.constants import Colours
+
+log = logging.getLogger(__name__)
+
+FACTS = json.loads(Path("bot/resources/holidays/valentines/valentine_facts.json").read_text("utf8"))
+
+
+class ValentineFacts(commands.Cog):
+ """A Cog for displaying facts about Saint Valentine."""
+
+ @commands.command(aliases=("whoisvalentine", "saint_valentine"))
+ async def who_is_valentine(self, ctx: commands.Context) -> None:
+ """Displays info about Saint Valentine."""
+ embed = discord.Embed(
+ title="Who is Saint Valentine?",
+ description=FACTS["whois"],
+ color=Colours.pink
+ )
+ embed.set_thumbnail(
+ url="https://upload.wikimedia.org/wikipedia/commons/thumb/f/f1/Saint_Valentine_-_"
+ "facial_reconstruction.jpg/1024px-Saint_Valentine_-_facial_reconstruction.jpg"
+ )
+
+ await ctx.send(embed=embed)
+
+ @commands.command()
+ async def valentine_fact(self, ctx: commands.Context) -> None:
+ """Shows a random fact about Valentine's Day."""
+ embed = discord.Embed(
+ title=choice(FACTS["titles"]),
+ description=choice(FACTS["text"]),
+ color=Colours.pink
+ )
+
+ await ctx.send(embed=embed)
+
+
+def setup(bot: Bot) -> None:
+ """Load the Who is Valentine Cog."""
+ bot.add_cog(ValentineFacts())