Skip to content

Commit aa72250

Browse files
AmbratolmAmbratolm
authored andcommitted
Improved API & added guilds/actors endpoints
1 parent d8b54f6 commit aa72250

File tree

14 files changed

+200
-73
lines changed

14 files changed

+200
-73
lines changed

api/main.py

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,17 @@
1+
import pathlib
12
from contextlib import asynccontextmanager
23
from typing import cast
34
from urllib.parse import urlparse
45

56
import uvicorn
67
import uvicorn.server
78
from colorama import Fore
8-
from discord.ext.commands import Bot
9-
from fastapi import FastAPI
9+
from fastapi import APIRouter, FastAPI
1010
from starlette.routing import Route
1111

12+
from bot.main import ActBot
1213
from utils.log import LOG_CONFIG, logger
13-
from utils.misc import text_block
14-
15-
from .routes import add_routes
14+
from utils.misc import import_classes, text_block
1615

1716
log = logger(__name__)
1817

@@ -21,7 +20,7 @@
2120
# * ACT API
2221
# ----------------------------------------------------------------------------------------------------
2322
class ActApi(FastAPI):
24-
def __init__(self, bot: Bot | None = None, url="", *args, **kwargs):
23+
def __init__(self, bot: "ActBot | None" = None, url="", *args, **kwargs):
2524
@asynccontextmanager
2625
async def lifespan(self: ActApi):
2726
try:
@@ -47,8 +46,7 @@ async def lifespan(self: ActApi):
4746
log_config=LOG_CONFIG,
4847
)
4948
)
50-
if bot:
51-
add_routes(self, bot)
49+
self.load_routers()
5250

5351
# ----------------------------------------------------------------------------------------------------
5452

@@ -71,6 +69,29 @@ def info_text(self):
7169

7270
# ----------------------------------------------------------------------------------------------------
7371

72+
def load_routers(self):
73+
router_classes = import_classes(
74+
f"{pathlib.Path(__file__).parent}/routers", APIRouter
75+
)
76+
log.loading("Loading routers...")
77+
loaded_routers_count = 0
78+
for router_class in router_classes:
79+
try:
80+
router: APIRouter = router_class(self.bot)
81+
self.include_router(router)
82+
log.info(
83+
f"{router.__class__.__name__} router loaded from {router.__module__} module."
84+
)
85+
loaded_routers_count += 1
86+
except Exception as e:
87+
log.error(
88+
f"Error loading {router_class.__class__.__name__} router from {router_class.__module__} module:\t",
89+
e,
90+
)
91+
log.success(f"{loaded_routers_count}/{len(router_classes)} routers loaded.")
92+
93+
# ----------------------------------------------------------------------------------------------------
94+
7495
async def open(self):
7596
log.loading(f"API server opening...")
7697
await self.server.serve()

api/routers/actor_router.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
from typing import cast
2+
3+
from fastapi import APIRouter, HTTPException
4+
5+
from bot.cogs.board_cog import BoardCog
6+
from bot.main import ActBot
7+
from db.actor import Actor
8+
9+
10+
# ----------------------------------------------------------------------------------------------------
11+
# * Actor Router
12+
# ----------------------------------------------------------------------------------------------------
13+
class ActorRouter(APIRouter):
14+
def __init__(self, bot: ActBot, *args, **kwargs):
15+
super().__init__(prefix="/actors", tags=["Actors"], *args, **kwargs)
16+
17+
# ----------------------------------------------------------------------------------------------------
18+
19+
@self.get("/{guild_id}/", response_model=list[Actor])
20+
async def get_actors(guild_id: int, limit: int = 10):
21+
try:
22+
for guild in bot.guilds:
23+
if guild.id == guild_id:
24+
return bot.get_db(guild).find(Actor, limit=limit)
25+
26+
raise HTTPException(
27+
status_code=404,
28+
detail=f"No guild with id '{guild_id}' found among the guilds the discord bot is member of.",
29+
)
30+
raise HTTPException(
31+
status_code=404,
32+
detail="No guilds found. The discord bot is not member of any guild",
33+
)
34+
except Exception as e:
35+
raise HTTPException(status_code=400, detail=str(e))
36+
37+
# ----------------------------------------------------------------------------------------------------
38+
39+
@self.get("/{guild_id}/top/", response_model=list[Actor])
40+
async def get_top_actors(guild_id: int, limit: int = 10): # Leaderboard
41+
try:
42+
for guild in bot.guilds:
43+
if guild.id == guild_id:
44+
return cast(
45+
BoardCog, bot.get_cog(BoardCog.__cog_name__)
46+
).get_top_actors(guild, limit)
47+
48+
raise HTTPException(
49+
status_code=404,
50+
detail=f"No guild with id '{guild_id}' found among the guilds the discord bot is member of.",
51+
)
52+
raise HTTPException(
53+
status_code=404,
54+
detail="No guilds found. The discord bot is not member of any guild",
55+
)
56+
except Exception as e:
57+
raise HTTPException(status_code=400, detail=str(e))

api/routers/default_router.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
from typing import cast
2+
3+
from discord import Guild
4+
from fastapi import APIRouter, HTTPException
5+
6+
from bot.cogs.board_cog import BoardCog
7+
from bot.main import ActBot
8+
from db.actor import Actor
9+
10+
11+
# ----------------------------------------------------------------------------------------------------
12+
# * Default Router
13+
# ----------------------------------------------------------------------------------------------------
14+
class DefaultRouter(APIRouter):
15+
def __init__(self, bot: ActBot, *args, **kwargs):
16+
super().__init__(tags=["Default"], *args, **kwargs)
17+
18+
@self.get("/")
19+
def get_root():
20+
return "Welcome to ACT API. For docs go to: ./docs"

api/routers/guild_router.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
from typing import cast
2+
3+
from discord import Guild
4+
from fastapi import APIRouter, HTTPException
5+
6+
from bot.cogs.board_cog import BoardCog
7+
from bot.main import ActBot
8+
from db.actor import Actor
9+
10+
11+
# ----------------------------------------------------------------------------------------------------
12+
# * Guild Router
13+
# ----------------------------------------------------------------------------------------------------
14+
class GuildRouter(APIRouter):
15+
def __init__(self, bot: ActBot, *args, **kwargs):
16+
super().__init__(prefix="/guilds", tags=["Guilds"], *args, **kwargs)
17+
18+
def guild_dict(guild: Guild):
19+
return {
20+
"id": guild.id,
21+
"name": guild.name,
22+
"description": guild.description,
23+
"icon": guild.icon.url if guild.icon else None,
24+
"banner": guild.banner.url if guild.banner else None,
25+
"created_at": guild.created_at,
26+
}
27+
28+
# ----------------------------------------------------------------------------------------------------
29+
30+
@self.get("/", response_model=list[dict])
31+
async def get_guilds():
32+
try:
33+
return [guild_dict(guild) for guild in bot.guilds]
34+
except Exception as e:
35+
raise HTTPException(status_code=400, detail=str(e))
36+
37+
# ----------------------------------------------------------------------------------------------------
38+
39+
@self.get("/{guild_id}", response_model=dict)
40+
async def get_guild(guild_id: int):
41+
try:
42+
for guild in bot.guilds:
43+
if guild.id == guild_id:
44+
return guild_dict(guild)
45+
raise HTTPException(
46+
status_code=404,
47+
detail=f"No guild with id '{guild_id}' found among the guilds the discord bot is member of.",
48+
)
49+
except Exception as e:
50+
raise HTTPException(status_code=400, detail=str(e))

api/routes.py

Lines changed: 0 additions & 32 deletions
This file was deleted.

bot/cogs/ai_cog.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22
import tomllib
33
from datetime import UTC, datetime
44

5-
from discord import Guild, Member, Message, User
5+
from discord import Guild, Interaction, Member, Message, User, app_commands
66
from discord.ext import tasks
77
from discord.ext.commands import Cog
88
from google.genai.errors import APIError
9-
from odmantic import AIOEngine, Model, query
9+
from odmantic import query
1010

1111
from bot.main import ActBot
1212
from db.actor import Actor
@@ -45,7 +45,16 @@ def cog_unload(self):
4545
self.cooldown_guild.cancel()
4646

4747
# ----------------------------------------------------------------------------------------------------
48+
# * Incite
49+
# ----------------------------------------------------------------------------------------------------
50+
@app_commands.checks.has_permissions(administrator=True)
51+
@app_commands.command(description="")
52+
async def incite(self, interaction: Interaction, member: Member):
53+
pass
4854

55+
# ----------------------------------------------------------------------------------------------------
56+
# * On Message
57+
# ----------------------------------------------------------------------------------------------------
4958
@Cog.listener()
5059
async def on_message(self, message: Message):
5160
# Ignore if bot own message or bot not mentioned

bot/cogs/board_cog.py

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import asyncio
22

3-
from discord import Color, Embed, Interaction, Member, User, app_commands
3+
from discord import Color, Embed, Guild, Interaction, Member, User, app_commands
44
from discord.ext.commands import Cog
55
from humanize import intcomma, intword, metric, naturalsize, naturaltime, ordinal
66
from odmantic import query
@@ -14,7 +14,7 @@
1414
# ----------------------------------------------------------------------------------------------------
1515
# * Board Cog
1616
# ----------------------------------------------------------------------------------------------------
17-
class Board(Cog, description="Allows players to view their data."):
17+
class BoardCog(Cog, description="Allows players to view their data."):
1818
def __init__(self, bot: ActBot):
1919
self.bot = bot
2020

@@ -81,17 +81,7 @@ async def leaderboard(self, interaction: Interaction):
8181

8282
# Get top actors
8383
await interaction.response.defer()
84-
db = self.bot.get_db(interaction.guild)
85-
actors = db.find(
86-
Actor,
87-
sort=(
88-
query.desc(Actor.rank),
89-
query.desc(Actor.level),
90-
query.desc(Actor.xp),
91-
query.desc(Actor.gold),
92-
),
93-
limit=10,
94-
)
84+
actors = self.get_top_actors(interaction.guild)
9585
if not actors:
9686
await interaction.followup.send(
9787
embed=Embed(
@@ -128,3 +118,18 @@ async def leaderboard(self, interaction: Interaction):
128118
pass
129119
embed.add_field(name="", value=leaderboard_text, inline=False)
130120
await interaction.followup.send(embed=embed)
121+
122+
# ----------------------------------------------------------------------------------------------------
123+
124+
def get_top_actors(self, guild: Guild | None, limit: int = 10):
125+
db = self.bot.get_db(guild)
126+
return db.find(
127+
Actor,
128+
sort=(
129+
query.desc(Actor.rank),
130+
query.desc(Actor.level),
131+
query.desc(Actor.xp),
132+
query.desc(Actor.gold),
133+
),
134+
limit=limit,
135+
)

bot/cogs/console_cog.py

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,20 @@
1-
import argparse
2-
import shlex
3-
4-
from discord import Color, Embed, Interaction, Member, Message, app_commands
5-
from discord.ext import commands
6-
from discord.ext.commands import Cog, GroupCog
7-
from tabulate import tabulate
1+
from discord import Interaction, app_commands
2+
from discord.ext.commands import Cog
83

94
from bot.main import ActBot
105
from bot.ui import EmbedX
11-
from db.actor import Actor
126

137

148
# ----------------------------------------------------------------------------------------------------
159
# * Console Cog
1610
# ----------------------------------------------------------------------------------------------------
17-
class Console(Cog, description="Provides control and management interface."):
11+
class ConsoleCog(Cog, description="Provides control and management interface."):
1812
def __init__(self, bot: ActBot):
1913
self.bot = bot
2014

15+
# ----------------------------------------------------------------------------------------------------
16+
# * Sync
17+
# ----------------------------------------------------------------------------------------------------
2118
@app_commands.checks.has_permissions(administrator=True)
2219
@app_commands.command(description="Synchronize commands")
2320
async def sync(self, interaction: Interaction):

bot/cogs/farm_cog.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
# ----------------------------------------------------------------------------------------------------
1313
# * Farm Cog
1414
# ----------------------------------------------------------------------------------------------------
15-
class Farm(Cog, description="Allows players to gain stats and roles."):
15+
class FarmCog(Cog, description="Allows players to gain stats and roles."):
1616
def __init__(self, bot: ActBot):
1717
self.bot = bot
1818

bot/cogs/filter_cog.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
# ----------------------------------------------------------------------------------------------------
1717
# * Filter Cog
1818
# ----------------------------------------------------------------------------------------------------
19-
class Filter(Cog, description="Filters blacklisted message content."):
19+
class FilterCog(Cog, description="Filters blacklisted message content."):
2020
TOLERANCE = 0.99 # Between 0 and 1
2121
MAX_OFFENSES = 5
2222
GOLD_PENALTY = 500
@@ -57,7 +57,7 @@ async def on_message(self, message: Message):
5757
if member.guild_permissions.administrator:
5858
return
5959
self.offenses[member.id] += 1
60-
if self.offenses[member.id] < Filter.MAX_OFFENSES:
60+
if self.offenses[member.id] < FilterCog.MAX_OFFENSES:
6161
return
6262

6363
# Penalize by gold

0 commit comments

Comments
 (0)