feat(discord-bridge): comenzi slash in loc de prefixul !
Comenzile devin application commands inregistrate pe guild (sync instantaneu, spre deosebire de cel global care dureaza ~1h): /new [fork], /cd <cale>, /model <sonnet|opus> cu Choice, /status, /stop, /cleanup [force], /help. - allowlist-ul se aplica identic la interactiuni (check_ids comun, ca sa nu existe a doua implementare care diverge); refuz efemer, fara executie - fiecare comanda face defer() inainte de lucru — altfel Discord marcheaza interactiunea esuata dupa 3s desi comanda a rulat - sync tolerant: la esec (lipsa scope applications.commands) botul porneste normal si logheaza linkul de reinvitare necesar - mesajele obisnuite raman neschimbate, inclusiv steering-ul mid-tur - linkul de invitatie primeste scope=bot%20applications.commands; referintele la ! din ops/ si documentatie trecute pe / Verificat in productie: 7 comenzi inregistrate pe guild, citite inapoi din API. Suita: 296 passed cu discord.py, 293 passed + 3 skipped fara. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B29CApsP1JkSdjYaGaHpE7
This commit is contained in:
@@ -81,6 +81,51 @@ class FakeMessage:
|
||||
self.reactions.append(emoji)
|
||||
|
||||
|
||||
class FakeResponse:
|
||||
"""`interaction.response`: se poate folosi o singura data, ca la Discord."""
|
||||
|
||||
def __init__(self):
|
||||
self.deferred = 0
|
||||
self.messages: list[FakeSent] = []
|
||||
|
||||
async def defer(self, **kw):
|
||||
self.deferred += 1
|
||||
|
||||
async def send_message(self, content=None, **kw):
|
||||
msg = FakeSent(None, content, **kw)
|
||||
self.messages.append(msg)
|
||||
return msg
|
||||
|
||||
|
||||
class FakeFollowup:
|
||||
def __init__(self, channel):
|
||||
self.channel = channel
|
||||
|
||||
async def send(self, content=None, **kw):
|
||||
return await self.channel.send(content, **kw)
|
||||
|
||||
|
||||
class FakeInteraction:
|
||||
"""Minimul din `discord.Interaction` pe care il atinge puntea."""
|
||||
|
||||
def __init__(self, *, channel=None, user=None, guild_id=GUILD,
|
||||
channel_id=None, iid="i1"):
|
||||
self.channel = channel if channel is not None else FakeChannel()
|
||||
self.channel_id = channel_id if channel_id is not None else self.channel.id
|
||||
self.guild_id = guild_id
|
||||
self.user = user if user is not None else FakeUser()
|
||||
self.id = iid
|
||||
self.response = FakeResponse()
|
||||
self.followup = FakeFollowup(self.channel)
|
||||
|
||||
@property
|
||||
def ephemeral(self) -> str:
|
||||
"""Textul raspunsurilor efemere (refuzuri)."""
|
||||
return "\n".join(
|
||||
(m.content or "") for m in self.response.messages if m.kwargs.get("ephemeral")
|
||||
)
|
||||
|
||||
|
||||
# ------------------------------------------------------------------ fixturi
|
||||
@pytest.fixture
|
||||
def allowed(monkeypatch):
|
||||
@@ -225,6 +270,19 @@ async def test_raspuns_lung_devine_atasament(bridge, store, monkeypatch):
|
||||
assert any(m.kwargs.get("file") is not None for m in msg.channel.sent)
|
||||
|
||||
|
||||
# ------------------------------------------------- prefixul vechi nu mai executa
|
||||
async def test_prefixul_vechi_da_doar_un_indiciu(bridge):
|
||||
ch = FakeChannel()
|
||||
assert await bridge.handle_message(FakeMessage("!status", channel=ch)) == "legacy-prefix"
|
||||
assert "/status" in ch.all_text
|
||||
assert bridge.runner.procs == {} # niciun tur pornit, niciun cost
|
||||
|
||||
|
||||
async def test_text_care_incepe_cu_exclamare_dar_nu_e_comanda_merge_la_claude(bridge):
|
||||
ch = FakeChannel()
|
||||
assert await bridge.handle_message(FakeMessage("!!! ce faci?", channel=ch)) == "ok"
|
||||
|
||||
|
||||
# ------------------------------------------------------------------ aprobari
|
||||
def test_decizia_cere_allowlist(bridge, allowed):
|
||||
assert "allowlist" in bridge.decide("777", "req-1", "allow")
|
||||
@@ -251,7 +309,7 @@ async def test_botul_porneste_si_fara_modulele_lui_b_si_c(bridge, monkeypatch):
|
||||
monkeypatch.setattr(bot, "cleanup", None)
|
||||
assert bridge.wire_approvals() is False
|
||||
ch = FakeChannel()
|
||||
await bridge.handle_message(FakeMessage("!cleanup", channel=ch))
|
||||
await bridge.handle_slash(FakeInteraction(channel=ch), "cleanup")
|
||||
assert "nu e disponibil" in ch.all_text
|
||||
assert bridge.approval_view("req-1") is None
|
||||
assert await bridge.handle_message(FakeMessage("salut", channel=ch)) == "ok"
|
||||
|
||||
Reference in New Issue
Block a user