aboutsummaryrefslogtreecommitdiffstats
path: root/bot/exts/valentines
diff options
context:
space:
mode:
Diffstat (limited to 'bot/exts/valentines')
-rw-r--r--bot/exts/valentines/__init__.py0
-rw-r--r--bot/exts/valentines/be_my_valentine.py237
-rw-r--r--bot/exts/valentines/lovecalculator.py104
-rw-r--r--bot/exts/valentines/movie_generator.py63
-rw-r--r--bot/exts/valentines/myvalenstate.py87
-rw-r--r--bot/exts/valentines/pickuplines.py45
-rw-r--r--bot/exts/valentines/savethedate.py42
-rw-r--r--bot/exts/valentines/valentine_zodiac.py58
-rw-r--r--bot/exts/valentines/whoisvalentine.py53
9 files changed, 689 insertions, 0 deletions
diff --git a/bot/exts/valentines/__init__.py b/bot/exts/valentines/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/bot/exts/valentines/__init__.py
diff --git a/bot/exts/valentines/be_my_valentine.py b/bot/exts/valentines/be_my_valentine.py
new file mode 100644
index 00000000..1e883d21
--- /dev/null
+++ b/bot/exts/valentines/be_my_valentine.py
@@ -0,0 +1,237 @@
+import logging
+import random
+from json import load
+from pathlib import Path
+from typing import Optional, Tuple
+
+import discord
+from discord.ext import commands
+from discord.ext.commands.cooldowns import BucketType
+
+from bot.constants import Channels, Client, Colours, Lovefest, Month
+from bot.utils.decorators import in_month
+
+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: commands.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/valentines/bemyvalentine_valentines.json")
+ with p.open() as json_data:
+ valentines = load(json_data)
+ return valentines
+
+ @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 ctx.send_help(ctx.command)
+
+ @lovefest_role.command(name="sub")
+ async def add_role(self, ctx: commands.Context) -> None:
+ """Adds the lovefest role."""
+ user = ctx.author
+ role = discord.utils.get(ctx.guild.roles, id=Lovefest.role_id)
+ if Lovefest.role_id not in [role.id for role in ctx.message.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 = discord.utils.get(ctx.guild.roles, id=Lovefest.role_id)
+ if Lovefest.role_id not in [role.id for role in ctx.message.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, BucketType.user)
+ @commands.group(name='bemyvalentine', invoke_without_command=True)
+ async def send_valentine(
+ self, ctx: commands.Context, user: Optional[discord.Member] = None, *, valentine_type: str = None
+ ) -> None:
+ """
+ Send a valentine to user, if specified, or to a random user with the lovefest role.
+
+ syntax: .bemyvalentine [user](optional) [p/poem/c/compliment/or you can type your own valentine message]
+ (optional)
+
+ example: .bemyvalentine (sends valentine as a poem or a compliment to a random user)
+ 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
+ msg = "You are supposed to use this command in the server."
+ return await ctx.send(msg)
+
+ if user:
+ if Lovefest.role_id not in [role.id for role in user.roles]:
+ message = f"You cannot send a valentine to {user} as he/she does not have the lovefest role!"
+ return await ctx.send(message)
+
+ if user == ctx.author:
+ # Well a user can't valentine himself/herself.
+ return await ctx.send("Come on dude, you can't send a valentine to yourself :expressionless:")
+
+ emoji_1, emoji_2 = self.random_emoji()
+ lovefest_role = discord.utils.get(ctx.guild.roles, id=Lovefest.role_id)
+ channel = self.bot.get_channel(Channels.seasonalbot_commands)
+ valentine, title = self.valentine_check(valentine_type)
+
+ if user is None:
+ author = ctx.author
+ user = self.random_user(author, lovefest_role.members)
+ if user is None:
+ return await ctx.send("There are no users avilable to whome your valentine can be sent.")
+
+ 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, BucketType.user)
+ @send_valentine.command(name='secret')
+ async def anonymous(
+ self, ctx: commands.Context, user: Optional[discord.Member] = None, *, valentine_type: str = None
+ ) -> None:
+ """
+ Send an anonymous Valentine via DM to to a user, if specified, or to a random with the lovefest role.
+
+ **This command should be DMed to the bot.**
+
+ syntax : .bemyvalentine secret [user](optional) [p/poem/c/compliment/or you can type your own valentine message]
+ (optional)
+
+ example : .bemyvalentine secret (sends valentine as a poem or a compliment to a random user in DM making you
+ anonymous)
+ 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 ctx.guild is not None:
+ # This command is only DM specific
+ msg = "You are not supposed to use this command in the server, DM the command to the bot."
+ return await ctx.send(msg)
+
+ if user:
+ if Lovefest.role_id not in [role.id for role in user.roles]:
+ message = f"You cannot send a valentine to {user} as he/she does not have the lovefest role!"
+ return await ctx.send(message)
+
+ if user == ctx.author:
+ # Well a user cant valentine himself/herself.
+ return await ctx.send('Come on dude, you cant send a valentine to yourself :expressionless:')
+
+ guild = self.bot.get_guild(id=Client.guild)
+ emoji_1, emoji_2 = self.random_emoji()
+ lovefest_role = discord.utils.get(guild.roles, id=Lovefest.role_id)
+ valentine, title = self.valentine_check(valentine_type)
+
+ if user is None:
+ author = ctx.author
+ user = self.random_user(author, lovefest_role.members)
+ if user is None:
+ return await ctx.send("There are no users avilable to whome your valentine can be sent.")
+
+ 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
+ )
+ try:
+ await user.send(embed=embed)
+ except discord.Forbidden:
+ await ctx.author.send(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:
+ valentine, title = self.random_valentine()
+
+ elif valentine_type.lower() in ['p', 'poem']:
+ valentine = self.valentine_poem()
+ title = 'A poem dedicated to'
+
+ elif valentine_type.lower() in ['c', 'compliment']:
+ valentine = self.valentine_compliment()
+ title = 'A compliment for'
+
+ else:
+ # in this case, the user decides to type his own valentine.
+ valentine = valentine_type
+ title = 'A message for'
+ return valentine, title
+
+ @staticmethod
+ def random_user(author: discord.Member, members: discord.Member) -> None:
+ """
+ Picks a random member from the list provided in `members`.
+
+ The invoking author is ignored.
+ """
+ if author in members:
+ members.remove(author)
+
+ return random.choice(members) if members else None
+
+ @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."""
+ valentine_poem = random.choice(self.valentines['valentine_poems'])
+ return valentine_poem
+
+ def valentine_compliment(self) -> str:
+ """Grabs a random compliment."""
+ valentine_compliment = random.choice(self.valentines['valentine_compliments'])
+ return valentine_compliment
+
+
+def setup(bot: commands.Bot) -> None:
+ """Be my Valentine Cog load."""
+ bot.add_cog(BeMyValentine(bot))
+ log.info("BeMyValentine cog loaded")
diff --git a/bot/exts/valentines/lovecalculator.py b/bot/exts/valentines/lovecalculator.py
new file mode 100644
index 00000000..03d3d7d5
--- /dev/null
+++ b/bot/exts/valentines/lovecalculator.py
@@ -0,0 +1,104 @@
+import bisect
+import hashlib
+import json
+import logging
+import random
+from pathlib import Path
+from typing import Union
+
+import discord
+from discord import Member
+from discord.ext import commands
+from discord.ext.commands import BadArgument, Cog, clean_content
+
+from bot.constants import Roles
+
+log = logging.getLogger(__name__)
+
+with Path("bot/resources/valentines/love_matches.json").open() as file:
+ LOVE_DATA = json.load(file)
+ 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."""
+
+ def __init__(self, bot: commands.Bot):
+ self.bot = bot
+
+ @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: Union[Member, str], whom: Union[Member, str] = None) -> None:
+ """
+ Tells you how much the two love each other.
+
+ This command accepts users or arbitrary strings as arguments.
+ Users are converted from:
+ - User ID
+ - Mention
+ - name#discrim
+ - name
+ - nickname
+
+ Any two arguments will always yield the same result, though the order of arguments matters:
+ Running .love joseph erlang will always yield the same result.
+ Running .love erlang joseph won't yield the same result as .love joseph erlang
+
+ If you want to use multiple words for one argument, you must include quotes.
+ .love "Zes Vappa" "morning coffee"
+
+ If only one argument is provided, the subject will become one of the helpers at random.
+ """
+ if whom is None:
+ staff = ctx.guild.get_role(Roles.helpers).members
+ whom = random.choice(staff)
+
+ def normalize(arg: Union[Member, str]) -> str:
+ if isinstance(arg, Member):
+ # If we are given a member, return name#discrim without any extra changes
+ arg = str(arg)
+ else:
+ # Otherwise normalise case and remove any leading/trailing whitespace
+ arg = arg.strip().title()
+ # This has to be done manually to be applied to usernames
+ return clean_content(escape_markdown=True).convert(ctx, arg)
+
+ who, whom = [await normalize(arg) for arg in (who, whom)]
+
+ # Make sure user didn't provide something silly such as 10 spaces
+ if not (who and whom):
+ raise BadArgument('Arguments be non-empty strings.')
+
+ # 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']
+ )
+
+ await ctx.send(embed=embed)
+
+
+def setup(bot: commands.Bot) -> None:
+ """Love calculator Cog load."""
+ bot.add_cog(LoveCalculator(bot))
+ log.info("LoveCalculator cog loaded")
diff --git a/bot/exts/valentines/movie_generator.py b/bot/exts/valentines/movie_generator.py
new file mode 100644
index 00000000..ce1d7d5b
--- /dev/null
+++ b/bot/exts/valentines/movie_generator.py
@@ -0,0 +1,63 @@
+import logging
+import random
+from os import environ
+from urllib import parse
+
+import discord
+from discord.ext import commands
+
+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: commands.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?" + parse.urlencode(params)
+ async with self.bot.http_session.get(request_url) 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"])
+ 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: commands.Bot) -> None:
+ """Romance movie Cog load."""
+ bot.add_cog(RomanceMovieFinder(bot))
+ log.info("RomanceMovieFinder cog loaded")
diff --git a/bot/exts/valentines/myvalenstate.py b/bot/exts/valentines/myvalenstate.py
new file mode 100644
index 00000000..0256c39a
--- /dev/null
+++ b/bot/exts/valentines/myvalenstate.py
@@ -0,0 +1,87 @@
+import collections
+import json
+import logging
+from pathlib import Path
+from random import choice
+
+import discord
+from discord.ext import commands
+
+from bot.constants import Colours
+
+log = logging.getLogger(__name__)
+
+with open(Path("bot/resources/valentines/valenstates.json"), "r") as file:
+ STATES = json.load(file)
+
+
+class MyValenstate(commands.Cog):
+ """A Cog to find your most likely Valentine's vacation destination."""
+
+ def __init__(self, bot: commands.Bot):
+ self.bot = bot
+
+ 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.message.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[:len(matches)-2])}, and {matches[len(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!"
+ leftovers = str(matches)
+ embed_text = f"You have another match, this being {leftovers}."
+ 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=f'{STATES[valenstate]["text"]}',
+ colour=Colours.pink
+ )
+ embed.add_field(name=embed_title, value=embed_text)
+ embed.set_image(url=STATES[valenstate]["flag"])
+ await ctx.channel.send(embed=embed)
+
+
+def setup(bot: commands.Bot) -> None:
+ """Valenstate Cog load."""
+ bot.add_cog(MyValenstate(bot))
+ log.info("MyValenstate cog loaded")
diff --git a/bot/exts/valentines/pickuplines.py b/bot/exts/valentines/pickuplines.py
new file mode 100644
index 00000000..8b2c9822
--- /dev/null
+++ b/bot/exts/valentines/pickuplines.py
@@ -0,0 +1,45 @@
+import logging
+import random
+from json import load
+from pathlib import Path
+
+import discord
+from discord.ext import commands
+
+from bot.constants import Colours
+
+log = logging.getLogger(__name__)
+
+with open(Path("bot/resources/valentines/pickup_lines.json"), "r", encoding="utf8") as f:
+ pickup_lines = load(f)
+
+
+class PickupLine(commands.Cog):
+ """A cog that gives random cheesy pickup lines."""
+
+ def __init__(self, bot: commands.Bot):
+ self.bot = bot
+
+ @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: commands.Bot) -> None:
+ """Pickup lines Cog load."""
+ bot.add_cog(PickupLine(bot))
+ log.info('PickupLine cog loaded')
diff --git a/bot/exts/valentines/savethedate.py b/bot/exts/valentines/savethedate.py
new file mode 100644
index 00000000..e0bc3904
--- /dev/null
+++ b/bot/exts/valentines/savethedate.py
@@ -0,0 +1,42 @@
+import logging
+import random
+from json import load
+from pathlib import Path
+
+import discord
+from discord.ext import commands
+
+from bot.constants import Colours
+
+log = logging.getLogger(__name__)
+
+HEART_EMOJIS = [":heart:", ":gift_heart:", ":revolving_hearts:", ":sparkling_heart:", ":two_hearts:"]
+
+with open(Path("bot/resources/valentines/date_ideas.json"), "r", encoding="utf8") as f:
+ VALENTINES_DATES = load(f)
+
+
+class SaveTheDate(commands.Cog):
+ """A cog that gives random suggestion for a Valentine's date."""
+
+ def __init__(self, bot: commands.Bot):
+ self.bot = bot
+
+ @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: commands.Bot) -> None:
+ """Save the date Cog Load."""
+ bot.add_cog(SaveTheDate(bot))
+ log.info("SaveTheDate cog loaded")
diff --git a/bot/exts/valentines/valentine_zodiac.py b/bot/exts/valentines/valentine_zodiac.py
new file mode 100644
index 00000000..c8d77e75
--- /dev/null
+++ b/bot/exts/valentines/valentine_zodiac.py
@@ -0,0 +1,58 @@
+import logging
+import random
+from json import load
+from pathlib import Path
+
+import discord
+from discord.ext import commands
+
+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, bot: commands.Bot):
+ self.bot = bot
+ self.zodiacs = self.load_json()
+
+ @staticmethod
+ def load_json() -> dict:
+ """Load zodiac compatibility from static JSON resource."""
+ p = Path("bot/resources/valentines/zodiac_compatibility.json")
+ with p.open() as json_data:
+ zodiacs = load(json_data)
+ return zodiacs
+
+ @commands.command(name="partnerzodiac")
+ async def counter_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.")
+
+ 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."""
+ bot.add_cog(ValentineZodiac(bot))
+ log.info("ValentineZodiac cog loaded")
diff --git a/bot/exts/valentines/whoisvalentine.py b/bot/exts/valentines/whoisvalentine.py
new file mode 100644
index 00000000..b8586dca
--- /dev/null
+++ b/bot/exts/valentines/whoisvalentine.py
@@ -0,0 +1,53 @@
+import json
+import logging
+from pathlib import Path
+from random import choice
+
+import discord
+from discord.ext import commands
+
+from bot.constants import Colours
+
+log = logging.getLogger(__name__)
+
+with open(Path("bot/resources/valentines/valentine_facts.json"), "r") as file:
+ FACTS = json.load(file)
+
+
+class ValentineFacts(commands.Cog):
+ """A Cog for displaying facts about Saint Valentine."""
+
+ def __init__(self, bot: commands.Bot):
+ self.bot = bot
+
+ @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.channel.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.channel.send(embed=embed)
+
+
+def setup(bot: commands.Bot) -> None:
+ """Who is Valentine Cog load."""
+ bot.add_cog(ValentineFacts(bot))
+ log.info("ValentineFacts cog loaded")