aboutsummaryrefslogtreecommitdiffstats
path: root/bot/exts/halloween/spookynamerate.py
diff options
context:
space:
mode:
Diffstat (limited to 'bot/exts/halloween/spookynamerate.py')
-rw-r--r--bot/exts/halloween/spookynamerate.py391
1 files changed, 0 insertions, 391 deletions
diff --git a/bot/exts/halloween/spookynamerate.py b/bot/exts/halloween/spookynamerate.py
deleted file mode 100644
index 5c21ead7..00000000
--- a/bot/exts/halloween/spookynamerate.py
+++ /dev/null
@@ -1,391 +0,0 @@
-import asyncio
-import json
-import random
-from collections import defaultdict
-from datetime import datetime, timedelta
-from logging import getLogger
-from os import getenv
-from pathlib import Path
-from typing import Optional
-
-from async_rediscache import RedisCache
-from discord import Embed, Reaction, TextChannel, User
-from discord.colour import Colour
-from discord.ext import tasks
-from discord.ext.commands import Cog, Context, group
-
-from bot.bot import Bot
-from bot.constants import Channels, Client, Colours, Month
-from bot.utils.decorators import InMonthCheckFailure
-
-logger = getLogger(__name__)
-
-EMOJIS_VAL = {
- "\N{Jack-O-Lantern}": 1,
- "\N{Ghost}": 2,
- "\N{Skull and Crossbones}": 3,
- "\N{Zombie}": 4,
- "\N{Face Screaming In Fear}": 5,
-}
-ADDED_MESSAGES = [
- "Let's see if you win?",
- ":jack_o_lantern: SPOOKY :jack_o_lantern:",
- "If you got it, haunt it.",
- "TIME TO GET YOUR SPOOKY ON! :skull:",
-]
-PING = "<@{id}>"
-
-EMOJI_MESSAGE = "\n".join(f"- {emoji} {val}" for emoji, val in EMOJIS_VAL.items())
-HELP_MESSAGE_DICT = {
- "title": "Spooky Name Rate",
- "description": f"Help for the `{Client.prefix}spookynamerate` command",
- "color": Colours.soft_orange,
- "fields": [
- {
- "name": "How to play",
- "value": (
- "Everyday, the bot will post a random name, which you will need to spookify using your creativity.\n"
- "You can rate each message according to how scary it is.\n"
- "At the end of the day, the author of the message with most reactions will be the winner of the day.\n"
- f"On a scale of 1 to {len(EMOJIS_VAL)}, the reactions order:\n"
- f"{EMOJI_MESSAGE}"
- ),
- "inline": False,
- },
- {
- "name": "How do I add my spookified name?",
- "value": f"Simply type `{Client.prefix}spookynamerate add my name`",
- "inline": False,
- },
- {
- "name": "How do I *delete* my spookified name?",
- "value": f"Simply type `{Client.prefix}spookynamerate delete`",
- "inline": False,
- },
- ],
-}
-
-# The names are from https://www.mockaroo.com/
-NAMES = json.loads(Path("bot/resources/halloween/spookynamerate_names.json").read_text("utf8"))
-FIRST_NAMES = NAMES["first_names"]
-LAST_NAMES = NAMES["last_names"]
-
-
-class SpookyNameRate(Cog):
- """
- A game that asks the user to spookify or halloweenify a name that is given everyday.
-
- It sends a random name everyday. The user needs to try and spookify it to his best ability and
- send that name back using the `spookynamerate add entry` command
- """
-
- # This cache stores the message id of each added word along with a dictionary which contains the name the author
- # added, the author's id, and the author's score (which is 0 by default)
- messages = RedisCache()
-
- # The data cache stores small information such as the current name that is going on and whether it is the first time
- # the bot is running
- data = RedisCache()
- debug = getenv("SPOOKYNAMERATE_DEBUG", False) # Enable if you do not want to limit the commands to October or if
- # you do not want to wait till 12 UTC. Note: if debug is enabled and you run `.cogs reload spookynamerate`, it
- # will automatically start the scoring and announcing the result (without waiting for 12, so do not expect it to.).
- # Also, it won't wait for the two hours (when the poll closes).
-
- def __init__(self, bot: Bot):
- self.bot = bot
- self.name = None
-
- self.bot.loop.create_task(self.load_vars())
-
- self.first_time = None
- self.poll = False
- self.announce_name.start()
- self.checking_messages = asyncio.Lock()
- # Define an asyncio.Lock() to make sure the dictionary isn't changed
- # when checking the messages for duplicate emojis'
-
- async def load_vars(self) -> None:
- """Loads the variables that couldn't be loaded in __init__."""
- self.first_time = await self.data.get("first_time", True)
- self.name = await self.data.get("name")
-
- @group(name="spookynamerate", invoke_without_command=True)
- async def spooky_name_rate(self, ctx: Context) -> None:
- """Get help on the Spooky Name Rate game."""
- await ctx.send(embed=Embed.from_dict(HELP_MESSAGE_DICT))
-
- @spooky_name_rate.command(name="list", aliases=("all", "entries"))
- async def list_entries(self, ctx: Context) -> None:
- """Send all the entries up till now in a single embed."""
- await ctx.send(embed=await self.get_responses_list(final=False))
-
- @spooky_name_rate.command(name="name")
- async def tell_name(self, ctx: Context) -> None:
- """Tell the current random name."""
- if not self.poll:
- await ctx.send(f"The name is **{self.name}**")
- return
-
- await ctx.send(
- f"The name ~~is~~ was **{self.name}**. The poll has already started, so you cannot "
- "add an entry."
- )
-
- @spooky_name_rate.command(name="add", aliases=("register",))
- async def add_name(self, ctx: Context, *, name: str) -> None:
- """Use this command to add/register your spookified name."""
- if self.poll:
- logger.info(f"{ctx.author} tried to add a name, but the poll had already started.")
- await ctx.send("Sorry, the poll has started! You can try and participate in the next round though!")
- return
-
- for data in (json.loads(user_data) for _, user_data in await self.messages.items()):
- if data["author"] == ctx.author.id:
- await ctx.send(
- "But you have already added an entry! Type "
- f"`{self.bot.command_prefix}spookynamerate "
- "delete` to delete it, and then you can add it again"
- )
- return
-
- elif data["name"] == name:
- await ctx.send("TOO LATE. Someone has already added this name.")
- return
-
- msg = await (await self.get_channel()).send(f"{ctx.author.mention} added the name {name!r}!")
-
- await self.messages.set(
- msg.id,
- json.dumps(
- {
- "name": name,
- "author": ctx.author.id,
- "score": 0,
- }
- ),
- )
-
- for emoji in EMOJIS_VAL:
- await msg.add_reaction(emoji)
-
- logger.info(f"{ctx.author} added the name {name!r}")
-
- @spooky_name_rate.command(name="delete")
- async def delete_name(self, ctx: Context) -> None:
- """Delete the user's name."""
- if self.poll:
- await ctx.send("You can't delete your name since the poll has already started!")
- return
- for message_id, data in await self.messages.items():
- data = json.loads(data)
-
- if ctx.author.id == data["author"]:
- await self.messages.delete(message_id)
- await ctx.send(f"Name deleted successfully ({data['name']!r})!")
- return
-
- await ctx.send(
- f"But you don't have an entry... :eyes: Type `{self.bot.command_prefix}spookynamerate add your entry`"
- )
-
- @Cog.listener()
- async def on_reaction_add(self, reaction: Reaction, user: User) -> None:
- """Ensures that each user adds maximum one reaction."""
- if user.bot or not await self.messages.contains(reaction.message.id):
- return
-
- async with self.checking_messages: # Acquire the lock so that the dictionary isn't reset while iterating.
- if reaction.emoji in EMOJIS_VAL:
- # create a custom counter
- reaction_counter = defaultdict(int)
- for msg_reaction in reaction.message.reactions:
- async for reaction_user in msg_reaction.users():
- if reaction_user == self.bot.user:
- continue
- reaction_counter[reaction_user] += 1
-
- if reaction_counter[user] > 1:
- await user.send(
- "Sorry, you have already added a reaction, "
- "please remove your reaction and try again."
- )
- await reaction.remove(user)
- return
-
- @tasks.loop(hours=24.0)
- async def announce_name(self) -> None:
- """Announces the name needed to spookify every 24 hours and the winner of the previous game."""
- if not self.in_allowed_month():
- return
-
- channel = await self.get_channel()
-
- if self.first_time:
- await channel.send(
- "Okkey... Welcome to the **Spooky Name Rate Game**! It's a relatively simple game.\n"
- f"Everyday, a random name will be sent in <#{Channels.community_bot_commands}> "
- "and you need to try and spookify it!\nRegister your name using "
- f"`{self.bot.command_prefix}spookynamerate add spookified name`"
- )
-
- await self.data.set("first_time", False)
- self.first_time = False
-
- else:
- if await self.messages.items():
- await channel.send(embed=await self.get_responses_list(final=True))
- self.poll = True
- if not SpookyNameRate.debug:
- await asyncio.sleep(2 * 60 * 60) # sleep for two hours
-
- logger.info("Calculating score")
- for message_id, data in await self.messages.items():
- data = json.loads(data)
-
- msg = await channel.fetch_message(message_id)
- score = 0
- for reaction in msg.reactions:
- reaction_value = EMOJIS_VAL.get(reaction.emoji, 0) # get the value of the emoji else 0
- score += reaction_value * (reaction.count - 1) # multiply by the num of reactions
- # subtract one, since one reaction was done by the bot
-
- logger.debug(f"{self.bot.get_user(data['author'])} got a score of {score}")
- data["score"] = score
- await self.messages.set(message_id, json.dumps(data))
-
- # Sort the winner messages
- winner_messages = sorted(
- ((msg_id, json.loads(usr_data)) for msg_id, usr_data in await self.messages.items()),
- key=lambda x: x[1]["score"],
- reverse=True,
- )
-
- winners = []
- for i, winner in enumerate(winner_messages):
- winners.append(winner)
- if len(winner_messages) > i + 1:
- if winner_messages[i + 1][1]["score"] != winner[1]["score"]:
- break
- elif len(winner_messages) == (i + 1) + 1: # The next element is the last element
- if winner_messages[i + 1][1]["score"] != winner[1]["score"]:
- break
-
- # one iteration is complete
- await channel.send("Today's Spooky Name Rate Game ends now, and the winner(s) is(are)...")
-
- async with channel.typing():
- await asyncio.sleep(1) # give the drum roll feel
-
- if not winners: # There are no winners (no participants)
- await channel.send("Hmm... Looks like no one participated! :cry:")
- return
-
- score = winners[0][1]["score"]
- congratulations = "to all" if len(winners) > 1 else PING.format(id=winners[0][1]["author"])
- names = ", ".join(f'{win[1]["name"]} ({PING.format(id=win[1]["author"])})' for win in winners)
-
- # display winners, their names and scores
- await channel.send(
- f"Congratulations {congratulations}!\n"
- f"You have a score of {score}!\n"
- f"Your name{ 's were' if len(winners) > 1 else 'was'}:\n{names}"
- )
-
- # Send random party emojis
- party = (random.choice([":partying_face:", ":tada:"]) for _ in range(random.randint(1, 10)))
- await channel.send(" ".join(party))
-
- async with self.checking_messages: # Acquire the lock to delete the messages
- await self.messages.clear() # reset the messages
-
- # send the next name
- self.name = f"{random.choice(FIRST_NAMES)} {random.choice(LAST_NAMES)}"
- await self.data.set("name", self.name)
-
- await channel.send(
- "Let's move on to the next name!\nAnd the next name is...\n"
- f"**{self.name}**!\nTry to spookify that... :smirk:"
- )
-
- self.poll = False # accepting responses
-
- @announce_name.before_loop
- async def wait_till_scheduled_time(self) -> None:
- """Waits till the next day's 12PM if crossed it, otherwise waits till the same day's 12PM."""
- if SpookyNameRate.debug:
- return
-
- now = datetime.utcnow()
- if now.hour < 12:
- twelve_pm = now.replace(hour=12, minute=0, second=0, microsecond=0)
- time_left = twelve_pm - now
- await asyncio.sleep(time_left.seconds)
- return
-
- tomorrow_12pm = now + timedelta(days=1)
- tomorrow_12pm = tomorrow_12pm.replace(hour=12, minute=0, second=0, microsecond=0)
- await asyncio.sleep((tomorrow_12pm - now).seconds)
-
- async def get_responses_list(self, final: bool = False) -> Embed:
- """Returns an embed containing the responses of the people."""
- channel = await self.get_channel()
-
- embed = Embed(color=Colour.red())
-
- if await self.messages.items():
- if final:
- embed.title = "Spooky Name Rate is about to end!"
- embed.description = (
- "This Spooky Name Rate round is about to end in 2 hours! You can review "
- "the entries below! Have you rated other's names?"
- )
- else:
- embed.title = "All the spookified names!"
- embed.description = "See a list of all the entries entered by everyone!"
- else:
- embed.title = "No one has added an entry yet..."
-
- for message_id, data in await self.messages.items():
- data = json.loads(data)
-
- embed.add_field(
- name=(self.bot.get_user(data["author"]) or await self.bot.fetch_user(data["author"])).name,
- value=f"[{(data)['name']}](https://discord.com/channels/{Client.guild}/{channel.id}/{message_id})",
- )
-
- return embed
-
- async def get_channel(self) -> Optional[TextChannel]:
- """Gets the sir-lancebot-channel after waiting until ready."""
- await self.bot.wait_until_ready()
- channel = self.bot.get_channel(
- Channels.community_bot_commands
- ) or await self.bot.fetch_channel(Channels.community_bot_commands)
- if not channel:
- logger.warning("Bot is unable to get the #seasonalbot-commands channel. Please check the channel ID.")
- return channel
-
- @staticmethod
- def in_allowed_month() -> bool:
- """Returns whether running in the limited month."""
- if SpookyNameRate.debug:
- return True
-
- if not Client.month_override:
- return datetime.utcnow().month == Month.OCTOBER
- return Client.month_override == Month.OCTOBER
-
- def cog_check(self, ctx: Context) -> bool:
- """A command to check whether the command is being called in October."""
- if not self.in_allowed_month():
- raise InMonthCheckFailure("You can only use these commands in October!")
- return True
-
- def cog_unload(self) -> None:
- """Stops the announce_name task."""
- self.announce_name.cancel()
-
-
-def setup(bot: Bot) -> None:
- """Load the SpookyNameRate Cog."""
- bot.add_cog(SpookyNameRate(bot))