diff options
author | 2020-09-18 23:01:42 +0800 | |
---|---|---|
committer | 2020-09-18 23:01:42 +0800 | |
commit | 636cda284a5a2c2f51a17e87c8b71c0f6edcfb22 (patch) | |
tree | 3523b6eb11f5d752f9060dfba18dbd68a14ac1b0 /bot | |
parent | Fix typo in casercipher help. (diff) | |
parent | Merge pull request #442 from python-discord/reddit-command-fix (diff) |
Merge branch 'master' into caesar-command
Diffstat (limited to 'bot')
-rw-r--r-- | bot/bot.py | 6 | ||||
-rw-r--r-- | bot/constants.py | 22 | ||||
-rw-r--r-- | bot/exts/christmas/adventofcode.py | 2 | ||||
-rw-r--r-- | bot/exts/easter/conversationstarters.py | 28 | ||||
-rw-r--r-- | bot/exts/evergreen/conversationstarters.py | 71 | ||||
-rw-r--r-- | bot/exts/evergreen/fun.py | 30 | ||||
-rw-r--r-- | bot/exts/evergreen/reddit.py | 6 | ||||
-rw-r--r-- | bot/exts/evergreen/snakes/snakes_cog.py | 4 | ||||
-rw-r--r-- | bot/exts/evergreen/status_cats.py | 33 | ||||
-rw-r--r-- | bot/exts/evergreen/wolfram.py | 278 | ||||
-rw-r--r-- | bot/exts/halloween/candy_collection.py | 6 | ||||
-rw-r--r-- | bot/resources/easter/starter.json | 24 | ||||
-rw-r--r-- | bot/resources/evergreen/py_topics.yaml | 89 | ||||
-rw-r--r-- | bot/resources/evergreen/starter.yaml | 22 | ||||
-rw-r--r-- | bot/resources/evergreen/trivia_quiz.json | 30 | ||||
-rw-r--r-- | bot/utils/decorators.py | 2 | ||||
-rw-r--r-- | bot/utils/pagination.py | 4 | ||||
-rw-r--r-- | bot/utils/randomization.py | 23 |
18 files changed, 589 insertions, 91 deletions
@@ -10,7 +10,7 @@ from aiohttp import AsyncResolver, ClientSession, TCPConnector from discord import DiscordException, Embed, Guild, User from discord.ext import commands -from bot.constants import Channels, Client +from bot.constants import Channels, Client, MODERATION_ROLES from bot.utils.decorators import mock_in_debug log = logging.getLogger(__name__) @@ -103,7 +103,7 @@ class SeasonalBot(commands.Bot): return False else: - log.info(f"Asset successfully applied") + log.info("Asset successfully applied") return True @mock_in_debug(return_value=True) @@ -203,7 +203,9 @@ class SeasonalBot(commands.Bot): await self._guild_available.wait() +_allowed_roles = [discord.Object(id_) for id_ in MODERATION_ROLES] bot = SeasonalBot( command_prefix=Client.prefix, activity=discord.Game(name=f"Commands: {Client.prefix}help"), + allowed_mentions=discord.AllowedMentions(everyone=False, roles=_allowed_roles), ) diff --git a/bot/constants.py b/bot/constants.py index bf6c5a40..6605882d 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -17,6 +17,7 @@ __all__ = ( "Month", "Roles", "Tokens", + "Wolfram", "MODERATION_ROLES", "STAFF_ROLES", "WHITELISTED_CHANNELS", @@ -92,10 +93,11 @@ class Colours: dark_green = 0x1f8b4c orange = 0xe67e22 pink = 0xcf84e0 + purple = 0xb734eb soft_green = 0x68c290 + soft_orange = 0xf9cb54 soft_red = 0xcd6d6d yellow = 0xf9f586 - purple = 0xb734eb class Emojis: @@ -106,12 +108,12 @@ class Emojis: trashcan = "<:trashcan:637136429717389331>" ok_hand = ":ok_hand:" - terning1 = "<:terning1:431249668983488527>" - terning2 = "<:terning2:462339216987127808>" - terning3 = "<:terning3:431249694467948544>" - terning4 = "<:terning4:579980271475228682>" - terning5 = "<:terning5:431249716328792064>" - terning6 = "<:terning6:431249726705369098>" + dice_1 = "<:dice_1:755891608859443290>" + dice_2 = "<:dice_2:755891608741740635>" + dice_3 = "<:dice_3:755891608251138158>" + dice_4 = "<:dice_4:755891607882039327>" + dice_5 = "<:dice_5:755891608091885627>" + dice_6 = "<:dice_6:755891607680843838>" issue = "<:IssueOpen:629695470327037963>" issue_closed = "<:IssueClosed:629695470570307614>" @@ -187,6 +189,12 @@ class Tokens(NamedTuple): github = environ.get("GITHUB_TOKEN") +class Wolfram(NamedTuple): + user_limit_day = int(environ.get("WOLFRAM_USER_LIMIT_DAY", 10)) + guild_limit_day = int(environ.get("WOLFRAM_GUILD_LIMIT_DAY", 67)) + key = environ.get("WOLFRAM_API_KEY") + + # Default role combinations MODERATION_ROLES = Roles.moderator, Roles.admin, Roles.owner STAFF_ROLES = Roles.helpers, Roles.moderator, Roles.admin, Roles.owner 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) diff --git a/bot/resources/easter/starter.json b/bot/resources/easter/starter.json deleted file mode 100644 index 31e2cbc9..00000000 --- a/bot/resources/easter/starter.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "starters": [ - "What is your favourite Easter candy or treat?", - "What is your earliest memory of Easter?", - "What is the title of the last book you read?", - "What is better: Milk, Dark or White chocolate?", - "What is your favourite holiday?", - "If you could have any superpower, what would it be?", - "Name one thing you like about a person to your right.", - "If you could be anyone else for one day, who would it be?", - "What Easter tradition do you enjoy most?", - "What is the best gift you've been given?", - "Name one famous person you would like to have at your easter dinner.", - "What was the last movie you saw in a cinema?", - "What is your favourite food?", - "If you could travel anywhere in the world, where would you go?", - "Tell us 5 things you do well.", - "What is your favourite place that you have visited?", - "What is your favourite color?", - "If you had $100 bill in your Easter Basket, what would you do with it?", - "What would you do if you know you could succeed at anything you chose to do?", - "If you could take only three things from your house, what would they be?" - ] -} diff --git a/bot/resources/evergreen/py_topics.yaml b/bot/resources/evergreen/py_topics.yaml new file mode 100644 index 00000000..1e53429a --- /dev/null +++ b/bot/resources/evergreen/py_topics.yaml @@ -0,0 +1,89 @@ +# Conversation starters for Python-related channels. + +# python-general +267624335836053506: + - What's your favorite PEP? + - What's your current text editor/IDE, and what functionality do you like about it the most when programming in Python? + - What functionality is your text editor/IDE missing for programming Python? + - What parts of your life has Python automated, if any? + - Which Python project are you the most proud of making? + - What made you want to learn Python? + - When did you start learning Python? + - What reasons are you learning Python for? + - Where's the strangest place you've seen Python? + - How has learning Python changed your life? + - Is there a package you wish existed but doesn't? What is it? + - What feature do you think should be added to Python? + - Has Python helped you in school? If so, how? + - What was the first thing you created with Python? + +# async +630504881542791169: + - Are there any frameworks you wish were async? + - How have coroutines changed the way you write Python? + +# c-extensions +728390945384431688: + - + +# computer-science +650401909852864553: + - + +# databases +342318764227821568: + - Where do you get your best data? + +# data-science +366673247892275221: + - + +# discord.py +343944376055103488: + - What unique features does your bot contain, if any? + - What commands/features are you proud of making? + - What feature would you be the most interested in making? + - What feature would you like to see added to the library? what feature in the library do you think is redundant? + - Do you think there's a way in which Discord could handle bots better? + +# esoteric-python +470884583684964352: + - What's a common part of programming we can make harder? + - What are the pros and cons of messing with __magic__()? + +# game-development +660625198390837248: + - + +# microcontrollers +545603026732318730: + - + +# networking +716325106619777044: + - If you could wish for a library involving networking, what would it be? + +# security +366674035876167691: + - If you could wish for a library involving net-sec, what would it be? + +# software-testing +463035728335732738: + - + +# tools-and-devops +463035462760792066: + - What editor would you recommend to a beginner? Why? + - What editor would you recommend to be the most efficient? Why? + +# unix +491523972836360192: + - + +# user-interfaces +338993628049571840: + - What's the most impressive Desktop Application you've made with Python so far? + +# web-development +366673702533988363: + - How has Python helped you in web development? diff --git a/bot/resources/evergreen/starter.yaml b/bot/resources/evergreen/starter.yaml new file mode 100644 index 00000000..53c89364 --- /dev/null +++ b/bot/resources/evergreen/starter.yaml @@ -0,0 +1,22 @@ +# Conversation starters for channels that are not Python-related. + +- What is your favourite Easter candy or treat? +- What is your earliest memory of Easter? +- What is the title of the last book you read? +- "What is better: Milk, Dark or White chocolate?" +- What is your favourite holiday? +- If you could have any superpower, what would it be? +- Name one thing you like about a person to your right. +- If you could be anyone else for one day, who would it be? +- What Easter tradition do you enjoy most? +- What is the best gift you've been given? +- Name one famous person you would like to have at your easter dinner. +- What was the last movie you saw in a cinema? +- What is your favourite food? +- If you could travel anywhere in the world, where would you go? +- Tell us 5 things you do well. +- What is your favourite place that you have visited? +- What is your favourite color? +- If you had $100 bill in your Easter Basket, what would you do with it? +- What would you do if you know you could succeed at anything you chose to do? +- If you could take only three things from your house, what would they be? diff --git a/bot/resources/evergreen/trivia_quiz.json b/bot/resources/evergreen/trivia_quiz.json index 6100ca62..8f0a4114 100644 --- a/bot/resources/evergreen/trivia_quiz.json +++ b/bot/resources/evergreen/trivia_quiz.json @@ -217,6 +217,36 @@ "question": "What does the acronym GPRS stand for?", "answer": "General Packet Radio Service", "info": "General Packet Radio Service (GPRS) is a packet-based mobile data service on the global system for mobile communications (GSM) of 3G and 2G cellular communication systems. It is a non-voice, high-speed and useful packet-switching technology intended for GSM networks." + }, + { + "id": 131, + "question": "In what country is the Ebro river located?", + "answer": "Spain", + "info": "The Ebro river is located in Spain. It is 930 kilometers long and it's the second longest river that ends on the Mediterranean Sea." + }, + { + "id": 132, + "question": "What year was the IBM PC model 5150 introduced into the market?", + "answer": "1981", + "info": "The IBM PC was introduced into the market in 1981. It used the Intel 8088, with a clock speed of 4.77 MHz, along with the MDA and CGA as a video card." + }, + { + "id": 133, + "question": "What's the world's largest urban area?", + "answer": "Tokyo", + "info": "Tokyo is the most populated city in the world, with a population of 37 million people. It is located in Japan." + }, + { + "id": 134, + "question": "How many planets are there in the Solar system?", + "answer": "8", + "info": "In the Solar system, there are 8 planets: Mercury, Venus, Earth, Mars, Jupiter, Saturn, Uranus and Neptune. Pluto isn't considered a planet in the Solar System anymore." + }, + { + "id": 135, + "question": "What is the capital of Iraq?", + "answer": "Baghdad", + "info": "Baghdad is the capital of Iraq. It has a population of 7 million people." } ] } diff --git a/bot/utils/decorators.py b/bot/utils/decorators.py index 519e61a9..9e6ef73d 100644 --- a/bot/utils/decorators.py +++ b/bot/utils/decorators.py @@ -285,7 +285,7 @@ def locked() -> t.Union[t.Callable, None]: embed = Embed() embed.colour = Colour.red() - log.debug(f"User tried to invoke a locked command.") + log.debug("User tried to invoke a locked command.") embed.description = ( "You're already using this command. Please wait until " "it is done before you use it again." diff --git a/bot/utils/pagination.py b/bot/utils/pagination.py index 9a7a0382..a4d0cc56 100644 --- a/bot/utils/pagination.py +++ b/bot/utils/pagination.py @@ -128,7 +128,7 @@ class LinePaginator(Paginator): if not lines: if exception_on_empty_embed: - log.exception(f"Pagination asked for empty lines iterable") + log.exception("Pagination asked for empty lines iterable") raise EmptyPaginatorEmbed("No lines to paginate") log.debug("No lines to add to paginator, adding '(nothing to display)' message") @@ -335,7 +335,7 @@ class ImagePaginator(Paginator): if not pages: if exception_on_empty_embed: - log.exception(f"Pagination asked for empty image list") + log.exception("Pagination asked for empty image list") raise EmptyPaginatorEmbed("No images to paginate") log.debug("No images to add to paginator, adding '(no images to display)' message") diff --git a/bot/utils/randomization.py b/bot/utils/randomization.py new file mode 100644 index 00000000..8f47679a --- /dev/null +++ b/bot/utils/randomization.py @@ -0,0 +1,23 @@ +import itertools +import random +import typing as t + + +class RandomCycle: + """ + Cycles through elements from a randomly shuffled iterable, repeating indefinitely. + + The iterable is reshuffled after each full cycle. + """ + + def __init__(self, iterable: t.Iterable) -> None: + self.iterable = list(iterable) + self.index = itertools.cycle(range(len(iterable))) + + def __next__(self) -> t.Any: + idx = next(self.index) + + if idx == 0: + random.shuffle(self.iterable) + + return self.iterable[idx] |