diff options
Diffstat (limited to 'bot/exts/evergreen')
| -rw-r--r-- | bot/exts/evergreen/avatar_modification/avatar_modify.py | 44 | ||||
| -rw-r--r-- | bot/exts/evergreen/githubinfo.py | 9 | ||||
| -rw-r--r-- | bot/exts/evergreen/help.py | 4 | ||||
| -rw-r--r-- | bot/exts/evergreen/issues.py | 6 | ||||
| -rw-r--r-- | bot/exts/evergreen/movie.py | 11 | ||||
| -rw-r--r-- | bot/exts/evergreen/snakes/_converter.py | 2 | ||||
| -rw-r--r-- | bot/exts/evergreen/stackoverflow.py | 88 | ||||
| -rw-r--r-- | bot/exts/evergreen/tic_tac_toe.py | 7 | ||||
| -rw-r--r-- | bot/exts/evergreen/trivia_quiz.py | 31 | ||||
| -rw-r--r-- | bot/exts/evergreen/wikipedia.py | 17 | ||||
| -rw-r--r-- | bot/exts/evergreen/wolfram.py | 34 |
11 files changed, 162 insertions, 91 deletions
diff --git a/bot/exts/evergreen/avatar_modification/avatar_modify.py b/bot/exts/evergreen/avatar_modification/avatar_modify.py index 185cdb38..765c316e 100644 --- a/bot/exts/evergreen/avatar_modification/avatar_modify.py +++ b/bot/exts/evergreen/avatar_modification/avatar_modify.py @@ -9,7 +9,6 @@ from concurrent.futures import ThreadPoolExecutor from pathlib import Path import discord -from aiohttp import client_exceptions from discord.ext import commands from bot.bot import Bot @@ -273,37 +272,6 @@ class AvatarModify(commands.Cog): await self.send_pride_image(ctx, image_bytes, pixels, flag, option) @prideavatar.command() - async def image(self, ctx: commands.Context, url: str, option: str = "lgbt", pixels: int = 64) -> None: - """ - This surrounds the image specified by the URL with a border of a specified LGBT flag. - - This defaults to the LGBT rainbow flag if none is given. - The amount of pixels can be given which determines the thickness of the flag border. - This has a maximum of 512px and defaults to a 64px border. - The full image is 1024x1024. - """ - option = option.lower() - pixels = max(0, min(512, pixels)) - flag = GENDER_OPTIONS.get(option) - if flag is None: - await ctx.send("I don't have that flag!") - return - - async with ctx.typing(): - try: - async with self.bot.http_session.get(url) as response: - if response.status != 200: - await ctx.send("Bad response from provided URL!") - return - image_bytes = await response.read() - except client_exceptions.ClientConnectorError: - raise commands.BadArgument("Cannot connect to provided URL!") - except client_exceptions.InvalidURL: - raise commands.BadArgument("Invalid URL!") - - await self.send_pride_image(ctx, image_bytes, pixels, flag, option) - - @prideavatar.command() async def flags(self, ctx: commands.Context) -> None: """This lists the flags that can be used with the prideavatar command.""" choices = sorted(set(GENDER_OPTIONS.values())) @@ -320,12 +288,9 @@ class AvatarModify(commands.Cog): root_aliases=("spookyavatar", "spookify", "savatar"), brief="Spookify an user's avatar." ) - async def spookyavatar(self, ctx: commands.Context, member: discord.Member = None) -> None: - """This "spookifies" the given user's avatar, with a random *spooky* effect.""" - if member is None: - member = ctx.author - - user = await self._fetch_user(member.id) + async def spookyavatar(self, ctx: commands.Context) -> None: + """This "spookifies" the user's avatar, with a random *spooky* effect.""" + user = await self._fetch_user(ctx.author.id) if not user: await ctx.send(f"{Emojis.cross_mark} Could not get user info.") return @@ -333,7 +298,7 @@ class AvatarModify(commands.Cog): async with ctx.typing(): image_bytes = await user.avatar_url_as(size=1024).read() - file_name = file_safe_name("spooky_avatar", member.display_name) + file_name = file_safe_name("spooky_avatar", ctx.author.display_name) file = await in_executor( PfpEffects.apply_effect, @@ -346,7 +311,6 @@ class AvatarModify(commands.Cog): title="Is this you or am I just really paranoid?", colour=Colours.soft_red ) - embed.set_author(name=member.name, icon_url=member.avatar_url) embed.set_image(url=f"attachment://{file_name}") embed.set_footer(text=f"Made by {ctx.author.display_name}.", icon_url=ctx.author.avatar_url) diff --git a/bot/exts/evergreen/githubinfo.py b/bot/exts/evergreen/githubinfo.py index 27e607e5..d29f3aa9 100644 --- a/bot/exts/evergreen/githubinfo.py +++ b/bot/exts/evergreen/githubinfo.py @@ -1,7 +1,7 @@ import logging import random from datetime import datetime -from urllib.parse import quote +from urllib.parse import quote, quote_plus import discord from discord.ext import commands @@ -37,7 +37,7 @@ class GithubInfo(commands.Cog): async def github_user_info(self, ctx: commands.Context, username: str) -> None: """Fetches a user's GitHub information.""" async with ctx.typing(): - user_data = await self.fetch_data(f"{GITHUB_API_URL}/users/{username}") + user_data = await self.fetch_data(f"{GITHUB_API_URL}/users/{quote_plus(username)}") # User_data will not have a message key if the user exists if "message" in user_data: @@ -91,7 +91,10 @@ class GithubInfo(commands.Cog): ) if user_data["type"] == "User": - embed.add_field(name="Gists", value=f"[{gists}](https://gist.github.com/{quote(username, safe='')})") + embed.add_field( + name="Gists", + value=f"[{gists}](https://gist.github.com/{quote_plus(username, safe='')})" + ) embed.add_field( name=f"Organization{'s' if len(orgs)!=1 else ''}", diff --git a/bot/exts/evergreen/help.py b/bot/exts/evergreen/help.py index 3c9ba4d2..bfb5db17 100644 --- a/bot/exts/evergreen/help.py +++ b/bot/exts/evergreen/help.py @@ -8,7 +8,7 @@ from typing import List, NamedTuple, Union from discord import Colour, Embed, HTTPException, Message, Reaction, User from discord.ext import commands from discord.ext.commands import CheckFailure, Cog as DiscordCog, Command, Context -from fuzzywuzzy import fuzz, process +from rapidfuzz import process from bot import constants from bot.bot import Bot @@ -159,7 +159,7 @@ class HelpSession: # Combine command and cog names choices = list(self._bot.all_commands) + list(self._bot.cogs) - result = process.extractBests(query, choices, scorer=fuzz.ratio, score_cutoff=90) + result = process.extract(query, choices, score_cutoff=90) raise HelpQueryNotFound(f'Query "{query}" not found.', dict(result)) diff --git a/bot/exts/evergreen/issues.py b/bot/exts/evergreen/issues.py index b67aa4a6..00810de8 100644 --- a/bot/exts/evergreen/issues.py +++ b/bot/exts/evergreen/issues.py @@ -135,7 +135,7 @@ class Issues(commands.Cog): # need from the initial API call. if "issues" in json_data["html_url"]: if json_data.get("state") == "open": - emoji = Emojis.issue + emoji = Emojis.issue_open else: emoji = Emojis.issue_closed @@ -149,10 +149,10 @@ class Issues(commands.Cog): if pull_data["draft"]: emoji = Emojis.pull_request_draft elif pull_data["state"] == "open": - emoji = Emojis.pull_request + emoji = Emojis.pull_request_open # When 'merged_at' is not None, this means that the state of the PR is merged elif pull_data["merged_at"] is not None: - emoji = Emojis.merge + emoji = Emojis.pull_request_merged else: emoji = Emojis.pull_request_closed diff --git a/bot/exts/evergreen/movie.py b/bot/exts/evergreen/movie.py index 10638aea..c6af4bcd 100644 --- a/bot/exts/evergreen/movie.py +++ b/bot/exts/evergreen/movie.py @@ -2,7 +2,6 @@ import logging import random from enum import Enum from typing import Any, Dict, List, Tuple -from urllib.parse import urlencode from aiohttp import ClientSession from discord import Embed @@ -121,10 +120,10 @@ class Movie(Cog): "with_genres": genre_id } - url = BASE_URL + "discover/movie?" + urlencode(params) + url = BASE_URL + "discover/movie" # Make discover request to TMDB, return result - async with client.get(url) as resp: + async with client.get(url, params=params) as resp: return await resp.json() async def get_pages(self, client: ClientSession, movies: Dict[str, Any], amount: int) -> List[Tuple[str, str]]: @@ -142,9 +141,11 @@ class Movie(Cog): async def get_movie(self, client: ClientSession, movie: int) -> Dict: """Get Movie by movie ID from TMDB. Return result dictionary.""" - url = BASE_URL + f"movie/{movie}?" + urlencode(MOVIE_PARAMS) + if not isinstance(movie, int): + raise ValueError("Error while fetching movie from TMDB, movie argument must be integer. ") + url = BASE_URL + f"movie/{movie}" - async with client.get(url) as resp: + async with client.get(url, params=MOVIE_PARAMS) as resp: return await resp.json() async def create_page(self, movie: Dict[str, Any]) -> Tuple[str, str]: diff --git a/bot/exts/evergreen/snakes/_converter.py b/bot/exts/evergreen/snakes/_converter.py index 26bde611..c8d1909b 100644 --- a/bot/exts/evergreen/snakes/_converter.py +++ b/bot/exts/evergreen/snakes/_converter.py @@ -5,7 +5,7 @@ from typing import Iterable, List import discord from discord.ext.commands import Context, Converter -from fuzzywuzzy import fuzz +from rapidfuzz import fuzz from bot.exts.evergreen.snakes._utils import SNAKE_RESOURCES from bot.utils import disambiguate diff --git a/bot/exts/evergreen/stackoverflow.py b/bot/exts/evergreen/stackoverflow.py new file mode 100644 index 00000000..40f149c9 --- /dev/null +++ b/bot/exts/evergreen/stackoverflow.py @@ -0,0 +1,88 @@ +import logging +from html import unescape +from urllib.parse import quote_plus + +from discord import Embed, HTTPException +from discord.ext import commands + +from bot import bot +from bot.constants import Colours, Emojis + +logger = logging.getLogger(__name__) + +BASE_URL = "https://api.stackexchange.com/2.2/search/advanced" +SO_PARAMS = { + "order": "desc", + "sort": "activity", + "site": "stackoverflow" +} +SEARCH_URL = "https://stackoverflow.com/search?q={query}" +ERR_EMBED = Embed( + title="Error in fetching results from Stackoverflow", + description=( + "Sorry, there was en error while trying to fetch data from the Stackoverflow website. Please try again in some " + "time. If this issue persists, please contact the staff or send a message in #dev-contrib." + ), + color=Colours.soft_red +) + + +class Stackoverflow(commands.Cog): + """Contains command to interact with stackoverflow from discord.""" + + def __init__(self, bot: bot.Bot): + self.bot = bot + + @commands.command(aliases=["so"]) + @commands.cooldown(1, 15, commands.cooldowns.BucketType.user) + async def stackoverflow(self, ctx: commands.Context, *, search_query: str) -> None: + """Sends the top 5 results of a search query from stackoverflow.""" + params = SO_PARAMS | {"q": search_query} + async with self.bot.http_session.get(url=BASE_URL, params=params) as response: + if response.status == 200: + data = await response.json() + else: + logger.error(f'Status code is not 200, it is {response.status}') + await ctx.send(embed=ERR_EMBED) + return + if not data['items']: + no_search_result = Embed( + title=f"No search results found for {search_query}", + color=Colours.soft_red + ) + await ctx.send(embed=no_search_result) + return + + top5 = data["items"][:5] + encoded_search_query = quote_plus(search_query) + embed = Embed( + title="Search results - Stackoverflow", + url=SEARCH_URL.format(query=encoded_search_query), + description=f"Here are the top {len(top5)} results:", + color=Colours.orange + ) + for item in top5: + embed.add_field( + name=unescape(item['title']), + value=( + f"[{Emojis.reddit_upvote} {item['score']} " + f"{Emojis.stackoverflow_views} {item['view_count']} " + f"{Emojis.reddit_comments} {item['answer_count']} " + f"{Emojis.stackoverflow_tag} {', '.join(item['tags'][:3])}]" + f"({item['link']})" + ), + inline=False) + embed.set_footer(text="View the original link for more results.") + try: + await ctx.send(embed=embed) + except HTTPException: + search_query_too_long = Embed( + title="Your search query is too long, please try shortening your search query", + color=Colours.soft_red + ) + await ctx.send(embed=search_query_too_long) + + +def setup(bot: bot.Bot) -> None: + """Load the Stackoverflow Cog.""" + bot.add_cog(Stackoverflow(bot)) diff --git a/bot/exts/evergreen/tic_tac_toe.py b/bot/exts/evergreen/tic_tac_toe.py index 48e8e142..164e056d 100644 --- a/bot/exts/evergreen/tic_tac_toe.py +++ b/bot/exts/evergreen/tic_tac_toe.py @@ -318,9 +318,14 @@ class TicTacToe(Cog): return game = self.games[game_id - 1] + if game.draw: + description = f"{game.players[0]} vs {game.players[1]} (draw)\n\n{game.format_board()}" + else: + description = f"{game.winner} :trophy: vs {game.loser}\n\n{game.format_board()}" + embed = discord.Embed( title=f"Match #{game_id} Game Board", - description=f"{game.winner} :trophy: vs {game.loser}\n\n{game.format_board()}" + description=description, ) await ctx.send(embed=embed) diff --git a/bot/exts/evergreen/trivia_quiz.py b/bot/exts/evergreen/trivia_quiz.py index a8d10afd..bc25cbf7 100644 --- a/bot/exts/evergreen/trivia_quiz.py +++ b/bot/exts/evergreen/trivia_quiz.py @@ -9,7 +9,7 @@ from typing import Callable, List, Optional import discord from discord.ext import commands -from fuzzywuzzy import fuzz +from rapidfuzz import fuzz from bot.bot import Bot from bot.constants import Colours, NEGATIVE_REPLIES, Roles @@ -17,8 +17,8 @@ from bot.constants import Colours, NEGATIVE_REPLIES, Roles logger = logging.getLogger(__name__) DEFAULT_QUESTION_LIMIT = 6 -STANDARD_VARIATION_TOLERANCE = 83 -DYNAMICALLY_GEN_VARIATION_TOLERANCE = 95 +STANDARD_VARIATION_TOLERANCE = 88 +DYNAMICALLY_GEN_VARIATION_TOLERANCE = 97 WRONG_ANS_RESPONSE = [ "No one answered correctly!", @@ -210,6 +210,8 @@ class TriviaQuiz(commands.Cog): "retro": "Questions related to retro gaming.", "math": "General questions about mathematics ranging from grade 8 to grade 12.", "science": "Put your understanding of science to the test!", + "cs": "A large variety of computer science questions.", + "python": "Trivia on our amazing language, Python!", } @staticmethod @@ -225,10 +227,12 @@ class TriviaQuiz(commands.Cog): Start a quiz! Questions for the quiz can be selected from the following categories: - - general: Test your general knowledge. (default) + - general: Test your general knowledge. - retro: Questions related to retro gaming. - math: General questions about mathematics ranging from grade 8 to grade 12. - science: Put your understanding of science to the test! + - cs: A large variety of computer science questions. + - python: Trivia on our amazing language, Python! (More to come!) """ @@ -290,7 +294,7 @@ class TriviaQuiz(commands.Cog): start_embed = self.make_start_embed(category) await ctx.send(embed=start_embed) # send an embed with the rules - await asyncio.sleep(1) + await asyncio.sleep(5) done_question = [] hint_no = 0 @@ -430,21 +434,18 @@ class TriviaQuiz(commands.Cog): """Generate a starting/introduction embed for the quiz.""" start_embed = discord.Embed( colour=Colours.blue, - title="Quiz game starting!", + title="A quiz game is starting!", description=( - f"This game consists of {self.question_limit + 1} questions.\n" - "**Rules: **No cheating and have fun!\n" + f"This game consists of {self.question_limit + 1} questions.\n\n" + "**Rules: **\n" + "1. Only enclose your answer in backticks when the question tells you to.\n" + "2. If the question specifies an answer format, follow it or else it won't be accepted.\n" + "3. You have 30s per question. Points for each question reduces by 25 after 10s or after a hint.\n" + "4. No cheating and have fun!\n\n" f"**Category**: {category}" ), ) - start_embed.set_footer( - text=( - "Points for each question reduces by 25 after 10s or after a hint. " - "Total time is 30s per question" - ) - ) - return start_embed @staticmethod diff --git a/bot/exts/evergreen/wikipedia.py b/bot/exts/evergreen/wikipedia.py index 83937438..7b96cb7b 100644 --- a/bot/exts/evergreen/wikipedia.py +++ b/bot/exts/evergreen/wikipedia.py @@ -13,9 +13,18 @@ from bot.utils import LinePaginator log = logging.getLogger(__name__) SEARCH_API = ( - "https://en.wikipedia.org/w/api.php?action=query&list=search&prop=info&inprop=url&utf8=&" - "format=json&origin=*&srlimit={number_of_results}&srsearch={string}" + "https://en.wikipedia.org/w/api.php" ) +WIKI_PARAMS = { + "action": "query", + "list": "search", + "prop": "info", + "inprop": "url", + "utf8": "", + "format": "json", + "origin": "*", + +} WIKI_THUMBNAIL = ( "https://upload.wikimedia.org/wikipedia/en/thumb/8/80/Wikipedia-logo-v2.svg" "/330px-Wikipedia-logo-v2.svg.png" @@ -35,8 +44,8 @@ class WikipediaSearch(commands.Cog): async def wiki_request(self, channel: TextChannel, search: str) -> Optional[List[str]]: """Search wikipedia search string and return formatted first 10 pages found.""" - url = SEARCH_API.format(number_of_results=10, string=search) - async with self.bot.http_session.get(url=url) as resp: + params = WIKI_PARAMS | {"srlimit": 10, "srsearch": search} + async with self.bot.http_session.get(url=SEARCH_API, params=params) as resp: if resp.status == 200: raw_data = await resp.json() number_of_results = raw_data["query"]["searchinfo"]["totalhits"] diff --git a/bot/exts/evergreen/wolfram.py b/bot/exts/evergreen/wolfram.py index d23afd6f..26674d37 100644 --- a/bot/exts/evergreen/wolfram.py +++ b/bot/exts/evergreen/wolfram.py @@ -1,7 +1,7 @@ import logging from io import BytesIO from typing import Callable, List, Optional, Tuple -from urllib import parse +from urllib.parse import urlencode import arrow import discord @@ -17,7 +17,7 @@ log = logging.getLogger(__name__) APPID = Wolfram.key DEFAULT_OUTPUT_FORMAT = "JSON" -QUERY = "http://api.wolframalpha.com/v2/{request}?{data}" +QUERY = "http://api.wolframalpha.com/v2/{request}" WOLF_IMAGE = "https://www.symbols.com/gi.php?type=1&id=2886&i=1" MAX_PODS = 20 @@ -108,7 +108,7 @@ def custom_cooldown(*ignore: List[int]) -> Callable: async def get_pod_pages(ctx: Context, bot: Bot, query: str) -> Optional[List[Tuple]]: """Get the Wolfram API pod pages for the provided query.""" async with ctx.typing(): - url_str = parse.urlencode({ + params = { "input": query, "appid": APPID, "output": DEFAULT_OUTPUT_FORMAT, @@ -116,27 +116,27 @@ async def get_pod_pages(ctx: Context, bot: Bot, query: str) -> Optional[List[Tup "location": "the moon", "latlong": "0.0,0.0", "ip": "1.1.1.1" - }) - request_url = QUERY.format(request="query", data=url_str) + } + request_url = QUERY.format(request="query") - async with bot.http_session.get(request_url) as response: + async with bot.http_session.get(url=request_url, params=params) as response: json = await response.json(content_type="text/plain") result = json["queryresult"] - + log_full_url = f"{request_url}?{urlencode(params)}" 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}" + f"processing a wolfram request: {log_full_url}, 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}") + log.warning(f"Something went wrong getting a response from wolfram: {log_full_url}, Response: {json}") await send_embed(ctx, message) return @@ -172,18 +172,18 @@ class Wolfram(Cog): @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({ + params = { "i": query, "appid": APPID, "location": "the moon", "latlong": "0.0,0.0", "ip": "1.1.1.1" - }) - query = QUERY.format(request="simple", data=url_str) + } + request_url = QUERY.format(request="simple") # Give feedback that the bot is working. async with ctx.typing(): - async with self.bot.http_session.get(query) as response: + async with self.bot.http_session.get(url=request_url, params=params) as response: status = response.status image_bytes = await response.read() @@ -257,18 +257,18 @@ class Wolfram(Cog): @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({ + params = { "i": query, "appid": APPID, "location": "the moon", "latlong": "0.0,0.0", "ip": "1.1.1.1" - }) - query = QUERY.format(request="result", data=url_str) + } + request_url = QUERY.format(request="result") # Give feedback that the bot is working. async with ctx.typing(): - async with self.bot.http_session.get(query) as response: + async with self.bot.http_session.get(url=request_url, params=params) as response: status = response.status response_text = await response.text() |