implemented discord adapter

This commit is contained in:
ben
2026-04-02 11:57:54 -04:00
parent 5210d2c032
commit 043c8b4df3
2 changed files with 58 additions and 14 deletions

View File

@@ -122,7 +122,7 @@ update the discord bot into a thin frontend that talks to the backend and verify
** evidence ** evidence
- commit: - commit:
- tests: - tests: https://youtu.be/20HxMMSqRyg?si=3v7mN2L88c_FxpQR 18m
1. start backend: `python3 -m uvicorn youdis.main:app --host 127.0.0.1 --port 8000` 1. start backend: `python3 -m uvicorn youdis.main:app --host 127.0.0.1 --port 8000`
2. create local env file: `cp .env.example .env` 2. create local env file: `cp .env.example .env`
3. add `api_token` to `.env` 3. add `api_token` to `.env`
@@ -142,17 +142,47 @@ KeyError: 'youtube'
#+end_src #+end_src
:end: :end:
#+end_:out:
#+begin_src
6. confirm channel response says the job was submitted to backend 6. confirm channel response says the job was submitted to backend
7. confirm requester receives DM updates for accepted/running/completed or failed 7. confirm requester receives DM updates for accepted/running/completed or failed
:out:
accepted job 4ef6fde5-57cc-478c-b0e2-18458c693fa4 for https://www.youtube.com/watch?v=dQw4w9WgXcQ
state=running | phase=running | [youtube] dQw4w9WgXcQ: Downloading webpage | path=/home/user/proj/youdis/downloads
state=running | phase=downloading | [download] Destination: /home/user/proj/youdis/downloads/Rick_Astley/NA/NANARickAstley-_Never_Gonna_Give_You_Up_Official_Video_4K_Remaster.f401.mp4 | path=/home/user/proj/youdis/downloads/Rick_Astley/NA/NANARickAstley-_Never_Gonna_Give_You_Up_Official_Video_4K_Remaster.f401.mp4
state=completed | [Metadata] There isn't any metadata to add | path=/home/user/proj/youdis/downloads/Rick_Astley/NA/NANARickAstley-_Never_Gonna_Give_You_Up_Official_Video_4K_Remaster.mp4
:end:
8. while first job is active, submit another `/youtube` and confirm busy behavior 8. while first job is active, submit another `/youtube` and confirm busy behavior
:out:
#+begin_src shell
Error: AttributeError
Traceback (most recent call last):
File "/home/user/proj/youdis/venv/lib/python3.10/site-packages/interactions/client/client.py", line 1900, in __dispatch_interaction
response = await callback
File "/home/user/proj/youdis/venv/lib/python3.10/site-packages/interactions/client/client.py", line 1771, in _run_slash_command
return await command(ctx, **ctx.kwargs)
File "/home/user/proj/youdis/venv/lib/python3.10/site-packages/interactions/models/internal/command.py", line 132, in __call__
await self.call_callback(self.callback, context)
File "/home/user/proj/youdis/venv/lib/python3.10/site-packages/interactions/models/internal/application_commands.py", line 833, in call_callback
return await self.call_with_binding(callback, ctx, *new_args, **new_kwargs)
File "/home/user/proj/youdis/venv/lib/python3.10/site-packages/interactions/models/internal/callback.py", line 43, in call_with_binding
return await callback(*args, **kwargs)
File "/home/user/proj/youdis/youdis/adapters/discord.py", line 167, in youtube
await ctx.channel.send(f"Submitted <{url}> to the backend. Status updates via DM.")
AttributeError: 'NoneType' object has no attribute 'send'
#+end_src
busy: busy with https://youtu.be/20HxMMSqRyg?si=3v7mN2L88c_FxpQR
state=running | phase=running | cancel requested | path=/home/user/proj/youdis/downloads/James_Hoffmann/NA/NANAThe_Beginner_s_Guide_To_Latte_Art.f401.mp4
:end:
9. run `/status` and confirm it reflects current or last backend job 9. run `/status` and confirm it reflects current or last backend job
:out:
last job: state=completed | [Metadata] There isn't any metadata to add | path=/home/user/proj/youdis/downloads/Rick_Astley/NA/NANARickAstley-_Never_Gonna_Give_You_Up_Official_Video_4K_Remaster.mp4
:end:
10. run `/interrupt` as owner and confirm cancellation is surfaced via DM 10. run `/interrupt` as owner and confirm cancellation is surfaced via DM
- datetime: :out:
last job: state=cancelled | cancelled | path=/home/user/proj/youdis/downloads/James_Hoffmann/NA/NANAThe_Beginner_s_Guide_To_Latte_Art.f401.mp4
:end:
- datetime:[2026-04-02 Thu 11:52]
*** testing tests *** org-block tests
#+begin_src shell :dir ~/proj/youdis :results output verbatim #+begin_src shell :dir ~/proj/youdis :results output verbatim
source ./venv/bin/activate source ./venv/bin/activate
python3 -m uvicorn youdis.main:app --host 127.0.0.1 --port 8000 python3 -m uvicorn youdis.main:app --host 127.0.0.1 --port 8000
@@ -177,7 +207,7 @@ python ./youdis.py
- progress updates are currently implemented by polling `/jobs/current` and DMing only when the summary changes - progress updates are currently implemented by polling `/jobs/current` and DMing only when the summary changes
- legacy auth/user-management commands were removed from the active adapter path and should be cleaned up formally in `2.0.3` - legacy auth/user-management commands were removed from the active adapter path and should be cleaned up formally in `2.0.3`
- `.env` is now supported for local/dev convenience, while real environment variables still override it in prod/docker - `.env` is now supported for local/dev convenience, while real environment variables still override it in prod/docker
- submitting via DM doesn't work
* [ ] 2.0.3: remove deprecated discord-bot functionality (2) * [ ] 2.0.3: remove deprecated discord-bot functionality (2)
delete or retire legacy bot behaviors that no longer fit once the backend split is in place delete or retire legacy bot behaviors that no longer fit once the backend split is in place
** pm notes ** pm notes

View File

@@ -64,6 +64,13 @@ async def dm(ctx: interactions.SlashContext, message: str) -> None:
await ctx.author.send(message) await ctx.author.send(message)
async def respond(ctx: interactions.SlashContext, message: str) -> None:
if ctx.channel is not None:
await ctx.channel.send(message)
return
await dm(ctx, message)
async def poll_job_updates(ctx: interactions.SlashContext, job_id: str) -> None: async def poll_job_updates(ctx: interactions.SlashContext, job_id: str) -> None:
last_sent = None last_sent = None
try: try:
@@ -105,14 +112,18 @@ def ensure_poll_task(ctx: interactions.SlashContext, job_id: str) -> None:
return return
poll_tasks[job_id] = asyncio.create_task(poll_job_updates(ctx, job_id)) poll_tasks[job_id] = asyncio.create_task(poll_job_updates(ctx, job_id))
@bot.listen()
@interactions.listen()
async def on_startup(): async def on_startup():
await get_session() await get_session()
print(f"discord adapter configured for backend {BACKEND_URL}") print(f"discord adapter configured for backend {BACKEND_URL}")
print(f"discord adapter default scope: {DEFAULT_SCOPE}")
print(f"discord adapter command cache keys: {sorted(bot._interaction_lookup.keys())}")
@bot.listen()
async def on_ready():
print(f"registered commands: {bot.application_commands}")
@interactions.listen() @bot.listen()
async def on_shutdown(): async def on_shutdown():
global http_session global http_session
for task in list(poll_tasks.values()): for task in list(poll_tasks.values()):
@@ -151,16 +162,16 @@ async def youtube(ctx: interactions.SlashContext, url: str):
state = job.get("state") state = job.get("state")
job_id = job.get("job_id", "unknown") job_id = job.get("job_id", "unknown")
if state == "busy": if state == "busy":
await ctx.channel.send(f"Backend is busy with another job. Details via DM.") await respond(ctx, "Backend is busy with another job. Details via DM.")
await dm(ctx, f"busy: {job.get('message')}") await dm(ctx, f"busy: {job.get('message')}")
return return
if state != "accepted": if state != "accepted":
await ctx.channel.send("Backend rejected the request. Details via DM.") await respond(ctx, "Backend rejected the request. Details via DM.")
await dm(ctx, format_status_message(job)) await dm(ctx, format_status_message(job))
return return
await ctx.channel.send(f"Submitted <{url}> to the backend. Status updates via DM.") await respond(ctx, f"Submitted <{url}> to the backend. Status updates via DM.")
await dm(ctx, f"accepted job {job_id} for <{url}>") await dm(ctx, f"accepted job {job_id} for <{url}>")
ensure_poll_task(ctx, job_id) ensure_poll_task(ctx, job_id)
@@ -211,4 +222,7 @@ def main() -> None:
api_token = getenv("DISCORD_BOT_TOKEN") api_token = getenv("DISCORD_BOT_TOKEN")
if not api_token: if not api_token:
raise ValueError("API token not set. Retrieve from your Discord bot.") raise ValueError("API token not set. Retrieve from your Discord bot.")
bot.add_command(youtube)
bot.add_command(status)
bot.add_command(interrupt)
bot.start(api_token) bot.start(api_token)