aboutsummaryrefslogtreecommitdiffstats
path: root/bot/exts
diff options
context:
space:
mode:
Diffstat (limited to 'bot/exts')
-rw-r--r--bot/exts/christmas/adventofcode.py2
-rw-r--r--bot/exts/easter/conversationstarters.py28
-rw-r--r--bot/exts/evergreen/conversationstarters.py71
-rw-r--r--bot/exts/evergreen/fun.py30
-rw-r--r--bot/exts/evergreen/reddit.py6
-rw-r--r--bot/exts/evergreen/snakes/snakes_cog.py4
-rw-r--r--bot/exts/evergreen/status_cats.py33
-rw-r--r--bot/exts/evergreen/wolfram.py278
-rw-r--r--bot/exts/halloween/candy_collection.py6
9 files changed, 403 insertions, 55 deletions
diff --git a/bot/exts/christmas/adventofcode.py b/bot/exts/christmas/adventofcode.py
index 00607074..b3fe0623 100644
--- a/bot/exts/christmas/adventofcode.py
+++ b/bot/exts/christmas/adventofcode.py
@@ -58,7 +58,7 @@ async def countdown_status(bot: commands.Bot) -> None:
hours, minutes = aligned_seconds // 3600, aligned_seconds // 60 % 60
if aligned_seconds == 0:
- playing = f"right now!"
+ playing = "right now!"
elif aligned_seconds == COUNTDOWN_STEP:
playing = f"in less than {minutes} minutes"
elif hours == 0:
diff --git a/bot/exts/easter/conversationstarters.py b/bot/exts/easter/conversationstarters.py
deleted file mode 100644
index a5f40445..00000000
--- a/bot/exts/easter/conversationstarters.py
+++ /dev/null
@@ -1,28 +0,0 @@
-import json
-import logging
-import random
-from pathlib import Path
-
-from discord.ext import commands
-
-log = logging.getLogger(__name__)
-
-with open(Path("bot/resources/easter/starter.json"), "r", encoding="utf8") as f:
- starters = json.load(f)
-
-
-class ConvoStarters(commands.Cog):
- """Easter conversation topics."""
-
- def __init__(self, bot: commands.Bot):
- self.bot = bot
-
- @commands.command()
- async def topic(self, ctx: commands.Context) -> None:
- """Responds with a random topic to start a conversation."""
- await ctx.send(random.choice(starters['starters']))
-
-
-def setup(bot: commands.Bot) -> None:
- """Conversation starters Cog load."""
- bot.add_cog(ConvoStarters(bot))
diff --git a/bot/exts/evergreen/conversationstarters.py b/bot/exts/evergreen/conversationstarters.py
new file mode 100644
index 00000000..576b8d76
--- /dev/null
+++ b/bot/exts/evergreen/conversationstarters.py
@@ -0,0 +1,71 @@
+from pathlib import Path
+
+import yaml
+from discord import Color, Embed
+from discord.ext import commands
+
+from bot.constants import WHITELISTED_CHANNELS
+from bot.utils.decorators import override_in_channel
+from bot.utils.randomization import RandomCycle
+
+SUGGESTION_FORM = 'https://forms.gle/zw6kkJqv8U43Nfjg9'
+
+with Path("bot/resources/evergreen/starter.yaml").open("r", encoding="utf8") as f:
+ STARTERS = yaml.load(f, Loader=yaml.FullLoader)
+
+with Path("bot/resources/evergreen/py_topics.yaml").open("r", encoding="utf8") as f:
+ # First ID is #python-general and the rest are top to bottom categories of Topical Chat/Help.
+ PY_TOPICS = yaml.load(f, Loader=yaml.FullLoader)
+
+ # Removing `None` from lists of topics, if not a list, it is changed to an empty one.
+ PY_TOPICS = {k: [i for i in v if i] if isinstance(v, list) else [] for k, v in PY_TOPICS.items()}
+
+ # All the allowed channels that the ".topic" command is allowed to be executed in.
+ ALL_ALLOWED_CHANNELS = list(PY_TOPICS.keys()) + list(WHITELISTED_CHANNELS)
+
+# Putting all topics into one dictionary and shuffling lists to reduce same-topic repetitions.
+ALL_TOPICS = {'default': STARTERS, **PY_TOPICS}
+TOPICS = {
+ channel: RandomCycle(topics or ['No topics found for this channel.'])
+ for channel, topics in ALL_TOPICS.items()
+}
+
+
+class ConvoStarters(commands.Cog):
+ """Evergreen conversation topics."""
+
+ def __init__(self, bot: commands.Bot):
+ self.bot = bot
+
+ @commands.command()
+ @override_in_channel(ALL_ALLOWED_CHANNELS)
+ async def topic(self, ctx: commands.Context) -> None:
+ """
+ Responds with a random topic to start a conversation.
+
+ If in a Python channel, a python-related topic will be given.
+
+ Otherwise, a random conversation topic will be received by the user.
+ """
+ # No matter what, the form will be shown.
+ embed = Embed(description=f'Suggest more topics [here]({SUGGESTION_FORM})!', color=Color.blurple())
+
+ try:
+ # Fetching topics.
+ channel_topics = TOPICS[ctx.channel.id]
+
+ # If the channel isn't Python-related.
+ except KeyError:
+ embed.title = f'**{next(TOPICS["default"])}**'
+
+ # If the channel ID doesn't have any topics.
+ else:
+ embed.title = f'**{next(channel_topics)}**'
+
+ finally:
+ await ctx.send(embed=embed)
+
+
+def setup(bot: commands.Bot) -> None:
+ """Conversation starters Cog load."""
+ bot.add_cog(ConvoStarters(bot))
diff --git a/bot/exts/evergreen/fun.py b/bot/exts/evergreen/fun.py
index c5f8f9c8..2f575c1c 100644
--- a/bot/exts/evergreen/fun.py
+++ b/bot/exts/evergreen/fun.py
@@ -66,17 +66,13 @@ class Fun(Cog):
elif num_rolls < 1:
output = ":no_entry: You must roll at least once."
for _ in range(num_rolls):
- terning = f"terning{random.randint(1, 6)}"
- output += getattr(Emojis, terning, '')
+ dice = f"dice_{random.randint(1, 6)}"
+ output += getattr(Emojis, dice, '')
await ctx.send(output)
@commands.command(name="uwu", aliases=("uwuwize", "uwuify",))
async def uwu_command(self, ctx: Context, *, text: str) -> None:
- """
- Converts a given `text` into it's uwu equivalent.
-
- Also accepts a valid discord Message ID or link.
- """
+ """Converts a given `text` into it's uwu equivalent."""
conversion_func = functools.partial(
utils.replace_many, replacements=UWU_WORDS, ignore_case=True, match_case=True
)
@@ -92,11 +88,7 @@ class Fun(Cog):
@commands.command(name="randomcase", aliases=("rcase", "randomcaps", "rcaps",))
async def randomcase_command(self, ctx: Context, *, text: str) -> None:
- """
- Randomly converts the casing of a given `text`.
-
- Also accepts a valid discord Message ID or link.
- """
+ """Randomly converts the casing of a given `text`."""
def conversion_func(text: str) -> str:
"""Randomly converts the casing of a given string."""
return "".join(
@@ -194,12 +186,14 @@ class Fun(Cog):
Union[Embed, None]: The embed if found in the valid Message, else None
"""
embed = None
- message = await Fun._get_discord_message(ctx, text)
- if isinstance(message, Message):
- text = message.content
- # Take first embed because we can't send multiple embeds
- if message.embeds:
- embed = message.embeds[0]
+
+ # message = await Fun._get_discord_message(ctx, text)
+ # if isinstance(message, Message):
+ # text = message.content
+ # # Take first embed because we can't send multiple embeds
+ # if message.embeds:
+ # embed = message.embeds[0]
+
return (text, embed)
@staticmethod
diff --git a/bot/exts/evergreen/reddit.py b/bot/exts/evergreen/reddit.py
index fe204419..49127bea 100644
--- a/bot/exts/evergreen/reddit.py
+++ b/bot/exts/evergreen/reddit.py
@@ -68,9 +68,9 @@ class Reddit(commands.Cog):
# -----------------------------------------------------------
# This code below is bound of change when the emojis are added.
- upvote_emoji = self.bot.get_emoji(638729835245731840)
- comment_emoji = self.bot.get_emoji(638729835073765387)
- user_emoji = self.bot.get_emoji(638729835442602003)
+ upvote_emoji = self.bot.get_emoji(755845219890757644)
+ comment_emoji = self.bot.get_emoji(755845255001014384)
+ user_emoji = self.bot.get_emoji(755845303822974997)
text_emoji = self.bot.get_emoji(676030265910493204)
video_emoji = self.bot.get_emoji(676030265839190047)
image_emoji = self.bot.get_emoji(676030265734201344)
diff --git a/bot/exts/evergreen/snakes/snakes_cog.py b/bot/exts/evergreen/snakes/snakes_cog.py
index b3896fcd..9bbad9fe 100644
--- a/bot/exts/evergreen/snakes/snakes_cog.py
+++ b/bot/exts/evergreen/snakes/snakes_cog.py
@@ -567,7 +567,7 @@ class Snakes(Cog):
antidote_embed = Embed(color=SNAKE_COLOR, title="Antidote")
antidote_embed.set_author(name=ctx.author.name, icon_url=ctx.author.avatar_url)
antidote_embed.set_image(url="https://i.makeagif.com/media/7-12-2015/Cj1pts.gif")
- antidote_embed.add_field(name=f"You have created the snake antidote!",
+ antidote_embed.add_field(name="You have created the snake antidote!",
value=f"The solution was: {' '.join(antidote_answer)}\n"
f"You had {10 - antidote_tries} tries remaining.")
await board_id.edit(embed=antidote_embed)
@@ -1078,7 +1078,7 @@ class Snakes(Cog):
query = snake['name']
# Build the URL and make the request
- url = f'https://www.googleapis.com/youtube/v3/search'
+ url = 'https://www.googleapis.com/youtube/v3/search'
response = await self.bot.http_session.get(
url,
params={
diff --git a/bot/exts/evergreen/status_cats.py b/bot/exts/evergreen/status_cats.py
new file mode 100644
index 00000000..586b8378
--- /dev/null
+++ b/bot/exts/evergreen/status_cats.py
@@ -0,0 +1,33 @@
+from http import HTTPStatus
+
+import discord
+from discord.ext import commands
+
+
+class StatusCats(commands.Cog):
+ """Commands that give HTTP statuses described and visualized by cats."""
+
+ def __init__(self, bot: commands.Bot):
+ self.bot = bot
+
+ @commands.command(aliases=['statuscat'])
+ async def http_cat(self, ctx: commands.Context, code: int) -> None:
+ """Sends an embed with an image of a cat, potraying the status code."""
+ embed = discord.Embed(title=f'**Status: {code}**')
+
+ try:
+ HTTPStatus(code)
+
+ except ValueError:
+ embed.set_footer(text='Inputted status code does not exist.')
+
+ else:
+ embed.set_image(url=f'https://http.cat/{code}.jpg')
+
+ finally:
+ await ctx.send(embed=embed)
+
+
+def setup(bot: commands.Bot) -> None:
+ """Load the StatusCats cog."""
+ bot.add_cog(StatusCats(bot))
diff --git a/bot/exts/evergreen/wolfram.py b/bot/exts/evergreen/wolfram.py
new file mode 100644
index 00000000..898e8d2a
--- /dev/null
+++ b/bot/exts/evergreen/wolfram.py
@@ -0,0 +1,278 @@
+import logging
+from io import BytesIO
+from typing import Callable, List, Optional, Tuple
+from urllib import parse
+
+import arrow
+import discord
+from discord import Embed
+from discord.ext import commands
+from discord.ext.commands import BucketType, Cog, Context, check, group
+
+from bot.constants import Colours, STAFF_ROLES, Wolfram
+from bot.utils.pagination import ImagePaginator
+
+log = logging.getLogger(__name__)
+
+APPID = Wolfram.key
+DEFAULT_OUTPUT_FORMAT = "JSON"
+QUERY = "http://api.wolframalpha.com/v2/{request}?{data}"
+WOLF_IMAGE = "https://www.symbols.com/gi.php?type=1&id=2886&i=1"
+
+MAX_PODS = 20
+
+# Allows for 10 wolfram calls pr user pr day
+usercd = commands.CooldownMapping.from_cooldown(Wolfram.user_limit_day, 60 * 60 * 24, BucketType.user)
+
+# Allows for max api requests / days in month per day for the entire guild (Temporary)
+guildcd = commands.CooldownMapping.from_cooldown(Wolfram.guild_limit_day, 60 * 60 * 24, BucketType.guild)
+
+
+async def send_embed(
+ ctx: Context,
+ message_txt: str,
+ colour: int = Colours.soft_red,
+ footer: str = None,
+ img_url: str = None,
+ f: discord.File = None
+) -> None:
+ """Generate & send a response embed with Wolfram as the author."""
+ embed = Embed(colour=colour)
+ embed.description = message_txt
+ embed.set_author(name="Wolfram Alpha",
+ icon_url=WOLF_IMAGE,
+ url="https://www.wolframalpha.com/")
+ if footer:
+ embed.set_footer(text=footer)
+
+ if img_url:
+ embed.set_image(url=img_url)
+
+ await ctx.send(embed=embed, file=f)
+
+
+def custom_cooldown(*ignore: List[int]) -> Callable:
+ """
+ Implement per-user and per-guild cooldowns for requests to the Wolfram API.
+
+ A list of roles may be provided to ignore the per-user cooldown
+ """
+ async def predicate(ctx: Context) -> bool:
+ if ctx.invoked_with == 'help':
+ # if the invoked command is help we don't want to increase the ratelimits since it's not actually
+ # invoking the command/making a request, so instead just check if the user/guild are on cooldown.
+ guild_cooldown = not guildcd.get_bucket(ctx.message).get_tokens() == 0 # if guild is on cooldown
+ if not any(r.id in ignore for r in ctx.author.roles): # check user bucket if user is not ignored
+ return guild_cooldown and not usercd.get_bucket(ctx.message).get_tokens() == 0
+ return guild_cooldown
+
+ user_bucket = usercd.get_bucket(ctx.message)
+
+ if all(role.id not in ignore for role in ctx.author.roles):
+ user_rate = user_bucket.update_rate_limit()
+
+ if user_rate:
+ # Can't use api; cause: member limit
+ cooldown = arrow.utcnow().shift(seconds=int(user_rate)).humanize(only_distance=True)
+ message = (
+ "You've used up your limit for Wolfram|Alpha requests.\n"
+ f"Cooldown: {cooldown}"
+ )
+ await send_embed(ctx, message)
+ return False
+
+ guild_bucket = guildcd.get_bucket(ctx.message)
+ guild_rate = guild_bucket.update_rate_limit()
+
+ # Repr has a token attribute to read requests left
+ log.debug(guild_bucket)
+
+ if guild_rate:
+ # Can't use api; cause: guild limit
+ message = (
+ "The max limit of requests for the server has been reached for today.\n"
+ f"Cooldown: {int(guild_rate)}"
+ )
+ await send_embed(ctx, message)
+ return False
+
+ return True
+
+ return check(predicate)
+
+
+async def get_pod_pages(ctx: Context, bot: commands.Bot, query: str) -> Optional[List[Tuple]]:
+ """Get the Wolfram API pod pages for the provided query."""
+ async with ctx.channel.typing():
+ url_str = parse.urlencode({
+ "input": query,
+ "appid": APPID,
+ "output": DEFAULT_OUTPUT_FORMAT,
+ "format": "image,plaintext"
+ })
+ request_url = QUERY.format(request="query", data=url_str)
+
+ async with bot.http_session.get(request_url) as response:
+ json = await response.json(content_type='text/plain')
+
+ result = json["queryresult"]
+
+ if result["error"]:
+ # API key not set up correctly
+ if result["error"]["msg"] == "Invalid appid":
+ message = "Wolfram API key is invalid or missing."
+ log.warning(
+ "API key seems to be missing, or invalid when "
+ f"processing a wolfram request: {url_str}, Response: {json}"
+ )
+ await send_embed(ctx, message)
+ return
+
+ message = "Something went wrong internally with your request, please notify staff!"
+ log.warning(f"Something went wrong getting a response from wolfram: {url_str}, Response: {json}")
+ await send_embed(ctx, message)
+ return
+
+ if not result["success"]:
+ message = f"I couldn't find anything for {query}."
+ await send_embed(ctx, message)
+ return
+
+ if not result["numpods"]:
+ message = "Could not find any results."
+ await send_embed(ctx, message)
+ return
+
+ pods = result["pods"]
+ pages = []
+ for pod in pods[:MAX_PODS]:
+ subs = pod.get("subpods")
+
+ for sub in subs:
+ title = sub.get("title") or sub.get("plaintext") or sub.get("id", "")
+ img = sub["img"]["src"]
+ pages.append((title, img))
+ return pages
+
+
+class Wolfram(Cog):
+ """Commands for interacting with the Wolfram|Alpha API."""
+
+ def __init__(self, bot: commands.Bot):
+ self.bot = bot
+
+ @group(name="wolfram", aliases=("wolf", "wa"), invoke_without_command=True)
+ @custom_cooldown(*STAFF_ROLES)
+ async def wolfram_command(self, ctx: Context, *, query: str) -> None:
+ """Requests all answers on a single image, sends an image of all related pods."""
+ url_str = parse.urlencode({
+ "i": query,
+ "appid": APPID,
+ })
+ query = QUERY.format(request="simple", data=url_str)
+
+ # Give feedback that the bot is working.
+ async with ctx.channel.typing():
+ async with self.bot.http_session.get(query) as response:
+ status = response.status
+ image_bytes = await response.read()
+
+ f = discord.File(BytesIO(image_bytes), filename="image.png")
+ image_url = "attachment://image.png"
+
+ if status == 501:
+ message = "Failed to get response"
+ footer = ""
+ color = Colours.soft_red
+ elif status == 400:
+ message = "No input found"
+ footer = ""
+ color = Colours.soft_red
+ elif status == 403:
+ message = "Wolfram API key is invalid or missing."
+ footer = ""
+ color = Colours.soft_red
+ else:
+ message = ""
+ footer = "View original for a bigger picture."
+ color = Colours.soft_orange
+
+ # Sends a "blank" embed if no request is received, unsure how to fix
+ await send_embed(ctx, message, color, footer=footer, img_url=image_url, f=f)
+
+ @wolfram_command.command(name="page", aliases=("pa", "p"))
+ @custom_cooldown(*STAFF_ROLES)
+ async def wolfram_page_command(self, ctx: Context, *, query: str) -> None:
+ """
+ Requests a drawn image of given query.
+
+ Keywords worth noting are, "like curve", "curve", "graph", "pokemon", etc.
+ """
+ pages = await get_pod_pages(ctx, self.bot, query)
+
+ if not pages:
+ return
+
+ embed = Embed()
+ embed.set_author(name="Wolfram Alpha",
+ icon_url=WOLF_IMAGE,
+ url="https://www.wolframalpha.com/")
+ embed.colour = Colours.soft_orange
+
+ await ImagePaginator.paginate(pages, ctx, embed)
+
+ @wolfram_command.command(name="cut", aliases=("c",))
+ @custom_cooldown(*STAFF_ROLES)
+ async def wolfram_cut_command(self, ctx: Context, *, query: str) -> None:
+ """
+ Requests a drawn image of given query.
+
+ Keywords worth noting are, "like curve", "curve", "graph", "pokemon", etc.
+ """
+ pages = await get_pod_pages(ctx, self.bot, query)
+
+ if not pages:
+ return
+
+ if len(pages) >= 2:
+ page = pages[1]
+ else:
+ page = pages[0]
+
+ await send_embed(ctx, page[0], colour=Colours.soft_orange, img_url=page[1])
+
+ @wolfram_command.command(name="short", aliases=("sh", "s"))
+ @custom_cooldown(*STAFF_ROLES)
+ async def wolfram_short_command(self, ctx: Context, *, query: str) -> None:
+ """Requests an answer to a simple question."""
+ url_str = parse.urlencode({
+ "i": query,
+ "appid": APPID,
+ })
+ query = QUERY.format(request="result", data=url_str)
+
+ # Give feedback that the bot is working.
+ async with ctx.channel.typing():
+ async with self.bot.http_session.get(query) as response:
+ status = response.status
+ response_text = await response.text()
+
+ if status == 501:
+ message = "Failed to get response"
+ color = Colours.soft_red
+ elif status == 400:
+ message = "No input found"
+ color = Colours.soft_red
+ elif response_text == "Error 1: Invalid appid":
+ message = "Wolfram API key is invalid or missing."
+ color = Colours.soft_red
+ else:
+ message = response_text
+ color = Colours.soft_orange
+
+ await send_embed(ctx, message, color)
+
+
+def setup(bot: commands.Bot) -> None:
+ """Load the Wolfram cog."""
+ bot.add_cog(Wolfram(bot))
diff --git a/bot/exts/halloween/candy_collection.py b/bot/exts/halloween/candy_collection.py
index 2c7d2f23..caf0df11 100644
--- a/bot/exts/halloween/candy_collection.py
+++ b/bot/exts/halloween/candy_collection.py
@@ -212,9 +212,9 @@ class CandyCollection(commands.Cog):
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...",
+ value="Candies will randomly appear on messages sent. "
+ "\nHit the candy when it appears as fast as possible to get the candy! "
+ "\nBut beware the ghosts...",
inline=False)
await ctx.send(embed=e)