aboutsummaryrefslogtreecommitdiffstats
path: root/bot/exts/evergreen
diff options
context:
space:
mode:
Diffstat (limited to 'bot/exts/evergreen')
-rw-r--r--bot/exts/evergreen/avatar_modification/avatar_modify.py44
-rw-r--r--bot/exts/evergreen/githubinfo.py9
-rw-r--r--bot/exts/evergreen/help.py4
-rw-r--r--bot/exts/evergreen/issues.py6
-rw-r--r--bot/exts/evergreen/movie.py11
-rw-r--r--bot/exts/evergreen/snakes/_converter.py2
-rw-r--r--bot/exts/evergreen/stackoverflow.py88
-rw-r--r--bot/exts/evergreen/tic_tac_toe.py7
-rw-r--r--bot/exts/evergreen/trivia_quiz.py31
-rw-r--r--bot/exts/evergreen/wikipedia.py17
-rw-r--r--bot/exts/evergreen/wolfram.py34
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()