diff --git a/didier/cogs/fun.py b/didier/cogs/fun.py index fe78275..f320ac4 100644 --- a/didier/cogs/fun.py +++ b/didier/cogs/fun.py @@ -5,12 +5,10 @@ from discord import app_commands from discord.ext import commands from database.crud.dad_jokes import get_random_dad_joke -from database.crud.memes import get_all_memes, get_meme_by_name +from database.crud.memes import get_meme_by_name from didier import Didier from didier.data.apis.imgflip import generate_meme from didier.exceptions.no_match import expect -from didier.menus.common import Menu -from didier.menus.memes import MemeSource from didier.views.modals import GenerateMeme @@ -50,29 +48,11 @@ class Fun(commands.Cog): Example: `memegen a b c d` will be parsed as `template: "a"`, `fields: ["b", "c", "d"]` Example: `memegen "a b" "c d"` will be parsed as `template: "a b"`, `fields: ["c d"]` - - In case a template only has 1 field, quotes aren't required and your arguments will be combined into one field. - - Example: if template `a` only has 1 field, - `memegen a b c d` will be parsed as `template: "a"`, `fields: ["bcd"]` """ async with ctx.typing(): meme = await self._do_generate_meme(template, shlex.split(fields)) return await ctx.reply(meme, mention_author=False) - @memegen_msg.command(name="list", aliases=["ls"]) - async def memegen_ls_msg(self, ctx: commands.Context): - """Get a list of all available meme templates. - - This command does _not_ have a /slash variant, as the memegen /slash commands provide autocompletion. - """ - async with self.client.postgres_session as session: - results = await get_all_memes(session) - - source = MemeSource(ctx, results) - menu = Menu(source) - await menu.start(ctx) - @memegen_msg.command(name="preview", aliases=["p"]) async def memegen_preview_msg(self, ctx: commands.Context, template: str): """Generate a preview for the meme template `template`, to see how the fields are structured.""" diff --git a/didier/cogs/other.py b/didier/cogs/other.py index fac1a04..20c4fd8 100644 --- a/didier/cogs/other.py +++ b/didier/cogs/other.py @@ -7,10 +7,9 @@ from discord.ext import commands from database.crud.links import get_link_by_name from database.schemas import Link from didier import Didier -from didier.data.apis import disease_sh, inspirobot, urban_dictionary +from didier.data.apis import inspirobot, urban_dictionary from didier.data.embeds.google import GoogleSearch from didier.data.scrapers import google -from didier.utils.discord.autocompletion.country import autocomplete_country class Other(commands.Cog): @@ -21,34 +20,16 @@ class Other(commands.Cog): def __init__(self, client: Didier): self.client = client - @commands.hybrid_command(name="corona", aliases=["covid", "rona"]) - async def covid(self, ctx: commands.Context, country: str = "Belgium"): - """Show Covid-19 info for a specific country. - - By default, this will fetch the numbers for Belgium. - - To get worldwide stats, use `all`, `global`, `world`, or `worldwide`. - """ - async with ctx.typing(): - if country.lower() in ["all", "global", "world", "worldwide"]: - data = await disease_sh.get_global_info(self.client.http_session) - else: - data = await disease_sh.get_country_info(self.client.http_session, country) - - await ctx.reply(embed=data.to_embed(), mention_author=False) - - @covid.autocomplete("country") - async def _covid_country_autocomplete(self, interaction: discord.Interaction, value: str): - """Autocompletion for the 'country'-parameter""" - return autocomplete_country(value)[:25] - @commands.hybrid_command( name="define", aliases=["ud", "urban"], description="Look up the definition of a word on the Urban Dictionary" ) async def define(self, ctx: commands.Context, *, query: str): """Look up the definition of `query` on the Urban Dictionary.""" async with ctx.typing(): - definitions = await urban_dictionary.lookup(self.client.http_session, query) + status_code, definitions = await urban_dictionary.lookup(self.client.http_session, query) + if not definitions: + return await ctx.reply(f"Something went wrong (status {status_code})") + await ctx.reply(embed=definitions[0].to_embed(), mention_author=False) @commands.hybrid_command(name="google", description="Google search") diff --git a/didier/cogs/school.py b/didier/cogs/school.py index aacdc4d..877cc3f 100644 --- a/didier/cogs/school.py +++ b/didier/cogs/school.py @@ -47,12 +47,12 @@ class School(commands.Cog): Schedules are personalized based on your roles in the server. If your schedule doesn't look right, make sure that you've got the correct roles selected. In case you do, ping D STIJN. """ + if day_dt is None: + day_dt = date.today() + + day_dt = skip_weekends(day_dt) + async with ctx.typing(): - if day_dt is None: - day_dt = date.today() - - day_dt = skip_weekends(day_dt) - try: member_instance = to_main_guild_member(self.client, ctx.author) diff --git a/didier/cogs/tasks.py b/didier/cogs/tasks.py index 37b1786..eaeaebe 100644 --- a/didier/cogs/tasks.py +++ b/didier/cogs/tasks.py @@ -2,7 +2,6 @@ import datetime import random import traceback -import discord from discord.ext import commands, tasks # type: ignore # Strange & incorrect Mypy error from overrides import overrides @@ -78,14 +77,7 @@ class Tasks(commands.Cog): Invoking the group itself shows the time until the next iteration """ - embed = discord.Embed(colour=discord.Colour.blue(), title="Tasks") - - for name, task in self._tasks.items(): - next_iter = task.next_iteration - timestamp = f"" if next_iter is not None else "N/A" - embed.add_field(name=name, value=timestamp) - - await ctx.reply(embed=embed, mention_author=False) + raise NotImplementedError() @tasks_group.command(name="Force", case_insensitive=True, usage="[Task]") async def force_task(self, ctx: commands.Context, name: str): diff --git a/didier/data/apis/disease_sh.py b/didier/data/apis/disease_sh.py deleted file mode 100644 index 809cdcb..0000000 --- a/didier/data/apis/disease_sh.py +++ /dev/null @@ -1,38 +0,0 @@ -from aiohttp import ClientSession - -from didier.data.embeds.disease_sh import CovidData -from didier.utils.http.requests import ensure_get - -__all__ = ["get_country_info", "get_global_info"] - - -async def get_country_info(http_session: ClientSession, country: str) -> CovidData: - """Fetch the info for a given country for today and yesterday""" - endpoint = f"https://disease.sh/v3/covid-19/countries/{country}" - - params = {"yesterday": 0, "strict": 1, "allowNull": 0} - async with ensure_get(http_session, endpoint, params=params) as response: - today = response - - params["yesterday"] = 1 - async with ensure_get(http_session, endpoint, params=params) as response: - yesterday = response - - data = {"today": today, "yesterday": yesterday} - return CovidData.parse_obj(data) - - -async def get_global_info(http_session: ClientSession) -> CovidData: - """Fetch the global info for today and yesterday""" - endpoint = "https://disease.sh/v3/covid-19/all" - - params = {"yesterday": 0, "allowNull": 0} - async with ensure_get(http_session, endpoint, params=params) as response: - today = response - - params["yesterday"] = 1 - async with ensure_get(http_session, endpoint, params=params) as response: - yesterday = response - - data = {"today": today, "yesterday": yesterday} - return CovidData.parse_obj(data) diff --git a/didier/data/apis/urban_dictionary.py b/didier/data/apis/urban_dictionary.py index 6d81934..e0ebf15 100644 --- a/didier/data/apis/urban_dictionary.py +++ b/didier/data/apis/urban_dictionary.py @@ -1,7 +1,8 @@ +from http import HTTPStatus + from aiohttp import ClientSession from didier.data.embeds.urban_dictionary import Definition -from didier.utils.http.requests import ensure_get __all__ = ["lookup", "PER_PAGE"] @@ -9,9 +10,13 @@ __all__ = ["lookup", "PER_PAGE"] PER_PAGE = 10 -async def lookup(http_session: ClientSession, query: str) -> list[Definition]: +async def lookup(http_session: ClientSession, query: str) -> tuple[int, list[Definition]]: """Fetch the Urban Dictionary definitions for a given word""" url = "https://api.urbandictionary.com/v0/define" - async with ensure_get(http_session, url, params={"term": query}) as response: - return list(map(Definition.parse_obj, response["list"])) + async with http_session.get(url, params={"term": query}) as response: + if response.status != HTTPStatus.OK: + return response.status, [] + + response_json = await response.json() + return 200, list(map(Definition.parse_obj, response_json["list"])) diff --git a/didier/data/embeds/disease_sh.py b/didier/data/embeds/disease_sh.py deleted file mode 100644 index d619419..0000000 --- a/didier/data/embeds/disease_sh.py +++ /dev/null @@ -1,100 +0,0 @@ -import discord -from overrides import overrides -from pydantic import BaseModel, Field, validator - -from didier.data.embeds.base import EmbedPydantic - -__all__ = ["CovidData"] - - -class _CovidNumbers(BaseModel): - """Covid numbers for a country - - For worldwide numbers, country_info will be None - """ - - updated: int - country: str = "Worldwide" - cases: int - today_cases: int = Field(alias="todayCases") - deaths: int - today_deaths: int = Field(alias="todayDeaths") - recovered: int - today_recovered: int = Field(alias="todayRecovered") - active: int - tests: int - - @validator("updated") - def updated_to_seconds(cls, value: int) -> int: - """Turn the updated field into seconds instead of milliseconds""" - return int(value) // 1000 - - -class CovidData(EmbedPydantic): - """Covid information from two days combined into one model""" - - today: _CovidNumbers - yesterday: _CovidNumbers - - @overrides - def to_embed(self, **kwargs) -> discord.Embed: - embed = discord.Embed(colour=discord.Colour.red(), title=f"Coronatracker {self.today.country}") - embed.description = f"Last update: " - embed.set_thumbnail(url="https://i.imgur.com/aWnDuBt.png") - - cases_indicator = self._trend_indicator(self.today.today_cases, self.yesterday.today_cases) - embed.add_field( - name="Cases (Today)", - value=f"{self.today.cases:,} **({self.today.today_cases:,})** {cases_indicator}".replace(",", "."), - inline=False, - ) - - active_indicator = self._trend_indicator(self.today.active, self.yesterday.active) - active_diff = self.today.active - self.yesterday.active - embed.add_field( - name="Active (Today)", - value=f"{self.today.active:,} **({self._with_sign(active_diff)})** {active_indicator}".replace(",", "."), - inline=False, - ) - - deaths_indicator = self._trend_indicator(self.today.today_deaths, self.yesterday.today_deaths) - embed.add_field( - name="Deaths (Today)", - value=f"{self.today.deaths:,} **({self.today.today_deaths:,})** {deaths_indicator}".replace(",", "."), - inline=False, - ) - - recovered_indicator = self._trend_indicator(self.today.today_recovered, self.yesterday.today_recovered) - embed.add_field( - name="Recovered (Today)", - value=f"{self.today.recovered} **({self.today.today_recovered:,})** {recovered_indicator}".replace( - ",", "." - ), - inline=False, - ) - - tests_diff = self.today.tests - self.yesterday.tests - embed.add_field( - name="Tests Administered (Today)", - value=f"{self.today.tests:,} **({tests_diff:,})**".replace(",", "."), - inline=False, - ) - - return embed - - def _with_sign(self, value: int) -> str: - """Prepend a + symbol if a number is positive""" - if value > 0: - return f"+{value:,}" - - return f"{value:,}" - - def _trend_indicator(self, today: int, yesterday: int) -> str: - """Function that returns a rise/decline indicator for the target key.""" - if today > yesterday: - return ":small_red_triangle:" - - if yesterday > today: - return ":small_red_triangle_down:" - - return "" diff --git a/didier/data/embeds/logging_embed.py b/didier/data/embeds/logging_embed.py deleted file mode 100644 index 784cc3f..0000000 --- a/didier/data/embeds/logging_embed.py +++ /dev/null @@ -1,21 +0,0 @@ -import logging - -import discord - -__all__ = ["create_logging_embed"] - - -def create_logging_embed(level: int, message: str) -> discord.Embed: - """Create an embed to send to the logging channel""" - colours = { - logging.DEBUG: discord.Colour.light_gray, - logging.ERROR: discord.Colour.red(), - logging.INFO: discord.Colour.blue(), - logging.WARNING: discord.Colour.yellow(), - } - - colour = colours.get(level, discord.Colour.red()) - embed = discord.Embed(colour=colour, title="Logging") - embed.description = message - - return embed diff --git a/didier/data/embeds/schedules.py b/didier/data/embeds/schedules.py index ed03a33..9eefd61 100644 --- a/didier/data/embeds/schedules.py +++ b/didier/data/embeds/schedules.py @@ -190,7 +190,8 @@ async def parse_schedule_from_content(content: str, *, database_session: AsyncSe if code not in course_codes: course = await get_course_by_code(database_session, code) if course is None: - continue + # raise ValueError(f"Unable to find course with code {code} (event {event.name})") # noqa: E800 + continue # TODO uncomment the line above after all courses have been added course_codes[code] = course diff --git a/didier/didier.py b/didier/didier.py index 050def6..249e5b2 100644 --- a/didier/didier.py +++ b/didier/didier.py @@ -16,7 +16,6 @@ from database.crud import command_stats, custom_commands from database.engine import DBSession from database.utils.caches import CacheManager from didier.data.embeds.error_embed import create_error_embed -from didier.data.embeds.logging_embed import create_logging_embed from didier.data.embeds.schedules import Schedule, parse_schedule from didier.exceptions import HTTPException, NoMatch from didier.utils.discord.prefix import get_prefix @@ -182,16 +181,15 @@ class Didier(commands.Bot): async def _log(self, level: int, message: str, log_to_discord: bool = True): """Log a message to the logging file, and optionally to the configured channel""" methods = { - logging.DEBUG: logger.debug, logging.ERROR: logger.error, - logging.INFO: logger.info, logging.WARNING: logger.warning, } methods.get(level, logger.error)(message) if log_to_discord: - embed = create_logging_embed(level, message) - await self.error_channel.send(embed=embed) + # TODO pretty embed + # different colours per level? + await self.error_channel.send(message) async def log_error(self, message: str, log_to_discord: bool = True): """Log an error message""" diff --git a/didier/menus/bookmarks.py b/didier/menus/bookmarks.py index d4c1e11..101cd6e 100644 --- a/didier/menus/bookmarks.py +++ b/didier/menus/bookmarks.py @@ -22,7 +22,7 @@ class BookmarkSource(PageSource[Bookmark]): description = "" - for bookmark in self.get_page_data(page): + for bookmark in self.dataset[page : page + self.per_page]: description += f"`#{bookmark.bookmark_id}`: [{bookmark.label}]({bookmark.jump_url})\n" embed.description = description.strip() diff --git a/didier/menus/common.py b/didier/menus/common.py index d1cb3b1..5d7a8e4 100644 --- a/didier/menus/common.py +++ b/didier/menus/common.py @@ -52,10 +52,6 @@ class PageSource(ABC, Generic[T]): """Method that builds the list of embeds from the input data""" raise NotImplementedError - def get_page_data(self, page: int) -> list[T]: - """Get the chunk of the dataset for page [page]""" - return self.dataset[page : page + self.per_page] - class Menu(discord.ui.View): """Base class for a menu""" diff --git a/didier/menus/memes.py b/didier/menus/memes.py deleted file mode 100644 index d7a4be4..0000000 --- a/didier/menus/memes.py +++ /dev/null @@ -1,27 +0,0 @@ -import discord -from discord.ext import commands -from overrides import overrides - -from database.schemas import MemeTemplate -from didier.menus.common import PageSource - -__all__ = ["MemeSource"] - - -class MemeSource(PageSource[MemeTemplate]): - """PageSource for meme templates""" - - @overrides - def create_embeds(self, ctx: commands.Context): - for page in range(self.page_count): - # The colour of the embed is (69,4,20) with the values +100 because they were too dark - embed = discord.Embed(title="Meme Templates", colour=discord.Colour.from_rgb(169, 14, 120)) - - description_data = [] - for template in self.get_page_data(page): - description_data.append(f"{template.name} ({template.field_count})") - - embed.description = "\n".join(description_data) - embed.set_footer(text="Format: Template Name (Field Count)") - - self.embeds.append(embed) diff --git a/didier/utils/discord/autocompletion/country.py b/didier/utils/discord/autocompletion/country.py deleted file mode 100644 index f11a110..0000000 --- a/didier/utils/discord/autocompletion/country.py +++ /dev/null @@ -1,250 +0,0 @@ -from discord import app_commands - -__all__ = ["autocomplete_country"] - -# This list was parsed out of a request to https://disease.sh/v3/covid-19/countries -country_list = [ - "Afghanistan", - "Albania", - "Algeria", - "Andorra", - "Angola", - "Anguilla", - "Antigua and Barbuda", - "Argentina", - "Armenia", - "Aruba", - "Australia", - "Austria", - "Azerbaijan", - "Bahamas", - "Bahrain", - "Bangladesh", - "Barbados", - "Belarus", - "Belgium", - "Belize", - "Benin", - "Bermuda", - "Bhutan", - "Bolivia", - "Bosnia", - "Botswana", - "Brazil", - "British Virgin Islands", - "Brunei", - "Bulgaria", - "Burkina Faso", - "Burundi", - "Cabo Verde", - "Cambodia", - "Cameroon", - "Canada", - "Caribbean Netherlands", - "Cayman Islands", - "Central African Republic", - "Chad", - "Channel Islands", - "Chile", - "China", - "Colombia", - "Comoros", - "Congo", - "Cook Islands", - "Costa Rica", - "Croatia", - "Cuba", - "Curaçao", - "Cyprus", - "Czechia", - "Côte d'Ivoire", - "DRC", - "Denmark", - "Diamond Princess", - "Djibouti", - "Dominica", - "Dominican Republic", - "Ecuador", - "Egypt", - "El Salvador", - "Equatorial Guinea", - "Eritrea", - "Estonia", - "Ethiopia", - "Falkland Islands (Malvinas)", - "Faroe Islands", - "Fiji", - "Finland", - "France", - "French Guiana", - "French Polynesia", - "Gabon", - "Gambia", - "Georgia", - "Germany", - "Ghana", - "Gibraltar", - "Greece", - "Greenland", - "Grenada", - "Guadeloupe", - "Guatemala", - "Guinea", - "Guinea-Bissau", - "Guyana", - "Haiti", - "Holy See (Vatican City State)", - "Honduras", - "Hong Kong", - "Hungary", - "Iceland", - "India", - "Indonesia", - "Iran", - "Iraq", - "Ireland", - "Isle of Man", - "Israel", - "Italy", - "Jamaica", - "Japan", - "Jordan", - "Kazakhstan", - "Kenya", - "Kiribati", - "Kuwait", - "Kyrgyzstan", - "Lao People's Democratic Republic", - "Latvia", - "Lebanon", - "Lesotho", - "Liberia", - "Libyan Arab Jamahiriya", - "Liechtenstein", - "Lithuania", - "Luxembourg", - "MS Zaandam", - "Macao", - "Macedonia", - "Madagascar", - "Malawi", - "Malaysia", - "Maldives", - "Mali", - "Malta", - "Marshall Islands", - "Martinique", - "Mauritania", - "Mauritius", - "Mayotte", - "Mexico", - "Micronesia", - "Moldova", - "Monaco", - "Mongolia", - "Montenegro", - "Montserrat", - "Morocco", - "Mozambique", - "Myanmar", - "N. Korea", - "Namibia", - "Nauru", - "Nepal", - "Netherlands", - "New Caledonia", - "New Zealand", - "Nicaragua", - "Niger", - "Nigeria", - "Niue", - "Norway", - "Oman", - "Pakistan", - "Palau", - "Palestine", - "Panama", - "Papua New Guinea", - "Paraguay", - "Peru", - "Philippines", - "Poland", - "Portugal", - "Qatar", - "Romania", - "Russia", - "Rwanda", - "Réunion", - "S. Korea", - "Saint Helena", - "Saint Kitts and Nevis", - "Saint Lucia", - "Saint Martin", - "Saint Pierre Miquelon", - "Saint Vincent and the Grenadines", - "Samoa", - "San Marino", - "Sao Tome and Principe", - "Saudi Arabia", - "Senegal", - "Serbia", - "Seychelles", - "Sierra Leone", - "Singapore", - "Sint Maarten", - "Slovakia", - "Slovenia", - "Solomon Islands", - "Somalia", - "South Africa", - "South Sudan", - "Spain", - "Sri Lanka", - "St. Barth", - "Sudan", - "Suriname", - "Swaziland", - "Sweden", - "Switzerland", - "Syrian Arab Republic", - "Taiwan", - "Tajikistan", - "Tanzania", - "Thailand", - "Timor-Leste", - "Togo", - "Tonga", - "Trinidad and Tobago", - "Tunisia", - "Turkey", - "Turks and Caicos Islands", - "Tuvalu", - "UAE", - "UK", - "USA", - "Uganda", - "Ukraine", - "Uruguay", - "Uzbekistan", - "Vanuatu", - "Venezuela", - "Vietnam", - "Wallis and Futuna", - "Western Sahara", - "Yemen", - "Zambia", - "Zimbabwe", -] - - -def autocomplete_country(argument: str) -> list[app_commands.Choice]: - """Autocompletion for country names""" - argument = argument.lower() - - global_autocomplete = ["Global"] - - return [ - app_commands.Choice(name=country, value=country) - for country in global_autocomplete + country_list - if argument in country.lower() - ] diff --git a/didier/utils/http/requests.py b/didier/utils/http/requests.py index 4d1ef76..ffcb498 100644 --- a/didier/utils/http/requests.py +++ b/didier/utils/http/requests.py @@ -1,6 +1,6 @@ import logging from contextlib import asynccontextmanager -from typing import AsyncGenerator, Optional +from typing import AsyncGenerator from aiohttp import ClientResponse, ClientSession, ContentTypeError @@ -19,10 +19,10 @@ def request_successful(response: ClientResponse) -> bool: @asynccontextmanager async def ensure_get( - http_session: ClientSession, endpoint: str, *, params: Optional[dict] = None, log_exceptions: bool = True + http_session: ClientSession, endpoint: str, *, log_exceptions: bool = True ) -> AsyncGenerator[dict, None]: """Context manager that automatically raises an exception if a GET-request fails""" - async with http_session.get(endpoint, params=params) as response: + async with http_session.get(endpoint) as response: try: content = await response.json() except ContentTypeError: diff --git a/requirements.txt b/requirements.txt index 322d468..f30a3c1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,6 +3,7 @@ alembic==1.8.0 asyncpg==0.25.0 beautifulsoup4==4.11.1 discord.py==2.0.1 +git+https://github.com/Rapptz/discord-ext-menus@8686b5d environs==9.5.0 feedparser==6.0.10 ics==0.7.2