mirror of
https://github.com/wlinator/luminara.git
synced 2024-10-02 18:03:12 +00:00
Add xkcd command (not functional)
This commit is contained in:
parent
118c56c44a
commit
47e7d7ad71
2 changed files with 394 additions and 0 deletions
73
modules/misc/xkcd.py
Normal file
73
modules/misc/xkcd.py
Normal file
|
@ -0,0 +1,73 @@
|
|||
from discord.ext import commands
|
||||
from lib.const import CONST
|
||||
from ui.embeds import builder
|
||||
from discord import app_commands
|
||||
import discord
|
||||
from wrappers.xkcd import Client, HttpError
|
||||
from typing import Optional
|
||||
|
||||
_xkcd = Client()
|
||||
|
||||
|
||||
async def print_comic(
|
||||
interaction: discord.Interaction,
|
||||
latest: bool = False,
|
||||
number: Optional[int] = None,
|
||||
) -> None:
|
||||
try:
|
||||
if latest:
|
||||
comic = _xkcd.get_latest_comic(raw_comic_image=True)
|
||||
elif number is not None:
|
||||
comic = _xkcd.get_comic(number, raw_comic_image=True)
|
||||
else:
|
||||
comic = _xkcd.get_random_comic(raw_comic_image=True)
|
||||
|
||||
await interaction.followup.send(
|
||||
embed=builder.create_success_embed(
|
||||
interaction,
|
||||
author_text=CONST.STRINGS["xkcd_title"].format(comic.id, comic.title),
|
||||
description=CONST.STRINGS["xkcd_description"].format(
|
||||
comic.explanation_url,
|
||||
comic.comic_url,
|
||||
),
|
||||
footer_text=CONST.STRINGS["xkcd_footer"],
|
||||
image_url=comic.image_url,
|
||||
show_name=False,
|
||||
),
|
||||
)
|
||||
|
||||
except HttpError:
|
||||
await interaction.followup.send(
|
||||
embed=builder.create_error_embed(
|
||||
interaction,
|
||||
author_text=CONST.STRINGS["xkcd_not_found_author"],
|
||||
description=CONST.STRINGS["xkcd_not_found"],
|
||||
footer_text=CONST.STRINGS["xkcd_footer"],
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class Xkcd(commands.Cog):
|
||||
def __init__(self, bot: commands.Bot):
|
||||
self.bot = bot
|
||||
|
||||
xkcd: app_commands.Group = app_commands.Group(
|
||||
name="xkcd",
|
||||
description="Xkcd commands",
|
||||
)
|
||||
|
||||
@xkcd.command(name="latest", description="Get the latest xkcd comic")
|
||||
async def xkcd_latest(self, interaction: discord.Interaction) -> None:
|
||||
await print_comic(interaction, latest=True)
|
||||
|
||||
@xkcd.command(name="random", description="Get a random xkcd comic")
|
||||
async def xkcd_random(self, interaction: discord.Interaction) -> None:
|
||||
await print_comic(interaction)
|
||||
|
||||
@xkcd.command(name="search", description="Search for an xkcd comic")
|
||||
async def xkcd_search(self, interaction: discord.Interaction, id: int) -> None:
|
||||
await print_comic(interaction, number=id)
|
||||
|
||||
|
||||
async def setup(bot: commands.Bot) -> None:
|
||||
await bot.add_cog(Xkcd(bot))
|
321
wrappers/xkcd.py
Normal file
321
wrappers/xkcd.py
Normal file
|
@ -0,0 +1,321 @@
|
|||
import datetime
|
||||
import imghdr
|
||||
import json
|
||||
import random
|
||||
from typing import Any
|
||||
|
||||
import httpx
|
||||
|
||||
|
||||
class HttpError(Exception):
|
||||
def __init__(self, status_code: int, reason: str) -> None:
|
||||
"""
|
||||
Initialize the HttpError.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
status_code : int
|
||||
The status code of the error.
|
||||
reason : str
|
||||
The reason of the error.
|
||||
"""
|
||||
self.status_code = status_code
|
||||
self.reason = reason
|
||||
super().__init__(f"HTTP Error {status_code}: {reason}")
|
||||
|
||||
|
||||
class Comic:
|
||||
"""
|
||||
A class representing a xkcd comic.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
xkcd_dict: dict[str, Any],
|
||||
raw_image: bytes | None = None,
|
||||
comic_url: str | None = None,
|
||||
explanation_url: str | None = None,
|
||||
) -> None:
|
||||
self.id: int | None = xkcd_dict.get("num")
|
||||
self.date: datetime.date | None = self._determine_date(xkcd_dict)
|
||||
self.title: str | None = xkcd_dict.get("safe_title")
|
||||
self.description: str | None = xkcd_dict.get("alt")
|
||||
self.transcript: str | None = xkcd_dict.get("transcript")
|
||||
self.image: bytes | None = raw_image
|
||||
self.image_extension: str | None = self._determine_image_extension()
|
||||
self.image_url: str | None = xkcd_dict.get("img")
|
||||
self.comic_url: str | None = comic_url
|
||||
self.explanation_url: str | None = explanation_url
|
||||
|
||||
@staticmethod
|
||||
def _determine_date(xkcd_dict: dict[str, Any]) -> datetime.date | None:
|
||||
"""
|
||||
Determine the date of the comic.
|
||||
Args:
|
||||
xkcd_dict:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
try:
|
||||
return datetime.date(
|
||||
int(xkcd_dict["year"]),
|
||||
int(xkcd_dict["month"]),
|
||||
int(xkcd_dict["day"]),
|
||||
)
|
||||
|
||||
except (KeyError, ValueError):
|
||||
return None
|
||||
|
||||
def _determine_image_extension(self) -> str | None:
|
||||
"""
|
||||
Determine the image extension of the comic.
|
||||
|
||||
Returns
|
||||
-------
|
||||
str | None
|
||||
The extension of the image.
|
||||
"""
|
||||
return f".{imghdr.what(None, h=self.image)}" if self.image else None
|
||||
|
||||
def update_raw_image(self, raw_image: bytes) -> None:
|
||||
"""
|
||||
Update the raw image of the comic.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
raw_image : bytes
|
||||
The raw image data.
|
||||
"""
|
||||
self.image = raw_image
|
||||
self.image_extension = self._determine_image_extension()
|
||||
|
||||
def __repr__(self) -> str:
|
||||
"""
|
||||
Return the representation of the comic.
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
The representation of the comic.
|
||||
"""
|
||||
return f"Comic({self.title})"
|
||||
|
||||
|
||||
class Client:
|
||||
def __init__(
|
||||
self,
|
||||
api_url: str = "https://xkcd.com",
|
||||
explanation_wiki_url: str = "https://www.explainxkcd.com/wiki/index.php/",
|
||||
) -> None:
|
||||
self._api_url = api_url
|
||||
self._explanation_wiki_url = explanation_wiki_url
|
||||
|
||||
def latest_comic_url(self) -> str:
|
||||
"""
|
||||
Get the URL for the latest comic.
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
The URL for the latest comic.
|
||||
"""
|
||||
return f"{self._api_url}/info.0.json"
|
||||
|
||||
def comic_id_url(self, comic_id: int) -> str:
|
||||
"""
|
||||
Get the URL for a specific comic ID.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
comic_id : int
|
||||
The ID of the comic.
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
The URL for the specific comic ID.
|
||||
"""
|
||||
return f"{self._api_url}/{comic_id}/info.0.json"
|
||||
|
||||
def _parse_response(self, response_text: str) -> Comic:
|
||||
"""
|
||||
Parse the response text into a Comic object.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
response_text : str
|
||||
The response text to parse.
|
||||
|
||||
Returns
|
||||
-------
|
||||
Comic
|
||||
The parsed comic object.
|
||||
"""
|
||||
response_dict: dict[str, Any] = json.loads(response_text)
|
||||
comic_url: str = f"{self._api_url}/{response_dict['num']}/"
|
||||
explanation_url: str = f"{self._explanation_wiki_url}{response_dict['num']}"
|
||||
|
||||
return Comic(
|
||||
response_dict,
|
||||
comic_url=comic_url,
|
||||
explanation_url=explanation_url,
|
||||
)
|
||||
|
||||
def _fetch_comic(self, comic_id: int, raw_comic_image: bool) -> Comic:
|
||||
"""
|
||||
Fetch a comic from the xkcd API.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
comic_id : int
|
||||
The ID of the comic to fetch.
|
||||
raw_comic_image : bool
|
||||
Whether to fetch the raw image data.
|
||||
|
||||
Returns
|
||||
-------
|
||||
Comic
|
||||
The fetched comic.
|
||||
"""
|
||||
comic = self._parse_response(self._request_comic(comic_id))
|
||||
|
||||
if raw_comic_image:
|
||||
raw_image = self._request_raw_image(comic.image_url)
|
||||
comic.update_raw_image(raw_image)
|
||||
|
||||
return comic
|
||||
|
||||
def get_latest_comic(self, raw_comic_image: bool = False) -> Comic:
|
||||
"""
|
||||
Get the latest xkcd comic.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
raw_comic_image : bool, optional
|
||||
Whether to fetch the raw image data, by default False
|
||||
|
||||
Returns
|
||||
-------
|
||||
Comic
|
||||
The latest xkcd comic.
|
||||
"""
|
||||
return self._fetch_comic(0, raw_comic_image)
|
||||
|
||||
def get_comic(self, comic_id: int, raw_comic_image: bool = False) -> Comic:
|
||||
"""
|
||||
Get a specific xkcd comic.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
comic_id : int
|
||||
The ID of the comic to fetch.
|
||||
raw_comic_image : bool, optional
|
||||
Whether to fetch the raw image data, by default False
|
||||
|
||||
Returns
|
||||
-------
|
||||
Comic
|
||||
The fetched xkcd comic.
|
||||
"""
|
||||
return self._fetch_comic(comic_id, raw_comic_image)
|
||||
|
||||
def get_random_comic(self, raw_comic_image: bool = False) -> Comic:
|
||||
"""
|
||||
Get a random xkcd comic.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
raw_comic_image : bool, optional
|
||||
Whether to fetch the raw image data, by default False
|
||||
|
||||
Returns
|
||||
-------
|
||||
Comic
|
||||
The random xkcd comic.
|
||||
"""
|
||||
latest_comic_id: int = self._parse_response(self._request_comic(0)).id or 0
|
||||
random_id: int = random.randint(1, latest_comic_id)
|
||||
|
||||
return self._fetch_comic(random_id, raw_comic_image)
|
||||
|
||||
def _request_comic(self, comic_id: int) -> str:
|
||||
"""
|
||||
Request the comic data from the xkcd API.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
comic_id : int
|
||||
The ID of the comic to fetch.
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
The response text.
|
||||
|
||||
Raises
|
||||
------
|
||||
HttpError
|
||||
If the request fails.
|
||||
"""
|
||||
comic_url = (
|
||||
self.latest_comic_url() if comic_id <= 0 else self.comic_id_url(comic_id)
|
||||
)
|
||||
|
||||
try:
|
||||
response = httpx.get(comic_url)
|
||||
response.raise_for_status()
|
||||
|
||||
except httpx.HTTPStatusError as exc:
|
||||
raise HttpError(
|
||||
exc.response.status_code,
|
||||
exc.response.reason_phrase,
|
||||
) from exc
|
||||
|
||||
return response.text
|
||||
|
||||
@staticmethod
|
||||
def _request_raw_image(raw_image_url: str | None) -> bytes:
|
||||
"""
|
||||
Request the raw image data from the xkcd API.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
raw_image_url : str | None
|
||||
The URL of the raw image data.
|
||||
|
||||
Returns
|
||||
-------
|
||||
bytes
|
||||
The raw image data.
|
||||
|
||||
Raises
|
||||
------
|
||||
HttpError
|
||||
If the request fails.
|
||||
"""
|
||||
if not raw_image_url:
|
||||
raise HttpError(404, "Image URL not found")
|
||||
|
||||
try:
|
||||
response = httpx.get(raw_image_url)
|
||||
response.raise_for_status()
|
||||
|
||||
except httpx.HTTPStatusError as exc:
|
||||
raise HttpError(
|
||||
exc.response.status_code,
|
||||
exc.response.reason_phrase,
|
||||
) from exc
|
||||
|
||||
return response.content
|
||||
|
||||
def __repr__(self) -> str:
|
||||
"""
|
||||
Return the representation of the client.
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
The representation of the client.
|
||||
"""
|
||||
return "Client()"
|
Loading…
Reference in a new issue