refactor(voice): Strict type checking in voice internals & DAVE Support (rec)#3159
refactor(voice): Strict type checking in voice internals & DAVE Support (rec)#3159Paillat-dev wants to merge 36 commits into
Conversation
Discord enforces DAVE (E2E encryption) on all voice channels. py-cord currently decodes Opus packets before stripping the DAVE application-layer encryption, causing OpusError: corrupted stream and making voice reception completely non-functional. Changes: - reader.py: Rewrite decrypt_rtp() to call dave.decrypt() BEFORE Opus decoding. Handles SSRC-to-user_id race condition by trying all known DAVE user IDs when the mapping is not yet populated, then caching the result. Falls back to OPUS_SILENCE on decrypt failure. - opus.py: Remove erroneous dave.decrypt() call in _decode_packet() that was applied to already-decoded PCM data, corrupting the audio stream. - router.py: Catch OpusError and AssertionError in _do_run() gracefully, emitting a single warning instead of crashing the router thread. Fixes: #3139
|
Thanks for opening this pull request! This pull request can be checked-out with: git fetch origin pull/3159/head:pr-3159
git checkout pr-3159This pull request can be installed with: pip install git+https://github.com/Pycord-Development/pycord@refs/pull/3159/head |
3890460 to
fee38b7
Compare
Signed-off-by: Paillat <paillat@pycord.dev>
This comment has been minimized.
This comment has been minimized.
|
FWIW, I've been running this PR for a few weeks now on both Discord iOS (cellular and WiFi) and macOS clients, and it's been rock solid. No issues. Setup:
General testing has been multi-user voice channels for up to 30 minutes with bot joining/leaving/reattaching mid-session. Great job! |
| if self.after and self.args: | ||
| try: | ||
| self.after(self.error) | ||
| r = self.after(self.sink, *self.args) | ||
| if isinstance(r, Coroutine): | ||
| self.client.loop.create_task( | ||
| r | ||
| ) # pyright: ignore[reportUnknownArgumentType] |
There was a problem hiding this comment.
an issue I observed using this API with a callback with no args is that the callback was silently skipped on stop_recording(). For example:
async def callback(sink):
pass
vc = await ctx.author.voice.channel.connect()
vc.start_recording(discord.sinks.WaveSink(), callback)
vc.stop_recording()callback will not fire when vc.stop_recording() is called, because self.args is empty. Pretty sure it's this line of code causing the issue (should be a check for self.args is not None maybe?)
Could be holding this wrong; I've never used Pycord before, and the original code which triggered this issue was authored by AI. Although I did trace the issue and write this comment/repro with my human brain.
| self.keep_alive.stop() | ||
|
|
||
| if self.after: | ||
| if self.after and self.args: |
There was a problem hiding this comment.
| if self.after and self.args: | |
| if self.after is not None and self.args is not None: |
When passing a callback with no extra args, self.args is an empty tuple (), which is falsy in python, causing the issue described by @tylanphear.

Summary
Information
examples, ...).
Checklist
type: ignorecomments were used, a comment is also left explaining why.