aboutsummaryrefslogtreecommitdiffstats
path: root/bot/cogs/hacktober
diff options
context:
space:
mode:
Diffstat (limited to 'bot/cogs/hacktober')
-rw-r--r--bot/cogs/hacktober/__init__.py0
-rw-r--r--bot/cogs/hacktober/candy_collection.py229
-rw-r--r--bot/cogs/hacktober/hacktoberstats.py327
-rw-r--r--bot/cogs/hacktober/halloween_facts.py76
-rw-r--r--bot/cogs/hacktober/halloweenify.py51
-rw-r--r--bot/cogs/hacktober/monstersurvey.py218
-rw-r--r--bot/cogs/hacktober/scarymovie.py137
-rw-r--r--bot/cogs/hacktober/spookyavatar.py54
-rw-r--r--bot/cogs/hacktober/spookygif.py37
-rw-r--r--bot/cogs/hacktober/spookyreact.py69
-rw-r--r--bot/cogs/hacktober/spookysound.py46
11 files changed, 0 insertions, 1244 deletions
diff --git a/bot/cogs/hacktober/__init__.py b/bot/cogs/hacktober/__init__.py
deleted file mode 100644
index e69de29b..00000000
--- a/bot/cogs/hacktober/__init__.py
+++ /dev/null
diff --git a/bot/cogs/hacktober/candy_collection.py b/bot/cogs/hacktober/candy_collection.py
deleted file mode 100644
index f5f17abb..00000000
--- a/bot/cogs/hacktober/candy_collection.py
+++ /dev/null
@@ -1,229 +0,0 @@
-import functools
-import json
-import os
-import random
-
-import discord
-from discord.ext import commands
-
-from bot.constants import HACKTOBER_CHANNEL_ID
-
-json_location = os.path.join("bot", "resources", "halloween", "candy_collection.json")
-
-# chance is 1 in x range, so 1 in 20 range would give 5% chance (for add candy)
-ADD_CANDY_REACTION_CHANCE = 20 # 5%
-ADD_CANDY_EXISTING_REACTION_CHANCE = 10 # 10%
-ADD_SKULL_REACTION_CHANCE = 50 # 2%
-ADD_SKULL_EXISTING_REACTION_CHANCE = 20 # 5%
-
-
-class CandyCollection:
- def __init__(self, bot):
- self.bot = bot
- with open(json_location) as candy:
- self.candy_json = json.load(candy)
- self.msg_reacted = self.candy_json['msg_reacted']
- self.get_candyinfo = dict()
- for userinfo in self.candy_json['records']:
- userid = userinfo['userid']
- self.get_candyinfo[userid] = userinfo
-
- async def on_message(self, message):
- """
- Randomly adds candy or skull to certain messages
- """
-
- # make sure its a human message
- if message.author.bot:
- return
- # ensure it's hacktober channel
- if message.channel.id != HACKTOBER_CHANNEL_ID:
- return
-
- # do random check for skull first as it has the lower chance
- if random.randint(1, ADD_SKULL_REACTION_CHANCE) == 1:
- d = {"reaction": '\N{SKULL}', "msg_id": message.id, "won": False}
- self.msg_reacted.append(d)
- return await message.add_reaction('\N{SKULL}')
- # check for the candy chance next
- if random.randint(1, ADD_CANDY_REACTION_CHANCE) == 1:
- d = {"reaction": '\N{CANDY}', "msg_id": message.id, "won": False}
- self.msg_reacted.append(d)
- return await message.add_reaction('\N{CANDY}')
-
- async def on_reaction_add(self, reaction, user):
- """
- Add/remove candies from a person if the reaction satisfies criteria
- """
-
- message = reaction.message
- # check to ensure the reactor is human
- if user.bot:
- return
- # check to ensure it is in correct channel
- if message.channel.id != HACKTOBER_CHANNEL_ID:
- return
-
- # if its not a candy or skull, and it is one of 10 most recent messages,
- # proceed to add a skull/candy with higher chance
- if str(reaction.emoji) not in ('\N{SKULL}', '\N{CANDY}'):
- if message.id in await self.ten_recent_msg():
- await self.reacted_msg_chance(message)
- return
-
- for react in self.msg_reacted:
- # check to see if the message id of a message we added a
- # reaction to is in json file, and if nobody has won/claimed it yet
- if react['msg_id'] == message.id and react['won'] is False:
- react['user_reacted'] = user.id
- react['won'] = True
- try:
- # if they have record/candies in json already it will do this
- user_records = self.get_candyinfo[user.id]
- if str(reaction.emoji) == '\N{CANDY}':
- user_records['record'] += 1
- if str(reaction.emoji) == '\N{SKULL}':
- if user_records['record'] <= 3:
- user_records['record'] = 0
- lost = 'all of your'
- else:
- lost = random.randint(1, 3)
- user_records['record'] -= lost
- await self.send_spook_msg(message.author, message.channel, lost)
-
- except KeyError:
- # otherwise it will raise KeyError so we need to add them to file
- if str(reaction.emoji) == '\N{CANDY}':
- print('ok')
- d = {"userid": user.id, "record": 1}
- self.candy_json['records'].append(d)
- await self.remove_reactions(reaction)
-
- async def reacted_msg_chance(self, message):
- """
- Randomly add a skull or candy to a message if there is a reaction there already
- (higher probability)
- """
-
- if random.randint(1, ADD_SKULL_EXISTING_REACTION_CHANCE) == 1:
- d = {"reaction": '\N{SKULL}', "msg_id": message.id, "won": False}
- self.msg_reacted.append(d)
- return await message.add_reaction('\N{SKULL}')
-
- if random.randint(1, ADD_CANDY_EXISTING_REACTION_CHANCE) == 1:
- d = {"reaction": '\N{CANDY}', "msg_id": message.id, "won": False}
- self.msg_reacted.append(d)
- return await message.add_reaction('\N{CANDY}')
-
- async def ten_recent_msg(self):
- """Get the last 10 messages sent in the channel"""
- ten_recent = []
- recent_msg = max(message.id for message
- in self.bot._connection._messages
- if message.channel.id == self.HACKTOBER_CHANNEL_ID)
-
- channel = await self.hacktober_channel()
- ten_recent.append(recent_msg.id)
-
- for i in range(9):
- o = discord.Object(id=recent_msg.id + i)
- msg = await next(channel.history(limit=1, before=o))
- ten_recent.append(msg.id)
-
- return ten_recent
-
- async def get_message(self, msg_id):
- """
- Get the message from it's ID.
- """
-
- try:
- o = discord.Object(id=msg_id + 1)
- # Use history rather than get_message due to
- # poor ratelimit (50/1s vs 1/1s)
- msg = await next(self.hacktober_channel.history(limit=1, before=o))
-
- if msg.id != msg_id:
- return None
-
- return msg
-
- except Exception:
- return None
-
- async def hacktober_channel(self):
- """
- Get #hacktoberbot channel from it's id
- """
- return self.bot.get_channel(id=HACKTOBER_CHANNEL_ID)
-
- async def remove_reactions(self, reaction):
- """
- Remove all candy/skull reactions
- """
-
- try:
- async for user in reaction.users():
- await reaction.message.remove_reaction(reaction.emoji, user)
-
- except discord.HTTPException:
- pass
-
- async def send_spook_msg(self, author, channel, candies):
- """
- Send a spooky message
- """
- e = discord.Embed(colour=author.colour)
- e.set_author(name="Ghosts and Ghouls and Jack o' lanterns at night; "
- f"I took {candies} candies and quickly took flight.")
- await channel.send(embed=e)
-
- def save_to_json(self):
- """
- Save json to the file.
- """
- with open(json_location, 'w') as outfile:
- json.dump(self.candy_json, outfile)
-
- @commands.command()
- async def candy(self, ctx):
- """
- Get the candy leaderboard and save to json when this is called
- """
-
- # use run_in_executor to prevent blocking
- thing = functools.partial(self.save_to_json)
- await self.bot.loop.run_in_executor(None, thing)
-
- emoji = (
- '\N{FIRST PLACE MEDAL}',
- '\N{SECOND PLACE MEDAL}',
- '\N{THIRD PLACE MEDAL}',
- '\N{SPORTS MEDAL}',
- '\N{SPORTS MEDAL}'
- )
-
- top_sorted = sorted(self.candy_json['records'], key=lambda k: k.get('record', 0), reverse=True)
- top_five = top_sorted[:5]
-
- usersid = []
- records = []
- for record in top_five:
- usersid.append(record['userid'])
- records.append(record['record'])
-
- value = '\n'.join(f'{emoji[index]} <@{usersid[index]}>: {records[index]}'
- for index in range(0, len(usersid))) or 'No Candies'
-
- e = discord.Embed(colour=discord.Colour.blurple())
- e.add_field(name="Top Candy Records", value=value, inline=False)
- e.add_field(name='\u200b',
- value=f"Candies will randomly appear on messages sent. "
- f"\nHit the candy when it appears as fast as possible to get the candy! "
- f"\nBut beware the ghosts...",
- inline=False)
- await ctx.send(embed=e)
-
-
-def setup(bot):
- bot.add_cog(CandyCollection(bot))
diff --git a/bot/cogs/hacktober/hacktoberstats.py b/bot/cogs/hacktober/hacktoberstats.py
deleted file mode 100644
index c473d3d0..00000000
--- a/bot/cogs/hacktober/hacktoberstats.py
+++ /dev/null
@@ -1,327 +0,0 @@
-import json
-import logging
-import re
-import typing
-from collections import Counter
-from datetime import datetime
-from pathlib import Path
-
-import aiohttp
-import discord
-from discord.ext import commands
-
-
-class Stats:
- def __init__(self, bot):
- self.bot = bot
- self.link_json = Path('./bot/resources', 'github_links.json')
- self.linked_accounts = self.load_linked_users()
-
- @commands.group(
- name='stats',
- aliases=('hacktoberstats', 'getstats', 'userstats'),
- invoke_without_command=True
- )
- async def hacktoberstats_group(self, ctx: commands.Context, github_username: str = None):
- """
- If invoked without a subcommand or github_username, get the invoking user's stats if
- they've linked their Discord name to GitHub using .stats link
-
- If invoked with a github_username, get that user's contributions
- """
- if not github_username:
- author_id, author_mention = Stats._author_mention_from_context(ctx)
-
- if str(author_id) in self.linked_accounts.keys():
- github_username = self.linked_accounts[author_id]["github_username"]
- logging.info(f"Getting stats for {author_id} linked GitHub account '{github_username}'")
- else:
- msg = (
- f"{author_mention}, you have not linked a GitHub account\n\n"
- f"You can link your GitHub account using:\n```{ctx.prefix}stats link github_username```\n"
- f"Or query GitHub stats directly using:\n```{ctx.prefix}stats github_username```"
- )
- await ctx.send(msg)
- return
-
- await self.get_stats(ctx, github_username)
-
- @hacktoberstats_group.command(name="link")
- async def link_user(self, ctx: commands.Context, github_username: str = None):
- """
- Link the invoking user's Github github_username to their Discord ID
-
- Linked users are stored as a nested dict:
- {
- Discord_ID: {
- "github_username": str
- "date_added": datetime
- }
- }
- """
- author_id, author_mention = Stats._author_mention_from_context(ctx)
- if github_username:
- if str(author_id) in self.linked_accounts.keys():
- old_username = self.linked_accounts[author_id]["github_username"]
- logging.info(f"{author_id} has changed their github link from '{old_username}' to '{github_username}'")
- await ctx.send(f"{author_mention}, your GitHub username has been updated to: '{github_username}'")
- else:
- logging.info(f"{author_id} has added a github link to '{github_username}'")
- await ctx.send(f"{author_mention}, your GitHub username has been added")
-
- self.linked_accounts[author_id] = {
- "github_username": github_username,
- "date_added": datetime.now()
- }
-
- self.save_linked_users()
- else:
- 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")
-
- @hacktoberstats_group.command(name="unlink")
- async def unlink_user(self, ctx: commands.Context):
- """
- Remove the invoking user's account link from the log
- """
- author_id, author_mention = Stats._author_mention_from_context(ctx)
-
- stored_user = self.linked_accounts.pop(author_id, None)
- if stored_user:
- await ctx.send(f"{author_mention}, your GitHub profile has been unlinked")
- logging.info(f"{author_id} has unlinked their GitHub account")
- else:
- await ctx.send(f"{author_mention}, you do not currently have a linked GitHub account")
- logging.info(f"{author_id} tried to unlink their GitHub account but no account was linked")
-
- self.save_linked_users()
-
- def load_linked_users(self) -> typing.Dict:
- """
- Load list of linked users from local JSON file
-
- Linked users are stored as a nested dict:
- {
- Discord_ID: {
- "github_username": str
- "date_added": datetime
- }
- }
- """
- if self.link_json.exists():
- logging.info(f"Loading linked GitHub accounts from '{self.link_json}'")
- with open(self.link_json, 'r') as fID:
- linked_accounts = json.load(fID)
-
- logging.info(f"Loaded {len(linked_accounts)} linked GitHub accounts from '{self.link_json}'")
- return linked_accounts
- else:
- logging.info(f"Linked account log: '{self.link_json}' does not exist")
- return {}
-
- def save_linked_users(self):
- """
- Save list of linked users to local JSON file
-
- Linked users are stored as a nested dict:
- {
- Discord_ID: {
- "github_username": str
- "date_added": datetime
- }
- }
- """
- logging.info(f"Saving linked_accounts to '{self.link_json}'")
- with open(self.link_json, 'w') as fID:
- json.dump(self.linked_accounts, fID, default=str)
- logging.info(f"linked_accounts saved to '{self.link_json}'")
-
- async def get_stats(self, ctx: commands.Context, github_username: str):
- """
- Query GitHub's API for PRs created by a GitHub user during the month of October that
- do not have an 'invalid' tag
-
- For example:
- !getstats heavysaturn
-
- If a valid github_username is provided, an embed is generated and posted to the channel
-
- Otherwise, post a helpful error message
- """
- async with ctx.typing():
- prs = await self.get_october_prs(github_username)
-
- if prs:
- stats_embed = self.build_embed(github_username, prs)
- await ctx.send('Here are some stats!', embed=stats_embed)
- else:
- await ctx.send(f"No October GitHub contributions found for '{github_username}'")
-
- def build_embed(self, github_username: str, prs: typing.List[dict]) -> discord.Embed:
- """
- Return a stats embed built from github_username's PRs
- """
- logging.info(f"Building Hacktoberfest embed for GitHub user: '{github_username}'")
- pr_stats = self._summarize_prs(prs)
-
- n = pr_stats['n_prs']
- if n >= 5:
- shirtstr = f"**{github_username} has earned a tshirt!**"
- elif n == 4:
- shirtstr = f"**{github_username} is 1 PR away from a tshirt!**"
- else:
- shirtstr = f"**{github_username} is {5 - n} PRs away from a tshirt!**"
-
- stats_embed = discord.Embed(
- title=f"{github_username}'s Hacktoberfest",
- color=discord.Color(0x9c4af7),
- description=f"{github_username} has made {n} {Stats._contributionator(n)} in October\n\n{shirtstr}\n\n"
- )
-
- stats_embed.set_thumbnail(url=f"https://www.github.com/{github_username}.png")
- stats_embed.set_author(
- name="Hacktoberfest",
- url="https://hacktoberfest.digitalocean.com",
- icon_url="https://hacktoberfest.digitalocean.com/assets/logo-hacktoberfest.png"
- )
- stats_embed.add_field(
- name="Top 5 Repositories:",
- value=self._build_top5str(pr_stats)
- )
-
- logging.info(f"Hacktoberfest PR built for GitHub user '{github_username}'")
- return stats_embed
-
- @staticmethod
- async def get_october_prs(github_username: str) -> typing.List[dict]:
- """
- Query GitHub's API for PRs created during the month of October by github_username
- that do not have an 'invalid' tag
-
- If PRs are found, return a list of dicts with basic PR information
-
- For each PR:
- {
- "repo_url": str
- "repo_shortname": str (e.g. "python-discord/seasonalbot")
- "created_at": datetime.datetime
- }
-
- Otherwise, return None
- """
- logging.info(f"Generating Hacktoberfest PR query for GitHub user: '{github_username}'")
- base_url = "https://api.github.com/search/issues?q="
- not_label = "invalid"
- action_type = "pr"
- is_query = f"public+author:{github_username}"
- date_range = "2018-10-01..2018-10-31"
- per_page = "300"
- query_url = (
- f"{base_url}"
- f"-label:{not_label}"
- f"+type:{action_type}"
- f"+is:{is_query}"
- f"+created:{date_range}"
- f"&per_page={per_page}"
- )
-
- headers = {"user-agent": "Discord Python Hactoberbot"}
- async with aiohttp.ClientSession() as session:
- async with session.get(query_url, headers=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}")
- return
- else:
- if jsonresp["total_count"] == 0:
- # Short circuit if there aren't any PRs
- logging.info(f"No Hacktoberfest PRs found for GitHub user: '{github_username}'")
- return
- else:
- logging.info(f"Found {len(jsonresp['items'])} Hacktoberfest PRs for GitHub user: '{github_username}'")
- outlist = []
- for item in jsonresp["items"]:
- shortname = Stats._get_shortname(item["repository_url"])
- itemdict = {
- "repo_url": f"https://www.github.com/{shortname}",
- "repo_shortname": shortname,
- "created_at": datetime.strptime(
- item["created_at"], r"%Y-%m-%dT%H:%M:%SZ"
- ),
- }
- outlist.append(itemdict)
- return outlist
-
- @staticmethod
- def _get_shortname(in_url: str) -> str:
- """
- Extract shortname from https://api.github.com/repos/* URL
-
- e.g. "https://api.github.com/repos/python-discord/seasonalbot"
- |
- V
- "python-discord/seasonalbot"
- """
- exp = r"https?:\/\/api.github.com\/repos\/([/\-\_\.\w]+)"
- return re.findall(exp, in_url)[0]
-
- @staticmethod
- def _summarize_prs(prs: typing.List[dict]) -> typing.Dict:
- """
- Generate statistics from an input list of PR dictionaries, as output by get_october_prs
-
- Return a dictionary containing:
- {
- "n_prs": int
- "top5": [(repo_shortname, ncontributions), ...]
- }
- """
- contributed_repos = [pr["repo_shortname"] for pr in prs]
- return {"n_prs": len(prs), "top5": Counter(contributed_repos).most_common(5)}
-
- @staticmethod
- def _build_top5str(stats: typing.List[tuple]) -> str:
- """
- Build a string from the Top 5 contributions that is compatible with a discord.Embed field
-
- Top 5 contributions should be a list of tuples, as output in the stats dictionary by
- _summarize_prs
-
- String is of the form:
- n contribution(s) to [shortname](url)
- ...
- """
- baseURL = "https://www.github.com/"
- contributionstrs = []
- for repo in stats['top5']:
- n = repo[1]
- contributionstrs.append(f"{n} {Stats._contributionator(n)} to [{repo[0]}]({baseURL}{repo[0]})")
-
- return "\n".join(contributionstrs)
-
- @staticmethod
- def _contributionator(n: int) -> str:
- """
- Return "contribution" or "contributions" based on the value of n
- """
- if n == 1:
- return "contribution"
- else:
- return "contributions"
-
- @staticmethod
- def _author_mention_from_context(ctx: commands.Context) -> typing.Tuple:
- """
- Return stringified Message author ID and mentionable string from commands.Context
- """
- author_id = str(ctx.message.author.id)
- author_mention = ctx.message.author.mention
-
- return author_id, author_mention
-
-
-def setup(bot):
- bot.add_cog(Stats(bot))
diff --git a/bot/cogs/hacktober/halloween_facts.py b/bot/cogs/hacktober/halloween_facts.py
deleted file mode 100644
index 7b5b866b..00000000
--- a/bot/cogs/hacktober/halloween_facts.py
+++ /dev/null
@@ -1,76 +0,0 @@
-import asyncio
-import json
-import random
-from datetime import timedelta
-from pathlib import Path
-
-import discord
-from discord.ext import commands
-
-from bot.constants import HACKTOBER_CHANNEL_ID
-
-SPOOKY_EMOJIS = [
- "\N{BAT}",
- "\N{DERELICT HOUSE BUILDING}",
- "\N{EXTRATERRESTRIAL ALIEN}",
- "\N{GHOST}",
- "\N{JACK-O-LANTERN}",
- "\N{SKULL}",
- "\N{SKULL AND CROSSBONES}",
- "\N{SPIDER WEB}",
-]
-PUMPKIN_ORANGE = discord.Color(0xFF7518)
-INTERVAL = timedelta(hours=6).total_seconds()
-
-
-class HalloweenFacts:
-
- def __init__(self, bot):
- self.bot = bot
- with open(Path("bot", "resources", "halloween", "halloween_facts.json"), "r") as file:
- self.halloween_facts = json.load(file)
- self.channel = None
- self.last_fact = None
-
- async def on_ready(self):
- self.channel = self.bot.get_channel(HACKTOBER_CHANNEL_ID)
- self.bot.loop.create_task(self._fact_publisher_task())
-
- async def _fact_publisher_task(self):
- """
- A background task that runs forever, sending Halloween facts at random to the Discord channel with id equal to
- HACKTOBER_CHANNEL_ID every INTERVAL seconds.
- """
- facts = list(enumerate(self.halloween_facts))
- while True:
- # Avoid choosing each fact at random to reduce chances of facts being reposted soon.
- random.shuffle(facts)
- for index, fact in facts:
- embed = self._build_embed(index, fact)
- await self.channel.send("Your regular serving of random Halloween facts", embed=embed)
- self.last_fact = (index, fact)
- await asyncio.sleep(INTERVAL)
-
- @commands.command(name="hallofact", aliases=["hallofacts"], brief="Get the most recent Halloween fact")
- async def get_last_fact(self, ctx):
- """
- Reply with the most recent Halloween fact.
- """
- if ctx.channel != self.channel:
- return
- index, fact = self.last_fact
- embed = self._build_embed(index, fact)
- await ctx.send("Halloween fact recap", embed=embed)
-
- @staticmethod
- def _build_embed(index, fact):
- """
- Builds a Discord embed from the given fact and its index.
- """
- emoji = random.choice(SPOOKY_EMOJIS)
- title = f"{emoji} Halloween Fact #{index + 1}"
- return discord.Embed(title=title, description=fact, color=PUMPKIN_ORANGE)
-
-
-def setup(bot):
- bot.add_cog(HalloweenFacts(bot))
diff --git a/bot/cogs/hacktober/halloweenify.py b/bot/cogs/hacktober/halloweenify.py
deleted file mode 100644
index 5d270974..00000000
--- a/bot/cogs/hacktober/halloweenify.py
+++ /dev/null
@@ -1,51 +0,0 @@
-from json import load
-from pathlib import Path
-from random import choice
-
-import discord
-from discord.ext import commands
-from discord.ext.commands.cooldowns import BucketType
-
-
-class Halloweenify:
- """
- A cog to change a invokers nickname to a spooky one!
- """
-
- def __init__(self, bot):
- self.bot = bot
-
- @commands.cooldown(1, 300, BucketType.user)
- @commands.command()
- async def halloweenify(self, ctx):
- """
- Change your nickname into a much spookier one!
- """
- async with ctx.typing():
- with open(Path('bot', 'resources', 'halloween', 'halloweenify.json'), 'r') as f:
- data = load(f)
-
- # Choose a random character from our list we loaded above and set apart the nickname and image url.
- character = choice(data['characters'])
- nickname = ''.join([nickname for nickname in character])
- image = ''.join([character[nickname] for nickname in character])
-
- # Build up a Embed
- embed = discord.Embed()
- embed.colour = discord.Colour.dark_orange()
- embed.title = 'Not spooky enough?'
- embed.description = (
- f'**{ctx.author.display_name}** wasn\'t spooky enough for you? That\'s understandable, '
- f'{ctx.author.display_name} isn\'t scary at all! '
- 'Let me think of something better. Hmm... I got it!\n\n '
- f'Your new nickname will be: \n :ghost: **{nickname}** :jack_o_lantern:'
- )
- embed.set_image(url=image)
-
- await ctx.author.edit(nick=nickname)
-
- await ctx.send(embed=embed)
-
-
-def setup(bot):
- bot.add_cog(Halloweenify(bot))
diff --git a/bot/cogs/hacktober/monstersurvey.py b/bot/cogs/hacktober/monstersurvey.py
deleted file mode 100644
index 2b78abc6..00000000
--- a/bot/cogs/hacktober/monstersurvey.py
+++ /dev/null
@@ -1,218 +0,0 @@
-import json
-import logging
-import os
-
-from discord import Embed
-from discord.ext import commands
-from discord.ext.commands import Bot, Context
-
-log = logging.getLogger(__name__)
-
-EMOJIS = {
- 'SUCCESS': u'\u2705',
- 'ERROR': u'\u274C'
-}
-
-
-class MonsterSurvey:
- """
- Vote for your favorite monster!
- This command allows users to vote for their favorite listed monster.
- Users may change their vote, but only their current vote will be counted.
- """
-
- def __init__(self, bot: Bot):
- """Initializes values for the bot to use within the voting commands."""
-
- self.bot = bot
- self.registry_location = os.path.join(os.getcwd(), 'bot', 'resources', 'halloween', 'monstersurvey.json')
- with open(self.registry_location, 'r') as jason:
- self.voter_registry = json.load(jason)
-
- def json_write(self):
- log.info("Saved Monster Survey Results")
- with open(self.registry_location, 'w') as jason:
- json.dump(self.voter_registry, jason, indent=2)
-
- def cast_vote(self, id: int, monster: str):
- """
- :param id: The id of the person voting
- :param monster: the string key of the json that represents a monster
- :return: None
- """
-
- vr = self.voter_registry
- for m in vr.keys():
- if id not in vr[m]['votes'] and m == monster:
- vr[m]['votes'].append(id)
- else:
- if id in vr[m]['votes'] and m != monster:
- vr[m]['votes'].remove(id)
-
- def get_name_by_leaderboard_index(self, n):
- n = n - 1
- vr = self.voter_registry
- top = sorted(vr, key=lambda k: len(vr[k]['votes']), reverse=True)
- name = top[n] if n >= 0 else None
- return name
-
- @commands.group(
- name='monster',
- aliases=['ms']
- )
- async def monster_group(self, ctx: Context):
- """
- The base voting command. If nothing is called, then it will return an embed.
- """
-
- if ctx.invoked_subcommand is None:
- async with ctx.typing():
- default_embed = Embed(
- title='Monster Voting',
- color=0xFF6800,
- description='Vote for your favorite monster!'
- )
- default_embed.add_field(
- name='.monster show monster_name(optional)',
- value='Show a specific monster. If none is listed, it will give you an error with valid choices.',
- inline=False)
- default_embed.add_field(
- name='.monster vote monster_name',
- value='Vote for a specific monster. You get one vote, but can change it at any time.',
- inline=False
- )
- default_embed.add_field(
- name='.monster leaderboard',
- value='Which monster has the most votes? This command will tell you.',
- inline=False
- )
- default_embed.set_footer(text=f"Monsters choices are: {', '.join(self.voter_registry.keys())}")
-
- await ctx.send(embed=default_embed)
-
- @monster_group.command(
- name='vote'
- )
- async def monster_vote(self, ctx: Context, name=None):
- """
- Casts a vote for a particular monster, or displays a list of monsters that can be voted for
- if one is not given.
- """
-
- if name is None:
- await ctx.invoke(self.monster_leaderboard)
- return
-
- async with ctx.typing():
- # Check to see if user used a numeric (leaderboard) index to vote
- try:
- idx = int(name)
- name = self.get_name_by_leaderboard_index(idx)
- except ValueError:
- name = name.lower()
-
- vote_embed = Embed(
- name='Monster Voting',
- color=0xFF6800
- )
-
- m = self.voter_registry.get(name)
- if m is None:
- vote_embed.description = f'You cannot vote for {name} because it\'s not in the running.'
- vote_embed.add_field(
- name='Use `.monster show {monster_name}` for more information on a specific monster',
- value='or use `.monster vote {monster}` to cast your vote for said monster.',
- inline=False
- )
- vote_embed.add_field(
- name='You may vote for or show the following monsters:',
- value=f"{', '.join(self.voter_registry.keys())}"
- )
- else:
- self.cast_vote(ctx.author.id, name)
- vote_embed.add_field(
- name='Vote successful!',
- value=f'You have successfully voted for {m["full_name"]}!',
- inline=False
- )
- vote_embed.set_thumbnail(url=m['image'])
- vote_embed.set_footer(text="Please note that any previous votes have been removed.")
- self.json_write()
-
- await ctx.send(embed=vote_embed)
-
- @monster_group.command(
- name='show'
- )
- async def monster_show(self, ctx: Context, name=None):
- """
- Shows the named monster. If one is not named, it sends the default voting embed instead.
-
- :param ctx:
- :param name:
- :return:
- """
-
- if name is None:
- await ctx.invoke(self.monster_leaderboard)
- return
-
- async with ctx.typing():
- # Check to see if user used a numeric (leaderboard) index to vote
- try:
- idx = int(name)
- name = self.get_name_by_leaderboard_index(idx)
- except ValueError:
- name = name.lower()
-
- m = self.voter_registry.get(name)
- if not m:
- await ctx.send('That monster does not exist.')
- await ctx.invoke(self.monster_vote)
- return
-
- embed = Embed(title=m['full_name'], color=0xFF6800)
- embed.add_field(name='Summary', value=m['summary'])
- embed.set_image(url=m['image'])
- embed.set_footer(text=f'To vote for this monster, type .monster vote {name}')
-
- await ctx.send(embed=embed)
-
- @monster_group.command(
- name='leaderboard',
- aliases=['lb']
- )
- async def monster_leaderboard(self, ctx: Context):
- """
- Shows the current standings.
- :param ctx:
- :return:
- """
-
- async with ctx.typing():
- vr = self.voter_registry
- top = sorted(vr, key=lambda k: len(vr[k]['votes']), reverse=True)
- total_votes = sum(len(m['votes']) for m in self.voter_registry.values())
-
- embed = Embed(title="Monster Survey Leader Board", color=0xFF6800)
- for rank, m in enumerate(top):
- votes = len(vr[m]['votes'])
- percentage = ((votes / total_votes) * 100) if total_votes > 0 else 0
- embed.add_field(name=f"{rank+1}. {vr[m]['full_name']}",
- value=(
- f"{votes} votes. {percentage:.1f}% of total votes.\n"
- f"Vote for this monster by typing "
- f"'.monster vote {m}'\n"
- f"Get more information on this monster by typing "
- f"'.monster show {m}'"
- ),
- inline=False)
-
- embed.set_footer(text="You can also vote by their rank number. '.monster vote {number}' ")
-
- await ctx.send(embed=embed)
-
-
-def setup(bot):
- bot.add_cog(MonsterSurvey(bot))
- log.debug("MonsterSurvey COG Loaded")
diff --git a/bot/cogs/hacktober/scarymovie.py b/bot/cogs/hacktober/scarymovie.py
deleted file mode 100644
index c2298c65..00000000
--- a/bot/cogs/hacktober/scarymovie.py
+++ /dev/null
@@ -1,137 +0,0 @@
-import random
-from os import environ
-
-import aiohttp
-from discord import Embed
-from discord.ext import commands
-
-
-TMDB_API_KEY = environ.get('TMDB_API_KEY')
-TMDB_TOKEN = environ.get('TMDB_TOKEN')
-
-
-class ScaryMovie:
- """
- Selects a random scary movie and embeds info into discord chat
- """
-
- def __init__(self, bot):
- self.bot = bot
-
- @commands.command(name='movie', alias=['tmdb'])
- async def random_movie(self, ctx):
- """
- Randomly select a scary movie and display information about it.
- """
- async with ctx.typing():
- selection = await self.select_movie()
- movie_details = await self.format_metadata(selection)
-
- await ctx.send(embed=movie_details)
-
- @staticmethod
- async def select_movie():
- """
- Selects a random movie and returns a json of movie details from TMDb
- """
-
- url = 'https://api.themoviedb.org/4/discover/movie'
- params = {
- 'with_genres': '27',
- 'vote_count.gte': '5'
- }
- headers = {
- 'Authorization': 'Bearer ' + TMDB_TOKEN,
- 'Content-Type': 'application/json;charset=utf-8'
- }
-
- # Get total page count of horror movies
- async with aiohttp.ClientSession() as session:
- response = await session.get(url=url, params=params, headers=headers)
- total_pages = await response.json()
- total_pages = total_pages.get('total_pages')
-
- # Get movie details from one random result on a random page
- params['page'] = random.randint(1, total_pages)
- response = await session.get(url=url, params=params, headers=headers)
- response = await response.json()
- selection_id = random.choice(response.get('results')).get('id')
-
- # Get full details and credits
- selection = await session.get(
- url='https://api.themoviedb.org/3/movie/' + str(selection_id),
- params={'api_key': TMDB_API_KEY, 'append_to_response': 'credits'}
- )
-
- return await selection.json()
-
- @staticmethod
- async def format_metadata(movie):
- """
- Formats raw TMDb data to be embedded in discord chat
- """
-
- # Build the relevant URLs.
- movie_id = movie.get("id")
- poster_path = movie.get("poster_path")
- tmdb_url = f'https://www.themoviedb.org/movie/{movie_id}' if movie_id else None
- poster = f'https://image.tmdb.org/t/p/original{poster_path}' if poster_path else None
-
- # Get cast names
- cast = []
- for actor in movie.get('credits', {}).get('cast', [])[:3]:
- cast.append(actor.get('name'))
-
- # Get director name
- director = movie.get('credits', {}).get('crew', [])
- if director:
- director = director[0].get('name')
-
- # Determine the spookiness rating
- rating = ''
- rating_count = movie.get('vote_average', 0)
-
- if rating_count:
- rating_count /= 2
-
- for _ in range(int(rating_count)):
- rating += ':skull:'
- if (rating_count % 1) >= .5:
- rating += ':bat:'
-
- # Try to get year of release and runtime
- year = movie.get('release_date', [])[:4]
- runtime = movie.get('runtime')
- runtime = f"{runtime} minutes" if runtime else None
-
- # Not all these attributes will always be present
- movie_attributes = {
- "Directed by": director,
- "Starring": ', '.join(cast),
- "Running time": runtime,
- "Release year": year,
- "Spookiness rating": rating,
- }
-
- embed = Embed(
- colour=0x01d277,
- title='**' + movie.get('title') + '**',
- url=tmdb_url,
- description=movie.get('overview')
- )
-
- if poster:
- embed.set_image(url=poster)
-
- # Add the attributes that we actually have data for, but not the others.
- for name, value in movie_attributes.items():
- if value:
- embed.add_field(name=name, value=value)
-
- embed.set_footer(text='powered by themoviedb.org')
-
- return embed
-
-
-def setup(bot):
- bot.add_cog(ScaryMovie(bot))
diff --git a/bot/cogs/hacktober/spookyavatar.py b/bot/cogs/hacktober/spookyavatar.py
deleted file mode 100644
index 6ce4471c..00000000
--- a/bot/cogs/hacktober/spookyavatar.py
+++ /dev/null
@@ -1,54 +0,0 @@
-import os
-from io import BytesIO
-
-import aiohttp
-import discord
-from discord.ext import commands
-from PIL import Image
-
-from bot.utils import spookifications
-
-
-class SpookyAvatar:
-
- """
- A cog that spookifies an avatar.
- """
-
- def __init__(self, bot):
- self.bot = bot
-
- async def get(self, url):
- """
- Returns the contents of the supplied url.
- """
- async with aiohttp.ClientSession() as session:
- async with session.get(url) as resp:
- return await resp.read()
-
- @commands.command(name='savatar', aliases=['spookyavatar', 'spookify'],
- brief='Spookify an user\'s avatar.')
- async def spooky_avatar(self, ctx, user: discord.Member = None):
- """
- A command to print the user's spookified avatar.
- """
- if user is None:
- user = ctx.message.author
-
- async with ctx.typing():
- embed = discord.Embed(colour=0xFF0000)
- embed.title = "Is this you or am I just really paranoid?"
- embed.set_author(name=str(user.name), icon_url=user.avatar_url)
- resp = await self.get(user.avatar_url)
- im = Image.open(BytesIO(resp))
- modified_im = spookifications.get_random_effect(im)
- modified_im.save(str(ctx.message.id)+'.png')
- f = discord.File(str(ctx.message.id)+'.png')
- embed.set_image(url='attachment://'+str(ctx.message.id)+'.png')
-
- await ctx.send(file=f, embed=embed)
- os.remove(str(ctx.message.id)+'.png')
-
-
-def setup(bot):
- bot.add_cog(SpookyAvatar(bot))
diff --git a/bot/cogs/hacktober/spookygif.py b/bot/cogs/hacktober/spookygif.py
deleted file mode 100644
index 98a411f6..00000000
--- a/bot/cogs/hacktober/spookygif.py
+++ /dev/null
@@ -1,37 +0,0 @@
-import aiohttp
-import discord
-from discord.ext import commands
-
-from bot.constants import GIPHY_TOKEN
-
-
-class SpookyGif:
- """
- A cog to fetch a random spooky gif from the web!
- """
-
- def __init__(self, bot):
- self.bot = bot
-
- @commands.command(name="spookygif", aliases=["sgif", "scarygif"])
- async def spookygif(self, ctx):
- """
- Fetches a random gif from the GIPHY API and responds with it.
- """
- async with ctx.typing():
- async with aiohttp.ClientSession() as session:
- params = {'api_key': GIPHY_TOKEN, 'tag': 'halloween', 'rating': 'g'}
- # Make a GET request to the Giphy API to get a random halloween gif.
- async with session.get('http://api.giphy.com/v1/gifs/random', params=params) as resp:
- data = await resp.json()
- url = data['data']['image_url']
-
- embed = discord.Embed(colour=0x9b59b6)
- embed.title = "A spooooky gif!"
- embed.set_image(url=url)
-
- await ctx.send(embed=embed)
-
-
-def setup(bot):
- bot.add_cog(SpookyGif(bot))
diff --git a/bot/cogs/hacktober/spookyreact.py b/bot/cogs/hacktober/spookyreact.py
deleted file mode 100644
index 8e9e8db6..00000000
--- a/bot/cogs/hacktober/spookyreact.py
+++ /dev/null
@@ -1,69 +0,0 @@
-import logging
-import re
-
-import discord
-
-SPOOKY_TRIGGERS = {
- 'spooky': (r"\bspo{2,}ky\b", "\U0001F47B"),
- 'skeleton': (r"\bskeleton\b", "\U0001F480"),
- 'doot': (r"\bdo{2,}t\b", "\U0001F480"),
- 'pumpkin': (r"\bpumpkin\b", "\U0001F383"),
- 'halloween': (r"\bhalloween\b", "\U0001F383"),
- 'jack-o-lantern': (r"\bjack-o-lantern\b", "\U0001F383"),
- 'danger': (r"\bdanger\b", "\U00002620")
-}
-
-
-class SpookyReact:
-
- """
- A cog that makes the bot react to message triggers.
- """
-
- def __init__(self, bot):
- self.bot = bot
-
- async def on_message(self, ctx: discord.Message):
- """
- A command to send the seasonalbot github project
-
- Lines that begin with the bot's command prefix are ignored
-
- Seasonalbot's own messages are ignored
- """
- for trigger in SPOOKY_TRIGGERS.keys():
- trigger_test = re.search(SPOOKY_TRIGGERS[trigger][0], ctx.content.lower())
- if trigger_test:
- # Check message for bot replies and/or command invocations
- # Short circuit if they're found, logging is handled in _short_circuit_check
- if await self._short_circuit_check(ctx):
- return
- else:
- await ctx.add_reaction(SPOOKY_TRIGGERS[trigger][1])
- logging.info(f"Added '{trigger}' reaction to message ID: {ctx.id}")
-
- async def _short_circuit_check(self, ctx: discord.Message) -> bool:
- """
- Short-circuit helper check.
-
- Return True if:
- * author is the bot
- * prefix is not None
- """
- # Check for self reaction
- if ctx.author == self.bot.user:
- logging.info(f"Ignoring reactions on self message. Message ID: {ctx.id}")
- return True
-
- # Check for command invocation
- # Because on_message doesn't give a full Context object, generate one first
- tmp_ctx = await self.bot.get_context(ctx)
- if tmp_ctx.prefix:
- logging.info(f"Ignoring reactions on command invocation. Message ID: {ctx.id}")
- return True
-
- return False
-
-
-def setup(bot):
- bot.add_cog(SpookyReact(bot))
diff --git a/bot/cogs/hacktober/spookysound.py b/bot/cogs/hacktober/spookysound.py
deleted file mode 100644
index e1598517..00000000
--- a/bot/cogs/hacktober/spookysound.py
+++ /dev/null
@@ -1,46 +0,0 @@
-import random
-from pathlib import Path
-
-import discord
-from discord.ext import commands
-
-from bot.constants import HACKTOBER_VOICE_CHANNEL_ID
-
-
-class SpookySound:
- """
- A cog that plays a spooky sound in a voice channel on command.
- """
-
- def __init__(self, bot):
- self.bot = bot
- self.sound_files = list(Path("./bot/resources/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):
- """
- 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_ready()
- self.channel = self.bot.get_channel(HACKTOBER_VOICE_CHANNEL_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):
- """
- Helper method to disconnect a given voice client.
- """
- await voice.disconnect()
-
-
-def setup(bot):
- bot.add_cog(SpookySound(bot))