aboutsummaryrefslogtreecommitdiffstats
path: root/bot
diff options
context:
space:
mode:
Diffstat (limited to 'bot')
-rw-r--r--bot/constants.py25
-rw-r--r--bot/exts/christmas/advent_of_code/_cog.py12
-rw-r--r--bot/exts/christmas/advent_of_code/_helpers.py2
-rw-r--r--bot/exts/easter/earth_photos.py6
-rw-r--r--bot/exts/evergreen/emoji.py (renamed from bot/exts/evergreen/emoji_count.py)68
-rw-r--r--bot/exts/evergreen/game.py3
-rw-r--r--bot/exts/evergreen/minesweeper.py3
-rw-r--r--bot/exts/evergreen/movie.py3
-rw-r--r--bot/exts/evergreen/snakes/_snakes_cog.py3
-rw-r--r--bot/exts/evergreen/source.py2
-rw-r--r--bot/exts/evergreen/space.py3
-rw-r--r--bot/exts/evergreen/status_codes.py4
-rw-r--r--bot/exts/evergreen/tic_tac_toe.py10
-rw-r--r--bot/exts/utils/extensions.py10
-rw-r--r--bot/exts/valentines/be_my_valentine.py3
-rw-r--r--bot/resources/halloween/spooky_rating.json18
-rw-r--r--bot/utils/extensions.py10
-rw-r--r--bot/utils/time.py84
18 files changed, 190 insertions, 79 deletions
diff --git a/bot/constants.py b/bot/constants.py
index 5c95d9c1..3ca2cda9 100644
--- a/bot/constants.py
+++ b/bot/constants.py
@@ -94,33 +94,18 @@ class Branding:
class Channels(NamedTuple):
- admins = 365960823622991872
advent_of_code = int(environ.get("AOC_CHANNEL_ID", 782715290437943306))
advent_of_code_commands = int(environ.get("AOC_COMMANDS_CHANNEL_ID", 607247579608121354))
- announcements = int(environ.get("CHANNEL_ANNOUNCEMENTS", 354619224620138496))
- big_brother_logs = 468507907357409333
bot = 267659945086812160
- checkpoint_test = 422077681434099723
organisation = 551789653284356126
- devalerts = 460181980097675264
devlog = int(environ.get("CHANNEL_DEVLOG", 622895325144940554))
dev_contrib = 635950537262759947
- dev_branding = 753252897059373066
- helpers = 385474242440986624
- message_log = 467752170159079424
- mod_alerts = 473092532147060736
- modlog = 282638479504965634
mod_meta = 775412552795947058
mod_tools = 775413915391098921
off_topic_0 = 291284109232308226
off_topic_1 = 463035241142026251
off_topic_2 = 463035268514185226
- python = 267624335836053506
- reddit = 458224812528238616
community_bot_commands = int(environ.get("CHANNEL_COMMUNITY_BOT_COMMANDS", 607247579608121354))
- staff_lounge = 464905259261755392
- verification = 352442727016693763
- python_discussion = 267624335836053506
hacktoberfest_2020 = 760857070781071431
voice_chat_0 = 412357430186344448
voice_chat_1 = 799647045886541885
@@ -264,20 +249,10 @@ if Client.month_override is not None:
class Roles(NamedTuple):
admin = int(environ.get("BOT_ADMIN_ROLE_ID", 267628507062992896))
- announcements = 463658397560995840
- champion = 430492892331769857
- contributor = 295488872404484098
- devops = 409416496733880320
- jammer = 423054537079783434
moderator = 267629731250176001
- muted = 277914926603829249
owner = 267627879762755584
- verified = 352427296948486144
helpers = int(environ.get("ROLE_HELPERS", 267630620367257601))
- rockstars = 458226413825294336
core_developers = 587606783669829632
- events_lead = 778361735739998228
- everyone_role = 267624335836053506
class Tokens(NamedTuple):
diff --git a/bot/exts/christmas/advent_of_code/_cog.py b/bot/exts/christmas/advent_of_code/_cog.py
index 466edd48..8376987d 100644
--- a/bot/exts/christmas/advent_of_code/_cog.py
+++ b/bot/exts/christmas/advent_of_code/_cog.py
@@ -12,6 +12,7 @@ from bot.constants import (
)
from bot.exts.christmas.advent_of_code import _helpers
from bot.utils.decorators import InChannelCheckFailure, in_month, whitelist_override, with_role
+from bot.utils.extensions import invoke_help_command
log = logging.getLogger(__name__)
@@ -36,9 +37,6 @@ class AdventOfCode(commands.Cog):
self.about_aoc_filepath = Path("./bot/resources/advent_of_code/about.json")
self.cached_about_aoc = self._build_about_embed()
- self.countdown_task = None
- self.status_task = None
-
notification_coro = _helpers.new_puzzle_notification(self.bot)
self.notification_task = self.bot.loop.create_task(notification_coro)
self.notification_task.set_name("Daily AoC Notification")
@@ -54,7 +52,7 @@ class AdventOfCode(commands.Cog):
async def adventofcode_group(self, ctx: commands.Context) -> None:
"""All of the Advent of Code commands."""
if not ctx.invoked_subcommand:
- await ctx.send_help(ctx.command)
+ await invoke_help_command(ctx)
@adventofcode_group.command(
name="subscribe",
@@ -173,6 +171,7 @@ class AdventOfCode(commands.Cog):
else:
await ctx.message.add_reaction(Emojis.envelope)
+ @in_month(Month.DECEMBER)
@adventofcode_group.command(
name="leaderboard",
aliases=("board", "lb"),
@@ -198,6 +197,7 @@ class AdventOfCode(commands.Cog):
await ctx.send(content=f"{header}\n\n{table}", embed=info_embed)
+ @in_month(Month.DECEMBER)
@adventofcode_group.command(
name="global",
aliases=("globalboard", "gb"),
@@ -244,7 +244,7 @@ class AdventOfCode(commands.Cog):
info_embed = _helpers.get_summary_embed(leaderboard)
await ctx.send(f"```\n{table}\n```", embed=info_embed)
- @with_role(Roles.admin, Roles.events_lead)
+ @with_role(Roles.admin)
@adventofcode_group.command(
name="refresh",
aliases=("fetch",),
@@ -268,7 +268,7 @@ class AdventOfCode(commands.Cog):
def cog_unload(self) -> None:
"""Cancel season-related tasks on cog unload."""
log.debug("Unloading the cog and canceling the background task.")
- self.countdown_task.cancel()
+ self.notification_task.cancel()
self.status_task.cancel()
def _build_about_embed(self) -> discord.Embed:
diff --git a/bot/exts/christmas/advent_of_code/_helpers.py b/bot/exts/christmas/advent_of_code/_helpers.py
index b7adc895..a16a4871 100644
--- a/bot/exts/christmas/advent_of_code/_helpers.py
+++ b/bot/exts/christmas/advent_of_code/_helpers.py
@@ -44,7 +44,7 @@ REQUIRED_CACHE_KEYS = (
AOC_EMBED_THUMBNAIL = (
"https://raw.githubusercontent.com/python-discord"
- "/branding/master/seasonal/christmas/server_icons/festive_256.gif"
+ "/branding/main/seasonal/christmas/server_icons/festive_256.gif"
)
# Create an easy constant for the EST timezone
diff --git a/bot/exts/easter/earth_photos.py b/bot/exts/easter/earth_photos.py
index 60e34b15..bf658391 100644
--- a/bot/exts/easter/earth_photos.py
+++ b/bot/exts/easter/earth_photos.py
@@ -47,8 +47,10 @@ class EarthPhotos(commands.Cog):
embed.set_image(url=embedlink)
embed.add_field(
name="Author",
- value=f"Photo by [{username}]({profile}{rf}) \
- on [Unsplash](https://unsplash.com{rf})."
+ value=(
+ f"Photo by [{username}]({profile}{rf}) "
+ f"on [Unsplash](https://unsplash.com{rf})."
+ )
)
await ctx.send(embed=embed)
diff --git a/bot/exts/evergreen/emoji_count.py b/bot/exts/evergreen/emoji.py
index cc43e9ab..fa3044e3 100644
--- a/bot/exts/evergreen/emoji_count.py
+++ b/bot/exts/evergreen/emoji.py
@@ -1,49 +1,52 @@
-import datetime
import logging
import random
+import textwrap
from collections import defaultdict
-from typing import List, Tuple
+from datetime import datetime
+from typing import List, Optional, Tuple
-import discord
+from discord import Color, Embed, Emoji
from discord.ext import commands
from bot.constants import Colours, ERROR_REPLIES
+from bot.utils.extensions import invoke_help_command
from bot.utils.pagination import LinePaginator
+from bot.utils.time import time_since
log = logging.getLogger(__name__)
-class EmojiCount(commands.Cog):
- """Command that give random emoji based on category."""
+class Emojis(commands.Cog):
+ """A collection of commands related to emojis in the server."""
def __init__(self, bot: commands.Bot):
self.bot = bot
@staticmethod
- def embed_builder(emoji: dict) -> Tuple[discord.Embed, List[str]]:
+ def embed_builder(emoji: dict) -> Tuple[Embed, List[str]]:
"""Generates an embed with the emoji names and count."""
- embed = discord.Embed(
+ embed = Embed(
color=Colours.orange,
title="Emoji Count",
- timestamp=datetime.datetime.utcnow()
+ timestamp=datetime.utcnow()
)
msg = []
if len(emoji) == 1:
for category_name, category_emojis in emoji.items():
if len(category_emojis) == 1:
- msg.append(f"There is **{len(category_emojis)}** emoji in **{category_name}** category")
+ msg.append(f"There is **{len(category_emojis)}** emoji in the **{category_name}** category.")
else:
- msg.append(f"There are **{len(category_emojis)}** emojis in **{category_name}** category")
+ msg.append(f"There are **{len(category_emojis)}** emojis in the **{category_name}** category.")
embed.set_thumbnail(url=random.choice(category_emojis).url)
else:
for category_name, category_emojis in emoji.items():
emoji_choice = random.choice(category_emojis)
if len(category_emojis) > 1:
- emoji_info = f"There are **{len(category_emojis)}** emojis in **{category_name}** category"
+ emoji_info = f"There are **{len(category_emojis)}** emojis in the **{category_name}** category."
else:
- emoji_info = f"There is **{len(category_emojis)}** emoji in **{category_name}** category"
+ emoji_info = f"There is **{len(category_emojis)}** emoji in the **{category_name}** category."
if emoji_choice.animated:
msg.append(f'<a:{emoji_choice.name}:{emoji_choice.id}> {emoji_info}')
else:
@@ -51,9 +54,9 @@ class EmojiCount(commands.Cog):
return embed, msg
@staticmethod
- def generate_invalid_embed(emojis: list) -> Tuple[discord.Embed, List[str]]:
- """Generates error embed."""
- embed = discord.Embed(
+ def generate_invalid_embed(emojis: list) -> Tuple[Embed, List[str]]:
+ """Generates error embed for invalid emoji categories."""
+ embed = Embed(
color=Colours.soft_red,
title=random.choice(ERROR_REPLIES)
)
@@ -64,11 +67,19 @@ class EmojiCount(commands.Cog):
emoji_dict[emoji.name.split("_")[0]].append(emoji)
error_comp = ', '.join(emoji_dict)
- msg.append(f"These are the valid categories\n```{error_comp}```")
+ msg.append(f"These are the valid emoji categories:\n```{error_comp}```")
return embed, msg
- @commands.command(name="emojicount", aliases=["ec", "emojis"])
- async def emoji_count(self, ctx: commands.Context, *, category_query: str = None) -> None:
+ @commands.group(name="emoji", invoke_without_command=True)
+ async def emoji_group(self, ctx: commands.Context, emoji: Optional[Emoji]) -> None:
+ """A group of commands related to emojis."""
+ if emoji is not None:
+ await ctx.invoke(self.info_command, emoji)
+ else:
+ await invoke_help_command(ctx)
+
+ @emoji_group.command(name="count", aliases=("c",))
+ async def count_command(self, ctx: commands.Context, *, category_query: str = None) -> None:
"""Returns embed with emoji category and info given by the user."""
emoji_dict = defaultdict(list)
@@ -91,7 +102,24 @@ class EmojiCount(commands.Cog):
embed, msg = self.embed_builder(emoji_dict)
await LinePaginator.paginate(lines=msg, ctx=ctx, embed=embed)
+ @emoji_group.command(name="info", aliases=("i",))
+ async def info_command(self, ctx: commands.Context, emoji: Emoji) -> None:
+ """Returns relevant information about a Discord Emoji."""
+ emoji_information = Embed(
+ title=f"Emoji Information: {emoji.name}",
+ description=textwrap.dedent(f"""
+ **Name:** {emoji.name}
+ **Created:** {time_since(emoji.created_at, precision="hours")}
+ **Date:** {datetime.strftime(emoji.created_at, "%d/%m/%Y")}
+ **ID:** {emoji.id}
+ """),
+ color=Color.blurple(),
+ url=str(emoji.url),
+ ).set_thumbnail(url=emoji.url)
+
+ await ctx.send(embed=emoji_information)
+
def setup(bot: commands.Bot) -> None:
- """Emoji Count Cog load."""
- bot.add_cog(EmojiCount(bot))
+ """Add the Emojis cog into the bot."""
+ bot.add_cog(Emojis(bot))
diff --git a/bot/exts/evergreen/game.py b/bot/exts/evergreen/game.py
index d37be0e2..068d3f68 100644
--- a/bot/exts/evergreen/game.py
+++ b/bot/exts/evergreen/game.py
@@ -15,6 +15,7 @@ from discord.ext.commands import Cog, Context, group
from bot.bot import Bot
from bot.constants import STAFF_ROLES, Tokens
from bot.utils.decorators import with_role
+from bot.utils.extensions import invoke_help_command
from bot.utils.pagination import ImagePaginator, LinePaginator
# Base URL of IGDB API
@@ -234,7 +235,7 @@ class Games(Cog):
"""
# When user didn't specified genre, send help message
if genre is None:
- await ctx.send_help("games")
+ await invoke_help_command(ctx)
return
# Capitalize genre for check
diff --git a/bot/exts/evergreen/minesweeper.py b/bot/exts/evergreen/minesweeper.py
index 286ac7a5..3031debc 100644
--- a/bot/exts/evergreen/minesweeper.py
+++ b/bot/exts/evergreen/minesweeper.py
@@ -8,6 +8,7 @@ from discord.ext import commands
from bot.constants import Client
from bot.utils.exceptions import UserNotPlayingError
+from bot.utils.extensions import invoke_help_command
MESSAGE_MAPPING = {
0: ":stop_button:",
@@ -83,7 +84,7 @@ class Minesweeper(commands.Cog):
@commands.group(name='minesweeper', aliases=('ms',), invoke_without_command=True)
async def minesweeper_group(self, ctx: commands.Context) -> None:
"""Commands for Playing Minesweeper."""
- await ctx.send_help(ctx.command)
+ await invoke_help_command(ctx)
@staticmethod
def get_neighbours(x: int, y: int) -> typing.Generator[typing.Tuple[int, int], None, None]:
diff --git a/bot/exts/evergreen/movie.py b/bot/exts/evergreen/movie.py
index 340a5724..b3bfe998 100644
--- a/bot/exts/evergreen/movie.py
+++ b/bot/exts/evergreen/movie.py
@@ -9,6 +9,7 @@ from discord import Embed
from discord.ext.commands import Bot, Cog, Context, group
from bot.constants import Tokens
+from bot.utils.extensions import invoke_help_command
from bot.utils.pagination import ImagePaginator
# Define base URL of TMDB
@@ -73,7 +74,7 @@ class Movie(Cog):
try:
result = await self.get_movies_list(self.http_session, MovieGenres[genre].value, 1)
except KeyError:
- await ctx.send_help('movies')
+ await invoke_help_command(ctx)
return
# Check if "results" is in result. If not, throw error.
diff --git a/bot/exts/evergreen/snakes/_snakes_cog.py b/bot/exts/evergreen/snakes/_snakes_cog.py
index d5e4f206..3732b559 100644
--- a/bot/exts/evergreen/snakes/_snakes_cog.py
+++ b/bot/exts/evergreen/snakes/_snakes_cog.py
@@ -22,6 +22,7 @@ from bot.constants import ERROR_REPLIES, Tokens
from bot.exts.evergreen.snakes import _utils as utils
from bot.exts.evergreen.snakes._converter import Snake
from bot.utils.decorators import locked
+from bot.utils.extensions import invoke_help_command
log = logging.getLogger(__name__)
@@ -440,7 +441,7 @@ class Snakes(Cog):
@group(name='snakes', aliases=('snake',), invoke_without_command=True)
async def snakes_group(self, ctx: Context) -> None:
"""Commands from our first code jam."""
- await ctx.send_help(ctx.command)
+ await invoke_help_command(ctx)
@bot_has_permissions(manage_messages=True)
@snakes_group.command(name='antidote')
diff --git a/bot/exts/evergreen/source.py b/bot/exts/evergreen/source.py
index cdfe54ec..45752bf9 100644
--- a/bot/exts/evergreen/source.py
+++ b/bot/exts/evergreen/source.py
@@ -76,7 +76,7 @@ class BotSource(commands.Cog):
file_location = Path(filename).relative_to(Path.cwd()).as_posix()
- url = f"{Source.github}/blob/master/{file_location}{lines_extension}"
+ url = f"{Source.github}/blob/main/{file_location}{lines_extension}"
return url, file_location, first_line_no or None
diff --git a/bot/exts/evergreen/space.py b/bot/exts/evergreen/space.py
index bc8e3118..323ff659 100644
--- a/bot/exts/evergreen/space.py
+++ b/bot/exts/evergreen/space.py
@@ -10,6 +10,7 @@ from discord.ext.commands import BadArgument, Cog, Context, Converter, group
from bot.bot import Bot
from bot.constants import Tokens
+from bot.utils.extensions import invoke_help_command
logger = logging.getLogger(__name__)
@@ -63,7 +64,7 @@ class Space(Cog):
@group(name="space", invoke_without_command=True)
async def space(self, ctx: Context) -> None:
"""Head command that contains commands about space."""
- await ctx.send_help("space")
+ await invoke_help_command(ctx)
@space.command(name="apod")
async def apod(self, ctx: Context, date: Optional[str] = None) -> None:
diff --git a/bot/exts/evergreen/status_codes.py b/bot/exts/evergreen/status_codes.py
index 874c87eb..7c00fe20 100644
--- a/bot/exts/evergreen/status_codes.py
+++ b/bot/exts/evergreen/status_codes.py
@@ -3,6 +3,8 @@ from http import HTTPStatus
import discord
from discord.ext import commands
+from bot.utils.extensions import invoke_help_command
+
HTTP_DOG_URL = "https://httpstatusdogs.com/img/{code}.jpg"
HTTP_CAT_URL = "https://http.cat/{code}.jpg"
@@ -17,7 +19,7 @@ class HTTPStatusCodes(commands.Cog):
async def http_status_group(self, ctx: commands.Context) -> None:
"""Group containing dog and cat http status code commands."""
if not ctx.invoked_subcommand:
- await ctx.send_help(ctx.command)
+ await invoke_help_command(ctx)
@http_status_group.command(name='cat')
async def http_cat(self, ctx: commands.Context, code: int) -> None:
diff --git a/bot/exts/evergreen/tic_tac_toe.py b/bot/exts/evergreen/tic_tac_toe.py
index e1190502..6e21528e 100644
--- a/bot/exts/evergreen/tic_tac_toe.py
+++ b/bot/exts/evergreen/tic_tac_toe.py
@@ -10,8 +10,8 @@ from bot.constants import Emojis
from bot.utils.pagination import LinePaginator
CONFIRMATION_MESSAGE = (
- "{opponent}, {requester} wants to play Tic-Tac-Toe against you. React to this message with "
- f"{Emojis.confirmation} to accept or with {Emojis.decline} to decline."
+ "{opponent}, {requester} wants to play Tic-Tac-Toe against you."
+ f"\nReact to this message with {Emojis.confirmation} to accept or with {Emojis.decline} to decline."
)
@@ -253,7 +253,7 @@ class TicTacToe(Cog):
@guild_only()
@is_channel_free()
@is_requester_free()
- @group(name="tictactoe", aliases=("ttt",), invoke_without_command=True)
+ @group(name="tictactoe", aliases=("ttt", "tic"), invoke_without_command=True)
async def tic_tac_toe(self, ctx: Context, opponent: t.Optional[discord.User]) -> None:
"""Tic Tac Toe game. Play against friends or AI. Use reactions to add your mark to field."""
if opponent == ctx.author:
@@ -276,6 +276,10 @@ class TicTacToe(Cog):
)
self.games.append(game)
if opponent is not None:
+ if opponent.bot: # check whether the opponent is a bot or not
+ await ctx.send("You can't play Tic-Tac-Toe with bots!")
+ return
+
confirmed, msg = await game.get_confirmation()
if not confirmed:
diff --git a/bot/exts/utils/extensions.py b/bot/exts/utils/extensions.py
index bb22c353..64e404d2 100644
--- a/bot/exts/utils/extensions.py
+++ b/bot/exts/utils/extensions.py
@@ -11,7 +11,7 @@ from bot import exts
from bot.bot import Bot
from bot.constants import Client, Emojis, MODERATION_ROLES, Roles
from bot.utils.checks import with_role_check
-from bot.utils.extensions import EXTENSIONS, unqualify
+from bot.utils.extensions import EXTENSIONS, invoke_help_command, unqualify
from bot.utils.pagination import LinePaginator
log = logging.getLogger(__name__)
@@ -77,7 +77,7 @@ class Extensions(commands.Cog):
@group(name="extensions", aliases=("ext", "exts", "c", "cogs"), invoke_without_command=True)
async def extensions_group(self, ctx: Context) -> None:
"""Load, unload, reload, and list loaded extensions."""
- await ctx.send_help(ctx.command)
+ await invoke_help_command(ctx)
@extensions_group.command(name="load", aliases=("l",))
async def load_command(self, ctx: Context, *extensions: Extension) -> None:
@@ -87,7 +87,7 @@ class Extensions(commands.Cog):
If '\*' or '\*\*' is given as the name, all unloaded extensions will be loaded.
""" # noqa: W605
if not extensions:
- await ctx.send_help(ctx.command)
+ await invoke_help_command(ctx)
return
if "*" in extensions or "**" in extensions:
@@ -104,7 +104,7 @@ class Extensions(commands.Cog):
If '\*' or '\*\*' is given as the name, all loaded extensions will be unloaded.
""" # noqa: W605
if not extensions:
- await ctx.send_help(ctx.command)
+ await invoke_help_command(ctx)
return
blacklisted = "\n".join(UNLOAD_BLACKLIST & set(extensions))
@@ -130,7 +130,7 @@ class Extensions(commands.Cog):
If '\*\*' is given as the name, all extensions, including unloaded ones, will be reloaded.
""" # noqa: W605
if not extensions:
- await ctx.send_help(ctx.command)
+ await invoke_help_command(ctx)
return
if "**" in extensions:
diff --git a/bot/exts/valentines/be_my_valentine.py b/bot/exts/valentines/be_my_valentine.py
index f3392bcb..09591cf8 100644
--- a/bot/exts/valentines/be_my_valentine.py
+++ b/bot/exts/valentines/be_my_valentine.py
@@ -10,6 +10,7 @@ from discord.ext.commands.cooldowns import BucketType
from bot.constants import Channels, Colours, Lovefest, Month
from bot.utils.decorators import in_month
+from bot.utils.extensions import invoke_help_command
log = logging.getLogger(__name__)
@@ -43,7 +44,7 @@ class BeMyValentine(commands.Cog):
2) use the command \".lovefest unsub\" to get rid of the lovefest role.
"""
if not ctx.invoked_subcommand:
- await ctx.send_help(ctx.command)
+ await invoke_help_command(ctx)
@lovefest_role.command(name="sub")
async def add_role(self, ctx: commands.Context) -> None:
diff --git a/bot/resources/halloween/spooky_rating.json b/bot/resources/halloween/spooky_rating.json
index 533e7107..8e3e66bb 100644
--- a/bot/resources/halloween/spooky_rating.json
+++ b/bot/resources/halloween/spooky_rating.json
@@ -2,46 +2,46 @@
"-1": {
"title": "\uD83D\uDD6F You're not scarin' anyone \uD83D\uDD6F",
"text": "No matter what you say or do, nobody even flinches when you try to scare them. Was your costume this year only a white sheet with holes for eyes? Or did you even bother with a costume at all? Either way, don't expect too many treats when going from door-to-door.",
- "image": "https://raw.githubusercontent.com/python-discord/sir-lancebot/master/bot/resources/halloween/spookyrating/candle.jpeg"
+ "image": "https://raw.githubusercontent.com/python-discord/sir-lancebot/main/bot/resources/halloween/spookyrating/candle.jpeg"
},
"5": {
"title": "\uD83D\uDC76 Like taking candy from a baby \uD83D\uDC76",
"text": "Your scaring will probably make a baby cry... but that's the limit on your frightening powers. Be careful not to get to the point where everyone's running away from you because they don't like you, not because they're scared of you.",
- "image": "https://raw.githubusercontent.com/python-discord/sir-lancebot/master/bot/resources/halloween/spookyrating/baby.jpeg"
+ "image": "https://raw.githubusercontent.com/python-discord/sir-lancebot/main/bot/resources/halloween/spookyrating/baby.jpeg"
},
"20": {
"title": "\uD83C\uDFDA You're skills are forming... \uD83C\uDFDA",
"text": "As you become the Devil's apprentice, you begin to make people jump every time you sneak up on them. A good start, but you have to learn not to wear the same costume every year until it doesn't fit you. People will notice you and your prowess will decrease.",
- "image": "https://raw.githubusercontent.com/python-discord/sir-lancebot/master/bot/resources/halloween/spookyrating/tiger.jpeg"
+ "image": "https://raw.githubusercontent.com/python-discord/sir-lancebot/main/bot/resources/halloween/spookyrating/tiger.jpeg"
},
"30": {
"title": "\uD83D\uDC80 Picture Perfect... \uD83D\uDC80",
"text": "You've nailed the costume this year! You look suuuper scary! Now make sure to play the part and act out your costume and you'll be sure to give a few people a massive fright!",
- "image": "https://raw.githubusercontent.com/python-discord/sir-lancebot/master/bot/resources/halloween/spookyrating/costume.jpeg"
+ "image": "https://raw.githubusercontent.com/python-discord/sir-lancebot/main/bot/resources/halloween/spookyrating/costume.jpeg"
},
"50": {
"title": "\uD83D\uDC7B Uhm... are you human \uD83D\uDC7B",
"text": "Uhm... you're too good to be human and now you're beginning to sound like a ghost. You're almost invisible when haunting and nobody truly knows where you are at any given time. But they will always scream at the sound of a ghost...",
- "image": "https://raw.githubusercontent.com/python-discord/sir-lancebot/master/bot/resources/halloween/spookyrating/ghost.jpeg"
+ "image": "https://raw.githubusercontent.com/python-discord/sir-lancebot/main/bot/resources/halloween/spookyrating/ghost.jpeg"
},
"65": {
"title": "\uD83C\uDF83 That potion can't be real \uD83C\uDF83",
"text": "You're carrying... some... unknown liquids and no one knows who they are but yourself. Be careful on who you use these powerful spells on, because no Mage has the power to do any irreversible enchantments because even you won't know what will happen to these mortals.",
- "image": "https://raw.githubusercontent.com/python-discord/sir-lancebot/master/bot/resources/halloween/spookyrating/necromancer.jepg"
+ "image": "https://raw.githubusercontent.com/python-discord/sir-lancebot/main/bot/resources/halloween/spookyrating/necromancer.jepg"
},
"80": {
"title": "\uD83E\uDD21 The most sinister face \uD83E\uDD21",
"text": "Who knew something intended to be playful could be so menacing... Especially other people seeing you in their nightmares, continuing to haunt them day by day, stuck in their head throughout the entire year. Make sure to pull a face they will never forget.",
- "image": "https://raw.githubusercontent.com/python-discord/sir-lancebot/master/bot/resources/halloween/spookyrating/clown.jpeg"
+ "image": "https://raw.githubusercontent.com/python-discord/sir-lancebot/main/bot/resources/halloween/spookyrating/clown.jpeg"
},
"95": {
"title": "\uD83D\uDE08 The Devil's Accomplice \uD83D\uDE08",
"text": "Imagine being allies with the most evil character with an aim to scare people to death. Force people to suffer as they proceed straight to hell to meet your boss and best friend. Not even you know the power He has...",
- "image": "https://raw.githubusercontent.com/python-discord/sir-lancebot/master/bot/resources/halloween/spookyrating/jackolantern.jpg"
+ "image": "https://raw.githubusercontent.com/python-discord/sir-lancebot/main/bot/resources/halloween/spookyrating/jackolantern.jpg"
},
"100": {
"title":"\uD83D\uDC7F The Devil Himself \uD83D\uDC7F",
"text": "You are the evillest creature in existence to scare anyone and everyone humanly possible. The reason your underlings are called mortals is that they die. With your help, they die a lot quicker. With all the evil power in the universe, you know what to do.",
- "image": "https://raw.githubusercontent.com/python-discord/sir-lancebot/master/bot/resources/halloween/spookyrating/devil.jpeg"
+ "image": "https://raw.githubusercontent.com/python-discord/sir-lancebot/main/bot/resources/halloween/spookyrating/devil.jpeg"
}
}
diff --git a/bot/utils/extensions.py b/bot/utils/extensions.py
index 50350ea8..459588a1 100644
--- a/bot/utils/extensions.py
+++ b/bot/utils/extensions.py
@@ -3,6 +3,8 @@ import inspect
import pkgutil
from typing import Iterator, NoReturn
+from discord.ext.commands import Context
+
from bot import exts
@@ -31,4 +33,12 @@ def walk_extensions() -> Iterator[str]:
yield module.name
+async def invoke_help_command(ctx: Context) -> None:
+ """Invoke the help command or default help command if help extensions is not loaded."""
+ if 'bot.exts.evergreen.help' in ctx.bot.extensions:
+ help_command = ctx.bot.get_command('help')
+ await ctx.invoke(help_command, ctx.command.qualified_name)
+ return
+ await ctx.send_help(ctx.command)
+
EXTENSIONS = frozenset(walk_extensions())
diff --git a/bot/utils/time.py b/bot/utils/time.py
new file mode 100644
index 00000000..fbf2fd21
--- /dev/null
+++ b/bot/utils/time.py
@@ -0,0 +1,84 @@
+import datetime
+
+from dateutil.relativedelta import relativedelta
+
+
+# All these functions are from https://github.com/python-discord/bot/blob/main/bot/utils/time.py
+def _stringify_time_unit(value: int, unit: str) -> str:
+ """
+ Returns a string to represent a value and time unit, ensuring that it uses the right plural form of the unit.
+
+ >>> _stringify_time_unit(1, "seconds")
+ "1 second"
+ >>> _stringify_time_unit(24, "hours")
+ "24 hours"
+ >>> _stringify_time_unit(0, "minutes")
+ "less than a minute"
+ """
+ if unit == "seconds" and value == 0:
+ return "0 seconds"
+ elif value == 1:
+ return f"{value} {unit[:-1]}"
+ elif value == 0:
+ return f"less than a {unit[:-1]}"
+ else:
+ return f"{value} {unit}"
+
+
+def humanize_delta(delta: relativedelta, precision: str = "seconds", max_units: int = 6) -> str:
+ """
+ Returns a human-readable version of the relativedelta.
+
+ precision specifies the smallest unit of time to include (e.g. "seconds", "minutes").
+ max_units specifies the maximum number of units of time to include (e.g. 1 may include days but not hours).
+ """
+ if max_units <= 0:
+ raise ValueError("max_units must be positive")
+
+ units = (
+ ("years", delta.years),
+ ("months", delta.months),
+ ("days", delta.days),
+ ("hours", delta.hours),
+ ("minutes", delta.minutes),
+ ("seconds", delta.seconds),
+ )
+
+ # Add the time units that are >0, but stop at accuracy or max_units.
+ time_strings = []
+ unit_count = 0
+ for unit, value in units:
+ if value:
+ time_strings.append(_stringify_time_unit(value, unit))
+ unit_count += 1
+
+ if unit == precision or unit_count >= max_units:
+ break
+
+ # Add the 'and' between the last two units, if necessary
+ if len(time_strings) > 1:
+ time_strings[-1] = f"{time_strings[-2]} and {time_strings[-1]}"
+ del time_strings[-2]
+
+ # If nothing has been found, just make the value 0 precision, e.g. `0 days`.
+ if not time_strings:
+ humanized = _stringify_time_unit(0, precision)
+ else:
+ humanized = ", ".join(time_strings)
+
+ return humanized
+
+
+def time_since(past_datetime: datetime.datetime, precision: str = "seconds", max_units: int = 6) -> str:
+ """
+ Takes a datetime and returns a human-readable string that describes how long ago that datetime was.
+
+ precision specifies the smallest unit of time to include (e.g. "seconds", "minutes").
+ max_units specifies the maximum number of units of time to include (e.g. 1 may include days but not hours).
+ """
+ now = datetime.datetime.utcnow()
+ delta = abs(relativedelta(now, past_datetime))
+
+ humanized = humanize_delta(delta, precision, max_units)
+
+ return f"{humanized} ago"