Skip to content

Commit 367f657

Browse files
Slash commands (#212)
* Initial slash commands (dm messages not funcitonal yet) * Mod combined implementation * Slash refactoring: removed duplicated logic and limited to user facing commands * Help slash and formatting * Invoke Command * BaseCmds is already Cog, this is redundant * update nextcord * help subcommands * subcommands example * oh come on read the code and stop blindly using AI * imports * fix that typo * restore text command for docsearch and add reply_generic to bot for future code reuse * ephemeral is the terminology discord and other bots use. let's keep the UX consistent. * fix: Formatting and Subcommands * Fix: corrected group call * Black Formatting * Fix: duplicate welcome * fix black line length PR bloat * Fix: Display name consistency * Poetry Lock * fix: amend issues found in testing * fix: amend issues found in testing --------- Co-authored-by: borketh <does.not.kompute@gmail.com>
1 parent e155b21 commit 367f657

20 files changed

Lines changed: 852 additions & 292 deletions

fred/cogs/crashes.py

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,18 @@
88
from asyncio import Task, TaskGroup
99
from os.path import split
1010
from pathlib import Path
11-
from typing import AsyncIterator, IO, Type, Coroutine, Generator, Optional, Any, Final, TypedDict, AsyncGenerator
11+
from typing import (
12+
AsyncIterator,
13+
IO,
14+
Type,
15+
Coroutine,
16+
Generator,
17+
Optional,
18+
Any,
19+
Final,
20+
TypedDict,
21+
AsyncGenerator,
22+
)
1223
from urllib.parse import urlparse
1324
from zipfile import ZipFile
1425

@@ -197,7 +208,11 @@ def replace_response_value_with_captured(m) -> str:
197208
return f"{{Group {group} not captured in crash regex!}}"
198209
return match.group(group)
199210

200-
response = re2.sub(r"{(\d+)}", replace_response_value_with_captured, str(crash["response"]))
211+
response = re2.sub(
212+
r"{(\d+)}",
213+
replace_response_value_with_captured,
214+
str(crash["response"]),
215+
)
201216
yield CrashResponse(name=crash["name"], value=response, inline=True)
202217

203218
async def detect_and_fetch_pastebin_content(self, text: str) -> str:
@@ -219,15 +234,21 @@ async def process_text(self, text: str, filename="") -> list[CrashResponse]:
219234
responses.extend(await self.process_text(await self.detect_and_fetch_pastebin_content(text)))
220235

221236
if match := await safe_search(
222-
r"([^\n]*Critical error:.*Engine exit[^\n]*\))", text, flags=re2.I | re2.M | re2.S
237+
r"([^\n]*Critical error:.*Engine exit[^\n]*\))",
238+
text,
239+
flags=re2.I | re2.M | re2.S,
223240
):
224241
filename = os.path.basename(filename)
225242
crash = match.group(1)
226243
responses.append(
227244
CrashResponse(
228245
name=f"Crash found in {filename}",
229246
value="It has been attached to this message.",
230-
attachment=File(io.StringIO(crash), filename="Abridged " + filename, force_close=True),
247+
attachment=File(
248+
io.StringIO(crash),
249+
filename="Abridged " + filename,
250+
force_close=True,
251+
),
231252
)
232253
)
233254

@@ -284,7 +305,8 @@ def _ext_filter(ext: str) -> bool:
284305

285306
async def _obtain_attachments(self, message: Message) -> AsyncGenerator[tuple[str, IO | Exception], None, None]:
286307
cdn_links = re2.findall(
287-
r"(https://(?:cdn.discordapp.com|media.discordapp.net)/attachments/\S+)", message.content
308+
r"(https://(?:cdn.discordapp.com|media.discordapp.net)/attachments/\S+)",
309+
message.content,
288310
)
289311

290312
yield bool(cdn_links or message.attachments)
@@ -349,7 +371,8 @@ async def process_message(self, message: Message) -> bool:
349371
self.logger.exception(file_or_exc)
350372
responses.append(
351373
CrashResponse(
352-
name="Download failed", value=f"Could not obtain file '{name}' due to `{file_or_exc}`"
374+
name="Download failed",
375+
value=f"Could not obtain file '{name}' due to `{file_or_exc}`",
353376
)
354377
)
355378
continue
@@ -582,7 +605,10 @@ def _get_fg_log_details(log_file: IO[bytes]):
582605
logger.info("Didn't find all four pieces of information normally found in a log!")
583606
logger.debug(json.dumps(info, indent=2))
584607

585-
mod_loader_logs = filter(lambda l: re2.search("LogSatisfactoryModLoader", l), map(lambda b: b.decode(), lines))
608+
mod_loader_logs = filter(
609+
lambda l: re2.search("LogSatisfactoryModLoader", l),
610+
map(lambda b: b.decode(), lines),
611+
)
586612

587613
for line in mod_loader_logs:
588614
if match := re2.search(r"(?<=v\.)(?P<sml>[\d.]+)", line):

fred/cogs/levelling.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,8 @@ async def validate_role(self):
7171
if not role:
7272
logpayload["role_id"] = role_id
7373
logger.warning(
74-
"Could not validate someone's level role because the role isn't in the main guild", extra=logpayload
74+
"Could not validate someone's level role because the role isn't in the main guild",
75+
extra=logpayload,
7576
)
7677
return
7778
self.DB_user.rank_role_id = role_id
@@ -80,7 +81,10 @@ async def validate_role(self):
8081
for member_role in self.member.roles:
8182
if config.RankRoles.fetch_by_role(member_role.id) is not None: # i.e. member_role is a rank role
8283
logpayload["role_id"] = member_role.id
83-
logger.info("Removing a mismatched level role from someone", extra=logpayload)
84+
logger.info(
85+
"Removing a mismatched level role from someone",
86+
extra=logpayload,
87+
)
8488
await self.member.remove_roles(member_role)
8589
logpayload["role_id"] = role.id
8690
logger.info("Removing a mismatched level role from someone", logpayload)

fred/cogs/mediaonly.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,10 @@ async def _process_message(self, message: Message, *, thread: bool) -> bool:
3333
ctx = await self.bot.get_context(message)
3434

3535
if await common.l4_only(ctx):
36-
self.logger.info("Message doesn't contain media but the author is a T3", extra=common.message_info(message))
36+
self.logger.info(
37+
"Message doesn't contain media but the author is a T3",
38+
extra=common.message_info(message),
39+
)
3740
return False
3841

3942
if thread:

fred/cogs/webhooklistener.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,10 @@ def do_POST(self):
9797

9898
# Return error if the payload type is that other weird format instead of a normal json
9999
if content_type != "application/json":
100-
logger.error("POST request has invalid content_type", extra={"content_type": content_type})
100+
logger.error(
101+
"POST request has invalid content_type",
102+
extra={"content_type": content_type},
103+
)
101104
self.send_error(400, "Bad Request", "Expected a JSON request")
102105
return
103106

fred/cogs/welcome.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,16 @@ async def on_member_join(self, member: Member):
1414

1515
async def send_welcome_message(self, member: Member | User):
1616
if welcome := config.Misc.fetch("welcome_message"):
17-
self.logger.info("Sending the welcome message to a new member", extra=common.user_info(member))
17+
self.logger.info(
18+
"Sending the welcome message to a new member",
19+
extra=common.user_info(member),
20+
)
1821
await self.bot.send_safe_direct_message(member, welcome)
1922

2023
if info := config.Misc.fetch("latest_info"):
21-
self.logger.info("Sending the latest information to a new member", extra=common.user_info(member))
24+
self.logger.info(
25+
"Sending the latest information to a new member",
26+
extra=common.user_info(member),
27+
)
2228
info = f"Here's the latest information :\n\n{info}"
2329
await self.bot.send_safe_direct_message(member, info)

fred/config.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,16 @@
66
from typing import Optional, Any, TypedDict
77

88
import nextcord
9-
from sqlobject import SQLObject, IntCol, BoolCol, JSONCol, BigIntCol, StringCol, FloatCol, sqlhub
9+
from sqlobject import (
10+
SQLObject,
11+
IntCol,
12+
BoolCol,
13+
JSONCol,
14+
BigIntCol,
15+
StringCol,
16+
FloatCol,
17+
sqlhub,
18+
)
1019
from sqlobject.dberrors import DuplicateEntryError
1120

1221

fred/fred.py

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,21 @@ async def safe_send(
227227

228228
return await to.send(content=content, files=files, **kwargs)
229229

230+
async def reply_generic(
231+
self,
232+
target: nextcord.Message | nextcord.Interaction | commands.Context,
233+
content: Optional[str] = None,
234+
propagate_reply: bool = True,
235+
**kwargs,
236+
) -> nextcord.Message:
237+
if isinstance(target, nextcord.Message):
238+
return await self.reply_to_msg(target, content, propagate_reply, **kwargs)
239+
if isinstance(target, nextcord.Interaction):
240+
return await target.send(content, **kwargs)
241+
if isinstance(target, commands.Context):
242+
return await self.reply_to_msg(target.message, content, propagate_reply, **kwargs)
243+
raise TypeError(f"Unsupported type {type(target)}")
244+
230245
async def reply_to_msg(
231246
self,
232247
message: nextcord.Message,
@@ -275,7 +290,7 @@ def check(message2: nextcord.Message):
275290
await self.reply_to_msg(message, "Timed out and aborted after 120 seconds.")
276291
raise asyncio.TimeoutError
277292

278-
return response.content, response.attachments[0] if response.attachments else None
293+
return response.content, (response.attachments[0] if response.attachments else None)
279294

280295
async def reply_yes_or_no(self, message: nextcord.Message, question: Optional[str] = None, **kwargs) -> bool:
281296
response, _ = await self.reply_question(message, question, **kwargs)
@@ -300,15 +315,21 @@ async def on_message(self, message: nextcord.Message):
300315
self.logger.info("Processing a DM", extra=common.message_info(message))
301316
if message.content.lower() == "start":
302317
config.Users.fetch(message.author.id).accepts_dms = True
303-
self.logger.info("A user now accepts to receive DMs", extra=common.message_info(message))
318+
self.logger.info(
319+
"A user now accepts to receive DMs",
320+
extra=common.message_info(message),
321+
)
304322
await self.reply_to_msg(
305323
message,
306324
"You will now receive direct messages from me again! If you change your mind, send a message that says `stop`.",
307325
)
308326
return
309327
elif message.content.lower() == "stop":
310328
config.Users.fetch(message.author.id).accepts_dms = False
311-
self.logger.info("A user now refuses to receive DMs", extra=common.message_info(message))
329+
self.logger.info(
330+
"A user now refuses to receive DMs",
331+
extra=common.message_info(message),
332+
)
312333
await self.reply_to_msg(
313334
message,
314335
"You will no longer receive direct messages from me! To resume, send a message that says `start`.",

0 commit comments

Comments
 (0)