diff --git a/.gitignore b/.gitignore index 2afeefd..12ba624 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,4 @@ -/configs.ts \ No newline at end of file +/configs.ts +/errors.txt +/unused_extras.ts +/test \ No newline at end of file diff --git a/commands.ts b/commands.ts index 637bd7a..c94818f 100644 --- a/commands.ts +++ b/commands.ts @@ -46,6 +46,10 @@ export async function parseCommand(bot: Bot, interaction: Interaction) { await skip(bot, interaction); break; } + case "stop": { + await pause(bot, interaction); + break; + } case "unloop": { await unloop(bot, interaction); break; diff --git a/commands/play.ts b/commands/play.ts index 845470e..567725a 100644 --- a/commands/play.ts +++ b/commands/play.ts @@ -7,9 +7,17 @@ import { type CreateSlashApplicationCommand } from "../deps.ts"; -import { ensureVoiceConnection, formatCallbackData, waitingForResponse } from "../utils.ts"; +import { YouTube } from "../discordeno-audio-plugin/deps.ts"; -function addedToQueueResponse(interaction: Interaction, title: string) { +import { ensureVoiceConnection, formatCallbackData, isPlaylist, waitingForResponse } from "../utils.ts"; + +async function addedPlaylistResponse(interaction: Interaction, url: string) { + const playlist = await YouTube.getPlaylist(url); + return formatCallbackData(`${interaction.user.username} added ${playlist.videoCount} videos from [**${playlist.title}**](${interaction!.data!.options![0].value}) to the queue.`, + "Added to queue"); +} + +function addedSongResponse(interaction: Interaction, title: string) { return formatCallbackData(`${interaction.user.username} added [**${title}**](${interaction!.data!.options![0].value}) to the queue.`, "Added to queue"); } @@ -74,7 +82,12 @@ export async function play(bot: Bot, interaction: Interaction) { const result = await player.pushQuery(interaction.guildId, interaction.user.username, href); if(result && result[0] && parsed_url.href.indexOf("youtube.com") !== -1 || parsed_url.href.indexOf("youtu.be") !== -1 && result[0].title) { - await editOriginalInteractionResponse(bot, interaction.token, addedToQueueResponse(interaction, result[0].title)); + if(isPlaylist(parsed_url.href)) + { + await editOriginalInteractionResponse(bot, interaction.token, await addedPlaylistResponse(interaction, parsed_url.href)); + } else { + await editOriginalInteractionResponse(bot, interaction.token, addedSongResponse(interaction, result[0].title)); + } } } else { // restart the player if there's no url diff --git a/discordeno-audio-plugin/deps.ts b/discordeno-audio-plugin/deps.ts index b570167..6aa2ad4 100644 --- a/discordeno-audio-plugin/deps.ts +++ b/discordeno-audio-plugin/deps.ts @@ -2,5 +2,5 @@ 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.7/mod.ts"; +export { getVideoInfo, ytDownload } from "https://deno.land/x/yt_download@1.7/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/universal.ts b/discordeno-audio-plugin/src/audio-source/universal.ts index 1567d65..160e067 100644 --- a/discordeno-audio-plugin/src/audio-source/universal.ts +++ b/discordeno-audio-plugin/src/audio-source/universal.ts @@ -1,7 +1,23 @@ +import { YouTube } from "../../deps.ts"; import { getYoutubeSources } from "./youtube.ts"; +import { isPlaylist } from "../../../utils.ts"; + export type LoadSource = typeof loadLocalOrYoutube; -export function loadLocalOrYoutube(query: string, guildId: bigint, added_by?: string) { - return getYoutubeSources(guildId, String(added_by), query); +export async function loadLocalOrYoutube(query: string, guildId: bigint, added_by?: string) { + const queries = []; + + if(isPlaylist(query)) + { + const playlist = await YouTube.getPlaylist(query); + for(const video of playlist.videos) { + const videoId = video.id ? video.id : ""; + queries.push(videoId); + } + } else { + queries.push(query); + } + + return getYoutubeSources(guildId, String(added_by), queries); } diff --git a/discordeno-audio-plugin/src/audio-source/youtube.ts b/discordeno-audio-plugin/src/audio-source/youtube.ts index 63411fe..4365688 100644 --- a/discordeno-audio-plugin/src/audio-source/youtube.ts +++ b/discordeno-audio-plugin/src/audio-source/youtube.ts @@ -1,22 +1,63 @@ -import { YouTube, ytDownload } from "../../deps.ts"; +import { getVideoInfo, YouTube, ytDownload } from "../../deps.ts"; import { bufferIter, retry } from "../../utils/mod.ts"; import { demux } from "../demux/mod.ts"; import { createAudioSource, empty } from "./audio-source.ts"; -import { errorMessageCallback, parseYoutubeId } from "../../../utils.ts"; +import { errorMessageCallback, isPlaylist } from "../../../utils.ts"; -export async function getYoutubeSources(guildId: bigint, added_by?: string, ...queries: string[]) { +export async function getYoutubeSources(guildId: bigint, added_by?: string, queries: string[]) { const sources = queries.map((query) => getYoutubeSource(query, guildId, added_by)); const awaitedSources = await Promise.all(sources); + return awaitedSources .filter((source) => source !== undefined) .map((source) => source!); } -export async function getYoutubeSource(query: string, guildId: bigint, added_by?: string) { +/*export async function getYoutubeSource(query: string, guildId: bigint, added_by?: string) { + if(isPlaylist(query)) { + const playlist = await YouTube.getPlaylist(query); + const count = playlist.videoCount; + const sources = []; + + for(const video of playlist.videos) { + const videoId = video.id ? video.id : ""; + sources.push(getVideo(videoId, guildId, added_by)); + } + + return sources; + } + + return await getVideo(query, guildId, added_by); +}*/ + +async function getYoutubeSource(query: string, guildId: bigint, added_by?: string) { try { - query = parseYoutubeId(query); - const results = await YouTube.search(query, { limit: 1, type: "video" }); + const result = await getVideoInfo(query); + if(result.videoDetails.videoId) { + const id = result.videoDetails.videoId; + const title = result.videoDetails.title; + + return createAudioSource(title, async () => { + const stream = await retry( + async () => + await ytDownload(id, { + mimeType: `audio/webm; codecs="opus"`, + }) + ); + if (stream === undefined) { + errorMessageCallback(guildId, `There was an error trying to play **${title}**:\n + The stream couldn't be found`); + console.log(`Failed to play ${title}\n Returning empty stream`); + return empty(); + } + return bufferIter(demux(stream)); + }, guildId, added_by); + } + //const result = await ytDownload(query, { mimeType: `audio/webm; codecs="opus"`, }); + //console.log(result); + + /*const results = await YouTube.search(query, { limit: 1, type: "video" }); if (results.length > 0) { const { id, title } = results[0]; return createAudioSource(title!, async () => { @@ -28,13 +69,13 @@ export async function getYoutubeSource(query: string, guildId: bigint, added_by? ); if (stream === undefined) { errorMessageCallback(guildId, `There was an error trying to play **${title}**:\n - something broke in getYoutubeSource`); + The stream couldn't be found`); console.log(`Failed to play ${title}\n Returning empty stream`); return empty(); } return bufferIter(demux(stream)); }, guildId, added_by); - } + }*/ } catch(err) { console.error(err); return undefined; diff --git a/discordeno-audio-plugin/src/connection-data.ts b/discordeno-audio-plugin/src/connection-data.ts index 808a930..a42b473 100644 --- a/discordeno-audio-plugin/src/connection-data.ts +++ b/discordeno-audio-plugin/src/connection-data.ts @@ -7,17 +7,17 @@ import { sendAudioPacket } from "./udp/packet.ts"; export type BotData = { bot: Bot; guildData: Map; - udpSource: EventSource<[UdpArgs]>; + udpSource: EventSource; bufferSize: number; loadSource: (query: string, guild_id: bigint, added_by?: string) => AudioSource[] | Promise; }; export type ConnectionData = { player: QueuePlayer; - audio: EventSource<[Uint8Array]>; + audio: EventSource; guildId: bigint; udpSocket: Deno.DatagramConn; - udpStream: () => AsyncIterableIterator; + udpRaw: EventSource; ssrcToUser: Map; usersToSsrc: Map; context: { @@ -56,7 +56,7 @@ function randomNBit(n: number) { export function createBotData( bot: Bot, - udpSource: EventSource<[UdpArgs]>, + udpSource: EventSource, loadSource: LoadSource, bufferSize = 10 ) { @@ -97,12 +97,12 @@ export function getConnectionData(botId: bigint, guildId: bigint) { } } udpSocket = udpSocket as Deno.DatagramConn; - const udpReceive = new EventSource<[Uint8Array]>(); + const udpRaw = new EventSource(); data = { player: undefined as unknown as QueuePlayer, guildId, udpSocket, - udpStream: () => udpReceive.iter().map(([packet]) => packet), + udpRaw, context: { ssrc: 1, ready: false, @@ -113,7 +113,7 @@ export function getConnectionData(botId: bigint, guildId: bigint) { reconnect: 0, }, connectInfo: {}, - audio: new EventSource<[Uint8Array]>(), + audio: new EventSource(), ssrcToUser: new Map(), usersToSsrc: new Map(), stopHeart: () => {}, @@ -125,7 +125,7 @@ export function getConnectionData(botId: bigint, guildId: bigint) { guildId, udpSocket, botData.udpSource, - udpReceive + udpRaw ); connectAudioIterable(data); } @@ -133,7 +133,7 @@ export function getConnectionData(botId: bigint, guildId: bigint) { } async function connectAudioIterable(conn: ConnectionData) { - for await (const [chunk] of conn.audio.iter()) { + for await (const chunk of conn.audio.iter()) { await sendAudioPacket(conn, chunk); } } @@ -155,8 +155,8 @@ async function connectSocketToSource( bot: Bot, guildId: bigint, socket: Deno.DatagramConn, - source: EventSource<[UdpArgs]>, - localSource: EventSource<[Uint8Array]> + source: EventSource, + localSource: EventSource ) { for await (const [data, _address] of socket) { source.trigger({ bot, guildId, data }); diff --git a/discordeno-audio-plugin/src/listen.ts b/discordeno-audio-plugin/src/listen.ts index 59c996f..5ad22e1 100644 --- a/discordeno-audio-plugin/src/listen.ts +++ b/discordeno-audio-plugin/src/listen.ts @@ -26,15 +26,11 @@ type ReceivedAudio = { }; export function createOnAudio( - source: EventSource< - [ - { - bot: Bot; - guildId: bigint; - data: Uint8Array; - } - ] - > + source: EventSource<{ + bot: Bot; + guildId: bigint; + data: Uint8Array; + }> ) { return ( guild: bigint | bigint[], @@ -44,7 +40,6 @@ export function createOnAudio( const users = asArray(user); return source .iter() - .map(([udp]) => udp) .filter(({ data }) => { return data[1] === 120; }) diff --git a/discordeno-audio-plugin/src/mod.ts b/discordeno-audio-plugin/src/mod.ts index dfe0fe9..c0c5e4f 100644 --- a/discordeno-audio-plugin/src/mod.ts +++ b/discordeno-audio-plugin/src/mod.ts @@ -33,7 +33,7 @@ export function enableAudioPlugin( } function createAudioHelpers(bot: Bot, loadSource: LoadSource) { - const udpSource = new EventSource<[UdpArgs]>(); + const udpSource = new EventSource(); createBotData(bot, udpSource, loadSource); const resetPlayer = (guildId: bigint) => { const conn = getConnectionData(bot.id, guildId); diff --git a/discordeno-audio-plugin/src/player/events.ts b/discordeno-audio-plugin/src/player/events.ts new file mode 100644 index 0000000..650a4dd --- /dev/null +++ b/discordeno-audio-plugin/src/player/events.ts @@ -0,0 +1,35 @@ +import { BasicSource } from "../../utils/event-source.ts"; + +export type AllEventTypes = RawEventTypes | QueueEventTypes; +export type RawEventTypes = "next" | "done"; +export type QueueEventTypes = "loop"; + +export type PlayerListener = ( + data: EventData[J] +) => void; + +type PlayerEvent = { + type: J; + data: EventData[J]; +}; +type EventData = { + next: T; + done: T; + loop: T; +}; + +export class PlayerEventSource { + #source = new BasicSource>(); + + on(event: J, listener: PlayerListener) { + return this.#source.listen((value) => { + if (value.type === event) { + listener((value as PlayerEvent).data); + } + }); + } + + trigger(event: J, data: EventData[J]) { + this.#source.trigger({ type: event, data }); + } +} \ No newline at end of file diff --git a/discordeno-audio-plugin/src/player/queue-player.ts b/discordeno-audio-plugin/src/player/queue-player.ts index 4fc4d7e..ce95fbd 100644 --- a/discordeno-audio-plugin/src/player/queue-player.ts +++ b/discordeno-audio-plugin/src/player/queue-player.ts @@ -1,50 +1,64 @@ import { Queue } from "../../utils/mod.ts"; import { AudioSource, LoadSource } from "../audio-source/mod.ts"; import { ConnectionData } from "../connection-data.ts"; +import { PlayerEventSource, AllEventTypes, PlayerListener } from "./events.ts"; import { RawPlayer } from "./raw-player.ts"; import { Player } from "./types.ts"; import { nowPlayingCallback } from "../../../commands/np.ts"; -export class QueuePlayer extends Queue implements Player { - playing = false; +export class QueuePlayer + extends Queue + implements Player +{ + playing = true; looping = false; playingSince?: number; nowPlaying?: AudioSource; #rawPlayer: RawPlayer; #loadSource: LoadSource; + #events = new PlayerEventSource(); constructor(conn: ConnectionData, loadSource: LoadSource) { super(); this.#loadSource = loadSource; this.#rawPlayer = new RawPlayer(conn); - this.#startQueue(); - this.playing = true; - super.waiting = true; + this.playNext(); + this.#rawPlayer.on("done", async () => { + const current = this.current(); + if (current) { + this.#events.trigger("done", current); + } + await this.playNext(); + if (!this.playing) { + this.pause(); + } + }); } - async #setSong(song: AudioSource) { + async playNext() { + let song; + const current = this.current(); + if (this.looping && current !== undefined) { + song = current; + this.#events.trigger("loop", song); + } else { + song = await super.next(); + this.#events.trigger("next", song); + } this.playingSince = Date.now(); this.nowPlaying = song; this.#rawPlayer.setAudio(await song.data()); await nowPlayingCallback(this.#rawPlayer.conn); - await this.#rawPlayer.onDone(); - if (this.looping) { - this.#setSong(song); - } else { - this.triggerNext(); - } } - async #startQueue() { - for await (const [song] of this.stream()) { - await this.#setSong(song); - } + clear() { + return super.clear(); } play() { - this.#rawPlayer.play(); this.playing = true; + this.#rawPlayer.play(); return Promise.resolve(); } @@ -54,12 +68,12 @@ export class QueuePlayer extends Queue implements Player { } stop() { - this.playingSince = undefined; - this.#rawPlayer.clear(); + this.skip(); this.pause(); } skip() { + this.looping = false; this.#rawPlayer.clear(); } @@ -71,8 +85,23 @@ export class QueuePlayer extends Queue implements Player { this.#rawPlayer.interrupt(undefined); } - interrupt(audio: AsyncIterableIterator) { - this.#rawPlayer.interrupt(audio); +/** + * Listen to events: + * + * `next`: New sound started playing + * + * `done`: Last sound is done playing + * + * `loop`: New loop iteration was started + * @param event Event to listen to + * @param listener Triggered on event + * @returns Function that disconnects the listener + */ + on( + event: J, + listener: PlayerListener + ) { + return this.#events.on(event, listener); } /** diff --git a/discordeno-audio-plugin/src/player/raw-player.ts b/discordeno-audio-plugin/src/player/raw-player.ts index da62e77..2ba6400 100644 --- a/discordeno-audio-plugin/src/player/raw-player.ts +++ b/discordeno-audio-plugin/src/player/raw-player.ts @@ -1,33 +1,23 @@ -import { EventSource } from "../../utils/mod.ts"; import { ConnectionData } from "../connection-data.ts"; import { FRAME_DURATION } from "../sample-consts.ts"; import { Player } from "./types.ts"; import { setDriftlessInterval, clearDriftless } from "npm:driftless"; +import { PlayerEventSource, RawEventTypes, PlayerListener } from "./events.ts"; -export class RawPlayer implements Player { +import { errorMessageCallback } from "../../../utils.ts"; + +export class RawPlayer implements Player> { #audio?: AsyncIterableIterator; #interrupt?: AsyncIterableIterator; playing = false; conn: ConnectionData; - #doneSource = new EventSource<[]>(); - #nextSource = new EventSource<[]>(); - #onNext = () => this.#nextSource.iter().nextValue(); + #events = new PlayerEventSource< + AsyncIterableIterator, + RawEventTypes + >(); constructor(conn: ConnectionData) { this.conn = conn; - this.play(); - } - - setAudio(audio: AsyncIterableIterator) { - this.#audio = audio; - this.#nextSource.trigger(); - } - - interrupt(audio?: AsyncIterableIterator) { - this.#interrupt = audio; - if (this.#audio === undefined) { - this.#nextSource.trigger(); - } } play() { @@ -40,28 +30,16 @@ export class RawPlayer implements Player { const inter = setDriftlessInterval(async () => { if (this.playing === false) { clearDriftless(inter); - } - if (this.#interrupt) { - const { done, value } = await this.#interrupt.next(); - if (done) { - this.#interrupt = undefined; - } else { - this.conn.audio.trigger(value); - return; - } - } - const nextAudioIter = await this.#audio?.next(); - if (nextAudioIter === undefined || nextAudioIter.done) { - this.#audio = undefined; - this.#doneSource.trigger(); - this.playing = false; - await this.#onNext(); - this.play(); return; } - this.conn.audio.trigger(nextAudioIter.value); + const frame = await this.#getFrame(); + if (frame === undefined) { + return; + } + this.conn.audio.trigger(frame); }, FRAME_DURATION); } catch(err) { + errorMessageCallback(this.conn.guildId, `The player broke for some reason: ${err}`); console.log("error while playing!!"); console.error(err); } @@ -72,16 +50,61 @@ export class RawPlayer implements Player { } stop() { - this.pause(); this.clear(); + this.pause(); } clear() { - this.#audio = undefined; + if (this.#audio) { + this.#events.trigger("done", this.#audio); + } this.#interrupt = undefined; } - async onDone() { - await this.#doneSource.iter().nextValue(); +setAudio(audio: AsyncIterableIterator) { + this.#audio = audio; + this.#events.trigger("next", audio); + this.play(); + } + + interrupt(audio?: AsyncIterableIterator) { + this.#interrupt = audio; + if (!this.playing) { + this.play(); + } + } + + on( + event: J, + listener: PlayerListener, J> + ) { + return this.#events.on(event, listener); + } + + async #getFrame() { + const interrupt = await this.#getNextFrame(this.#interrupt); + if (interrupt !== undefined) { + return interrupt; + } + const audio = await this.#getNextFrame(this.#audio); + if (audio === undefined) { + this.#handleAudioStopped(); + } + return audio; + } + + async #getNextFrame(source: AsyncIterableIterator | undefined) { + const nextResult = await source?.next(); + return nextResult !== undefined && !nextResult?.done + ? nextResult.value + : undefined; + } + + #handleAudioStopped() { + if (this.#audio !== undefined) { + this.#events.trigger("done", this.#audio); + } + this.#audio = undefined; + this.playing = false; } } diff --git a/discordeno-audio-plugin/src/player/types.ts b/discordeno-audio-plugin/src/player/types.ts index d9610c7..05348c4 100644 --- a/discordeno-audio-plugin/src/player/types.ts +++ b/discordeno-audio-plugin/src/player/types.ts @@ -1,8 +1,13 @@ -export type Player = { +import { PlayerListener, RawEventTypes } from "./events.ts"; + +export type Player = { playing: boolean; play(): void; pause(): void; stop(): void; clear(): void; - interrupt(audio: AsyncIterableIterator): void; + on( + event: J, + listener: PlayerListener + ): () => void; }; diff --git a/discordeno-audio-plugin/src/udp/discover.ts b/discordeno-audio-plugin/src/udp/discover.ts index 715da1b..4a94b48 100644 --- a/discordeno-audio-plugin/src/udp/discover.ts +++ b/discordeno-audio-plugin/src/udp/discover.ts @@ -12,7 +12,7 @@ export async function discoverIP( port, transport: "udp", }); - const { value } = await conn.udpStream().next(); + const value = await conn.udpRaw.next(); const decoder = new TextDecoder(); const localIp = decoder.decode(value.slice(8, value.indexOf(0, 8))); const localPort = new DataView(value.buffer).getUint16(72, false); diff --git a/discordeno-audio-plugin/utils/event-source.ts b/discordeno-audio-plugin/utils/event-source.ts index 21f90a2..856cd83 100644 --- a/discordeno-audio-plugin/utils/event-source.ts +++ b/discordeno-audio-plugin/utils/event-source.ts @@ -1,28 +1,17 @@ import { IterSource, fromCallback } from "./iterator/mod.ts"; -import { Arr } from "./types.ts"; -type Listener = (...arg: T) => void; +type Listener = (arg: T) => void; -export class EventSource { +export class BasicSource { listeners: Listener[] = []; - iter: IterSource["iterator"]; - disconnect: IterSource["disconnect"]; - constructor() { - const { iterator, disconnect } = fromCallback((listener) => - this.addListener(listener) - ); - this.iter = iterator; - this.disconnect = disconnect; - } - - trigger(...arg: T) { + trigger(value: T) { for (const listener of this.listeners) { - listener(...arg); + listener(value); } } - addListener(listener: Listener) { + listen(listener: Listener) { this.listeners.push(listener); return () => { this.removeListener(listener); @@ -33,4 +22,31 @@ export class EventSource { const index = this.listeners.indexOf(listener); this.listeners.splice(index, 1); } + + listenOnce(listener: Listener) { + const disconnect = this.listen((value) => { + disconnect(); + listener(value); + }); + } + + next() { + return new Promise((resolve) => { + this.listenOnce(resolve); + }); + } } + +export class EventSource extends BasicSource { + iter: IterSource["iterator"]; + disconnect: IterSource["disconnect"]; + + constructor() { + super(); + const { iterator, disconnect } = fromCallback((listener) => + this.listen(listener) + ); + this.iter = iterator; + this.disconnect = disconnect; + } +} \ No newline at end of file diff --git a/discordeno-audio-plugin/utils/iterator/mod.ts b/discordeno-audio-plugin/utils/iterator/mod.ts index b47acdd..12319c7 100644 --- a/discordeno-audio-plugin/utils/iterator/mod.ts +++ b/discordeno-audio-plugin/utils/iterator/mod.ts @@ -1,22 +1,21 @@ -import { Arr } from "../types.ts"; import { pushIter, addIterUtils } from "./util/mod.ts"; type Listener = { push: (arg: T) => void; done: () => void }; -export type IterSource = ReturnType>; +export type IterSource = ReturnType>; -export function fromCallback( - source: (listener: (...values: T) => void) => void, +export function fromCallback( + source: (listener: (value: T) => void) => void, disconnect?: () => void ) { let isDone = false; let listeners: Listener[] = []; - function trigger(...values: T) { + function trigger(value: T) { if (isDone) { return; } for (const listener of listeners) { - listener.push(values); + listener.push(value); } } source(trigger); @@ -53,4 +52,4 @@ export function fromCallback( }, disconnect: done, }; -} +} \ No newline at end of file diff --git a/discordeno-audio-plugin/utils/queue.ts b/discordeno-audio-plugin/utils/queue.ts index e7891b9..c179aad 100644 --- a/discordeno-audio-plugin/utils/queue.ts +++ b/discordeno-audio-plugin/utils/queue.ts @@ -1,15 +1,15 @@ import { assertEquals } from "https://deno.land/std@0.104.0/testing/asserts.ts"; import { arrayMove, arrayShuffle } from "./array.ts"; -import { EventSource } from "./event-source.ts"; export class Queue { #current: T | undefined; #queue: T[] = []; - #source = new EventSource<[T]>(); - waiting = false; + #waiting: ((value: T) => void)[] = []; clear() { + const cleared = this.#queue; this.#queue = []; + return cleared; } current() { @@ -22,10 +22,15 @@ export class Queue { push(...values: T[]) { this.#queue.push(...values); - if (this.waiting) { - this.triggerNext(); - this.waiting = false; + for (const waiting of this.#waiting) { + const value = this.#queue.shift(); + this.#current = value; + if (value == undefined) { + break; + } + waiting(value); } + this.#waiting = []; } unshift(...values: T[]) { @@ -52,18 +57,15 @@ export class Queue { return false; } - triggerNext() { - const value = this.#queue.shift(); + async next() { + let value = this.#queue.shift(); this.#current = value; if (value === undefined) { - this.waiting = true; - } else { - this.#source.trigger(value); + value = await new Promise((resolve) => { + this.#waiting.push(resolve); + }); } - } - - stream() { - return this.#source.iter(); + return value; } } @@ -71,18 +73,15 @@ Deno.test({ name: "Test", fn: async () => { const queue = new Queue(); + const promise0 = queue.next(); queue.push("Hello"); queue.push("World!"); - const messages = queue.stream(); - queue.triggerNext(); - queue.triggerNext(); - queue.triggerNext(); - queue.triggerNext(); - queue.triggerNext(); - assertEquals("Hello", await messages.nextValue()); - assertEquals("World!", await messages.nextValue()); - assertEquals(undefined, await messages.nextValue()); - assertEquals(undefined, await messages.nextValue()); - assertEquals(undefined, await messages.nextValue()); + assertEquals("Hello", await promise0); + assertEquals("World!", await queue.next()); + const promise1 = queue.next(); + const promise2 = queue.next(); + queue.push("Multiple", "Words!"); + assertEquals("Multiple", await promise1); + assertEquals("Words!", await promise2); }, -}); +}); \ No newline at end of file diff --git a/discordeno-audio-plugin/utils/types.ts b/discordeno-audio-plugin/utils/types.ts deleted file mode 100644 index 824ad40..0000000 --- a/discordeno-audio-plugin/utils/types.ts +++ /dev/null @@ -1 +0,0 @@ -export type Arr = readonly unknown[]; diff --git a/main.ts b/main.ts index ed14fc4..87a462c 100644 --- a/main.ts +++ b/main.ts @@ -7,20 +7,18 @@ import { upsertGlobalApplicationCommands } from "./deps.ts"; import { parseCommand } from "./commands.ts"; -import { cyan, yellow } from "https://deno.land/std@0.161.0/fmt/colors.ts"; +import { cyan } from "https://deno.land/std@0.161.0/fmt/colors.ts"; import { helpCommand } from "./commands/help.ts"; import { leaveCommand } from "./commands/leave.ts"; import { loopCommand } from "./commands/loop.ts"; import { npCommand } from "./commands/np.ts"; -import { pauseCommand } from "./commands/pause.ts"; +import { pauseCommand, stopCommand } from "./commands/pause.ts"; import { playCommand } from "./commands/play.ts"; import { skipCommand } from "./commands/skip.ts"; import { unloopCommand } from "./commands/unloop.ts"; import { enableAudioPlugin } from "./discordeno-audio-plugin/mod.ts"; -let sessionId = ""; - const baseBot = createBot({ token: configs.discord_token, intents: Intents.Guilds | Intents.GuildMessages | Intents.GuildVoiceStates, @@ -28,10 +26,9 @@ const baseBot = createBot({ export const bot = enableAudioPlugin(baseBot); -bot.events.ready = async function (bot, payload) { +bot.events.ready = async function () { //await registerCommands(bot); console.log(`${cyan("permanent waves")} is ready to go`); - sessionId = payload.sessionId; } // Another way to do events @@ -42,7 +39,7 @@ bot.events.interactionCreate = async function (bot, interaction) { await startBot(bot); async function registerCommands(bot: Bot) { - console.log(await upsertGlobalApplicationCommands(bot, [helpCommand, leaveCommand, loopCommand, npCommand, pauseCommand, playCommand, skipCommand, unloopCommand])); + console.log(await upsertGlobalApplicationCommands(bot, [helpCommand, leaveCommand, loopCommand, npCommand, pauseCommand, playCommand, skipCommand, stopCommand, unloopCommand])); } /* diff --git a/test/utils.ts b/test/utils.ts deleted file mode 100644 index a0b90bb..0000000 --- a/test/utils.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { assertEquals } from "https://deno.land/std@0.177.0/testing/asserts.ts"; - -import { parseYoutubeId } from "../utils.ts"; - -Deno.test("parseYoutubeId", () => { - const url = parseYoutubeId("https://www.youtube.com/watch?v=ylAF0WNvLx0"); - - assertEquals(url, "ylAF0WNvLx0"); -}); \ No newline at end of file diff --git a/utils.ts b/utils.ts index a783b54..7284241 100644 --- a/utils.ts +++ b/utils.ts @@ -95,6 +95,14 @@ export async function errorMessageCallback(guildId: bigint, message: string) { await sendMessage(bot, channel.id, errorMessage(message)); } +export function isPlaylist(query: string) { + if(query.includes("playlist")){ + return true; + } + + return false; +} + export function parseYoutubeId(url: string) { return url.substring(url.indexOf("?")+3); } \ No newline at end of file