diff options
-rw-r--r-- | Pipfile | 2 | ||||
-rw-r--r-- | Pipfile.lock | 36 | ||||
-rw-r--r-- | bot/__init__.py | 5 | ||||
-rw-r--r-- | bot/__main__.py | 33 | ||||
-rw-r--r-- | bot/bot.py | 42 | ||||
-rw-r--r-- | bot/cogs/gif.py | 32 | ||||
-rw-r--r-- | bot/cogs/hacktober/__init__.py | 0 | ||||
-rw-r--r-- | bot/cogs/template.py | 39 | ||||
-rw-r--r-- | bot/constants.py | 76 | ||||
-rw-r--r-- | bot/decorators.py | 48 | ||||
-rw-r--r-- | bot/resources/avatars/christmas.png | bin | 0 -> 44843 bytes | |||
-rw-r--r-- | bot/resources/avatars/spooky.png | bin | 0 -> 37202 bytes | |||
-rw-r--r-- | bot/resources/avatars/standard.png | bin | 0 -> 52156 bytes | |||
-rw-r--r-- | bot/resources/halloween/github_links.json (renamed from bot/cogs/__init__.py) | 0 | ||||
-rw-r--r-- | bot/resources/halloween/monstersurvey.json | 5 | ||||
-rw-r--r-- | bot/resources/halloween/spookysounds/109710__tomlija__horror-gate.mp3 (renamed from bot/resources/spookysounds/109710__tomlija__horror-gate.mp3) | bin | 118125 -> 118125 bytes | |||
-rw-r--r-- | bot/resources/halloween/spookysounds/126113__klankbeeld__laugh.mp3 (renamed from bot/resources/spookysounds/126113__klankbeeld__laugh.mp3) | bin | 112365 -> 112365 bytes | |||
-rw-r--r-- | bot/resources/halloween/spookysounds/133674__klankbeeld__horror-laugh-original-132802-nanakisan-evil-laugh-08.mp3 (renamed from bot/resources/spookysounds/133674__klankbeeld__horror-laugh-original-132802-nanakisan-evil-laugh-08.mp3) | bin | 137385 -> 137385 bytes | |||
-rw-r--r-- | bot/resources/halloween/spookysounds/14570__oscillator__ghost-fx.mp3 (renamed from bot/resources/spookysounds/14570__oscillator__ghost-fx.mp3) | bin | 135405 -> 135405 bytes | |||
-rw-r--r-- | bot/resources/halloween/spookysounds/168650__0xmusex0__doorcreak.mp3 (renamed from bot/resources/spookysounds/168650__0xmusex0__doorcreak.mp3) | bin | 162421 -> 162421 bytes | |||
-rw-r--r-- | bot/resources/halloween/spookysounds/171078__klankbeeld__horror-scream-woman-long.mp3 (renamed from bot/resources/spookysounds/171078__klankbeeld__horror-scream-woman-long.mp3) | bin | 131625 -> 131625 bytes | |||
-rw-r--r-- | bot/resources/halloween/spookysounds/193812__geoneo0__four-voices-whispering-6.mp3 (renamed from bot/resources/spookysounds/193812__geoneo0__four-voices-whispering-6.mp3) | bin | 163257 -> 163257 bytes | |||
-rw-r--r-- | bot/resources/halloween/spookysounds/237282__devilfish101__frantic-violin-screech.mp3 (renamed from bot/resources/spookysounds/237282__devilfish101__frantic-violin-screech.mp3) | bin | 131566 -> 131566 bytes | |||
-rw-r--r-- | bot/resources/halloween/spookysounds/249686__cylon8472__cthulhu-growl.mp3 (renamed from bot/resources/spookysounds/249686__cylon8472__cthulhu-growl.mp3) | bin | 153226 -> 153226 bytes | |||
-rw-r--r-- | bot/resources/halloween/spookysounds/35716__analogchill__scream.mp3 (renamed from bot/resources/spookysounds/35716__analogchill__scream.mp3) | bin | 114773 -> 114773 bytes | |||
-rw-r--r-- | bot/resources/halloween/spookysounds/413315__inspectorj__something-evil-approaches-a.mp3 (renamed from bot/resources/spookysounds/413315__inspectorj__something-evil-approaches-a.mp3) | bin | 298717 -> 298717 bytes | |||
-rw-r--r-- | bot/resources/halloween/spookysounds/60571__gabemiller74__breathofdeath.mp3 (renamed from bot/resources/spookysounds/60571__gabemiller74__breathofdeath.mp3) | bin | 177049 -> 177049 bytes | |||
-rw-r--r-- | bot/resources/halloween/spookysounds/Female_Monster_Growls_.mp3 (renamed from bot/resources/spookysounds/Female_Monster_Growls_.mp3) | bin | 148276 -> 148276 bytes | |||
-rw-r--r-- | bot/resources/halloween/spookysounds/Male_Zombie_Roar_.mp3 (renamed from bot/resources/spookysounds/Male_Zombie_Roar_.mp3) | bin | 62171 -> 62171 bytes | |||
-rw-r--r-- | bot/resources/halloween/spookysounds/Monster_Alien_Growl_Calm_.mp3 (renamed from bot/resources/spookysounds/Monster_Alien_Growl_Calm_.mp3) | bin | 133651 -> 133651 bytes | |||
-rw-r--r-- | bot/resources/halloween/spookysounds/Monster_Alien_Grunt_Hiss_.mp3 (renamed from bot/resources/spookysounds/Monster_Alien_Grunt_Hiss_.mp3) | bin | 74718 -> 74718 bytes | |||
-rw-r--r-- | bot/resources/halloween/spookysounds/sources.txt (renamed from bot/resources/spookysounds/sources.txt) | 0 | ||||
-rw-r--r-- | bot/seasons/__init__.py | 12 | ||||
-rw-r--r-- | bot/seasons/christmas/__init__.py | 16 | ||||
-rw-r--r-- | bot/seasons/evergreen/__init__.py | 13 | ||||
-rw-r--r-- | bot/seasons/evergreen/error_handler.py (renamed from bot/cogs/error_handler.py) | 29 | ||||
-rw-r--r-- | bot/seasons/evergreen/uptime.py (renamed from bot/cogs/evergreen/uptime.py) | 7 | ||||
-rw-r--r-- | bot/seasons/halloween/__init__.py | 16 | ||||
-rw-r--r-- | bot/seasons/halloween/candy_collection.py (renamed from bot/cogs/hacktober/candy_collection.py) | 15 | ||||
-rw-r--r-- | bot/seasons/halloween/hacktoberstats.py (renamed from bot/cogs/hacktober/hacktoberstats.py) | 30 | ||||
-rw-r--r-- | bot/seasons/halloween/halloween_facts.py (renamed from bot/cogs/hacktober/halloween_facts.py) | 38 | ||||
-rw-r--r-- | bot/seasons/halloween/halloweenify.py (renamed from bot/cogs/hacktober/halloweenify.py) | 4 | ||||
-rw-r--r-- | bot/seasons/halloween/monstersurvey.py (renamed from bot/cogs/hacktober/monstersurvey.py) | 6 | ||||
-rw-r--r-- | bot/seasons/halloween/scarymovie.py (renamed from bot/cogs/hacktober/scarymovie.py) | 6 | ||||
-rw-r--r-- | bot/seasons/halloween/spookyavatar.py (renamed from bot/cogs/hacktober/spookyavatar.py) | 8 | ||||
-rw-r--r-- | bot/seasons/halloween/spookygif.py (renamed from bot/cogs/hacktober/spookygif.py) | 12 | ||||
-rw-r--r-- | bot/seasons/halloween/spookyreact.py (renamed from bot/cogs/hacktober/spookyreact.py) | 7 | ||||
-rw-r--r-- | bot/seasons/halloween/spookysound.py (renamed from bot/cogs/hacktober/spookysound.py) | 10 | ||||
-rw-r--r-- | bot/seasons/season.py | 180 | ||||
-rw-r--r-- | bot/utils/halloween/__init__.py (renamed from bot/cogs/evergreen/__init__.py) | 0 | ||||
-rw-r--r-- | bot/utils/halloween/spookifications.py (renamed from bot/utils/spookifications.py) | 0 |
51 files changed, 548 insertions, 179 deletions
@@ -6,9 +6,9 @@ name = "pypi" [packages] "discord-py" = {ref = "rewrite", git = "https://github.com/Rapptz/discord.py"} arrow = "*" +aiodns = "*" pillow = "*" - [dev-packages] "flake8" = "*" "flake8-bugbear" = "*" diff --git a/Pipfile.lock b/Pipfile.lock index be72165f..2a64593b 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "577eb73d927cf69687453acf1d06c40b81aedab40a238402f14d5a9385413bc0" + "sha256": "60a2d099395f3bba77488211c02604c5ad301bf7b806beda2b06601e889c5597" }, "pipfile-spec": 6, "requires": { @@ -16,6 +16,14 @@ ] }, "default": { + "aiodns": { + "hashes": [ + "sha256:99d0652f2c02f73bfa646bf44af82705260a523014576647d7959e664830b26b", + "sha256:d8677adc679ce8d0ef706c14d9c3d2f27a0e0cc11d59730cdbaf218ad52dd9ea" + ], + "index": "pypi", + "version": "==1.1.1" + }, "arrow": { "hashes": [ "sha256:a558d3b7b6ce7ffc74206a86c147052de23d3d4ef0e17c210dd478c53575c4cd" @@ -83,6 +91,32 @@ "index": "pypi", "version": "==5.3.0" }, + "pycares": { + "hashes": [ + "sha256:0e81c971236bb0767354f1456e67ab6ae305f248565ce77cd413a311f9572bf5", + "sha256:11c0ff3ccdb5a838cbd59a4e59df35d31355a80a61393bca786ca3b44569ba10", + "sha256:170d62bd300999227e64da4fa85459728cc96e62e44780bbc86a915fdae01f78", + "sha256:36f4c03df57c41a87eb3d642201684eb5a8bc194f4bafaa9f60ee6dc0aef8e40", + "sha256:371ce688776da984c4105c8ca760cc60944b9b49ccf8335c71dc7669335e6173", + "sha256:3a2234516f7db495083d8bba0ccdaabae587e62cfcd1b8154d5d0b09d3a48dfc", + "sha256:3f288586592c697109b2b06e3988b7e17d9765887b5fc367010ee8500cbddc86", + "sha256:40134cee03c8bbfbc644d4c0bc81796e12dd012a5257fb146c5a5417812ee5f7", + "sha256:722f5d2c5f78d47b13b0112f6daff43ce4e08e8152319524d14f1f917cc5125e", + "sha256:7b18fab0ed534a898552df91bc804bd62bb3a2646c11e054baca14d23663e1d6", + "sha256:8a39d03bd99ea191f86b990ef67ecce878d6bf6518c5cde9173fb34fb36beb5e", + "sha256:8ea263de8bf1a30b0d87150b4aa0e3203cf93bc1723ea3e7408a7d25e1299217", + "sha256:943e2dc67ff45ab4c81d628c959837d01561d7e185080ab7a276b8ca67573fb5", + "sha256:9d56a54c93e64b30c0d31f394d9890f175edec029cd846221728f99263cdee82", + "sha256:b95b339c11d824f0bb789d31b91c8534916fcbdce248cccce216fa2630bb8a90", + "sha256:bbfd9aba1e172cd2ab7b7142d49b28cf44d6451c4a66a870aff1dc3cb84849c7", + "sha256:d8637bcc2f901aa61ec1d754abc862f9f145cb0346a0249360df4c159377018e", + "sha256:e2446577eeea79d2179c9469d9d4ce3ab8a07d7985465c3cb91e7d74abc329b6", + "sha256:e72fa163f37ae3b09f143cc6690a36f012d13e905d142e1beed4ec0e593ff657", + "sha256:f32b7c63094749fbc0c1106c9a785666ec8afd49ecfe7002a30bb7c42e62b47c", + "sha256:f50be4dd53f009cfb4b98c3c6b240e18ff9b17e3f1c320bd594bb83eddabfcb2" + ], + "version": "==2.3.0" + }, "python-dateutil": { "hashes": [ "sha256:063df5763652e21de43de7d9e00ccf239f953a832941e37be541614732cdfc93", diff --git a/bot/__init__.py b/bot/__init__.py index 6b3a2a6f..dc97df3d 100644 --- a/bot/__init__.py +++ b/bot/__init__.py @@ -4,6 +4,8 @@ from pathlib import Path import arrow +from bot.constants import Client + # start datetime start_time = arrow.utcnow() @@ -19,7 +21,8 @@ file_handler.setLevel(logging.DEBUG) # console handler prints to terminal console_handler = logging.StreamHandler() -console_handler.setLevel(logging.INFO) +level = logging.DEBUG if Client.debug else logging.INFO +console_handler.setLevel(level) # remove old loggers if any root = logging.getLogger() diff --git a/bot/__main__.py b/bot/__main__.py index b74e4f54..a3b68ec1 100644 --- a/bot/__main__.py +++ b/bot/__main__.py @@ -1,33 +1,8 @@ import logging -from os import environ -from pathlib import Path -from traceback import format_exc -from discord.ext import commands +from bot.constants import Client, bot -SEASONALBOT_TOKEN = environ.get('SEASONALBOT_TOKEN') -log = logging.getLogger() +log = logging.getLogger(__name__) -if SEASONALBOT_TOKEN: - token_dl = len(SEASONALBOT_TOKEN) // 8 - log.info(f'Bot token loaded: {SEASONALBOT_TOKEN[:token_dl]}...{SEASONALBOT_TOKEN[-token_dl:]}') -else: - log.error(f'Bot token not found: {SEASONALBOT_TOKEN}') - -ghost_unicode = "\N{GHOST}" -bot = commands.Bot(command_prefix=commands.when_mentioned_or(".", f"{ghost_unicode} ", ghost_unicode)) - -log.info('Start loading extensions from ./bot/cogs/halloween/') - - -if __name__ == '__main__': - # Scan for files in the /cogs/ directory and make a list of the file names. - cogs = [file.stem for file in Path('bot', 'cogs', 'hacktober').glob('*.py') if not file.stem.startswith("__")] - for extension in cogs: - try: - bot.load_extension(f'bot.cogs.hacktober.{extension}') - log.info(f'Successfully loaded extension: {extension}') - except Exception as e: - log.error(f'Failed to load extension {extension}: {repr(e)} {format_exc()}') - -bot.run(SEASONALBOT_TOKEN) +bot.load_extension("bot.seasons") +bot.run(Client.token) diff --git a/bot/bot.py b/bot/bot.py new file mode 100644 index 00000000..24d099ad --- /dev/null +++ b/bot/bot.py @@ -0,0 +1,42 @@ +import logging +import socket +from traceback import format_exc +from typing import List + +from aiohttp import AsyncResolver, ClientSession, TCPConnector +from discord.ext.commands import Bot + +log = logging.getLogger(__name__) + +__all__ = ('SeasonalBot',) + + +class SeasonalBot(Bot): + def __init__(self, **kwargs): + super().__init__(**kwargs) + self.http_session = ClientSession( + connector=TCPConnector( + resolver=AsyncResolver(), + family=socket.AF_INET, + ) + ) + + def load_extensions(self, exts: List[str]): + """ + Unload all current cogs, then load in the ones passed into `cogs` + """ + + # Unload all cogs + extensions = list(self.extensions.keys()) + for extension in extensions: + if extension != "bot.seasons": # We shouldn't unload the manager. + self.unload_extension(extension) + + # Load in the list of cogs that was passed in here + for extension in exts: + cog = extension.split(".")[-1] + try: + self.load_extension(extension) + log.info(f'Successfully loaded extension: {cog}') + except Exception as e: + log.error(f'Failed to load extension {cog}: {repr(e)} {format_exc()}') diff --git a/bot/cogs/gif.py b/bot/cogs/gif.py deleted file mode 100644 index cacb77ce..00000000 --- a/bot/cogs/gif.py +++ /dev/null @@ -1,32 +0,0 @@ -from os import environ - -import aiohttp -from discord.ext import commands - - -class SpookyGif: - """ - A cog to fetch a random spooky gif from the web! - """ - - def __init__(self, bot): - self.bot = bot - self.GIPHY_TOKEN = environ.get('GIPHY_TOKEN') - - @commands.command() - async def gif(self, ctx): - """ - Fetches a random gif from the GIPHY API and responds with it. - """ - - async with aiohttp.ClientSession() as session: - params = {'api_key': self.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']['url'] - await ctx.send(url) - - -def setup(bot): - bot.add_cog(SpookyGif(bot)) 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/template.py b/bot/cogs/template.py deleted file mode 100644 index e1b646e3..00000000 --- a/bot/cogs/template.py +++ /dev/null @@ -1,39 +0,0 @@ -from discord.ext import commands - - -class Template: - - """ - A template cog that contains examples of commands and command groups. - """ - - def __init__(self, bot): - self.bot = bot - - @commands.command(name='repo', aliases=['repository', 'project'], brief='A link to the repository of this bot.') - async def repository(self, ctx): - """ - A command to send the seasonalbot github project - """ - await ctx.send('https://github.com/python-discord/seasonalbot') - - @commands.group(name='git', invoke_without_command=True, brief="A link to resources for learning Git") - async def github(self, ctx): - """ - A command group with the name git. You can now create sub-commands such as git commit. - """ - - await ctx.send('Resources to learn **Git**: https://try.github.io/.') - - @github.command() - async def commit(self, ctx): - """ - A command that belongs to the git command group. Invoked using git commit. - """ - - await ctx.send('`git commit -m "First commit"` commits tracked changes.') - - -# Required in order to load the cog, use the class name in the add_cog function. -def setup(bot): - bot.add_cog(Template(bot)) diff --git a/bot/constants.py b/bot/constants.py index 7c2561a7..4294b8e1 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -1,5 +1,73 @@ -import os +import logging +from os import environ +from typing import NamedTuple -HACKTOBER_CHANNEL_ID = 414574275865870337 -HACKTOBER_VOICE_CHANNEL_ID = 514420006474219521 -GIPHY_TOKEN = os.environ.get("GIPHY_TOKEN") +from bot.bot import SeasonalBot + +__all__ = ('Client', 'Roles', 'bot') + +log = logging.getLogger(__name__) + + +class Channels(NamedTuple): + admins = 365960823622991872 + announcements = 354619224620138496 + big_brother_logs = 468507907357409333 + bot = 267659945086812160 + checkpoint_test = 422077681434099723 + devalerts = 460181980097675264 + devlog = 409308876241108992 + devtest = 414574275865870337 + help_0 = 303906576991780866 + help_1 = 303906556754395136 + help_2 = 303906514266226689 + help_3 = 439702951246692352 + help_4 = 451312046647148554 + help_5 = 454941769734422538 + helpers = 385474242440986624 + message_log = 467752170159079424 + mod_alerts = 473092532147060736 + modlog = 282638479504965634 + off_topic_0 = 291284109232308226 + off_topic_1 = 463035241142026251 + off_topic_2 = 463035268514185226 + python = 267624335836053506 + reddit = 458224812528238616 + staff_lounge = 464905259261755392 + verification = 352442727016693763 + + +class Client(NamedTuple): + guild = int(environ.get('SEASONALBOT_GUILD', 267624335836053506)) + prefix = "." + token = environ.get('SEASONALBOT_TOKEN') + debug = environ.get('SEASONALBOT_DEBUG', '').lower() == 'true' + season_override = environ.get('SEASON_OVERRIDE') + + +class Hacktoberfest(NamedTuple): + channel_id = 498804484324196362 + voice_id = 514420006474219521 + + +class Roles(NamedTuple): + admin = 267628507062992896 + announcements = 463658397560995840 + champion = 430492892331769857 + contributor = 295488872404484098 + developer = 352427296948486144 + devops = 409416496733880320 + jammer = 423054537079783434 + moderator = 267629731250176001 + muted = 277914926603829249 + owner = 267627879762755584 + verified = 352427296948486144 + helpers = 267630620367257601 + rockstars = 458226413825294336 + + +class Tokens(NamedTuple): + giphy = environ.get("GIPHY_TOKEN") + + +bot = SeasonalBot(command_prefix=Client.prefix) diff --git a/bot/decorators.py b/bot/decorators.py new file mode 100644 index 00000000..b84b2c36 --- /dev/null +++ b/bot/decorators.py @@ -0,0 +1,48 @@ +import logging + +from discord.ext import commands +from discord.ext.commands import Context + +log = logging.getLogger(__name__) + + +def with_role(*role_ids: int): + async def predicate(ctx: Context): + if not ctx.guild: # Return False in a DM + log.debug(f"{ctx.author} tried to use the '{ctx.command.name}'command from a DM. " + "This command is restricted by the with_role decorator. Rejecting request.") + return False + + for role in ctx.author.roles: + if role.id in role_ids: + log.debug(f"{ctx.author} has the '{role.name}' role, and passes the check.") + return True + + log.debug(f"{ctx.author} does not have the required role to use " + f"the '{ctx.command.name}' command, so the request is rejected.") + return False + return commands.check(predicate) + + +def without_role(*role_ids: int): + async def predicate(ctx: Context): + if not ctx.guild: # Return False in a DM + log.debug(f"{ctx.author} tried to use the '{ctx.command.name}' command from a DM. " + "This command is restricted by the without_role decorator. Rejecting request.") + return False + + author_roles = [role.id for role in ctx.author.roles] + check = all(role not in author_roles for role in role_ids) + log.debug(f"{ctx.author} tried to call the '{ctx.command.name}' command. " + f"The result of the without_role check was {check}.") + return check + return commands.check(predicate) + + +def in_channel(channel_id): + async def predicate(ctx: Context): + check = ctx.channel.id == channel_id + log.debug(f"{ctx.author} tried to call the '{ctx.command.name}' command. " + f"The result of the in_channel check was {check}.") + return check + return commands.check(predicate) diff --git a/bot/resources/avatars/christmas.png b/bot/resources/avatars/christmas.png Binary files differnew file mode 100644 index 00000000..55b72fac --- /dev/null +++ b/bot/resources/avatars/christmas.png diff --git a/bot/resources/avatars/spooky.png b/bot/resources/avatars/spooky.png Binary files differnew file mode 100644 index 00000000..4ab33188 --- /dev/null +++ b/bot/resources/avatars/spooky.png diff --git a/bot/resources/avatars/standard.png b/bot/resources/avatars/standard.png Binary files differnew file mode 100644 index 00000000..c14ff42a --- /dev/null +++ b/bot/resources/avatars/standard.png diff --git a/bot/cogs/__init__.py b/bot/resources/halloween/github_links.json index e69de29b..e69de29b 100644 --- a/bot/cogs/__init__.py +++ b/bot/resources/halloween/github_links.json diff --git a/bot/resources/halloween/monstersurvey.json b/bot/resources/halloween/monstersurvey.json index b430b6c0..d8cc72e7 100644 --- a/bot/resources/halloween/monstersurvey.json +++ b/bot/resources/halloween/monstersurvey.json @@ -10,7 +10,6 @@ "summary": "Count Dracula is an undead, centuries-old vampire, and a Transylvanian nobleman who claims to be a Sz\u00c3\u00a9kely descended from Attila the Hun. He inhabits a decaying castle in the Carpathian Mountains near the Borgo Pass. Unlike the vampires of Eastern European folklore, which are portrayed as repulsive, corpse-like creatures, Dracula wears a veneer of aristocratic charm. In his conversations with Jonathan Harker, he reveals himself as deeply proud of his boyar heritage and nostalgic for the past, which he admits have become only a memory of heroism, honour and valour in modern times.", "image": "https://upload.wikimedia.org/wikipedia/commons/thumb/9/90/Bela_Lugosi_as_Dracula%2C_anonymous_photograph_from_1931%2C_Universal_Studios.jpg/250px-Bela_Lugosi_as_Dracula%2C_anonymous_photograph_from_1931%2C_Universal_Studios.jpg", "votes": [ - 224734305581137921 ] }, "goofy": { @@ -24,8 +23,6 @@ "summary": "Who let this guy write this? That's who the real monster is.", "image": "https://avatars0.githubusercontent.com/u/24819750?s=460&v=4", "votes": [ - 95872159741644800, - 129606635545952258 ] } -}
\ No newline at end of file +} diff --git a/bot/resources/spookysounds/109710__tomlija__horror-gate.mp3 b/bot/resources/halloween/spookysounds/109710__tomlija__horror-gate.mp3 Binary files differindex 495f2bd1..495f2bd1 100644 --- a/bot/resources/spookysounds/109710__tomlija__horror-gate.mp3 +++ b/bot/resources/halloween/spookysounds/109710__tomlija__horror-gate.mp3 diff --git a/bot/resources/spookysounds/126113__klankbeeld__laugh.mp3 b/bot/resources/halloween/spookysounds/126113__klankbeeld__laugh.mp3 Binary files differindex 538feabc..538feabc 100644 --- a/bot/resources/spookysounds/126113__klankbeeld__laugh.mp3 +++ b/bot/resources/halloween/spookysounds/126113__klankbeeld__laugh.mp3 diff --git a/bot/resources/spookysounds/133674__klankbeeld__horror-laugh-original-132802-nanakisan-evil-laugh-08.mp3 b/bot/resources/halloween/spookysounds/133674__klankbeeld__horror-laugh-original-132802-nanakisan-evil-laugh-08.mp3 Binary files differindex 17f66698..17f66698 100644 --- a/bot/resources/spookysounds/133674__klankbeeld__horror-laugh-original-132802-nanakisan-evil-laugh-08.mp3 +++ b/bot/resources/halloween/spookysounds/133674__klankbeeld__horror-laugh-original-132802-nanakisan-evil-laugh-08.mp3 diff --git a/bot/resources/spookysounds/14570__oscillator__ghost-fx.mp3 b/bot/resources/halloween/spookysounds/14570__oscillator__ghost-fx.mp3 Binary files differindex 5670657c..5670657c 100644 --- a/bot/resources/spookysounds/14570__oscillator__ghost-fx.mp3 +++ b/bot/resources/halloween/spookysounds/14570__oscillator__ghost-fx.mp3 diff --git a/bot/resources/spookysounds/168650__0xmusex0__doorcreak.mp3 b/bot/resources/halloween/spookysounds/168650__0xmusex0__doorcreak.mp3 Binary files differindex 42f9e9fd..42f9e9fd 100644 --- a/bot/resources/spookysounds/168650__0xmusex0__doorcreak.mp3 +++ b/bot/resources/halloween/spookysounds/168650__0xmusex0__doorcreak.mp3 diff --git a/bot/resources/spookysounds/171078__klankbeeld__horror-scream-woman-long.mp3 b/bot/resources/halloween/spookysounds/171078__klankbeeld__horror-scream-woman-long.mp3 Binary files differindex 1cdb0f4d..1cdb0f4d 100644 --- a/bot/resources/spookysounds/171078__klankbeeld__horror-scream-woman-long.mp3 +++ b/bot/resources/halloween/spookysounds/171078__klankbeeld__horror-scream-woman-long.mp3 diff --git a/bot/resources/spookysounds/193812__geoneo0__four-voices-whispering-6.mp3 b/bot/resources/halloween/spookysounds/193812__geoneo0__four-voices-whispering-6.mp3 Binary files differindex 89150d57..89150d57 100644 --- a/bot/resources/spookysounds/193812__geoneo0__four-voices-whispering-6.mp3 +++ b/bot/resources/halloween/spookysounds/193812__geoneo0__four-voices-whispering-6.mp3 diff --git a/bot/resources/spookysounds/237282__devilfish101__frantic-violin-screech.mp3 b/bot/resources/halloween/spookysounds/237282__devilfish101__frantic-violin-screech.mp3 Binary files differindex b5f85f8d..b5f85f8d 100644 --- a/bot/resources/spookysounds/237282__devilfish101__frantic-violin-screech.mp3 +++ b/bot/resources/halloween/spookysounds/237282__devilfish101__frantic-violin-screech.mp3 diff --git a/bot/resources/spookysounds/249686__cylon8472__cthulhu-growl.mp3 b/bot/resources/halloween/spookysounds/249686__cylon8472__cthulhu-growl.mp3 Binary files differindex d141f68e..d141f68e 100644 --- a/bot/resources/spookysounds/249686__cylon8472__cthulhu-growl.mp3 +++ b/bot/resources/halloween/spookysounds/249686__cylon8472__cthulhu-growl.mp3 diff --git a/bot/resources/spookysounds/35716__analogchill__scream.mp3 b/bot/resources/halloween/spookysounds/35716__analogchill__scream.mp3 Binary files differindex a0614b53..a0614b53 100644 --- a/bot/resources/spookysounds/35716__analogchill__scream.mp3 +++ b/bot/resources/halloween/spookysounds/35716__analogchill__scream.mp3 diff --git a/bot/resources/spookysounds/413315__inspectorj__something-evil-approaches-a.mp3 b/bot/resources/halloween/spookysounds/413315__inspectorj__something-evil-approaches-a.mp3 Binary files differindex 38374316..38374316 100644 --- a/bot/resources/spookysounds/413315__inspectorj__something-evil-approaches-a.mp3 +++ b/bot/resources/halloween/spookysounds/413315__inspectorj__something-evil-approaches-a.mp3 diff --git a/bot/resources/spookysounds/60571__gabemiller74__breathofdeath.mp3 b/bot/resources/halloween/spookysounds/60571__gabemiller74__breathofdeath.mp3 Binary files differindex f769d9d8..f769d9d8 100644 --- a/bot/resources/spookysounds/60571__gabemiller74__breathofdeath.mp3 +++ b/bot/resources/halloween/spookysounds/60571__gabemiller74__breathofdeath.mp3 diff --git a/bot/resources/spookysounds/Female_Monster_Growls_.mp3 b/bot/resources/halloween/spookysounds/Female_Monster_Growls_.mp3 Binary files differindex 8b04f0f5..8b04f0f5 100644 --- a/bot/resources/spookysounds/Female_Monster_Growls_.mp3 +++ b/bot/resources/halloween/spookysounds/Female_Monster_Growls_.mp3 diff --git a/bot/resources/spookysounds/Male_Zombie_Roar_.mp3 b/bot/resources/halloween/spookysounds/Male_Zombie_Roar_.mp3 Binary files differindex 964d685e..964d685e 100644 --- a/bot/resources/spookysounds/Male_Zombie_Roar_.mp3 +++ b/bot/resources/halloween/spookysounds/Male_Zombie_Roar_.mp3 diff --git a/bot/resources/spookysounds/Monster_Alien_Growl_Calm_.mp3 b/bot/resources/halloween/spookysounds/Monster_Alien_Growl_Calm_.mp3 Binary files differindex 9e643773..9e643773 100644 --- a/bot/resources/spookysounds/Monster_Alien_Growl_Calm_.mp3 +++ b/bot/resources/halloween/spookysounds/Monster_Alien_Growl_Calm_.mp3 diff --git a/bot/resources/spookysounds/Monster_Alien_Grunt_Hiss_.mp3 b/bot/resources/halloween/spookysounds/Monster_Alien_Grunt_Hiss_.mp3 Binary files differindex ad99cf76..ad99cf76 100644 --- a/bot/resources/spookysounds/Monster_Alien_Grunt_Hiss_.mp3 +++ b/bot/resources/halloween/spookysounds/Monster_Alien_Grunt_Hiss_.mp3 diff --git a/bot/resources/spookysounds/sources.txt b/bot/resources/halloween/spookysounds/sources.txt index 7df03c2e..7df03c2e 100644 --- a/bot/resources/spookysounds/sources.txt +++ b/bot/resources/halloween/spookysounds/sources.txt diff --git a/bot/seasons/__init__.py b/bot/seasons/__init__.py new file mode 100644 index 00000000..c43334a4 --- /dev/null +++ b/bot/seasons/__init__.py @@ -0,0 +1,12 @@ +import logging + +from bot.seasons.season import SeasonBase, SeasonManager, get_season + +__all__ = ("SeasonBase", "get_season") + +log = logging.getLogger(__name__) + + +def setup(bot): + bot.add_cog(SeasonManager(bot)) + log.debug("SeasonManager cog loaded") diff --git a/bot/seasons/christmas/__init__.py b/bot/seasons/christmas/__init__.py new file mode 100644 index 00000000..cd5ce307 --- /dev/null +++ b/bot/seasons/christmas/__init__.py @@ -0,0 +1,16 @@ +from bot.seasons import SeasonBase + + +class Christmas(SeasonBase): + name = "christmas" + start_date = "01/12" + end_date = "31/12" + bot_name = "Santabot" + + def __init__(self, bot): + self.bot = bot + + @property + def bot_avatar(self): + with open(self.avatar_path("christmas.png"), "rb") as avatar: + return bytearray(avatar.read()) diff --git a/bot/seasons/evergreen/__init__.py b/bot/seasons/evergreen/__init__.py new file mode 100644 index 00000000..e4367aaa --- /dev/null +++ b/bot/seasons/evergreen/__init__.py @@ -0,0 +1,13 @@ +from bot.seasons import SeasonBase + + +class Evergreen(SeasonBase): + bot_name = "SeasonalBot" + + def __init__(self, bot): + self.bot = bot + + @property + def bot_avatar(self): + with open(self.avatar_path("standard.png"), "rb") as avatar: + return bytearray(avatar.read()) diff --git a/bot/cogs/error_handler.py b/bot/seasons/evergreen/error_handler.py index 79780251..6de35e60 100644 --- a/bot/cogs/error_handler.py +++ b/bot/seasons/evergreen/error_handler.py @@ -5,6 +5,8 @@ import traceback from discord.ext import commands
+log = logging.getLogger(__name__)
+
class CommandErrorHandler:
"""A error handler for the PythonDiscord server!"""
@@ -17,36 +19,36 @@ class CommandErrorHandler: if hasattr(ctx.command, 'on_error'):
return logging.debug(
- "A command error occured but " +
+ "A command error occured but "
"the command had it's own error handler"
)
error = getattr(error, 'original', error)
if isinstance(error, commands.CommandNotFound):
return logging.debug(
- f"{ctx.author} called '{ctx.message.content}' " +
+ f"{ctx.author} called '{ctx.message.content}' "
"but no command was found"
)
if isinstance(error, commands.UserInputError):
logging.debug(
- f"{ctx.author} called the command '{ctx.command}' " +
+ f"{ctx.author} called the command '{ctx.command}' "
"but entered invalid input!"
)
return await ctx.send(
- ":no_entry: The command you specified failed to run." +
+ ":no_entry: The command you specified failed to run."
"This is because the arguments you provided were invalid."
)
if isinstance(error, commands.CommandOnCooldown):
logging.debug(
- f"{ctx.author} called the command '{ctx.command}' " +
+ f"{ctx.author} called the command '{ctx.command}' "
"but they were on cooldown!"
)
return await ctx.send(
- "This command is on cooldown," +
+ "This command is on cooldown,"
f" please retry in {math.ceil(error.retry_after)}s."
)
if isinstance(error, commands.DisabledCommand):
logging.debug(
- f"{ctx.author} called the command '{ctx.command}' " +
+ f"{ctx.author} called the command '{ctx.command}' "
"but the command was disabled!"
)
return await ctx.send(
@@ -54,7 +56,7 @@ class CommandErrorHandler: )
if isinstance(error, commands.NoPrivateMessage):
logging.debug(
- f"{ctx.author} called the command '{ctx.command}' " +
+ f"{ctx.author} called the command '{ctx.command}' "
"in a private message however the command was guild only!"
)
return await ctx.author.send(
@@ -63,7 +65,7 @@ class CommandErrorHandler: if isinstance(error, commands.BadArgument):
if ctx.command.qualified_name == 'tag list':
logging.debug(
- f"{ctx.author} called the command '{ctx.command}' " +
+ f"{ctx.author} called the command '{ctx.command}' "
"but entered an invalid user!"
)
return await ctx.send(
@@ -71,7 +73,7 @@ class CommandErrorHandler: )
else:
logging.debug(
- f"{ctx.author} called the command '{ctx.command}' " +
+ f"{ctx.author} called the command '{ctx.command}' "
"but entered a bad argument!"
)
return await ctx.send(
@@ -79,7 +81,7 @@ class CommandErrorHandler: )
if isinstance(error, commands.CheckFailure):
logging.debug(
- f"{ctx.author} called the command '{ctx.command}' " +
+ f"{ctx.author} called the command '{ctx.command}' "
"but the checks failed!"
)
return await ctx.send(
@@ -90,8 +92,8 @@ class CommandErrorHandler: file=sys.stderr
)
logging.warning(
- f"{ctx.author} called the command '{ctx.command}' " +
- "however the command failed to run with the error:" +
+ f"{ctx.author} called the command '{ctx.command}' "
+ "however the command failed to run with the error:"
f"-------------\n{error}"
)
traceback.print_exception(
@@ -104,3 +106,4 @@ class CommandErrorHandler: def setup(bot):
bot.add_cog(CommandErrorHandler(bot))
+ log.debug("CommandErrorHandler cog loaded")
diff --git a/bot/cogs/evergreen/uptime.py b/bot/seasons/evergreen/uptime.py index ec4a3083..1321da19 100644 --- a/bot/cogs/evergreen/uptime.py +++ b/bot/seasons/evergreen/uptime.py @@ -1,9 +1,13 @@ +import logging + import arrow from dateutil.relativedelta import relativedelta from discord.ext import commands from bot import start_time +log = logging.getLogger(__name__) + class Uptime: """ @@ -13,7 +17,7 @@ class Uptime: def __init__(self, bot): self.bot = bot - @commands.command(name='uptime') + @commands.command(name="uptime") async def uptime(self, ctx): """ Returns the uptime of the bot. @@ -31,3 +35,4 @@ class Uptime: # Required in order to load the cog, use the class name in the add_cog function. def setup(bot): bot.add_cog(Uptime(bot)) + log.debug("Uptime cog loaded") diff --git a/bot/seasons/halloween/__init__.py b/bot/seasons/halloween/__init__.py new file mode 100644 index 00000000..40b9ce90 --- /dev/null +++ b/bot/seasons/halloween/__init__.py @@ -0,0 +1,16 @@ +from bot.seasons import SeasonBase + + +class Halloween(SeasonBase): + name = "halloween" + start_date = "01/10" + end_date = "31/10" + bot_name = "Spookybot" + + def __init__(self, bot): + self.bot = bot + + @property + def bot_avatar(self): + with open(self.avatar_path("spooky.png"), "rb") as avatar: + return bytearray(avatar.read()) diff --git a/bot/cogs/hacktober/candy_collection.py b/bot/seasons/halloween/candy_collection.py index f5f17abb..80f30a1b 100644 --- a/bot/cogs/hacktober/candy_collection.py +++ b/bot/seasons/halloween/candy_collection.py @@ -1,12 +1,15 @@ import functools import json +import logging import os import random import discord from discord.ext import commands -from bot.constants import HACKTOBER_CHANNEL_ID +from bot.constants import Hacktoberfest + +log = logging.getLogger(__name__) json_location = os.path.join("bot", "resources", "halloween", "candy_collection.json") @@ -37,7 +40,7 @@ class CandyCollection: if message.author.bot: return # ensure it's hacktober channel - if message.channel.id != HACKTOBER_CHANNEL_ID: + if message.channel.id != Hacktoberfest.channel_id: return # do random check for skull first as it has the lower chance @@ -60,8 +63,9 @@ class CandyCollection: # 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: + if message.channel.id != Hacktoberfest.channel_id: return # if its not a candy or skull, and it is one of 10 most recent messages, @@ -120,7 +124,7 @@ class CandyCollection: ten_recent = [] recent_msg = max(message.id for message in self.bot._connection._messages - if message.channel.id == self.HACKTOBER_CHANNEL_ID) + if message.channel.id == Hacktoberfest.channel_id) channel = await self.hacktober_channel() ten_recent.append(recent_msg.id) @@ -155,7 +159,7 @@ class CandyCollection: """ Get #hacktoberbot channel from it's id """ - return self.bot.get_channel(id=HACKTOBER_CHANNEL_ID) + return self.bot.get_channel(id=Hacktoberfest.channel_id) async def remove_reactions(self, reaction): """ @@ -227,3 +231,4 @@ class CandyCollection: def setup(bot): bot.add_cog(CandyCollection(bot)) + log.debug("CandyCollection cog loaded") diff --git a/bot/cogs/hacktober/hacktoberstats.py b/bot/seasons/halloween/hacktoberstats.py index c473d3d0..41cf10ee 100644 --- a/bot/cogs/hacktober/hacktoberstats.py +++ b/bot/seasons/halloween/hacktoberstats.py @@ -10,16 +10,18 @@ import aiohttp import discord from discord.ext import commands +log = logging.getLogger(__name__) -class Stats: + +class HacktoberStats: def __init__(self, bot): self.bot = bot - self.link_json = Path('./bot/resources', 'github_links.json') + self.link_json = Path("bot", "resources", "github_links.json") self.linked_accounts = self.load_linked_users() @commands.group( - name='stats', - aliases=('hacktoberstats', 'getstats', 'userstats'), + name='hacktoberstats', + aliases=('hackstats',), invoke_without_command=True ) async def hacktoberstats_group(self, ctx: commands.Context, github_username: str = None): @@ -30,7 +32,7 @@ class Stats: 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) + author_id, author_mention = HacktoberStats._author_mention_from_context(ctx) if str(author_id) in self.linked_accounts.keys(): github_username = self.linked_accounts[author_id]["github_username"] @@ -59,7 +61,7 @@ class Stats: } } """ - author_id, author_mention = Stats._author_mention_from_context(ctx) + author_id, author_mention = HacktoberStats._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"] @@ -84,7 +86,7 @@ class Stats: """ Remove the invoking user's account link from the log """ - author_id, author_mention = Stats._author_mention_from_context(ctx) + author_id, author_mention = HacktoberStats._author_mention_from_context(ctx) stored_user = self.linked_accounts.pop(author_id, None) if stored_user: @@ -175,7 +177,12 @@ class Stats: 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" + description=( + f"{github_username} has made {n} " + f"{HacktoberStats._contributionator(n)} in " + f"October\n\n" + f"{shirtstr}\n\n" + ) ) stats_embed.set_thumbnail(url=f"https://www.github.com/{github_username}.png") @@ -244,7 +251,7 @@ class Stats: 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"]) + shortname = HacktoberStats._get_shortname(item["repository_url"]) itemdict = { "repo_url": f"https://www.github.com/{shortname}", "repo_shortname": shortname, @@ -298,7 +305,7 @@ class Stats: contributionstrs = [] for repo in stats['top5']: n = repo[1] - contributionstrs.append(f"{n} {Stats._contributionator(n)} to [{repo[0]}]({baseURL}{repo[0]})") + contributionstrs.append(f"{n} {HacktoberStats._contributionator(n)} to [{repo[0]}]({baseURL}{repo[0]})") return "\n".join(contributionstrs) @@ -324,4 +331,5 @@ class Stats: def setup(bot): - bot.add_cog(Stats(bot)) + bot.add_cog(HacktoberStats(bot)) + log.debug("HacktoberStats cog loaded") diff --git a/bot/cogs/hacktober/halloween_facts.py b/bot/seasons/halloween/halloween_facts.py index 7b5b866b..098ee432 100644 --- a/bot/cogs/hacktober/halloween_facts.py +++ b/bot/seasons/halloween/halloween_facts.py @@ -1,5 +1,5 @@ -import asyncio import json +import logging import random from datetime import timedelta from pathlib import Path @@ -7,7 +7,9 @@ from pathlib import Path import discord from discord.ext import commands -from bot.constants import HACKTOBER_CHANNEL_ID +from bot.constants import Hacktoberfest + +log = logging.getLogger(__name__) SPOOKY_EMOJIS = [ "\N{BAT}", @@ -30,37 +32,24 @@ class HalloweenFacts: 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 + self.facts = list(enumerate(self.halloween_facts)) + random.shuffle(self.facts) async def on_ready(self): - self.channel = self.bot.get_channel(HACKTOBER_CHANNEL_ID) + self.channel = self.bot.get_channel(Hacktoberfest.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) + def random_fact(self): + return random.choice(self.facts) - @commands.command(name="hallofact", aliases=["hallofacts"], brief="Get the most recent Halloween fact") - async def get_last_fact(self, ctx): + @commands.command(name="spookyfact", aliases=("halloweenfact",), brief="Get the most recent Halloween fact") + async def get_random_fact(self, ctx): """ Reply with the most recent Halloween fact. """ - if ctx.channel != self.channel: - return - index, fact = self.last_fact + index, fact = self.random_fact() embed = self._build_embed(index, fact) - await ctx.send("Halloween fact recap", embed=embed) + await ctx.send(embed=embed) @staticmethod def _build_embed(index, fact): @@ -74,3 +63,4 @@ class HalloweenFacts: def setup(bot): bot.add_cog(HalloweenFacts(bot)) + log.debug("HalloweenFacts cog loaded") diff --git a/bot/cogs/hacktober/halloweenify.py b/bot/seasons/halloween/halloweenify.py index 5d270974..cda07472 100644 --- a/bot/cogs/hacktober/halloweenify.py +++ b/bot/seasons/halloween/halloweenify.py @@ -1,3 +1,4 @@ +import logging from json import load from pathlib import Path from random import choice @@ -6,6 +7,8 @@ import discord from discord.ext import commands from discord.ext.commands.cooldowns import BucketType +log = logging.getLogger(__name__) + class Halloweenify: """ @@ -49,3 +52,4 @@ class Halloweenify: def setup(bot): bot.add_cog(Halloweenify(bot)) + log.debug("Halloweenify cog loaded") diff --git a/bot/cogs/hacktober/monstersurvey.py b/bot/seasons/halloween/monstersurvey.py index 2b78abc6..08873f24 100644 --- a/bot/cogs/hacktober/monstersurvey.py +++ b/bot/seasons/halloween/monstersurvey.py @@ -58,7 +58,7 @@ class MonsterSurvey: @commands.group( name='monster', - aliases=['ms'] + aliases=('ms',) ) async def monster_group(self, ctx: Context): """ @@ -180,7 +180,7 @@ class MonsterSurvey: @monster_group.command( name='leaderboard', - aliases=['lb'] + aliases=('lb',) ) async def monster_leaderboard(self, ctx: Context): """ @@ -215,4 +215,4 @@ class MonsterSurvey: def setup(bot): bot.add_cog(MonsterSurvey(bot)) - log.debug("MonsterSurvey COG Loaded") + log.debug("MonsterSurvey cog loaded") diff --git a/bot/cogs/hacktober/scarymovie.py b/bot/seasons/halloween/scarymovie.py index c2298c65..b280781e 100644 --- a/bot/cogs/hacktober/scarymovie.py +++ b/bot/seasons/halloween/scarymovie.py @@ -1,3 +1,4 @@ +import logging import random from os import environ @@ -5,6 +6,8 @@ import aiohttp from discord import Embed from discord.ext import commands +log = logging.getLogger(__name__) + TMDB_API_KEY = environ.get('TMDB_API_KEY') TMDB_TOKEN = environ.get('TMDB_TOKEN') @@ -18,7 +21,7 @@ class ScaryMovie: def __init__(self, bot): self.bot = bot - @commands.command(name='movie', alias=['tmdb']) + @commands.command(name='scarymovie', alias=['smovie']) async def random_movie(self, ctx): """ Randomly select a scary movie and display information about it. @@ -135,3 +138,4 @@ class ScaryMovie: def setup(bot): bot.add_cog(ScaryMovie(bot)) + log.debug("ScaryMovie cog loaded") diff --git a/bot/cogs/hacktober/spookyavatar.py b/bot/seasons/halloween/spookyavatar.py index 6ce4471c..b37a03f9 100644 --- a/bot/cogs/hacktober/spookyavatar.py +++ b/bot/seasons/halloween/spookyavatar.py @@ -1,3 +1,4 @@ +import logging import os from io import BytesIO @@ -6,7 +7,9 @@ import discord from discord.ext import commands from PIL import Image -from bot.utils import spookifications +from bot.utils.halloween import spookifications + +log = logging.getLogger(__name__) class SpookyAvatar: @@ -26,7 +29,7 @@ class SpookyAvatar: async with session.get(url) as resp: return await resp.read() - @commands.command(name='savatar', aliases=['spookyavatar', 'spookify'], + @commands.command(name='savatar', aliases=('spookyavatar', 'spookify'), brief='Spookify an user\'s avatar.') async def spooky_avatar(self, ctx, user: discord.Member = None): """ @@ -52,3 +55,4 @@ class SpookyAvatar: def setup(bot): bot.add_cog(SpookyAvatar(bot)) + log.debug("SpookyAvatar cog loaded") diff --git a/bot/cogs/hacktober/spookygif.py b/bot/seasons/halloween/spookygif.py index 98a411f6..1233773b 100644 --- a/bot/cogs/hacktober/spookygif.py +++ b/bot/seasons/halloween/spookygif.py @@ -1,8 +1,12 @@ +import logging + import aiohttp import discord from discord.ext import commands -from bot.constants import GIPHY_TOKEN +from bot.constants import Tokens + +log = logging.getLogger(__name__) class SpookyGif: @@ -13,14 +17,15 @@ class SpookyGif: def __init__(self, bot): self.bot = bot - @commands.command(name="spookygif", aliases=["sgif", "scarygif"]) + @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'} + params = {'api_key': Tokens.giphy, '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() @@ -35,3 +40,4 @@ class SpookyGif: def setup(bot): bot.add_cog(SpookyGif(bot)) + log.debug("SpookyGif cog loaded") diff --git a/bot/cogs/hacktober/spookyreact.py b/bot/seasons/halloween/spookyreact.py index 8e9e8db6..f63cd7e5 100644 --- a/bot/cogs/hacktober/spookyreact.py +++ b/bot/seasons/halloween/spookyreact.py @@ -3,6 +3,8 @@ import re import discord +log = logging.getLogger(__name__) + SPOOKY_TRIGGERS = { 'spooky': (r"\bspo{2,}ky\b", "\U0001F47B"), 'skeleton': (r"\bskeleton\b", "\U0001F480"), @@ -52,14 +54,14 @@ class SpookyReact: """ # Check for self reaction if ctx.author == self.bot.user: - logging.info(f"Ignoring reactions on self message. Message ID: {ctx.id}") + logging.debug(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}") + logging.debug(f"Ignoring reactions on command invocation. Message ID: {ctx.id}") return True return False @@ -67,3 +69,4 @@ class SpookyReact: def setup(bot): bot.add_cog(SpookyReact(bot)) + log.debug("SpookyReact cog loaded") diff --git a/bot/cogs/hacktober/spookysound.py b/bot/seasons/halloween/spookysound.py index e1598517..4cab1239 100644 --- a/bot/cogs/hacktober/spookysound.py +++ b/bot/seasons/halloween/spookysound.py @@ -1,10 +1,13 @@ +import logging import random from pathlib import Path import discord from discord.ext import commands -from bot.constants import HACKTOBER_VOICE_CHANNEL_ID +from bot.constants import Hacktoberfest + +log = logging.getLogger(__name__) class SpookySound: @@ -14,7 +17,7 @@ class SpookySound: def __init__(self, bot): self.bot = bot - self.sound_files = list(Path("./bot/resources/spookysounds").glob("*.mp3")) + self.sound_files = list(Path("bot", "resources", "halloween", "spookysounds").glob("*.mp3")) self.channel = None @commands.cooldown(rate=1, per=1) @@ -26,7 +29,7 @@ class SpookySound: """ if not self.channel: await self.bot.wait_until_ready() - self.channel = self.bot.get_channel(HACKTOBER_VOICE_CHANNEL_ID) + self.channel = self.bot.get_channel(Hacktoberfest.voice_id) await ctx.send("Initiating spooky sound...") file_path = random.choice(self.sound_files) @@ -44,3 +47,4 @@ class SpookySound: def setup(bot): bot.add_cog(SpookySound(bot)) + log.debug("SpookySound cog loaded") diff --git a/bot/seasons/season.py b/bot/seasons/season.py new file mode 100644 index 00000000..5ab364c5 --- /dev/null +++ b/bot/seasons/season.py @@ -0,0 +1,180 @@ +import asyncio +import datetime +import importlib +import logging +import pkgutil +from pathlib import Path + +from discord.ext import commands + +from bot.constants import Client, Roles +from bot.decorators import with_role + +log = logging.getLogger(__name__) + + +def get_seasons(): + """ + Returns all the Season objects + located in bot/seasons/ + """ + seasons = [] + + for module in pkgutil.iter_modules([Path('bot', 'seasons')]): + if module.ispkg: + seasons.append(module[1]) + + return seasons + + +def get_season_class(season_name): + season_lib = importlib.import_module(f'bot.seasons.{season_name}') + return getattr(season_lib, season_name.capitalize()) + + +def get_season(bot, season_name: str = None, date: datetime.date = None): + """ + Returns a Season object based on either a string or a date. + """ + + # If either both or neither are set, raise an error. + if not bool(season_name) ^ bool(date): + raise UserWarning("This function requires either a season or a date in order to run.") + + seasons = get_seasons() + + # Use season override if season name not provided + if not season_name and Client.season_override: + log.debug(f"Season override found: {Client.season_override}") + season_name = Client.season_override + + # If name provided grab the specified class or fallback to evergreen. + if season_name: + season_name = season_name.lower() + if season_name not in seasons: + season_name = 'evergreen' + season_class = get_season_class(season_name) + return season_class(bot) + + # If not, we have to figure out if the date matches any of the seasons. + seasons.remove('evergreen') + for season_name in seasons: + season_class = get_season_class(season_name) + # check if date matches before returning an instance + if season_class.start() <= date <= season_class.end(): + return season_class(bot) + else: + evergreen_class = get_season_class('evergreen') + return evergreen_class(bot) + + +class SeasonBase: + name = None + date_format = "%d/%m-%Y" + + @staticmethod + def current_year(): + return datetime.date.today().year + + @classmethod + def start(cls): + return datetime.datetime.strptime(f"{cls.start_date}-{cls.current_year()}", cls.date_format).date() + + @classmethod + def end(cls): + return datetime.datetime.strptime(f"{cls.end_date}-{cls.current_year()}", cls.date_format).date() + + @staticmethod + def avatar_path(*path_segments): + return Path('bot', 'resources', 'avatars', *path_segments) + + async def load(self): + """ + Loads in the bot name, the bot avatar, + and the extensions that are relevant to that season. + """ + + guild = self.bot.get_guild(Client.guild) + + # Change only nickname if in debug mode due to ratelimits for user edits + if Client.debug: + if guild.me.display_name != self.bot_name: + log.debug(f"Changing nickname to {self.bot_name}") + await guild.me.edit(nick=self.bot_name) + else: + if self.bot.user.name != self.bot_name: + # attempt to change user details + log.debug(f"Changing username to {self.bot_name}") + await self.bot.user.edit(name=self.bot_name, avatar=self.bot_avatar) + + # fallback on nickname if failed due to ratelimit + if self.bot.user.name != self.bot_name: + log.info(f"User details failed to change: Changing nickname to {self.bot_name}") + await guild.me.edit(nick=self.bot_name) + + # remove nickname if an old one exists + if guild.me.nick and guild.me.nick != self.bot_name: + log.debug(f"Clearing old nickname of {guild.me.nick}") + await guild.me.edit(nick=None) + + # Prepare all the seasonal cogs, and then the evergreen ones. + extensions = [] + for ext_folder in {self.name, "evergreen"}: + if ext_folder: + log.info(f'Start loading extensions from seasons/{ext_folder}/') + path = Path('bot', 'seasons', ext_folder) + for ext_name in [i[1] for i in pkgutil.iter_modules([path])]: + extensions.append(f"bot.seasons.{ext_folder}.{ext_name}") + + # Finally we can load all the cogs we've prepared. + self.bot.load_extensions(extensions) + + +class SeasonManager: + """ + A cog for managing seasons. + """ + + def __init__(self, bot): + self.bot = bot + self.season = get_season(bot, date=datetime.date.today()) + self.season_task = bot.loop.create_task(self.load_seasons()) + + # Figure out number of seconds until a minute past midnight + tomorrow = datetime.datetime.now() + datetime.timedelta(1) + midnight = datetime.datetime( + year=tomorrow.year, + month=tomorrow.month, + day=tomorrow.day, + hour=0, + minute=0, + second=0 + ) + self.sleep_time = (midnight - datetime.datetime.now()).seconds + 60 + + async def load_seasons(self): + await self.bot.wait_until_ready() + await self.season.load() + + while True: + await asyncio.sleep(self.sleep_time) # sleep until midnight + self.sleep_time = 86400 # next time, sleep for 24 hours. + + # If the season has changed, load it. + new_season = get_season(self.bot, date=datetime.date.today()) + if new_season != self.season: + await self.season.load() + + @with_role(Roles.moderator, Roles.admin, Roles.owner) + @commands.command('season') + async def change_season(self, ctx, new_season: str): + """ + Changes the currently active season on the bot. + """ + + self.season = get_season(self.bot, season_name=new_season) + await self.season.load() + await ctx.send(f"Season changed to {new_season}.") + + def __unload(self): + self.season_task.cancel() diff --git a/bot/cogs/evergreen/__init__.py b/bot/utils/halloween/__init__.py index e69de29b..e69de29b 100644 --- a/bot/cogs/evergreen/__init__.py +++ b/bot/utils/halloween/__init__.py diff --git a/bot/utils/spookifications.py b/bot/utils/halloween/spookifications.py index 5f2369ae..5f2369ae 100644 --- a/bot/utils/spookifications.py +++ b/bot/utils/halloween/spookifications.py |