Files
bancho.py/app/discord.py
2025-04-04 21:30:31 +09:00

174 lines
5.4 KiB
Python

"""Functionality related to Discord interactivity."""
from __future__ import annotations
from typing import Any
from tenacity import retry
from tenacity import stop_after_attempt
from tenacity import wait_exponential
from app.state import services
class Footer:
def __init__(self, text: str, **kwargs: Any) -> None:
self.text = text
self.icon_url = kwargs.get("icon_url")
self.proxy_icon_url = kwargs.get("proxy_icon_url")
class Image:
def __init__(self, **kwargs: Any) -> None:
self.url = kwargs.get("url")
self.proxy_url = kwargs.get("proxy_url")
self.height = kwargs.get("height")
self.width = kwargs.get("width")
class Thumbnail:
def __init__(self, **kwargs: Any) -> None:
self.url = kwargs.get("url")
self.proxy_url = kwargs.get("proxy_url")
self.height = kwargs.get("height")
self.width = kwargs.get("width")
class Video:
def __init__(self, **kwargs: Any) -> None:
self.url = kwargs.get("url")
self.height = kwargs.get("height")
self.width = kwargs.get("width")
class Provider:
def __init__(self, **kwargs: str) -> None:
self.url = kwargs.get("url")
self.name = kwargs.get("name")
class Author:
def __init__(self, **kwargs: str) -> None:
self.name = kwargs.get("name")
self.url = kwargs.get("url")
self.icon_url = kwargs.get("icon_url")
self.proxy_icon_url = kwargs.get("proxy_icon_url")
class Field:
def __init__(self, name: str, value: str, inline: bool = False) -> None:
self.name = name
self.value = value
self.inline = inline
class Embed:
def __init__(self, **kwargs: Any) -> None:
self.title = kwargs.get("title")
self.type = kwargs.get("type")
self.description = kwargs.get("description")
self.url = kwargs.get("url")
self.timestamp = kwargs.get("timestamp") # datetime
self.color = kwargs.get("color", 0x000000)
self.footer: Footer | None = kwargs.get("footer")
self.image: Image | None = kwargs.get("image")
self.thumbnail: Thumbnail | None = kwargs.get("thumbnail")
self.video: Video | None = kwargs.get("video")
self.provider: Provider | None = kwargs.get("provider")
self.author: Author | None = kwargs.get("author")
self.fields: list[Field] = kwargs.get("fields", [])
def set_footer(self, **kwargs: Any) -> None:
self.footer = Footer(**kwargs)
def set_image(self, **kwargs: Any) -> None:
self.image = Image(**kwargs)
def set_thumbnail(self, **kwargs: Any) -> None:
self.thumbnail = Thumbnail(**kwargs)
def set_video(self, **kwargs: Any) -> None:
self.video = Video(**kwargs)
def set_provider(self, **kwargs: Any) -> None:
self.provider = Provider(**kwargs)
def set_author(self, **kwargs: Any) -> None:
self.author = Author(**kwargs)
def add_field(self, name: str, value: str, inline: bool = False) -> None:
self.fields.append(Field(name, value, inline))
class Webhook:
"""A class to represent a single-use Discord webhook."""
def __init__(self, url: str, **kwargs: Any) -> None:
self.url = url
self.content = kwargs.get("content")
self.username = kwargs.get("username")
self.avatar_url = kwargs.get("avatar_url")
self.tts = kwargs.get("tts")
self.file = kwargs.get("file")
self.embeds = kwargs.get("embeds", [])
def add_embed(self, embed: Embed) -> None:
self.embeds.append(embed)
@property
def json(self) -> Any:
if not any([self.content, self.file, self.embeds]):
raise Exception(
"Webhook must contain at least one " "of (content, file, embeds).",
)
if self.content and len(self.content) > 2000:
raise Exception("Webhook content must be under " "2000 characters.")
payload: dict[str, Any] = {"embeds": []}
for key in ("content", "username", "avatar_url", "tts", "file"):
val = getattr(self, key)
if val is not None:
payload[key] = val
for embed in self.embeds:
embed_payload = {}
# simple params
for key in ("title", "type", "description", "url", "timestamp", "color"):
val = getattr(embed, key)
if val is not None:
embed_payload[key] = val
# class params, must turn into dict
for key in ("footer", "image", "thumbnail", "video", "provider", "author"):
val = getattr(embed, key)
if val is not None:
embed_payload[key] = val.__dict__
if embed.fields:
embed_payload["fields"] = [f.__dict__ for f in embed.fields]
payload["embeds"].append(embed_payload)
return payload
@retry(
stop=stop_after_attempt(10),
wait=wait_exponential(multiplier=1, min=4, max=10),
)
async def post(self) -> None:
"""Post the webhook in JSON format."""
# TODO: if `self.file is not None`, then we should
# use multipart/form-data instead of json payload.
headers = {"Content-Type": "application/json"}
response = await services.http_client.post(
self.url,
json=self.json,
headers=headers,
)
response.raise_for_status()