play from youtube

This commit is contained in:
minish 2025-01-13 22:11:21 -05:00
parent b2cbe50d74
commit 6f16acf5e9
Signed by: min
GPG Key ID: FEECFF24EF0CE9E9
9 changed files with 258 additions and 53 deletions

View File

@ -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"

View File

@ -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: {}

16
src/commands/echo.ts Normal file
View File

@ -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;

View File

@ -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);
},
};

View File

@ -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");
},
};

View File

@ -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<boolean | undefined>;
handler(
msg: Message,
replies: {
reply: (text: string) => Promise<void>;
say: (text: string) => Promise<void>;
}
): Promise<void>;
handler(ex: Extras, ...args: string[]): Promise<void>;
};
const commands: Command[] = [cmdHelp, cmdHi, cmdPlay];
const commands: Command[] = [cmdHelp, cmdHi, cmdPlay, cmdEcho];
export default commands;

View File

@ -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);
},
};

View File

@ -1 +1,2 @@
export const PREFIX = "!!";
export const SPLIT = /\s/;

View File

@ -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("❌");
});