From 6f16acf5e90d7b361280e541d784fcba4a132181 Mon Sep 17 00:00:00 2001 From: minish Date: Mon, 13 Jan 2025 22:11:21 -0500 Subject: [PATCH] play from youtube --- package.json | 1 + pnpm-lock.yaml | 154 ++++++++++++++++++++++++++++++++++++++++++ src/commands/echo.ts | 16 +++++ src/commands/help.ts | 11 +-- src/commands/hi.ts | 9 ++- src/commands/index.ts | 36 +++++++--- src/commands/play.ts | 45 +++++++----- src/constants.ts | 1 + src/index.ts | 38 +++++------ 9 files changed, 258 insertions(+), 53 deletions(-) create mode 100644 src/commands/echo.ts diff --git a/package.json b/package.json index d446f83..d33a0a1 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "license": "GPL-3.0-only", "dependencies": { "@discordjs/voice": "^0.17.0", + "@distube/ytdl-core": "4.15.6", "dotenv": "^16.4.7", "libsodium-wrappers": "^0.7.15", "oceanic.js": "^1.11.2" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bb5848a..ef26ffc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,6 +11,9 @@ importers: '@discordjs/voice': specifier: ^0.17.0 version: 0.17.0 + '@distube/ytdl-core': + specifier: 4.15.6 + version: 4.15.6 dotenv: specifier: ^16.4.7 version: 16.4.7 @@ -38,6 +41,11 @@ packages: engines: {node: '>=16.11.0'} deprecated: This version uses deprecated encryption modes. Please use a newer version. + '@distube/ytdl-core@4.15.6': + resolution: {integrity: sha512-T5bwnJ14pxHX96rzWzNC5lSHLxPmjXAEExh+VmI8CR2KqplNCMc1a0S+uo0WcIBM/FMw8tFSq5SqErwrZEM+vw==} + engines: {node: '>=14.0'} + deprecated: This version is deprecated, please upgrade to the latest version. + '@esbuild/aix-ppc64@0.24.2': resolution: {integrity: sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==} engines: {node: '>=18'} @@ -192,12 +200,29 @@ packages: resolution: {integrity: sha512-fK6L7rxcq6/z+AaQMtiFTkvbHkBLNlwyRxHpKawP0x3u9+NC6MQTnFW+AdpwC6gfHTW0051cokQgtTN2FqlxQA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@fastify/busboy@2.1.1': + resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==} + engines: {node: '>=14'} + '@types/node@22.10.5': resolution: {integrity: sha512-F8Q+SeGimwOo86fiovQh8qiXfFEh2/ocYv7tU5pJ3EXMSSxk1Joj5wefpFK2fHTf/N6HKGSxIDBT9f3gCxXPkQ==} '@types/ws@8.5.13': resolution: {integrity: sha512-osM/gWBTPKgHV8XkTunnegTRIsvF6owmf5w+JtAfOw472dptdm0dlGv4xCt6GwQRcC2XVOvvRE/0bAoQcL2QkA==} + agent-base@7.1.3: + resolution: {integrity: sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==} + engines: {node: '>= 14'} + + debug@4.4.0: + resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + discord-api-types@0.37.83: resolution: {integrity: sha512-urGGYeWtWNYMKnYlZnOnDHm8fVRffQs3U0SpE8RHeiuLKb/u92APS8HoQnPTFbnXmY1vVnXjXO4dOxcAn3J+DA==} @@ -210,12 +235,37 @@ packages: engines: {node: '>=18'} hasBin: true + http-cookie-agent@6.0.8: + resolution: {integrity: sha512-qnYh3yLSr2jBsTYkw11elq+T361uKAJaZ2dR4cfYZChw1dt9uL5t3zSUwehoqqVb4oldk1BpkXKm2oat8zV+oA==} + engines: {node: '>=18.0.0'} + peerDependencies: + tough-cookie: ^4.0.0 || ^5.0.0 + undici: ^5.11.0 || ^6.0.0 + peerDependenciesMeta: + undici: + optional: true + + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + libsodium-wrappers@0.7.15: resolution: {integrity: sha512-E4anqJQwcfiC6+Yrl01C1m8p99wEhLmJSs0VQqST66SbQXXBoaJY0pF4BNjRYa/sOQAxx6lXAaAFIlx+15tXJQ==} libsodium@0.7.15: resolution: {integrity: sha512-sZwRknt/tUpE2AwzHq3jEyUU5uvIZHtSssktXq7owd++3CSgn8RGrv6UZJJBpP7+iBghBqe7Z06/2M31rI2NKw==} + m3u8stream@0.8.6: + resolution: {integrity: sha512-LZj8kIVf9KCphiHmH7sbFQTVe4tOemb202fWwvJwR9W5ENW/1hxJN6ksAWGhQgSBSa3jyWhnjKU1Fw1GaOdbyA==} + engines: {node: '>=12'} + + miniget@4.2.3: + resolution: {integrity: sha512-SjbDPDICJ1zT+ZvQwK0hUcRY4wxlhhNpHL9nJOB2MEAXRGagTljsO8MEDzQMTFf0Q8g4QNi8P9lEm/g7e+qgzA==} + engines: {node: '>=12'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + oceanic.js@1.11.2: resolution: {integrity: sha512-kXMoZiIrIFq0QCfsZGJ+eOK+1IFkVsTpvwcQ3nfY6ioDKGVdHQjVIXNJAYvJb5c5la//2OU8WGpqsmp/bAP8aw==} engines: {node: '>=18.13.0'} @@ -237,12 +287,43 @@ packages: opusscript: optional: true + psl@1.15.0: + resolution: {integrity: sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + querystringify@2.2.0: + resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} + + requires-port@1.0.0: + resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} + + sax@1.4.1: + resolution: {integrity: sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==} + + tough-cookie@4.1.4: + resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==} + engines: {node: '>=6'} + tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} undici-types@6.20.0: resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==} + undici@5.28.4: + resolution: {integrity: sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==} + engines: {node: '>=14.0'} + + universalify@0.2.0: + resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==} + engines: {node: '>= 4.0.0'} + + url-parse@1.5.10: + resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} + ws@8.18.0: resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} engines: {node: '>=10.0.0'} @@ -272,6 +353,18 @@ snapshots: - opusscript - utf-8-validate + '@distube/ytdl-core@4.15.6': + dependencies: + http-cookie-agent: 6.0.8(tough-cookie@4.1.4)(undici@5.28.4) + https-proxy-agent: 7.0.6 + m3u8stream: 0.8.6 + miniget: 4.2.3 + sax: 1.4.1 + tough-cookie: 4.1.4 + undici: 5.28.4 + transitivePeerDependencies: + - supports-color + '@esbuild/aix-ppc64@0.24.2': optional: true @@ -349,6 +442,8 @@ snapshots: '@eslint/js@9.18.0': {} + '@fastify/busboy@2.1.1': {} + '@types/node@22.10.5': dependencies: undici-types: 6.20.0 @@ -357,6 +452,12 @@ snapshots: dependencies: '@types/node': 22.10.5 + agent-base@7.1.3: {} + + debug@4.4.0: + dependencies: + ms: 2.1.3 + discord-api-types@0.37.83: {} dotenv@16.4.7: {} @@ -389,12 +490,35 @@ snapshots: '@esbuild/win32-ia32': 0.24.2 '@esbuild/win32-x64': 0.24.2 + http-cookie-agent@6.0.8(tough-cookie@4.1.4)(undici@5.28.4): + dependencies: + agent-base: 7.1.3 + tough-cookie: 4.1.4 + optionalDependencies: + undici: 5.28.4 + + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.3 + debug: 4.4.0 + transitivePeerDependencies: + - supports-color + libsodium-wrappers@0.7.15: dependencies: libsodium: 0.7.15 libsodium@0.7.15: {} + m3u8stream@0.8.6: + dependencies: + miniget: 4.2.3 + sax: 1.4.1 + + miniget@4.2.3: {} + + ms@2.1.3: {} + oceanic.js@1.11.2: dependencies: tslib: 2.8.1 @@ -411,8 +535,38 @@ snapshots: prism-media@1.3.5: {} + psl@1.15.0: + dependencies: + punycode: 2.3.1 + + punycode@2.3.1: {} + + querystringify@2.2.0: {} + + requires-port@1.0.0: {} + + sax@1.4.1: {} + + tough-cookie@4.1.4: + dependencies: + psl: 1.15.0 + punycode: 2.3.1 + universalify: 0.2.0 + url-parse: 1.5.10 + tslib@2.8.1: {} undici-types@6.20.0: {} + undici@5.28.4: + dependencies: + '@fastify/busboy': 2.1.1 + + universalify@0.2.0: {} + + url-parse@1.5.10: + dependencies: + querystringify: 2.2.0 + requires-port: 1.0.0 + ws@8.18.0: {} diff --git a/src/commands/echo.ts b/src/commands/echo.ts new file mode 100644 index 0000000..c465454 --- /dev/null +++ b/src/commands/echo.ts @@ -0,0 +1,16 @@ +import { Message } from "oceanic.js"; +import { Command, Extras } from "."; + +const cmdEcho: Command = { + names: ["echo", "say"], + desc: "repeat text back to you", + splitArgs: false, + + checkPerm: async () => true, + + async handler(ex: Extras, content: string) { + await ex.say(content); + }, +}; + +export default cmdEcho; diff --git a/src/commands/help.ts b/src/commands/help.ts index 16ba605..51b42c7 100644 --- a/src/commands/help.ts +++ b/src/commands/help.ts @@ -1,14 +1,17 @@ -import commands, { Command } from "."; +import commands, { Command, Extras } from "."; import { filterAsync } from "../util"; const cmdHelp: Command = { names: ["help", "cmds"], desc: "list out commands you have access to", + splitArgs: false, + checkPerm: async () => true, - async handler(msg, { reply }) { + + async handler(ex: Extras) { const availCmds = await filterAsync( commands, - async (cmd) => await cmd.checkPerm(msg) + async (cmd) => await cmd.checkPerm(ex.msg) ); let text = `// List of commands (${availCmds.length} count)\n`; @@ -22,7 +25,7 @@ const cmdHelp: Command = { } text = "```\n" + text + "\n```"; - await reply(text); + await ex.reply(text); }, }; diff --git a/src/commands/hi.ts b/src/commands/hi.ts index c872311..26994ce 100644 --- a/src/commands/hi.ts +++ b/src/commands/hi.ts @@ -1,14 +1,17 @@ import { Message } from "oceanic.js"; -import { Command } from "."; +import { Command, Extras } from "."; const cmdHi: Command = { names: ["hi", "hello"], desc: "test command", + splitArgs: false, + async checkPerm(msg: Message) { return msg.member?.roles.includes("1327065762031992924"); }, - async handler(msg: Message, { say }) { - await say("Test message"); + + async handler(ex: Extras) { + await ex.say("Test message"); }, }; diff --git a/src/commands/index.ts b/src/commands/index.ts index 0db6f6d..f31e5d3 100644 --- a/src/commands/index.ts +++ b/src/commands/index.ts @@ -2,20 +2,40 @@ import { Message } from "oceanic.js"; import cmdHelp from "./help"; import cmdHi from "./hi"; import cmdPlay from "./play"; +import cmdEcho from "./echo"; + +export class Extras { + constructor(readonly msg: Message) { + this.msg = msg; + } + + reply = async (text: string) => { + await this.msg.channel?.createMessage({ + content: text, + allowedMentions: { + repliedUser: false, + }, + messageReference: { + messageID: this.msg.id, + }, + }); + }; + + say = async (text: string) => { + await this.msg.channel?.createMessage({ + content: text, + }); + }; +} export type Command = { names: string[]; desc: string; + splitArgs: boolean; checkPerm(msg: Message): Promise; - handler( - msg: Message, - replies: { - reply: (text: string) => Promise; - say: (text: string) => Promise; - } - ): Promise; + handler(ex: Extras, ...args: string[]): Promise; }; -const commands: Command[] = [cmdHelp, cmdHi, cmdPlay]; +const commands: Command[] = [cmdHelp, cmdHi, cmdPlay, cmdEcho]; export default commands; diff --git a/src/commands/play.ts b/src/commands/play.ts index 4ba172a..150d6a6 100644 --- a/src/commands/play.ts +++ b/src/commands/play.ts @@ -4,28 +4,36 @@ import { createAudioResource, VoiceConnectionStatus, } from "@discordjs/voice"; -import { Command } from "."; +import { Command, Extras } from "."; import { existsSync } from "node:fs"; +import { Duplex, Readable } from "node:stream"; +import ytdl from "@distube/ytdl-core"; const cmdPlay: Command = { - names: ["play", "soundtest"], + names: ["play"], desc: "test voice features", + splitArgs: true, + async checkPerm(msg) { return msg.author.id === "778441081883983893"; }, - async handler(msg, { reply, say }) { - const voiceState = msg.member?.voiceState; - if (!voiceState?.channelID) - return await reply("You are not in a voice channel"); - if (msg.guildID !== null && msg.client.getVoiceConnection(msg.guildID)) { - return await reply("I am already connected"); + async handler(ex: Extras, ...args: string[]) { + const voiceState = ex.msg.member?.voiceState; + if (!voiceState?.channelID) + return await ex.reply("You are not in a voice channel"); + + if ( + ex.msg.guildID !== null && + ex.msg.client.getVoiceConnection(ex.msg.guildID) + ) { + return await ex.reply("I am already connected"); } const conn = voiceState.channel?.join({ selfMute: false, }); - if (!conn) return await reply("Failed to join channel"); + if (!conn) return await ex.reply("Failed to join channel"); conn.on(VoiceConnectionStatus.Disconnected, () => { console.log("Rejoining from disconnection!!"); @@ -43,19 +51,24 @@ const cmdPlay: Command = { }); player.once(AudioPlayerStatus.Playing, async () => { - await say("Playing audio"); + await ex.say("Playing audio"); }); player.on(AudioPlayerStatus.Idle, async () => { - await say("Audio finished playing"); + await ex.say("Audio finished playing"); }); - const name = msg.content.match(/\s(.*)$/)?.[1]; - if (!name || !existsSync(name)) { - return await reply("Audio file not found"); + const url = args[0]; + if (!url) { + return await ex.reply("No url was specified"); } - console.log(name); - const audio = createAudioResource(name); + const readable = ytdl(url, { + filter: "audioonly", + quality: "highestaudio", + playerClients: ["IOS", "ANDROID"], + }); + + const audio = createAudioResource(readable); player.play(audio); }, }; diff --git a/src/constants.ts b/src/constants.ts index 5949fdf..d34b2ca 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1 +1,2 @@ export const PREFIX = "!!"; +export const SPLIT = /\s/; diff --git a/src/index.ts b/src/index.ts index 1ba9501..f755c11 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,7 @@ import { Client } from "oceanic.js"; import "dotenv/config"; -import commands from "./commands"; -import { PREFIX } from "./constants"; +import commands, { Extras } from "./commands"; +import { PREFIX, SPLIT } from "./constants"; function token() { return ( @@ -29,33 +29,27 @@ client.on("ready", async () => { client.on("messageCreate", async (msg) => { if (msg.author.bot) return; - const content = msg.content.toLowerCase().trim(); - if (!content.startsWith(PREFIX)) return; + const scanContent = msg.content.toLowerCase(); + if (!scanContent.startsWith(PREFIX)) return; + const cmdName = scanContent.split(SPLIT)[0]?.slice(PREFIX.length); + + if (cmdName === undefined) return; - const cmdName = content.split(/\s/)[0]?.slice(PREFIX.length) ?? ""; const cmd = commands.find((cmd) => cmd.names.includes(cmdName)); if (cmd === undefined) return; if (await cmd.checkPerm(msg)) { - const reply = async (text: string) => { - await msg.channel?.createMessage({ - content: text, - allowedMentions: { - repliedUser: false, - }, - messageReference: { - messageID: msg.id, - }, - }); - }; - const say = async (text: string) => { - await msg.channel?.createMessage({ - content: text, - }); - }; + const ex = new Extras(msg); - await cmd?.handler(msg, { reply, say }); + const args = msg.content.split(SPLIT); + args.shift(); + + if (cmd.splitArgs) await cmd.handler(ex, ...args); + else { + const content = msg.content.slice(PREFIX.length + cmdName.length); + await cmd.handler(ex, content); + } } else await msg.createReaction("❌"); });