play from arbitrary files
This commit is contained in:
parent
44c1e5ec70
commit
b2cbe50d74
|
@ -5,6 +5,7 @@
|
|||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"start": "pnpm build && node .",
|
||||
"watch": "pnpm build watch",
|
||||
"build": "node build.mjs",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
|
@ -12,12 +13,14 @@
|
|||
"author": "",
|
||||
"license": "GPL-3.0-only",
|
||||
"dependencies": {
|
||||
"@discordjs/voice": "^0.18.0",
|
||||
"@discordjs/voice": "^0.17.0",
|
||||
"dotenv": "^16.4.7",
|
||||
"libsodium-wrappers": "^0.7.15",
|
||||
"oceanic.js": "^1.11.2"
|
||||
},
|
||||
"packageManager": "pnpm@9.15.3+sha512.1f79bc245a66eb0b07c5d4d83131240774642caaa86ef7d0434ab47c0d16f66b04e21e0c086eb61e62c77efc4d7f7ec071afad3796af64892fae66509173893a",
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.18.0",
|
||||
"@types/node": "^22.10.5",
|
||||
"esbuild": "0.24.2"
|
||||
}
|
||||
|
|
|
@ -9,15 +9,21 @@ importers:
|
|||
.:
|
||||
dependencies:
|
||||
'@discordjs/voice':
|
||||
specifier: ^0.18.0
|
||||
version: 0.18.0
|
||||
specifier: ^0.17.0
|
||||
version: 0.17.0
|
||||
dotenv:
|
||||
specifier: ^16.4.7
|
||||
version: 16.4.7
|
||||
libsodium-wrappers:
|
||||
specifier: ^0.7.15
|
||||
version: 0.7.15
|
||||
oceanic.js:
|
||||
specifier: ^1.11.2
|
||||
version: 1.11.2
|
||||
devDependencies:
|
||||
'@eslint/js':
|
||||
specifier: ^9.18.0
|
||||
version: 9.18.0
|
||||
'@types/node':
|
||||
specifier: ^22.10.5
|
||||
version: 22.10.5
|
||||
|
@ -32,10 +38,6 @@ packages:
|
|||
engines: {node: '>=16.11.0'}
|
||||
deprecated: This version uses deprecated encryption modes. Please use a newer version.
|
||||
|
||||
'@discordjs/voice@0.18.0':
|
||||
resolution: {integrity: sha512-BvX6+VJE5/vhD9azV9vrZEt9hL1G+GlOdsQaVl5iv9n87fkXjf3cSwllhR3GdaUC8m6dqT8umXIWtn3yCu4afg==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
'@esbuild/aix-ppc64@0.24.2':
|
||||
resolution: {integrity: sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==}
|
||||
engines: {node: '>=18'}
|
||||
|
@ -186,15 +188,16 @@ packages:
|
|||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@eslint/js@9.18.0':
|
||||
resolution: {integrity: sha512-fK6L7rxcq6/z+AaQMtiFTkvbHkBLNlwyRxHpKawP0x3u9+NC6MQTnFW+AdpwC6gfHTW0051cokQgtTN2FqlxQA==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
|
||||
'@types/node@22.10.5':
|
||||
resolution: {integrity: sha512-F8Q+SeGimwOo86fiovQh8qiXfFEh2/ocYv7tU5pJ3EXMSSxk1Joj5wefpFK2fHTf/N6HKGSxIDBT9f3gCxXPkQ==}
|
||||
|
||||
'@types/ws@8.5.13':
|
||||
resolution: {integrity: sha512-osM/gWBTPKgHV8XkTunnegTRIsvF6owmf5w+JtAfOw472dptdm0dlGv4xCt6GwQRcC2XVOvvRE/0bAoQcL2QkA==}
|
||||
|
||||
discord-api-types@0.37.115:
|
||||
resolution: {integrity: sha512-ivPnJotSMrXW8HLjFu+0iCVs8zP6KSliMelhr7HgcB2ki1QzpORkb26m71l1pzSnnGfm7gb5n/VtRTtpw8kXFA==}
|
||||
|
||||
discord-api-types@0.37.83:
|
||||
resolution: {integrity: sha512-urGGYeWtWNYMKnYlZnOnDHm8fVRffQs3U0SpE8RHeiuLKb/u92APS8HoQnPTFbnXmY1vVnXjXO4dOxcAn3J+DA==}
|
||||
|
||||
|
@ -207,6 +210,12 @@ packages:
|
|||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
|
||||
libsodium-wrappers@0.7.15:
|
||||
resolution: {integrity: sha512-E4anqJQwcfiC6+Yrl01C1m8p99wEhLmJSs0VQqST66SbQXXBoaJY0pF4BNjRYa/sOQAxx6lXAaAFIlx+15tXJQ==}
|
||||
|
||||
libsodium@0.7.15:
|
||||
resolution: {integrity: sha512-sZwRknt/tUpE2AwzHq3jEyUU5uvIZHtSssktXq7owd++3CSgn8RGrv6UZJJBpP7+iBghBqe7Z06/2M31rI2NKw==}
|
||||
|
||||
oceanic.js@1.11.2:
|
||||
resolution: {integrity: sha512-kXMoZiIrIFq0QCfsZGJ+eOK+1IFkVsTpvwcQ3nfY6ioDKGVdHQjVIXNJAYvJb5c5la//2OU8WGpqsmp/bAP8aw==}
|
||||
engines: {node: '>=18.13.0'}
|
||||
|
@ -262,22 +271,6 @@ snapshots:
|
|||
- node-opus
|
||||
- opusscript
|
||||
- utf-8-validate
|
||||
optional: true
|
||||
|
||||
'@discordjs/voice@0.18.0':
|
||||
dependencies:
|
||||
'@types/ws': 8.5.13
|
||||
discord-api-types: 0.37.115
|
||||
prism-media: 1.3.5
|
||||
tslib: 2.8.1
|
||||
ws: 8.18.0
|
||||
transitivePeerDependencies:
|
||||
- '@discordjs/opus'
|
||||
- bufferutil
|
||||
- ffmpeg-static
|
||||
- node-opus
|
||||
- opusscript
|
||||
- utf-8-validate
|
||||
|
||||
'@esbuild/aix-ppc64@0.24.2':
|
||||
optional: true
|
||||
|
@ -354,6 +347,8 @@ snapshots:
|
|||
'@esbuild/win32-x64@0.24.2':
|
||||
optional: true
|
||||
|
||||
'@eslint/js@9.18.0': {}
|
||||
|
||||
'@types/node@22.10.5':
|
||||
dependencies:
|
||||
undici-types: 6.20.0
|
||||
|
@ -362,10 +357,7 @@ snapshots:
|
|||
dependencies:
|
||||
'@types/node': 22.10.5
|
||||
|
||||
discord-api-types@0.37.115: {}
|
||||
|
||||
discord-api-types@0.37.83:
|
||||
optional: true
|
||||
discord-api-types@0.37.83: {}
|
||||
|
||||
dotenv@16.4.7: {}
|
||||
|
||||
|
@ -397,6 +389,12 @@ snapshots:
|
|||
'@esbuild/win32-ia32': 0.24.2
|
||||
'@esbuild/win32-x64': 0.24.2
|
||||
|
||||
libsodium-wrappers@0.7.15:
|
||||
dependencies:
|
||||
libsodium: 0.7.15
|
||||
|
||||
libsodium@0.7.15: {}
|
||||
|
||||
oceanic.js@1.11.2:
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
import commands, { Command } from ".";
|
||||
import { filterAsync } from "../util";
|
||||
|
||||
const cmdHelp: Command = {
|
||||
names: ["help", "cmds"],
|
||||
desc: "list out commands you have access to",
|
||||
checkPerm: async () => true,
|
||||
async handler(msg, { reply }) {
|
||||
const availCmds = await filterAsync(
|
||||
commands,
|
||||
async (cmd) => await cmd.checkPerm(msg)
|
||||
);
|
||||
|
||||
let text = `// List of commands (${availCmds.length} count)\n`;
|
||||
|
||||
for (let cmd of availCmds) {
|
||||
text += `${cmd.names[0]} `;
|
||||
if (cmd.names.length > 1) {
|
||||
text += `(aka: ${cmd.names.slice(1).join(", ")}) `;
|
||||
}
|
||||
text += `- ${cmd.desc}\n`;
|
||||
}
|
||||
|
||||
text = "```\n" + text + "\n```";
|
||||
await reply(text);
|
||||
},
|
||||
};
|
||||
|
||||
export default cmdHelp;
|
|
@ -1,19 +1,15 @@
|
|||
import { Message } from "oceanic.js";
|
||||
import { Command } from ".";
|
||||
|
||||
const hi: Command = {
|
||||
name: "hi",
|
||||
const cmdHi: Command = {
|
||||
names: ["hi", "hello"],
|
||||
desc: "test command",
|
||||
async checkPerm(msg: Message): Promise<boolean> {
|
||||
if (msg.member === undefined) return false;
|
||||
|
||||
return msg.member.roles.includes("1327065762031992924");
|
||||
async checkPerm(msg: Message) {
|
||||
return msg.member?.roles.includes("1327065762031992924");
|
||||
},
|
||||
async handler(msg: Message) {
|
||||
msg.channel?.createMessage({
|
||||
content: "Test message",
|
||||
});
|
||||
async handler(msg: Message, { say }) {
|
||||
await say("Test message");
|
||||
},
|
||||
};
|
||||
|
||||
export default hi;
|
||||
export default cmdHi;
|
||||
|
|
|
@ -1,13 +1,21 @@
|
|||
import { Message } from "oceanic.js";
|
||||
import hi from "./hi";
|
||||
import cmdHelp from "./help";
|
||||
import cmdHi from "./hi";
|
||||
import cmdPlay from "./play";
|
||||
|
||||
export type Command = {
|
||||
name: string;
|
||||
desc?: string;
|
||||
checkPerm(msg: Message): Promise<boolean>;
|
||||
handler(msg: Message): Promise<void>;
|
||||
names: string[];
|
||||
desc: string;
|
||||
checkPerm(msg: Message): Promise<boolean | undefined>;
|
||||
handler(
|
||||
msg: Message,
|
||||
replies: {
|
||||
reply: (text: string) => Promise<void>;
|
||||
say: (text: string) => Promise<void>;
|
||||
}
|
||||
): Promise<void>;
|
||||
};
|
||||
|
||||
const commands: Command[] = [hi];
|
||||
const commands: Command[] = [cmdHelp, cmdHi, cmdPlay];
|
||||
|
||||
export default commands;
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
import {
|
||||
AudioPlayerStatus,
|
||||
createAudioPlayer,
|
||||
createAudioResource,
|
||||
VoiceConnectionStatus,
|
||||
} from "@discordjs/voice";
|
||||
import { Command } from ".";
|
||||
import { existsSync } from "node:fs";
|
||||
|
||||
const cmdPlay: Command = {
|
||||
names: ["play", "soundtest"],
|
||||
desc: "test voice features",
|
||||
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");
|
||||
}
|
||||
|
||||
const conn = voiceState.channel?.join({
|
||||
selfMute: false,
|
||||
});
|
||||
if (!conn) return await reply("Failed to join channel");
|
||||
|
||||
conn.on(VoiceConnectionStatus.Disconnected, () => {
|
||||
console.log("Rejoining from disconnection!!");
|
||||
conn.rejoin();
|
||||
});
|
||||
|
||||
const player = createAudioPlayer();
|
||||
conn.subscribe(player);
|
||||
|
||||
conn.on("stateChange", (oldSt, newSt) => {
|
||||
console.log(`Moving from ${oldSt.status} to ${newSt.status}`);
|
||||
});
|
||||
conn.on("error", (err) => {
|
||||
console.error("Connection experienced error!", err);
|
||||
});
|
||||
|
||||
player.once(AudioPlayerStatus.Playing, async () => {
|
||||
await say("Playing audio");
|
||||
});
|
||||
player.on(AudioPlayerStatus.Idle, async () => {
|
||||
await say("Audio finished playing");
|
||||
});
|
||||
|
||||
const name = msg.content.match(/\s(.*)$/)?.[1];
|
||||
if (!name || !existsSync(name)) {
|
||||
return await reply("Audio file not found");
|
||||
}
|
||||
console.log(name);
|
||||
|
||||
const audio = createAudioResource(name);
|
||||
player.play(audio);
|
||||
},
|
||||
};
|
||||
|
||||
export default cmdPlay;
|
|
@ -0,0 +1 @@
|
|||
export const PREFIX = "!!";
|
47
src/index.ts
47
src/index.ts
|
@ -1,15 +1,20 @@
|
|||
import { Client } from "oceanic.js";
|
||||
import "dotenv/config";
|
||||
import commands from "./commands";
|
||||
import { PREFIX } from "./constants";
|
||||
|
||||
const token = process.env.DISCORD_TOKEN;
|
||||
if (!token) {
|
||||
function token() {
|
||||
return (
|
||||
process.env.DISCORD_TOKEN ??
|
||||
(() => {
|
||||
console.error("No `DISCORD_TOKEN` was specified!!");
|
||||
process.exit(1);
|
||||
})()
|
||||
);
|
||||
}
|
||||
|
||||
const client = new Client({
|
||||
auth: `Bot ${token}`,
|
||||
auth: `Bot ${token()}`,
|
||||
gateway: {
|
||||
intents: ["ALL"],
|
||||
},
|
||||
|
@ -21,25 +26,37 @@ client.on("ready", async () => {
|
|||
console.log("Ready as", client.user.tag);
|
||||
});
|
||||
|
||||
const PREFIX = "!!";
|
||||
|
||||
client.on("messageCreate", async (msg) => {
|
||||
if (msg.author.bot) return;
|
||||
|
||||
let first = msg.content.split(/\s/).shift();
|
||||
if (first?.startsWith(PREFIX) === false) return;
|
||||
let cmdName = first!.slice(PREFIX.length);
|
||||
const content = msg.content.toLowerCase().trim();
|
||||
if (!content.startsWith(PREFIX)) return;
|
||||
|
||||
let command = commands.find((c) => c.name === cmdName);
|
||||
const cmdName = content.split(/\s/)[0]?.slice(PREFIX.length) ?? "";
|
||||
const cmd = commands.find((cmd) => cmd.names.includes(cmdName));
|
||||
|
||||
if (command === undefined) return;
|
||||
if (cmd === undefined) return;
|
||||
|
||||
if ((await command.checkPerm(msg)) === false) {
|
||||
await msg.createReaction("❌");
|
||||
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,
|
||||
});
|
||||
};
|
||||
|
||||
await command?.handler(msg);
|
||||
await cmd?.handler(msg, { reply, say });
|
||||
} else await msg.createReaction("❌");
|
||||
});
|
||||
|
||||
client.on("error", (err) => {
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
export async function filterAsync<T>(
|
||||
arr: T[],
|
||||
predicate: (v) => Promise<boolean>
|
||||
): Promise<T[]> {
|
||||
const results = await Promise.all(arr.map(predicate));
|
||||
return arr.filter((_v, i) => results[i]);
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"target": "ESNext",
|
||||
"allowJs": true,
|
||||
"resolveJsonModule": true,
|
||||
"moduleDetection": "force",
|
||||
"isolatedModules": true,
|
||||
|
||||
"strict": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"noImplicitOverride": true,
|
||||
|
||||
"module": "NodeNext",
|
||||
"outDir": "dist",
|
||||
"sourceMap": true,
|
||||
|
||||
"lib": ["ESNext"]
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue