Skip to content

Commit 9ddd65a

Browse files
committed
add docs, remove some stuff
1 parent f784a61 commit 9ddd65a

File tree

4 files changed

+101
-190
lines changed

4 files changed

+101
-190
lines changed

bot/cogs/__init__.py

+2
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,5 @@ def __str__(self) -> str:
1414

1515
EXTENSIONS = [module.name for module in iter_modules(__path__, f"{__package__}.")]
1616
VERSION: VersionInfo = VersionInfo(major=0, minor=3, micro=1, releaselevel="final")
17+
18+
del Literal, NamedTuple

bot/cogs/admin.py

+1-118
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,16 @@
11
from __future__ import annotations
22

3-
import asyncio
4-
import importlib
5-
import os
6-
import re
7-
import subprocess # nosec # We already know this is dangerous, but it's needed
8-
import sys
93
from typing import TYPE_CHECKING, Literal, Optional
104

115
import discord
126
from discord.ext import commands
137
from discord.ext.commands import Greedy
148

159
if TYPE_CHECKING:
16-
from utils import RoboContext
10+
from utils.context import RoboContext
1711

1812
from bot.rodhaj import Rodhaj
1913

20-
GIT_PULL_REGEX = re.compile(r"\s+(?P<filename>.*)\b\s+\|\s+[\d]")
21-
2214

2315
class Admin(commands.Cog, command_attrs=dict(hidden=True)):
2416
"""Administrative commands for Rodhaj"""
@@ -33,78 +25,6 @@ def display_emoji(self) -> discord.PartialEmoji:
3325
async def cog_check(self, ctx: RoboContext) -> bool:
3426
return await self.bot.is_owner(ctx.author)
3527

36-
async def reload_or_load_extension(self, module: str) -> None:
37-
try:
38-
await self.bot.reload_extension(module)
39-
except commands.ExtensionNotLoaded:
40-
await self.bot.load_extension(module)
41-
42-
def find_modules_from_git(self, output: str) -> list[tuple[int, str]]:
43-
files = GIT_PULL_REGEX.findall(output)
44-
ret: list[tuple[int, str]] = []
45-
for file in files:
46-
root, ext = os.path.splitext(file)
47-
if ext != ".py" or root.endswith("__init__"):
48-
continue
49-
50-
true_root = ".".join(root.split("/")[1:])
51-
52-
if true_root.startswith("cogs") or true_root.startswith("libs"):
53-
# A subdirectory within these are a part of the codebase
54-
55-
ret.append((true_root.count(".") + 1, true_root))
56-
57-
# For reload order, the submodules should be reloaded first
58-
ret.sort(reverse=True)
59-
return ret
60-
61-
async def run_process(self, command: str) -> list[str]:
62-
process = await asyncio.create_subprocess_shell(
63-
command, stdout=subprocess.PIPE, stderr=subprocess.PIPE
64-
)
65-
result = await process.communicate()
66-
67-
return [output.decode() for output in result]
68-
69-
def tick(self, opt: Optional[bool], label: Optional[str] = None) -> str:
70-
lookup = {
71-
True: "\U00002705",
72-
False: "\U0000274c",
73-
None: "\U000023e9",
74-
}
75-
emoji = lookup.get(opt, "\U0000274c")
76-
if label is not None:
77-
return f"{emoji}: {label}"
78-
return emoji
79-
80-
def format_results(self, statuses: list) -> str:
81-
desc = "\U00002705 - Successful reload | \U0000274c - Failed reload | \U000023e9 - Skipped\n\n"
82-
status = "\n".join(f"- {status}: `{module}`" for status, module in statuses)
83-
desc += status
84-
return desc
85-
86-
async def reload_exts(self, module: str) -> list[tuple[str, str]]:
87-
statuses = []
88-
try:
89-
await self.reload_or_load_extension(module)
90-
statuses.append((self.tick(True), module))
91-
except commands.ExtensionError:
92-
statuses.append((self.tick(False), module))
93-
94-
return statuses
95-
96-
def reload_lib_modules(self, module: str) -> list[tuple[str, str]]:
97-
statuses = []
98-
try:
99-
actual_module = sys.modules[module]
100-
importlib.reload(actual_module)
101-
statuses.append((self.tick(True), module))
102-
except KeyError:
103-
statuses.append((self.tick(None), module))
104-
except Exception:
105-
statuses.append((self.tick(False), module))
106-
return statuses
107-
10828
# Umbra's sync command
10929
# To learn more about it, see the link below (and ?tag ass on the dpy server):
11030
# https://about.abstractumbra.dev/discord.py/2023/01/29/sync-command-example.html
@@ -147,43 +67,6 @@ async def sync(
14767

14868
await ctx.send(f"Synced the tree to {ret}/{len(guilds)}.")
14969

150-
@commands.command(name="reload-all", hidden=True)
151-
async def reload(self, ctx: RoboContext) -> None:
152-
"""Reloads all cogs and utils"""
153-
async with ctx.typing():
154-
stdout, _ = await self.run_process("git pull")
155-
156-
# progress and stuff is redirected to stderr in git pull
157-
# however, things like "fast forward" and files
158-
# along with the text "already up-to-date" are in stdout
159-
160-
if stdout.startswith("Already up-to-date."):
161-
await ctx.send(stdout)
162-
return
163-
164-
modules = self.find_modules_from_git(stdout)
165-
166-
mods_text = "\n".join(
167-
f"{index}. `{module}`" for index, (_, module) in enumerate(modules, start=1)
168-
)
169-
prompt_text = (
170-
f"This will update the following modules, are you sure?\n{mods_text}"
171-
)
172-
173-
confirm = await ctx.prompt(prompt_text)
174-
if not confirm:
175-
await ctx.send("Aborting....")
176-
return
177-
178-
statuses = []
179-
for is_submodule, module in modules:
180-
if is_submodule:
181-
statuses = self.reload_lib_modules(module)
182-
else:
183-
statuses = await self.reload_exts(module)
184-
185-
await ctx.send(self.format_results(statuses))
186-
18770

18871
async def setup(bot: Rodhaj) -> None:
18972
await bot.add_cog(Admin(bot))

bot/cogs/config.py

+87-65
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
Any,
1111
AsyncIterator,
1212
Literal,
13-
NamedTuple,
1413
Optional,
1514
Union,
1615
overload,
@@ -49,12 +48,30 @@
4948
)
5049
OPTIONS_FILE = Path(__file__).parents[1] / "locale" / "options.json"
5150

51+
### Enums
5252

53-
class BlocklistTicket(NamedTuple):
53+
54+
class ConfigType(Enum):
55+
TOGGLE = 0
56+
SET = 1
57+
58+
59+
### Structs
60+
61+
62+
class BlocklistTicket(msgspec.Struct, frozen=True):
5463
cog: Tickets
5564
thread: discord.Thread
5665

5766

67+
class ConfigHelpEntry(msgspec.Struct, frozen=True):
68+
key: str
69+
default: str
70+
description: str
71+
examples: list[str]
72+
notes: list[str]
73+
74+
5875
class BlocklistEntity(msgspec.Struct, frozen=True):
5976
bot: Rodhaj
6077
guild_id: int
@@ -66,63 +83,6 @@ def format(self) -> str:
6683
return f"{name} (ID: {self.entity_id})"
6784

6885

69-
class BlocklistPages(SimplePages):
70-
def __init__(self, entries: list[BlocklistEntity], *, ctx: GuildContext):
71-
converted = [entry.format() for entry in entries]
72-
super().__init__(converted, ctx=ctx)
73-
74-
75-
class Blocklist:
76-
def __init__(self, bot: Rodhaj):
77-
self.bot = bot
78-
self._blocklist: dict[int, BlocklistEntity] = {}
79-
80-
async def _load(self, connection: Union[asyncpg.Connection, asyncpg.Pool]):
81-
query = """
82-
SELECT guild_id, entity_id
83-
FROM blocklist;
84-
"""
85-
rows = await connection.fetch(query)
86-
return {
87-
row["entity_id"]: BlocklistEntity(bot=self.bot, **dict(row)) for row in rows
88-
}
89-
90-
async def load(self, connection: Optional[asyncpg.Connection] = None):
91-
try:
92-
self._blocklist = await self._load(connection or self.bot.pool)
93-
except Exception:
94-
self._blocklist = {}
95-
96-
@overload
97-
def get(self, key: int) -> Optional[BlocklistEntity]: ...
98-
99-
@overload
100-
def get(self, key: int) -> BlocklistEntity: ...
101-
102-
def get(self, key: int, default: Any = None) -> Optional[BlocklistEntity]:
103-
return self._blocklist.get(key, default)
104-
105-
def __contains__(self, item: int) -> bool:
106-
return item in self._blocklist
107-
108-
def __getitem__(self, item: int) -> BlocklistEntity:
109-
return self._blocklist[item]
110-
111-
def __len__(self) -> int:
112-
return len(self._blocklist)
113-
114-
def all(self) -> dict[int, BlocklistEntity]:
115-
return self._blocklist
116-
117-
def replace(self, blocklist: dict[int, BlocklistEntity]) -> None:
118-
self._blocklist = blocklist
119-
120-
121-
class ConfigType(Enum):
122-
TOGGLE = 0
123-
SET = 1
124-
125-
12686
# Msgspec Structs are usually extremely fast compared to slotted classes
12787
class GuildConfig(msgspec.Struct):
12888
bot: Rodhaj
@@ -196,6 +156,55 @@ def ticket_channel(self) -> Optional[discord.ForumChannel]:
196156
return guild and guild.get_channel(self.ticket_channel_id) # type: ignore
197157

198158

159+
### Core classes
160+
161+
162+
class Blocklist:
163+
def __init__(self, bot: Rodhaj):
164+
self.bot = bot
165+
self._blocklist: dict[int, BlocklistEntity] = {}
166+
167+
async def _load(self, connection: Union[asyncpg.Connection, asyncpg.Pool]):
168+
query = """
169+
SELECT guild_id, entity_id
170+
FROM blocklist;
171+
"""
172+
rows = await connection.fetch(query)
173+
return {
174+
row["entity_id"]: BlocklistEntity(bot=self.bot, **dict(row)) for row in rows
175+
}
176+
177+
async def load(self, connection: Optional[asyncpg.Connection] = None):
178+
try:
179+
self._blocklist = await self._load(connection or self.bot.pool)
180+
except Exception:
181+
self._blocklist = {}
182+
183+
@overload
184+
def get(self, key: int) -> Optional[BlocklistEntity]: ...
185+
186+
@overload
187+
def get(self, key: int) -> BlocklistEntity: ...
188+
189+
def get(self, key: int, default: Any = None) -> Optional[BlocklistEntity]:
190+
return self._blocklist.get(key, default)
191+
192+
def __contains__(self, item: int) -> bool:
193+
return item in self._blocklist
194+
195+
def __getitem__(self, item: int) -> BlocklistEntity:
196+
return self._blocklist[item]
197+
198+
def __len__(self) -> int:
199+
return len(self._blocklist)
200+
201+
def all(self) -> dict[int, BlocklistEntity]:
202+
return self._blocklist
203+
204+
def replace(self, blocklist: dict[int, BlocklistEntity]) -> None:
205+
self._blocklist = blocklist
206+
207+
199208
class GuildWebhookDispatcher:
200209
def __init__(self, bot: Rodhaj, guild_id: int):
201210
self.bot = bot
@@ -234,12 +243,7 @@ async def get_config(self) -> Optional[GuildWebhook]:
234243
return GuildWebhook(bot=self.bot, **dict(rows))
235244

236245

237-
class ConfigHelpEntry(msgspec.Struct, frozen=True):
238-
key: str
239-
default: str
240-
description: str
241-
examples: list[str]
242-
notes: list[str]
246+
### Embeds
243247

244248

245249
class ConfigEntryEmbed(Embed):
@@ -256,6 +260,15 @@ def __init__(self, entry: ConfigHelpEntry, **kwargs):
256260
)
257261

258262

263+
### UI elements (Sources and Pages)
264+
265+
266+
class BlocklistPages(SimplePages):
267+
def __init__(self, entries: list[BlocklistEntity], *, ctx: GuildContext):
268+
converted = [entry.format() for entry in entries]
269+
super().__init__(converted, ctx=ctx)
270+
271+
259272
class ConfigHelpPageSource(menus.ListPageSource):
260273
async def format_page(self, menu: ConfigHelpPages, entry: ConfigHelpEntry):
261274
embed = ConfigEntryEmbed(entry=entry)
@@ -309,6 +322,9 @@ def __init__(
309322
self.embed = discord.Embed(colour=discord.Colour.from_rgb(200, 168, 255))
310323

311324

325+
### Flags and Converters
326+
327+
312328
class ConfigOptionFlags(commands.FlagConverter):
313329
active: Optional[bool] = commands.flag(
314330
name="active",
@@ -377,6 +393,9 @@ async def convert(self, ctx: GuildContext, argument: str):
377393
return argument
378394

379395

396+
### Command cog
397+
398+
380399
class Config(commands.Cog):
381400
"""Config and setup commands for Rodhaj"""
382401

@@ -511,6 +530,7 @@ def clean_prefixes(self, prefixes: Union[str, list[str]]) -> str:
511530
return ", ".join(f"`{prefix}`" for prefix in prefixes[2:])
512531

513532
### Misc Utilities
533+
514534
async def _handle_error(
515535
self, error: commands.CommandError, *, ctx: GuildContext
516536
) -> None:
@@ -520,6 +540,8 @@ async def _handle_error(
520540
elif isinstance(error, commands.BadArgument):
521541
await ctx.send(str(error))
522542

543+
### Commands
544+
523545
@is_manager()
524546
@bot_check_permissions(manage_channels=True, manage_webhooks=True)
525547
@commands.guild_only()

0 commit comments

Comments
 (0)