From 891b081791db9460964e99eb41d28b9bb8626d0b Mon Sep 17 00:00:00 2001 From: Lexie Love Date: Fri, 10 Feb 2023 11:11:01 -0600 Subject: [PATCH] update discordeno-audio-plugin and finally switch to yt_download, update discordeno --- deps.ts | 24 ++++----- discordeno-audio-plugin/deps.ts | 8 ++- .../src/audio-source/youtube.ts | 19 ++----- .../src/connection-data.ts | 5 ++ discordeno-audio-plugin/src/listen.ts | 12 +---- .../src/websocket/handlers.ts | 53 +++++++------------ .../src/websocket/heartbeat.ts | 36 ++++++++----- discordeno-audio-plugin/src/websocket/mod.ts | 31 ++++++----- .../src/websocket/opcodes.ts | 31 +++++++++++ discordeno-audio-plugin/utils/file.ts | 2 +- utils.ts | 4 +- 11 files changed, 119 insertions(+), 106 deletions(-) create mode 100644 discordeno-audio-plugin/src/websocket/opcodes.ts diff --git a/deps.ts b/deps.ts index 8feaab9..5f1f417 100644 --- a/deps.ts +++ b/deps.ts @@ -1,4 +1,4 @@ -export { ApplicationCommandOptionTypes, createBot, Intents, startBot } from "https://deno.land/x/discordeno@17.0.1/mod.ts"; +export { ApplicationCommandOptionTypes, createBot, Intents, startBot } from "https://deno.land/x/discordeno@18.0.1/mod.ts"; export { createGlobalApplicationCommand, editGlobalApplicationCommand, @@ -9,15 +9,15 @@ export { sendFollowupMessage, sendMessage, upsertGlobalApplicationCommands -} from "https://deno.land/x/discordeno@17.0.1/helpers/mod.ts"; -export { type BigString, type CreateApplicationCommand, type CreateSlashApplicationCommand, type InteractionCallbackData } from "https://deno.land/x/discordeno@17.0.1/types/mod.ts"; -export { type CreateMessage } from "https://deno.land/x/discordeno@17.0.1/helpers/messages/mod.ts"; -export { type InteractionResponse } from "https://deno.land/x/discordeno@17.0.1/types/discordeno.ts"; -export { editOriginalInteractionResponse, sendInteractionResponse } from "https://deno.land/x/discordeno@17.0.1/helpers/interactions/mod.ts"; -export { sendPrivateInteractionResponse } from "https://deno.land/x/discordeno@17.0.1/plugins/mod.ts"; -export { type Channel } from "https://deno.land/x/discordeno@17.0.1/transformers/channel.ts"; -export { type Bot } from "https://deno.land/x/discordeno@17.0.1/bot.ts"; -export { type Interaction } from "https://deno.land/x/discordeno@17.0.1/transformers/interaction.ts"; -export { type ApplicationCommandOption, type ApplicationCommandOptionChoice, type Embed } from "https://deno.land/x/discordeno@17.0.1/transformers/mod.ts"; -export { leaveVoiceChannel } from "https://deno.land/x/discordeno@17.0.1/helpers/guilds/mod.ts"; +} from "https://deno.land/x/discordeno@18.0.1/helpers/mod.ts"; +export { type BigString, type CreateApplicationCommand, type CreateSlashApplicationCommand, type InteractionCallbackData } from "https://deno.land/x/discordeno@18.0.1/types/mod.ts"; +export { type CreateMessage } from "https://deno.land/x/discordeno@18.0.1/helpers/messages/mod.ts"; +export { type InteractionResponse } from "https://deno.land/x/discordeno@18.0.1/types/discordeno.ts"; +export { editOriginalInteractionResponse, sendInteractionResponse } from "https://deno.land/x/discordeno@18.0.1/helpers/interactions/mod.ts"; +export { sendPrivateInteractionResponse } from "https://deno.land/x/discordeno@18.0.1/plugins/mod.ts"; +export { type Channel } from "https://deno.land/x/discordeno@18.0.1/transformers/channel.ts"; +export { type Bot } from "https://deno.land/x/discordeno@18.0.1/bot.ts"; +export { type Interaction } from "https://deno.land/x/discordeno@18.0.1/transformers/interaction.ts"; +export { type ApplicationCommandOption, type ApplicationCommandOptionChoice, type Embed } from "https://deno.land/x/discordeno@18.0.1/transformers/mod.ts"; +export { leaveVoiceChannel } from "https://deno.land/x/discordeno@18.0.1/helpers/guilds/mod.ts"; export { getConnectionData } from "./discordeno-audio-plugin/src/connection-data.ts"; \ No newline at end of file diff --git a/discordeno-audio-plugin/deps.ts b/discordeno-audio-plugin/deps.ts index 52d1500..215113d 100644 --- a/discordeno-audio-plugin/deps.ts +++ b/discordeno-audio-plugin/deps.ts @@ -1,8 +1,6 @@ -export * from "https://deno.land/x/discordeno@17.1.0/mod.ts"; -export * from "https://deno.land/x/discordeno@17.1.0/plugins/cache/mod.ts"; +export * from "https://deno.land/x/discordeno@18.0.1/mod.ts"; +export * from "https://deno.land/x/discordeno@18.0.1/plugins/cache/mod.ts"; export * as opus from "https://unpkg.com/@evan/wasm@0.0.95/target/opus/deno.js"; export * from "https://unpkg.com/@evan/wasm@0.0.95/target/nacl/deno.js"; -export { ytDownload } from "https://deno.land/x/yt_download@1.2/mod.ts"; -export type { VideoFormat } from "https://deno.land/x/ytdl_core@v0.1.2/src/types.ts"; -export { downloadFromInfo, getInfo, ytdl } from "https://deno.land/x/ytdl_core@v0.1.2/mod.ts"; +export { ytDownload } from "https://deno.land/x/yt_download@1.3/mod.ts"; export { default as YouTube } from "https://deno.land/x/youtube_sr@v4.1.17/mod.ts"; \ No newline at end of file diff --git a/discordeno-audio-plugin/src/audio-source/youtube.ts b/discordeno-audio-plugin/src/audio-source/youtube.ts index 2ef7a0a..a87345b 100644 --- a/discordeno-audio-plugin/src/audio-source/youtube.ts +++ b/discordeno-audio-plugin/src/audio-source/youtube.ts @@ -1,20 +1,8 @@ -import { YouTube, ytdl } from "../../deps.ts"; +import { YouTube, ytDownload } from "../../deps.ts"; import { bufferIter } from "../../utils/mod.ts"; import { demux } from "../demux/mod.ts"; import { createAudioSource, empty } from "./audio-source.ts"; -function supportedFormatFilter(format: { - codecs: string; - container: string; - audioSampleRate?: string; -}) { - return ( - format.codecs === "opus" && - format.container === "webm" && - format.audioSampleRate === "48000" - ); -} - export async function getYoutubeSources(added_by?: string, ...queries: string[]) { const sources = queries.map((query) => getYoutubeSource(query, added_by)); const awaitedSources = await Promise.all(sources); @@ -30,11 +18,12 @@ export async function getYoutubeSource(query: string, added_by?: string) { const { id, title } = results[0]; return createAudioSource(title!, async () => { try { - const stream = await ytdl(id!, { - filter: supportedFormatFilter + const stream = await ytDownload(id!, { + mimeType: `audio/webm; codecs="opus"`, }); return bufferIter(demux(stream)); } catch { + console.error("error"); console.log(`Failed to play ${title}\n Returning empty stream`); return empty(); } diff --git a/discordeno-audio-plugin/src/connection-data.ts b/discordeno-audio-plugin/src/connection-data.ts index d325627..ddad6e9 100644 --- a/discordeno-audio-plugin/src/connection-data.ts +++ b/discordeno-audio-plugin/src/connection-data.ts @@ -26,6 +26,9 @@ export type ConnectionData = { speaking: boolean; sequence: number; timestamp: number; + missedHeart: number; + lastHeart?: number; + reconnect: number; }; connectInfo: { endpoint?: string; @@ -106,6 +109,8 @@ export function getConnectionData(botId: bigint, guildId: bigint) { speaking: false, sequence: randomNBit(16), timestamp: randomNBit(32), + missedHeart: 0, + reconnect: 0, }, connectInfo: {}, audio: new EventSource<[Uint8Array]>(), diff --git a/discordeno-audio-plugin/src/listen.ts b/discordeno-audio-plugin/src/listen.ts index 420b062..59c996f 100644 --- a/discordeno-audio-plugin/src/listen.ts +++ b/discordeno-audio-plugin/src/listen.ts @@ -76,16 +76,6 @@ export function createOnAudio( } }) .filter((data) => data !== undefined) - .map((data) => data!) - .filter((data) => !silence(...data.decoded)); + .map((data) => data!); }; } - -function silence(...values: number[]) { - for (const value of values) { - if (value !== 0) { - return false; - } - } - return true; -} diff --git a/discordeno-audio-plugin/src/websocket/handlers.ts b/discordeno-audio-plugin/src/websocket/handlers.ts index 2d0d53d..9f145d4 100644 --- a/discordeno-audio-plugin/src/websocket/handlers.ts +++ b/discordeno-audio-plugin/src/websocket/handlers.ts @@ -3,42 +3,23 @@ import { ConnectionData, setUserSSRC } from "../connection-data.ts"; import { discoverIP } from "../udp/discover.ts"; import { setSpeaking } from "../udp/speaking.ts"; import { sendHeart } from "./heartbeat.ts"; - -export enum ServerVoiceOpcodes { - /** Complete the websocket handshake. */ - Ready = VoiceOpcodes.Ready, - /** Describe the session. */ - SessionDescription = VoiceOpcodes.SessionDescription, - /** Indicate which users are speaking. */ - Speaking = VoiceOpcodes.Speaking, - /** Sent to acknowledge a received client heartbeat. */ - HeartbeatACK = VoiceOpcodes.HeartbeatACK, - /** Time to wait between sending heartbeats in milliseconds. */ - Hello = VoiceOpcodes.Hello, - /** Acknowledge a successful session resume. */ - Resumed = VoiceOpcodes.Resumed, - /** A client has disconnected from the voice channel */ - ClientDisconnect = VoiceOpcodes.ClientDisconnect, - /** Weird OP code containing video and audio codecs */ - Undocumented = 14, -} +import { ReceiveVoiceOpcodes } from "./opcodes.ts"; export const socketHandlers: Record< - ServerVoiceOpcodes, + ReceiveVoiceOpcodes, (connectionData: ConnectionData, d: any) => void > = { - [ServerVoiceOpcodes.Ready]: ready, - [ServerVoiceOpcodes.SessionDescription]: sessionDescription, - [ServerVoiceOpcodes.Speaking]: speaking, - [ServerVoiceOpcodes.HeartbeatACK]: heartbeatACK, - [ServerVoiceOpcodes.Hello]: hello, - [ServerVoiceOpcodes.Resumed]: resumed, - [ServerVoiceOpcodes.ClientDisconnect]: clientDisconnect, - [ServerVoiceOpcodes.Undocumented]: undocumented, + [ReceiveVoiceOpcodes.Ready]: ready, + [ReceiveVoiceOpcodes.SessionDescription]: sessionDescription, + [ReceiveVoiceOpcodes.Speaking]: speaking, + [ReceiveVoiceOpcodes.HeartbeatACK]: heartbeatACK, + [ReceiveVoiceOpcodes.Hello]: hello, + [ReceiveVoiceOpcodes.Resumed]: resumed, + [ReceiveVoiceOpcodes.ClientDisconnect]: clientDisconnect, }; function hello(conn: ConnectionData, d: { heartbeat_interval: number }) { - conn.stopHeart = sendHeart(conn.ws!, d.heartbeat_interval); + conn.stopHeart = sendHeart(conn, d.heartbeat_interval); } function ready(conn: ConnectionData, d: any) { @@ -65,8 +46,9 @@ function ready(conn: ConnectionData, d: any) { } function resumed(conn: ConnectionData) { - console.log("Resumed success"); conn.context.ready = true; + conn.context.reconnect = 0; + setSpeaking(conn, true); } function sessionDescription(conn: ConnectionData, d: any) { @@ -75,6 +57,7 @@ function sessionDescription(conn: ConnectionData, d: any) { conn.secret = new Uint8Array(secret); conn.mode = mode; conn.context.ready = true; + conn.context.reconnect = 0; setSpeaking(conn, true); } @@ -83,8 +66,12 @@ function speaking(conn: ConnectionData, d: any) { const ssrc = Number(d.ssrc); setUserSSRC(conn, user_id, ssrc); } -function heartbeatACK() {} + +function heartbeatACK(conn: ConnectionData, d: number) { + if (conn.context.lastHeart === d) { + conn.context.missedHeart = 0; + conn.context.lastHeart = undefined; + } +} function clientDisconnect() {} - -function undocumented() {} diff --git a/discordeno-audio-plugin/src/websocket/heartbeat.ts b/discordeno-audio-plugin/src/websocket/heartbeat.ts index ab49463..b62b76e 100644 --- a/discordeno-audio-plugin/src/websocket/heartbeat.ts +++ b/discordeno-audio-plugin/src/websocket/heartbeat.ts @@ -1,29 +1,37 @@ import { VoiceOpcodes } from "../../deps.ts"; import { setDriftlessTimeout } from "npm:driftless"; +import { ConnectionData } from "../mod.ts"; -function createHeartBeat(time: number) { - return JSON.stringify({ - op: VoiceOpcodes.Heartbeat, - d: time, - }); +function sendHeartBeat(conn: ConnectionData) { + if (conn.context.lastHeart !== undefined) { + conn.context.missedHeart++; + } + conn.context.lastHeart = Date.now(); + conn.ws?.send( + JSON.stringify({ + op: VoiceOpcodes.Heartbeat, + d: conn.context.lastHeart, + }) + ); } -export function sendHeart(ws: WebSocket, interval: number) { +export function sendHeart(conn: ConnectionData, interval: number) { let last = Date.now(); - let timestamp = 0; - const heartbeat = createHeartBeat(timestamp); - if (ws.readyState === WebSocket.OPEN) { - ws.send(heartbeat); + if (conn.ws?.readyState === WebSocket.OPEN) { + sendHeartBeat(conn); } let done = false; const repeatBeat = () => { - if (done || ws.readyState !== WebSocket.OPEN) { + if (done || conn.ws?.readyState !== WebSocket.OPEN) { + return; + } + if (conn.context.missedHeart >= 3) { + console.log("Missed too many heartbeats, attempting reconnect"); + conn.ws?.close(); return; } - timestamp += interval; - const heartbeat = createHeartBeat(timestamp); last = Date.now(); - ws.send(heartbeat); + sendHeartBeat(conn); setDriftlessTimeout(repeatBeat, interval + (last - Date.now())); }; setDriftlessTimeout(repeatBeat, interval + (last - Date.now())); diff --git a/discordeno-audio-plugin/src/websocket/mod.ts b/discordeno-audio-plugin/src/websocket/mod.ts index 09e027c..0364fbd 100644 --- a/discordeno-audio-plugin/src/websocket/mod.ts +++ b/discordeno-audio-plugin/src/websocket/mod.ts @@ -1,6 +1,7 @@ -import { VoiceOpcodes } from "../../deps.ts"; +import { VoiceCloseEventCodes } from "../../deps.ts"; import { ConnectionData } from "../connection-data.ts"; -import { ServerVoiceOpcodes, socketHandlers } from "./handlers.ts"; +import { socketHandlers } from "./handlers.ts"; +import { SendVoiceOpcodes } from "./opcodes.ts"; export function connectWebSocket( conn: ConnectionData, @@ -19,7 +20,7 @@ export function connectWebSocket( const ws = new WebSocket(`wss://${endpoint}?v=4`); conn.ws = ws; const identifyRequest = JSON.stringify({ - op: VoiceOpcodes.Identify, + op: SendVoiceOpcodes.Identify, d: { server_id: guildId.toString(), user_id: userId.toString(), @@ -28,7 +29,7 @@ export function connectWebSocket( }, }); const resumeRequest = JSON.stringify({ - op: VoiceOpcodes.Resume, + op: SendVoiceOpcodes.Resume, d: { server_id: guildId.toString(), session_id: sessionId, @@ -69,7 +70,7 @@ function handleOpen( function handleMessage(conn: ConnectionData, ev: MessageEvent) { const data = JSON.parse(ev.data); - socketHandlers[data.op as ServerVoiceOpcodes](conn, data.d); + socketHandlers[data.op]?.(conn, data.d); } function handleClose( @@ -80,15 +81,19 @@ function handleClose( ) { conn.stopHeart(); conn.context.ready = false; - if (event.code < 4000) { - console.log("The socket lost its connection for some reason"); - conn.resume = true; - connectWebSocket(conn, userId, guildId); - } else if (event.code === 4014) { - conn.context.speaking = false; - } else if (event.code === 4006) { - conn.context.speaking = false; + conn.context.speaking = false; + conn.context.lastHeart = undefined; + conn.context.missedHeart = 0; + if (VoiceCloseEventCodes.Disconnect === event.code) { + console.log("Couldn't reconnect :("); + return; } + if (conn.context.reconnect >= 3) { + return; + } + conn.context.reconnect++; + conn.resume = true; + connectWebSocket(conn, userId, guildId); } /** diff --git a/discordeno-audio-plugin/src/websocket/opcodes.ts b/discordeno-audio-plugin/src/websocket/opcodes.ts new file mode 100644 index 0000000..2a6098c --- /dev/null +++ b/discordeno-audio-plugin/src/websocket/opcodes.ts @@ -0,0 +1,31 @@ +import { VoiceOpcodes } from "../../deps.ts"; + +export enum ReceiveVoiceOpcodes { + /** Complete the websocket handshake. */ + Ready = VoiceOpcodes.Ready, + /** Describe the session. */ + SessionDescription = VoiceOpcodes.SessionDescription, + /** Indicate which users are speaking. */ + Speaking = VoiceOpcodes.Speaking, + /** Sent to acknowledge a received client heartbeat. */ + HeartbeatACK = VoiceOpcodes.HeartbeatACK, + /** Time to wait between sending heartbeats in milliseconds. */ + Hello = VoiceOpcodes.Hello, + /** Acknowledge a successful session resume. */ + Resumed = VoiceOpcodes.Resumed, + /** A client has disconnected from the voice channel */ + ClientDisconnect = VoiceOpcodes.ClientDisconnect, +} + +export enum SendVoiceOpcodes { + /** Begin a voice websocket connection. */ + Identify = VoiceOpcodes.Identify, + /** Select the voice protocol. */ + SelectProtocol = VoiceOpcodes.SelectProtocol, + /** Keep the websocket connection alive. */ + Heartbeat = VoiceOpcodes.Heartbeat, + /** Indicate which users are speaking. */ + Speaking = VoiceOpcodes.Speaking, + /** Resume a connection. */ + Resume = VoiceOpcodes.Resume, +} \ No newline at end of file diff --git a/discordeno-audio-plugin/utils/file.ts b/discordeno-audio-plugin/utils/file.ts index 3e551af..8b0b7fb 100644 --- a/discordeno-audio-plugin/utils/file.ts +++ b/discordeno-audio-plugin/utils/file.ts @@ -1,4 +1,4 @@ -export async function* streamAsyncIterator(stream: ReadableStream) { +export async function* streamAsyncIterator(stream: ReadableStream) { // Get a lock on the stream const reader = stream.getReader(); diff --git a/utils.ts b/utils.ts index 5912371..b3aceee 100644 --- a/utils.ts +++ b/utils.ts @@ -1,6 +1,6 @@ import { ytdl } from "https://deno.land/x/ytdl_core@v0.1.1/mod.ts"; -import { Bot } from "https://deno.land/x/discordeno@17.0.1/bot.ts"; -import { connectToVoiceChannel } from "https://deno.land/x/discordeno@17.0.1/helpers/guilds/mod.ts"; +import { Bot } from "https://deno.land/x/discordeno@18.0.1/bot.ts"; +import { connectToVoiceChannel } from "https://deno.land/x/discordeno@18.0.1/helpers/guilds/mod.ts"; import { configs } from "./configs.ts"; import { getChannel, getChannels, getGuild, type BigString, type Embed, type InteractionCallbackData, type InteractionResponse } from "./deps.ts";