Skip to content

Commit

Permalink
Merge pull request #3 from sown/api
Browse files Browse the repository at this point in the history
Use the Ferry API
  • Loading branch information
trickeydan authored Feb 23, 2024
2 parents ff72e8b + fae8a03 commit 52dddaf
Show file tree
Hide file tree
Showing 10 changed files with 396 additions and 158 deletions.
5 changes: 3 additions & 2 deletions example-config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ timezone = "Europe/London"
guild_id = 1234567890

[ferry]
announcement_channel_id = 1234567890
accusation_channel_id = 1234567890
api_url = "https://example.com/api/v1/"
api_key = "abc"
channel_id = 1234567890
banned_word = "train"
emoji_reacts = "🚂😠🚇"

Expand Down
11 changes: 3 additions & 8 deletions kmibot/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,16 +41,11 @@ def get_pub_by_name(self, name: str) -> Optional[PubInfo]:


class FerryConfig(BaseModel):
announcement_channel_id: int
accusation_channel_id: int
api_url: str
api_key: str
channel_id: int
banned_word: str
emoji_reacts: str
sentences: list[str]


class RoleInfo(BaseModel):
name: str
colour: str


class BotConfig(BaseSettings):
Expand Down
24 changes: 13 additions & 11 deletions kmibot/modules/ferry/__init__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
from __future__ import annotations

import asyncio
from logging import getLogger
from typing import TYPE_CHECKING

import discord

from kmibot.modules import Module
from kmibot.modules.ferry.api import FerryAPI

from .commands import FerryCommand
from .modals import AccuseModal
Expand All @@ -16,9 +19,10 @@


class FerryModule(Module):
def __init__(self, client: "DiscordClient") -> None:
def __init__(self, client: DiscordClient) -> None:
self.client = client
self.command_group = FerryCommand(client.config, self)
self.api_client = FerryAPI(client.config.ferry.api_url, client.config.ferry.api_key)
client.tree.add_command(self.command_group, guild=client.guild)
client.tree.context_menu(name="Accuse of Ferrying", guild=client.guild)(
self.accuse_context_menu
Expand All @@ -39,19 +43,17 @@ def __init__(self, client: "DiscordClient") -> None:
client.on_reaction_add = self.on_reaction_add # type: ignore[attr-defined]

@property
def announce_channel(self) -> discord.TextChannel:
announce_channel = self.client.get_channel(self.client.config.ferry.announcement_channel_id)
assert isinstance(announce_channel, discord.TextChannel)
return announce_channel
def channel(self) -> discord.TextChannel:
channel = self.client.get_channel(self.client.config.ferry.channel_id)
assert isinstance(channel, discord.TextChannel)
return channel

@property
def accuse_channel(self) -> discord.TextChannel:
accuse_channel = self.client.get_channel(self.client.config.ferry.accusation_channel_id)
assert isinstance(accuse_channel, discord.TextChannel)
return accuse_channel
async def on_ready(self, client: DiscordClient) -> None:
user = await self.api_client.get_current_user()
LOGGER.info(f"Authenticated to Ferry API as {user.username}")

async def on_reaction_add(self, reaction: discord.Reaction, user: discord.User) -> None:
await self.command_group.handle_emoji(reaction, user)
pass

async def on_message(self, message: discord.Message) -> None:
assert self.client.user
Expand Down
132 changes: 132 additions & 0 deletions kmibot/modules/ferry/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
from datetime import datetime
from http import HTTPStatus
from logging import getLogger
from typing import Any
from uuid import UUID
import discord
import httpx
from pydantic import BaseModel, TypeAdapter

LOGGER = getLogger(__name__)


class UserSchema(BaseModel):
username: str


class PersonSchema(BaseModel):
id: UUID
display_name: str
discord_id: int | None
current_score: float
created_at: datetime
updated_at: datetime

def get_display_for_message(self) -> str:
if self.discord_id:
return f"<@{self.discord_id}>"
return self.display_name


class PersonLinkSchema(BaseModel):
id: UUID
display_name: str


class ConsequenceLinkSchema(BaseModel):
id: UUID
content: str


class RatificationSchema(BaseModel):
id: UUID
consequence: ConsequenceLinkSchema
created_by: PersonLinkSchema
created_at: datetime
updated_at: datetime


class AccusationSchema(BaseModel):
id: UUID
quote: str
suspect: PersonLinkSchema
created_by: PersonLinkSchema
ratification: RatificationSchema | None
created_at: datetime
updated_at: datetime


class FerryAPI:
def __init__(self, api_url: str, api_key: str) -> None:
self._api_url = api_url
self._api_key = api_key

self._client = httpx.AsyncClient()

async def _request(self, method: str, endpoint: str, **kwargs) -> Any:
headers = {
"Accept": "application/json",
"Content-Type": "application/json",
"Authorization": f"Bearer {self._api_key}",
}
resp = await self._client.request(
method, self._api_url + endpoint, headers=headers, **kwargs
)
resp.raise_for_status()
return resp.json()

async def get_current_user(self) -> UserSchema:
data = await self._request("GET", "/users/me")
return UserSchema.model_validate(data)

async def get_leaderboard(
self,
) -> list[PersonSchema]:
data = await self._request("GET", "/people/?ordering=-current_score&limit=10")
ta = TypeAdapter(list[PersonSchema])
return ta.validate_python(data["items"])

async def get_person(self, person_id: UUID) -> PersonSchema:
data = await self._request("GET", f"/people/{person_id}")
return PersonSchema.model_validate(data)

async def get_person_for_discord_member(
self, member: discord.User | discord.Member
) -> PersonSchema:
try:
data = await self._request("GET", f"/people/by-discord-id/{member.id}")
return PersonSchema.model_validate(data)
except httpx.HTTPStatusError as exc:
if exc.response.status_code == HTTPStatus.NOT_FOUND:
LOGGER.info(f"Creating new person for {member}")
payload = {"display_name": member.display_name, "discord_id": member.id}
data = await self._request("POST", "/people/", json=payload)
return PersonSchema.model_validate(data)
else:
raise

async def create_accusation(
self, created_by: UUID, suspect: UUID, quote: str
) -> AccusationSchema:
payload = {
"quote": quote,
"suspect": str(suspect),
"created_by": str(created_by),
}
data = await self._request("POST", "/accusations/", json=payload)
return AccusationSchema.model_validate(data)

async def get_accusation(self, accusation_id: UUID) -> AccusationSchema:
data = await self._request("GET", f"/accusations/{accusation_id}")
return AccusationSchema.model_validate(data)

async def create_ratification(
self, accusation_id: UUID, created_by: UUID
) -> RatificationSchema:
payload = {
"created_by": str(created_by),
}
data = await self._request(
"POST", f"/accusations/{accusation_id}/ratification", json=payload
)
return RatificationSchema.model_validate(data)
Loading

0 comments on commit 52dddaf

Please sign in to comment.