banterbotapi 0.2.4__tar.gz → 0.2.5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {banterbotapi-0.2.4 → banterbotapi-0.2.5}/PKG-INFO +1 -1
- {banterbotapi-0.2.4 → banterbotapi-0.2.5}/banterapi/__init__.py +1 -1
- {banterbotapi-0.2.4 → banterbotapi-0.2.5}/banterapi/client.py +179 -8
- {banterbotapi-0.2.4 → banterbotapi-0.2.5}/banterbotapi.egg-info/PKG-INFO +1 -1
- {banterbotapi-0.2.4 → banterbotapi-0.2.5}/pyproject.toml +1 -1
- {banterbotapi-0.2.4 → banterbotapi-0.2.5}/LICENSE +0 -0
- {banterbotapi-0.2.4 → banterbotapi-0.2.5}/README.md +0 -0
- {banterbotapi-0.2.4 → banterbotapi-0.2.5}/banterapi/commands.py +0 -0
- {banterbotapi-0.2.4 → banterbotapi-0.2.5}/banterapi/embed.py +0 -0
- {banterbotapi-0.2.4 → banterbotapi-0.2.5}/banterapi/errors.py +0 -0
- {banterbotapi-0.2.4 → banterbotapi-0.2.5}/banterapi/gateway.py +0 -0
- {banterbotapi-0.2.4 → banterbotapi-0.2.5}/banterapi/http.py +0 -0
- {banterbotapi-0.2.4 → banterbotapi-0.2.5}/banterapi/intents.py +0 -0
- {banterbotapi-0.2.4 → banterbotapi-0.2.5}/banterapi/interactions.py +0 -0
- {banterbotapi-0.2.4 → banterbotapi-0.2.5}/banterapi/models.py +0 -0
- {banterbotapi-0.2.4 → banterbotapi-0.2.5}/banterapi/permissions.py +0 -0
- {banterbotapi-0.2.4 → banterbotapi-0.2.5}/banterbotapi.egg-info/SOURCES.txt +0 -0
- {banterbotapi-0.2.4 → banterbotapi-0.2.5}/banterbotapi.egg-info/dependency_links.txt +0 -0
- {banterbotapi-0.2.4 → banterbotapi-0.2.5}/banterbotapi.egg-info/requires.txt +0 -0
- {banterbotapi-0.2.4 → banterbotapi-0.2.5}/banterbotapi.egg-info/top_level.txt +0 -0
- {banterbotapi-0.2.4 → banterbotapi-0.2.5}/setup.cfg +0 -0
|
@@ -23,7 +23,7 @@ The library mirrors discord.py conventions where possible. See the Bot,
|
|
|
23
23
|
Intents, Embed, and Permissions classes for the main entry points.
|
|
24
24
|
"""
|
|
25
25
|
|
|
26
|
-
__version__ = "0.2.
|
|
26
|
+
__version__ = "0.2.5"
|
|
27
27
|
|
|
28
28
|
from .client import Bot
|
|
29
29
|
from .intents import Intents
|
|
@@ -47,6 +47,140 @@ _DEFAULT_BASE_URL = "https://banterchat.org"
|
|
|
47
47
|
_GATEWAY_PATH = "/api/v1/bot/gateway"
|
|
48
48
|
|
|
49
49
|
|
|
50
|
+
def _auto_options_from_signature(fn):
|
|
51
|
+
"""Best-effort convert a prefix-command signature into slash options.
|
|
52
|
+
|
|
53
|
+
Skips the leading ctx parameter. Maps int → integer, bool → boolean,
|
|
54
|
+
everything else → string. A parameter with a default is marked
|
|
55
|
+
optional; one without is required. Keyword-only (*, text: str)
|
|
56
|
+
gets the same treatment as a normal positional — the interaction
|
|
57
|
+
adapter joins all option values into args_raw so the
|
|
58
|
+
remainder-grabbing behaviour still works.
|
|
59
|
+
"""
|
|
60
|
+
from .commands import OPTION_STRING, OPTION_INTEGER, OPTION_BOOLEAN
|
|
61
|
+
try:
|
|
62
|
+
sig = inspect.signature(fn)
|
|
63
|
+
except (ValueError, TypeError):
|
|
64
|
+
return []
|
|
65
|
+
params = list(sig.parameters.values())[1:] # skip ctx
|
|
66
|
+
out = []
|
|
67
|
+
for p in params:
|
|
68
|
+
if p.kind is inspect.Parameter.VAR_POSITIONAL:
|
|
69
|
+
# *args — too unstructured for the slash UI to render.
|
|
70
|
+
continue
|
|
71
|
+
if p.annotation is int:
|
|
72
|
+
opt_type = OPTION_INTEGER
|
|
73
|
+
elif p.annotation is bool:
|
|
74
|
+
opt_type = OPTION_BOOLEAN
|
|
75
|
+
else:
|
|
76
|
+
opt_type = OPTION_STRING
|
|
77
|
+
required = p.default is inspect.Parameter.empty
|
|
78
|
+
out.append({
|
|
79
|
+
"name": p.name,
|
|
80
|
+
"type": opt_type,
|
|
81
|
+
"description": p.name,
|
|
82
|
+
"required": required,
|
|
83
|
+
})
|
|
84
|
+
return out
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class _InteractionContext:
|
|
88
|
+
"""Context adapter for prefix commands invoked via slash.
|
|
89
|
+
|
|
90
|
+
Presents the same surface as commands.Context (bot/message/author/
|
|
91
|
+
channel_id/guild_id/reply/send/trigger_typing) but routes replies
|
|
92
|
+
through interaction.respond() so the server emits the correct
|
|
93
|
+
slash_command_response event + ephemeral gating.
|
|
94
|
+
|
|
95
|
+
Built on the fly in Bot._dispatch for interaction_create events
|
|
96
|
+
when the command is registered as a prefix command with slash=True.
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
def __init__(self, bot, interaction):
|
|
100
|
+
self.bot = bot
|
|
101
|
+
self.interaction = interaction
|
|
102
|
+
self.invoked_with = interaction.command_name
|
|
103
|
+
# Build args_raw from the options dict in stable (alphabetical)
|
|
104
|
+
# order so prefix-command parsers that read positional args
|
|
105
|
+
# still work. Values are space-joined; string values with
|
|
106
|
+
# spaces are NOT quoted — options with spaces need to declare
|
|
107
|
+
# a keyword-only parameter (def cmd(ctx, *, text: str)) which
|
|
108
|
+
# consumes the rest of the string.
|
|
109
|
+
opts = interaction.options or {}
|
|
110
|
+
self.args_raw = " ".join(str(opts[k]) for k in sorted(opts.keys()))
|
|
111
|
+
# author_perms isn't carried in the interaction event today;
|
|
112
|
+
# set to 0 and rely on server-side perm checks. has_permissions
|
|
113
|
+
# will return False for everything, which is safe-by-default.
|
|
114
|
+
self.author_perms = 0
|
|
115
|
+
# Synthetic author + channel + message stubs so handlers that
|
|
116
|
+
# access these attrs don't crash. Kept minimal — handlers that
|
|
117
|
+
# need real user data should use the Interaction API directly
|
|
118
|
+
# or upgrade to @bot.slash_command.
|
|
119
|
+
self.channel_id = interaction.channel_id
|
|
120
|
+
self.guild_id = interaction.guild_id
|
|
121
|
+
self.author = _StubUser(interaction.user_id)
|
|
122
|
+
self.channel = None
|
|
123
|
+
self.message = _StubMessage(interaction)
|
|
124
|
+
|
|
125
|
+
def has_permissions(self, required):
|
|
126
|
+
# Without perm data in the interaction payload we can't check.
|
|
127
|
+
# Return False so guarded commands fail closed; handlers that
|
|
128
|
+
# rely on this should be @bot.slash_command instead.
|
|
129
|
+
return False
|
|
130
|
+
|
|
131
|
+
async def send(self, content="", embed=None):
|
|
132
|
+
return await self.reply(content=content, embed=embed)
|
|
133
|
+
|
|
134
|
+
async def reply(self, content="", embed=None):
|
|
135
|
+
if self.interaction._responded:
|
|
136
|
+
await self.interaction.followup(content=content, embed=embed)
|
|
137
|
+
else:
|
|
138
|
+
await self.interaction.respond(content=content, embed=embed)
|
|
139
|
+
|
|
140
|
+
async def trigger_typing(self):
|
|
141
|
+
# Mapped to defer so the thinking indicator stays up.
|
|
142
|
+
if not self.interaction._responded:
|
|
143
|
+
try:
|
|
144
|
+
await self.interaction.defer()
|
|
145
|
+
except Exception:
|
|
146
|
+
pass
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
class _StubUser:
|
|
150
|
+
__slots__ = ("id", "username", "display_name", "bot")
|
|
151
|
+
|
|
152
|
+
def __init__(self, user_id):
|
|
153
|
+
self.id = user_id
|
|
154
|
+
self.username = ""
|
|
155
|
+
self.display_name = ""
|
|
156
|
+
self.bot = False
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
class _StubMessage:
|
|
160
|
+
"""Enough of a Message to keep handlers that touch ctx.message happy."""
|
|
161
|
+
__slots__ = ("_interaction", "id", "user_id", "channel_id", "guild_id", "content")
|
|
162
|
+
|
|
163
|
+
def __init__(self, interaction):
|
|
164
|
+
self._interaction = interaction
|
|
165
|
+
self.id = interaction.id
|
|
166
|
+
self.user_id = interaction.user_id
|
|
167
|
+
self.channel_id = interaction.channel_id
|
|
168
|
+
self.guild_id = interaction.guild_id
|
|
169
|
+
self.content = ""
|
|
170
|
+
|
|
171
|
+
async def reply(self, content="", embed=None):
|
|
172
|
+
if self._interaction._responded:
|
|
173
|
+
await self._interaction.followup(content=content, embed=embed)
|
|
174
|
+
else:
|
|
175
|
+
await self._interaction.respond(content=content, embed=embed)
|
|
176
|
+
|
|
177
|
+
async def add_reaction(self, emoji):
|
|
178
|
+
# Not meaningful on a synthetic interaction message. No-op so
|
|
179
|
+
# example code doesn't crash; bot devs who need real reactions
|
|
180
|
+
# should use @bot.slash_command + explicit channel.send().
|
|
181
|
+
return None
|
|
182
|
+
|
|
183
|
+
|
|
50
184
|
class Bot:
|
|
51
185
|
"""Main bot entry point.
|
|
52
186
|
|
|
@@ -213,14 +347,20 @@ class Bot:
|
|
|
213
347
|
"name": cmd.name,
|
|
214
348
|
"description": description or cmd.help or cmd.name,
|
|
215
349
|
}
|
|
350
|
+
from .commands import SlashOption as _SO
|
|
216
351
|
if options:
|
|
217
|
-
# Defer import to avoid a circular: commands.py is
|
|
218
|
-
# loaded by client.py, and SlashOption lives there.
|
|
219
|
-
from .commands import SlashOption as _SO
|
|
220
352
|
entry["options"] = [
|
|
221
353
|
(o.to_dict() if isinstance(o, _SO) else dict(o))
|
|
222
354
|
for o in options
|
|
223
355
|
]
|
|
356
|
+
else:
|
|
357
|
+
# No explicit options declared: try to auto-generate
|
|
358
|
+
# from the function signature so prefix-style typed
|
|
359
|
+
# handlers (def echo(ctx, *, text: str)) work via
|
|
360
|
+
# slash without the author repeating themselves.
|
|
361
|
+
auto = _auto_options_from_signature(fn)
|
|
362
|
+
if auto:
|
|
363
|
+
entry["options"] = auto
|
|
224
364
|
self._slash_commands.append(entry)
|
|
225
365
|
return fn
|
|
226
366
|
return decorator
|
|
@@ -413,13 +553,9 @@ class Bot:
|
|
|
413
553
|
return
|
|
414
554
|
|
|
415
555
|
if event_type == "interaction_create":
|
|
416
|
-
# Route slash-command invocations to their registered
|
|
417
|
-
# handlers. Falls back to a no-op (plus on_raw_event) if
|
|
418
|
-
# the command name isn't known — that shouldn't normally
|
|
419
|
-
# happen because the server filters by command ownership,
|
|
420
|
-
# but we don't want to crash the dispatch loop on drift.
|
|
421
556
|
from .interactions import Interaction
|
|
422
557
|
interaction = Interaction(payload, client=self)
|
|
558
|
+
# Dedicated @bot.slash_command handler — takes an Interaction.
|
|
423
559
|
handler = self._slash_handlers.get(interaction.command_name)
|
|
424
560
|
if handler is not None:
|
|
425
561
|
try:
|
|
@@ -428,6 +564,41 @@ class Bot:
|
|
|
428
564
|
await result
|
|
429
565
|
except Exception:
|
|
430
566
|
log.exception("slash handler %r raised", interaction.command_name)
|
|
567
|
+
await self._fire("on_interaction", interaction)
|
|
568
|
+
return
|
|
569
|
+
# Fall back to a prefix-style @bot.command handler. Build a
|
|
570
|
+
# Context adapter so the handler's existing ctx.reply / send
|
|
571
|
+
# / args-parsing keep working; responses route through
|
|
572
|
+
# interaction.respond.
|
|
573
|
+
cmd = self._commands.get(interaction.command_name)
|
|
574
|
+
if cmd is not None:
|
|
575
|
+
ctx = _InteractionContext(self, interaction)
|
|
576
|
+
try:
|
|
577
|
+
await cmd.invoke(ctx)
|
|
578
|
+
# If the handler never called reply/send, ack the
|
|
579
|
+
# interaction so the server stops spinning. Sending
|
|
580
|
+
# an empty message would fail validation; defer is
|
|
581
|
+
# the safe no-op.
|
|
582
|
+
if not interaction._responded:
|
|
583
|
+
try:
|
|
584
|
+
await interaction.defer()
|
|
585
|
+
except Exception:
|
|
586
|
+
pass
|
|
587
|
+
except CommandError as e:
|
|
588
|
+
await self._fire("on_command_error", ctx, e)
|
|
589
|
+
if not interaction._responded:
|
|
590
|
+
try:
|
|
591
|
+
await interaction.respond(f"error: {e}")
|
|
592
|
+
except Exception:
|
|
593
|
+
pass
|
|
594
|
+
except Exception as e:
|
|
595
|
+
await self._fire("on_command_error", ctx, e)
|
|
596
|
+
log.exception("prefix handler %r raised on interaction", interaction.command_name)
|
|
597
|
+
if not interaction._responded:
|
|
598
|
+
try:
|
|
599
|
+
await interaction.respond(f"error: {e}")
|
|
600
|
+
except Exception:
|
|
601
|
+
pass
|
|
431
602
|
else:
|
|
432
603
|
log.warning(
|
|
433
604
|
"received interaction for unknown slash command %r",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|