all repos — simple-discord-music-bot @ 9028915a52f199d9c710a9cf35f8903433faec88

A Discord bot making use of discord.js and play-yt.

migrated
Bi-Rabittoh andronacomarco@gmail.com
Mon, 08 Jan 2024 21:09:38 +0100
commit

9028915a52f199d9c710a9cf35f8903433faec88

parent

3b18ef76a09748a67db32742c1911d937086dba4

M .gitignore.gitignore

@@ -1,2 +1,3 @@

node_modules config.json +build
M .vscode/launch.json.vscode/launch.json

@@ -8,14 +8,13 @@ {

"type": "node", "request": "launch", "name": "Launch Program", - "skipFiles": [ - "<node_internals>/**" - ], - "program": "${workspaceFolder}/index.js" + "program": "${workspaceFolder}/src/index.ts", + "preLaunchTask": "tsc: build - tsconfig.json", + "outFiles": ["${workspaceFolder}/build/**/*.js"] }, { "name": "Deploy commands", - "program": "${workspaceFolder}/tools/deploy-commands.js", + "program": "${workspaceFolder}/build/tools/deploy-commands.js", "request": "launch", "skipFiles": [ "<node_internals>/**"

@@ -24,7 +23,7 @@ "type": "node"

}, { "name": "Delete commands", - "program": "${workspaceFolder}/tools/delete-commands.js", + "program": "${workspaceFolder}/build/tools/delete-commands.js", "request": "launch", "skipFiles": [ "<node_internals>/**"
A .vscode/tasks.json

@@ -0,0 +1,17 @@

+{ + "version": "2.0.0", + "tasks": [ + { + "type": "typescript", + "tsconfig": "tsconfig.json", + "problemMatcher": [ + "$tsc" + ], + "group": { + "kind": "build", + "isDefault": true + }, + "label": "tsc: build - tsconfig.json" + } + ] +}
D commands/clear.js

@@ -1,19 +0,0 @@

-const { SlashCommandBuilder } = require('discord.js'); -const { getChannel, clearQueue } = require('../functions/music'); - -module.exports = { - data: new SlashCommandBuilder() - .setName('clear') - .setDescription('Clear the queue.'), - - async execute(interaction) { - const channel = await getChannel(interaction); - if (typeof channel == 'string') - return await interaction.reply({ content: channel, ephemeral: true }); - - if (clearQueue()) - return await interaction.reply({ content: 'Queue cleared.' }); - - return await interaction.reply({ content: 'Error.' }); - }, -};
M commands/outro.jssrc/commands/outro.ts

@@ -1,6 +1,6 @@

-const { SlashCommandBuilder } = require('discord.js'); -const { playOutro, getChannel } = require('../functions/music'); -const path = require('node:path'); +import { SlashCommandBuilder } from 'discord.js'; +import { playOutro, getChannel } from '../functions/music'; +import path from 'node:path'; const { outros } = require(path.join(process.cwd(), 'config.json')); function getOutroUrl(outro) {

@@ -22,7 +22,7 @@ .addStringOption(option =>

option.setName('kick') .setDescription('Do you actually want to log off?') .setRequired(false) - .addChoices({ name: 'Yes', value: 'true', default: 'true' }, { name: 'No', value: 'false' })), + .addChoices({ name: 'Yes', value: 'true' }, { name: 'No', value: 'false' })), async execute(interaction) { const channel = await getChannel(interaction);
M commands/play.jssrc/commands/play.ts

@@ -1,6 +1,6 @@

-const { SlashCommandBuilder } = require('discord.js'); -const play = require('play-dl'); -const { playUrls, getChannel } = require('../functions/music'); +import { SlashCommandBuilder } from 'discord.js'; +import play from 'play-dl'; +import { playUrls, getChannel } from '../functions/music'; module.exports = { data: new SlashCommandBuilder()

@@ -40,7 +40,7 @@ case 'playlist':

const playlist = await play.playlist_info(url, { incomplete : true }); const videos = await playlist.all_videos(); const urls = videos.map((e) => e.url); - result = await playUrls(urls, channel); + const result = await playUrls(urls, channel); if (result) return await interaction.editReply(`Added ${urls.length} videos from the following playlist: ${playlist.title}.`); else
M commands/queue.jssrc/commands/queue.ts

@@ -1,5 +1,5 @@

-const { SlashCommandBuilder } = require('discord.js'); -const { getChannel, getQueue } = require('../functions/music'); +import { SlashCommandBuilder } from 'discord.js'; +import { getChannel, getQueue } from '../functions/music'; const CHARACTER_LIMIT_API = 2000;

@@ -14,7 +14,7 @@ let reply = "Queue:"

let new_string = ""; const characterLimit = CHARACTER_LIMIT_API - nowPlaying.length - 6; // 4 chars for "\n...", 2 chars for "\n\n" - for (r in result) { + for (let r in result) { new_string = "\n" + (r + 1) + ". <" + result[r] + ">"; if (reply.length + new_string.length > characterLimit) { reply += "\n...";

@@ -37,7 +37,7 @@ return await interaction.reply({ content: channel, ephemeral: true });

const result = await getQueue(); if (result) { - reply = getReply(result); + const reply = getReply(result); return await interaction.reply({ content: reply }); } return await interaction.reply({ content: 'Queue is empty.' });
M commands/radio.jssrc/commands/radio.ts

@@ -1,6 +1,6 @@

-const { SlashCommandBuilder } = require('discord.js'); -const { playStream, getChannel } = require('../functions/music'); -const path = require('node:path'); +import { SlashCommandBuilder } from 'discord.js'; +import { playStream, getChannel } from '../functions/music'; +import path from 'node:path'; const { radios } = require(path.join(process.cwd(), 'config.json')); module.exports = {
M commands/skip.jssrc/commands/skip.ts

@@ -1,5 +1,5 @@

-const { SlashCommandBuilder } = require('discord.js'); -const { getChannel, skipMusic } = require('../functions/music'); +import { SlashCommandBuilder } from 'discord.js'; +import { getChannel, skipMusic } from '../functions/music'; module.exports = { data: new SlashCommandBuilder()
M commands/stop.jssrc/commands/stop.ts

@@ -1,5 +1,5 @@

-const { SlashCommandBuilder } = require('discord.js'); -const { getChannel, stopMusic } = require('../functions/music'); +import { SlashCommandBuilder } from 'discord.js'; +import { getChannel, stopMusic } from '../functions/music'; module.exports = { data: new SlashCommandBuilder()
D functions/music.js

@@ -1,69 +0,0 @@

-const { - createAudioResource, - joinVoiceChannel, - AudioPlayerStatus, -} = require('@discordjs/voice'); -const play = require('play-dl'); -const { MyQueue } = require('./myqueue') - -const q = new MyQueue(); - -function getChannelConnection(channel) { - const guild = channel.guild; - return joinVoiceChannel({ - channelId: channel.id, - guildId: guild.id, - adapterCreator: guild.voiceAdapterCreator - }) -} - -module.exports = { - async playUrls(urls, channel) { - if (!channel) { - console.log('Channel error:', channel); - return; - } - - q.connection = getChannelConnection(channel); - return q.addArray(urls); - }, - async playStream(url, channel) { - if (!channel) { - console.log('Channel error:', channel); - return; - } - q.connection = getChannelConnection(channel); - q.add(createAudioResource(url, { inputType: 'mp3' })); - }, - async playOutro(url, channel) { - if (!channel) { - console.log('Channel error:', channel); - return; - } - q.connection = getChannelConnection(channel); - q.outro(url); - }, - async getChannel(interaction) { - const member = interaction.member; - if (!member) - return 'Please use this in your current server.'; - - const channel = member.voice.channel; - if (!channel) - return 'You\'re not in a voice channel.'; - - return channel; - }, - async stopMusic() { - return q.stop(); - }, - async skipMusic() { - return q.next(); - }, - async getQueue() { - return q.queue; - }, - clearQueue() { - return q.clear(); - } -};
M functions/myqueue.jssrc/functions/myqueue.ts

@@ -1,18 +1,17 @@

-const { - createAudioResource, - createAudioPlayer, - AudioPlayerStatus -} = require('@discordjs/voice'); -const play = require('play-dl'); +import { createAudioResource, createAudioPlayer, NoSubscriberBehavior, AudioPlayerStatus, VoiceConnection, AudioPlayer } from '@discordjs/voice'; +import play from 'play-dl'; async function resourceFromUrl(url) { const stream = await play.stream(url); return createAudioResource(stream.stream, { inputType: stream.type }) } -class MyQueue { +export default class MyQueue { #nowPlaying = null; #queue = Array(); + connection: VoiceConnection; + player: AudioPlayer; + static _instance: MyQueue; get queue() { if (this.#nowPlaying) return [this.#nowPlaying].concat(this.#queue);

@@ -28,10 +27,11 @@

this.#nowPlaying = ""; this.connection = null; this.#queue = Array(); - this.player = createAudioPlayer(); + this.player = createAudioPlayer({ behaviors: { noSubscriber: NoSubscriberBehavior.Stop } }); this.player.on(AudioPlayerStatus.Idle, () => { if (this.#queue.length > 0) return this.next(); + //@ts-expect-error this.player.subscribers.forEach((e) => e.connection.disconnect()); }); }

@@ -89,6 +89,7 @@ if (this.player.state.status == AudioPlayerStatus.Paused) {

this.resume(); return } + //@ts-expect-error p.subscribers.forEach((e) => e.connection.disconnect()); });

@@ -97,4 +98,4 @@ this.connection.subscribe(p);

} } -module.exports = { MyQueue } +(module).exports = MyQueue;
M index.jssrc/index.ts

@@ -1,10 +1,11 @@

-const fs = require('node:fs'); -const path = require('node:path'); -const { Client, Collection, GatewayIntentBits, ActivityType } = require('discord.js'); +import fs from 'node:fs'; +import path from 'node:path'; +import { Client, Collection, GatewayIntentBits, ActivityType } from 'discord.js'; +import { SlashCommand } from "./types"; const { token } = require(path.join(process.cwd(), 'config.json')); const client = new Client({ intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildVoiceStates] }); -client.commands = new Collection(); +client.slashCommands = new Collection<string, SlashCommand>(); const commandsPath = path.join(__dirname, 'commands'); const commandFiles = fs.readdirSync(commandsPath).filter(file => file.endsWith('.js'));

@@ -12,7 +13,7 @@ for (const file of commandFiles) {

const filePath = path.join(commandsPath, file); const command = require(filePath); if ('data' in command && 'execute' in command) { - client.commands.set(command.data.name, command); + client.slashCommands.set(command.data.name, command); } else { console.log(`[WARNING] The command at ${filePath} is missing a required "data" or "execute" property.`); }

@@ -25,7 +26,7 @@ });

client.on('interactionCreate', async interaction => { if (!interaction.isChatInputCommand()) return; - const command = client.commands.get(interaction.commandName); + const command = client.slashCommands.get(interaction.commandName); if (!command) return; try { await command.execute(interaction);
M package-lock.jsonpackage-lock.json

@@ -9,14 +9,14 @@ "name": "simple-discord-music-bot",

"version": "1.0.0", "license": "GPL3", "dependencies": { - "@discordjs/rest": "^1.5.0", "@discordjs/voice": "^0.16.0", - "discord.js": "^14.3.0", + "discord.js": "^14.14.1", "libsodium-wrappers": "^0.7.10", "play-dl": "^1.9.6" }, "devDependencies": { - "eslint": "^8.23.1" + "eslint": "^8.23.1", + "typescript": "^5.3.3" } }, "node_modules/@aashutoshrathi/word-wrap": {

@@ -45,19 +45,6 @@ "engines": {

"node": ">=16.11.0" } }, - "node_modules/@discordjs/builders/node_modules/@discordjs/util": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@discordjs/util/-/util-1.0.2.tgz", - "integrity": "sha512-IRNbimrmfb75GMNEjyznqM1tkI7HrZOf14njX7tCAAUetyZM1Pr8hX/EK2lxBCOgWDRmigbp24fD1hdMfQK5lw==", - "engines": { - "node": ">=16.11.0" - } - }, - "node_modules/@discordjs/builders/node_modules/discord-api-types": { - "version": "0.37.61", - "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.61.tgz", - "integrity": "sha512-o/dXNFfhBpYHpQFdT6FWzeO7pKc838QeeZ9d91CfVAtpr5XLK4B/zYxQbYgPdoMiTDvJfzcsLW5naXgmHGDNXw==" - }, "node_modules/@discordjs/collection": { "version": "1.5.3", "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-1.5.3.tgz",

@@ -77,35 +64,39 @@ "engines": {

"node": ">=16.11.0" } }, - "node_modules/@discordjs/formatters/node_modules/discord-api-types": { - "version": "0.37.61", - "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.61.tgz", - "integrity": "sha512-o/dXNFfhBpYHpQFdT6FWzeO7pKc838QeeZ9d91CfVAtpr5XLK4B/zYxQbYgPdoMiTDvJfzcsLW5naXgmHGDNXw==" - }, "node_modules/@discordjs/rest": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-1.7.1.tgz", - "integrity": "sha512-Ofa9UqT0U45G/eX86cURQnX7gzOJLG2oC28VhIk/G6IliYgQF7jFByBJEykPSHE4MxPhqCleYvmsrtfKh1nYmQ==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-2.2.0.tgz", + "integrity": "sha512-nXm9wT8oqrYFRMEqTXQx9DUTeEtXUDMmnUKIhZn6O2EeDY9VCdwj23XCPq7fkqMPKdF7ldAfeVKyxxFdbZl59A==", "dependencies": { - "@discordjs/collection": "^1.5.1", - "@discordjs/util": "^0.3.0", + "@discordjs/collection": "^2.0.0", + "@discordjs/util": "^1.0.2", "@sapphire/async-queue": "^1.5.0", - "@sapphire/snowflake": "^3.4.2", - "discord-api-types": "^0.37.41", - "file-type": "^18.3.0", - "tslib": "^2.5.0", - "undici": "^5.22.0" + "@sapphire/snowflake": "^3.5.1", + "@vladfrangu/async_event_emitter": "^2.2.2", + "discord-api-types": "0.37.61", + "magic-bytes.js": "^1.5.0", + "tslib": "^2.6.2", + "undici": "5.27.2" }, "engines": { - "node": ">=16.9.0" + "node": ">=16.11.0" + } + }, + "node_modules/@discordjs/rest/node_modules/@discordjs/collection": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.0.0.tgz", + "integrity": "sha512-YTWIXLrf5FsrLMycpMM9Q6vnZoR/lN2AWX23/Cuo8uOOtS8eHB2dyQaaGnaF8aZPYnttf2bkLMcXn/j6JUOi3w==", + "engines": { + "node": ">=18" } }, "node_modules/@discordjs/util": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@discordjs/util/-/util-0.3.1.tgz", - "integrity": "sha512-HxXKYKg7vohx2/OupUN/4Sd02Ev3PBJ5q0gtjdcvXb0ErCva8jNHWfe/v5sU3UKjIB/uxOhc+TDOnhqffj9pRA==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@discordjs/util/-/util-1.0.2.tgz", + "integrity": "sha512-IRNbimrmfb75GMNEjyznqM1tkI7HrZOf14njX7tCAAUetyZM1Pr8hX/EK2lxBCOgWDRmigbp24fD1hdMfQK5lw==", "engines": { - "node": ">=16.9.0" + "node": ">=16.11.0" } }, "node_modules/@discordjs/voice": {

@@ -123,11 +114,6 @@ "engines": {

"node": ">=16.11.0" } }, - "node_modules/@discordjs/voice/node_modules/discord-api-types": { - "version": "0.37.61", - "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.61.tgz", - "integrity": "sha512-o/dXNFfhBpYHpQFdT6FWzeO7pKc838QeeZ9d91CfVAtpr5XLK4B/zYxQbYgPdoMiTDvJfzcsLW5naXgmHGDNXw==" - }, "node_modules/@discordjs/ws": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@discordjs/ws/-/ws-1.0.2.tgz",

@@ -155,49 +141,6 @@ "engines": {

"node": ">=18" } }, - "node_modules/@discordjs/ws/node_modules/@discordjs/rest": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-2.2.0.tgz", - "integrity": "sha512-nXm9wT8oqrYFRMEqTXQx9DUTeEtXUDMmnUKIhZn6O2EeDY9VCdwj23XCPq7fkqMPKdF7ldAfeVKyxxFdbZl59A==", - "dependencies": { - "@discordjs/collection": "^2.0.0", - "@discordjs/util": "^1.0.2", - "@sapphire/async-queue": "^1.5.0", - "@sapphire/snowflake": "^3.5.1", - "@vladfrangu/async_event_emitter": "^2.2.2", - "discord-api-types": "0.37.61", - "magic-bytes.js": "^1.5.0", - "tslib": "^2.6.2", - "undici": "5.27.2" - }, - "engines": { - "node": ">=16.11.0" - } - }, - "node_modules/@discordjs/ws/node_modules/@discordjs/util": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@discordjs/util/-/util-1.0.2.tgz", - "integrity": "sha512-IRNbimrmfb75GMNEjyznqM1tkI7HrZOf14njX7tCAAUetyZM1Pr8hX/EK2lxBCOgWDRmigbp24fD1hdMfQK5lw==", - "engines": { - "node": ">=16.11.0" - } - }, - "node_modules/@discordjs/ws/node_modules/discord-api-types": { - "version": "0.37.61", - "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.61.tgz", - "integrity": "sha512-o/dXNFfhBpYHpQFdT6FWzeO7pKc838QeeZ9d91CfVAtpr5XLK4B/zYxQbYgPdoMiTDvJfzcsLW5naXgmHGDNXw==" - }, - "node_modules/@discordjs/ws/node_modules/undici": { - "version": "5.27.2", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.27.2.tgz", - "integrity": "sha512-iS857PdOEy/y3wlM3yRp+6SNQQ6xU0mmZcwRSriqk+et/cwWAtwmIGf6WkoDN2EK/AMdCO/dfXzIwi+rFMrjjQ==", - "dependencies": { - "@fastify/busboy": "^2.0.0" - }, - "engines": { - "node": ">=14.0" - } - }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",

@@ -352,23 +295,18 @@ "node": ">=v18"

} }, "node_modules/@sapphire/snowflake": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-3.5.2.tgz", - "integrity": "sha512-FTm9RdyELF21PQN5dS/HLRs90XqWclHa+p0gkonc+BA2X2QKfFySHSjUbO65rmArd/ghR9Ahj2fMfedTZEqzOw==", + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-3.5.1.tgz", + "integrity": "sha512-BxcYGzgEsdlG0dKAyOm0ehLGm2CafIrfQTZGWgkfKYbj+pNNsorZ7EotuZukc2MT70E0UbppVbtpBrqpzVzjNA==", "engines": { "node": ">=v14.0.0", "npm": ">=7.0.0" } }, - "node_modules/@tokenizer/token": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", - "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==" - }, "node_modules/@types/node": { - "version": "20.10.6", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.6.tgz", - "integrity": "sha512-Vac8H+NlRNNlAmDfGUP7b5h/KA+AtWIzuXy0E6OyP8f1tCLYAtPvKRRDJjAPqhpCb0t6U2j7/xqAuLEebW2kiw==", + "version": "20.10.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.7.tgz", + "integrity": "sha512-fRbIKb8C/Y2lXxB5eVMj4IU7xpdox0Lh8bUPEdtLysaylsml1hOOx1+STloRs/B9nf7C6kPRmmg/V7aQW7usNg==", "dependencies": { "undici-types": "~5.26.4" }

@@ -566,9 +504,9 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",

"dev": true }, "node_modules/discord-api-types": { - "version": "0.37.67", - "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.67.tgz", - "integrity": "sha512-4HEzUEmwGPXWJdVhGZ/K+9eWs8kurdn5r/I8qD3/0hb14I0MZvx8K/JOyLhKLgcR4/8/jcr6Xej820BNgNXN7A==" + "version": "0.37.61", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.61.tgz", + "integrity": "sha512-o/dXNFfhBpYHpQFdT6FWzeO7pKc838QeeZ9d91CfVAtpr5XLK4B/zYxQbYgPdoMiTDvJfzcsLW5naXgmHGDNXw==" }, "node_modules/discord.js": { "version": "14.14.1",

@@ -592,50 +530,6 @@ "ws": "8.14.2"

}, "engines": { "node": ">=16.11.0" - } - }, - "node_modules/discord.js/node_modules/@discordjs/rest": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-2.2.0.tgz", - "integrity": "sha512-nXm9wT8oqrYFRMEqTXQx9DUTeEtXUDMmnUKIhZn6O2EeDY9VCdwj23XCPq7fkqMPKdF7ldAfeVKyxxFdbZl59A==", - "dependencies": { - "@discordjs/collection": "^2.0.0", - "@discordjs/util": "^1.0.2", - "@sapphire/async-queue": "^1.5.0", - "@sapphire/snowflake": "^3.5.1", - "@vladfrangu/async_event_emitter": "^2.2.2", - "discord-api-types": "0.37.61", - "magic-bytes.js": "^1.5.0", - "tslib": "^2.6.2", - "undici": "5.27.2" - }, - "engines": { - "node": ">=16.11.0" - } - }, - "node_modules/discord.js/node_modules/@discordjs/rest/node_modules/@discordjs/collection": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.0.0.tgz", - "integrity": "sha512-YTWIXLrf5FsrLMycpMM9Q6vnZoR/lN2AWX23/Cuo8uOOtS8eHB2dyQaaGnaF8aZPYnttf2bkLMcXn/j6JUOi3w==", - "engines": { - "node": ">=18" - } - }, - "node_modules/discord.js/node_modules/@discordjs/util": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@discordjs/util/-/util-1.0.2.tgz", - "integrity": "sha512-IRNbimrmfb75GMNEjyznqM1tkI7HrZOf14njX7tCAAUetyZM1Pr8hX/EK2lxBCOgWDRmigbp24fD1hdMfQK5lw==", - "engines": { - "node": ">=16.11.0" - } - }, - "node_modules/discord.js/node_modules/@sapphire/snowflake": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-3.5.1.tgz", - "integrity": "sha512-BxcYGzgEsdlG0dKAyOm0ehLGm2CafIrfQTZGWgkfKYbj+pNNsorZ7EotuZukc2MT70E0UbppVbtpBrqpzVzjNA==", - "engines": { - "node": ">=v14.0.0", - "npm": ">=7.0.0" } }, "node_modules/discord.js/node_modules/@types/ws": {

@@ -646,22 +540,6 @@ "dependencies": {

"@types/node": "*" } }, - "node_modules/discord.js/node_modules/discord-api-types": { - "version": "0.37.61", - "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.61.tgz", - "integrity": "sha512-o/dXNFfhBpYHpQFdT6FWzeO7pKc838QeeZ9d91CfVAtpr5XLK4B/zYxQbYgPdoMiTDvJfzcsLW5naXgmHGDNXw==" - }, - "node_modules/discord.js/node_modules/undici": { - "version": "5.27.2", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.27.2.tgz", - "integrity": "sha512-iS857PdOEy/y3wlM3yRp+6SNQQ6xU0mmZcwRSriqk+et/cwWAtwmIGf6WkoDN2EK/AMdCO/dfXzIwi+rFMrjjQ==", - "dependencies": { - "@fastify/busboy": "^2.0.0" - }, - "engines": { - "node": ">=14.0" - } - }, "node_modules/discord.js/node_modules/ws": { "version": "8.14.2", "resolved": "https://registry.npmjs.org/ws/-/ws-8.14.2.tgz",

@@ -886,22 +764,6 @@ "engines": {

"node": "^10.12.0 || >=12.0.0" } }, - "node_modules/file-type": { - "version": "18.7.0", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-18.7.0.tgz", - "integrity": "sha512-ihHtXRzXEziMrQ56VSgU7wkxh55iNchFkosu7Y9/S+tXHdKyrGjVK0ujbqNnsxzea+78MaLhN6PGmfYSAv1ACw==", - "dependencies": { - "readable-web-to-node-stream": "^3.0.2", - "strtok3": "^7.0.0", - "token-types": "^5.0.1" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sindresorhus/file-type?sponsor=1" - } - }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",

@@ -1006,25 +868,6 @@ "engines": {

"node": ">=8" } }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, "node_modules/ignore": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz",

@@ -1072,7 +915,8 @@ },

"node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true }, "node_modules/is-extglob": { "version": "2.1.1",

@@ -1330,18 +1174,6 @@ "engines": {

"node": ">=8" } }, - "node_modules/peek-readable": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-5.0.0.tgz", - "integrity": "sha512-YtCKvLUOvwtMGmrniQPdO7MwPjgkFBtFIrmfSbYmYuq3tKDV/mcfAhBth1+C3ru7uXIZasc/pHnb+YDYNkkj4A==", - "engines": { - "node": ">=14.16" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, "node_modules/play-audio": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/play-audio/-/play-audio-0.5.2.tgz",

@@ -1421,34 +1253,6 @@ "url": "https://feross.org/support"

} ] }, - "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/readable-web-to-node-stream": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.2.tgz", - "integrity": "sha512-ePeK6cc1EcKLEhJFt/AebMCLL+GgSKhuygrZ/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGw==", - "dependencies": { - "readable-stream": "^3.6.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",

@@ -1506,25 +1310,6 @@ "dependencies": {

"queue-microtask": "^1.2.2" } }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",

@@ -1546,14 +1331,6 @@ "engines": {

"node": ">=8" } }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",

@@ -1578,22 +1355,6 @@ "funding": {

"url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/strtok3": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-7.0.0.tgz", - "integrity": "sha512-pQ+V+nYQdC5H3Q7qBZAz/MO6lwGhoC2gOAjuouGf/VO0m7vQRh8QNMl2Uf6SwAtzZ9bOw3UIeBukEGNJl5dtXQ==", - "dependencies": { - "@tokenizer/token": "^0.3.0", - "peek-readable": "^5.0.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",

@@ -1612,22 +1373,6 @@ "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",

"integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, - "node_modules/token-types": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/token-types/-/token-types-5.0.1.tgz", - "integrity": "sha512-Y2fmSnZjQdDb9W4w4r1tswlMHylzWIeOKpx0aZH9BgGtACHhrk3OkT52AzwcuqTRBZtvvnTjDBh8eynMulu8Vg==", - "dependencies": { - "@tokenizer/token": "^0.3.0", - "ieee754": "^1.2.1" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, "node_modules/ts-mixer": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.3.tgz",

@@ -1662,10 +1407,23 @@ "funding": {

"url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/typescript": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", + "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "node_modules/undici": { - "version": "5.28.2", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.2.tgz", - "integrity": "sha512-wh1pHJHnUeQV5Xa8/kyQhO7WFa8M34l026L5P/+2TYiakvGy5Rdc8jWZVyG7ieht/0WgJLEd3kcU5gKx+6GC8w==", + "version": "5.27.2", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.27.2.tgz", + "integrity": "sha512-iS857PdOEy/y3wlM3yRp+6SNQQ6xU0mmZcwRSriqk+et/cwWAtwmIGf6WkoDN2EK/AMdCO/dfXzIwi+rFMrjjQ==", "dependencies": { "@fastify/busboy": "^2.0.0" },

@@ -1686,11 +1444,6 @@ "dev": true,

"dependencies": { "punycode": "^2.1.0" } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, "node_modules/which": { "version": "2.0.2",
M package.jsonpackage.json

@@ -4,20 +4,21 @@ "version": "1.0.0",

"description": "A music-focused Discord bot.", "main": "index.js", "scripts": { - "start": "node index.js", + "build": "npx tsc", + "start": "node build/index.js", "deploy-commands": "node tools/deploy-commands.js", "delete-commands": "node tools/delete-commands.js" }, "author": "Marco Andronaco", "license": "GPL3", "dependencies": { - "@discordjs/rest": "^1.5.0", "@discordjs/voice": "^0.16.0", - "discord.js": "^14.3.0", + "discord.js": "^14.14.1", "libsodium-wrappers": "^0.7.10", "play-dl": "^1.9.6" }, "devDependencies": { - "eslint": "^8.23.1" + "eslint": "^8.23.1", + "typescript": "^5.3.3" } }
A src/commands/clear.ts

@@ -0,0 +1,17 @@

+import { SlashCommandBuilder } from 'discord.js'; +import * as music from '../functions/music'; + +module.exports = { + data: new SlashCommandBuilder() + .setName('clear') + .setDescription('Clear the queue.'), + + async execute(interaction) { + const channel = await music.getChannel(interaction); + if (typeof channel == 'string') + return await interaction.reply({ content: channel, ephemeral: true }); + + music.clearQueue() + return await interaction.reply({ content: 'Queue cleared.' }); + }, +};
A src/functions/music.ts

@@ -0,0 +1,69 @@

+import { createAudioResource, joinVoiceChannel, AudioPlayerStatus, CreateAudioResourceOptions, StreamType } from '@discordjs/voice'; +import MyQueue from './myqueue'; + +const q = new MyQueue(); + +export function getChannelConnection(channel) { + const guild = channel.guild; + return joinVoiceChannel({ + channelId: channel.id, + guildId: guild.id, + adapterCreator: guild.voiceAdapterCreator + }); +} + +export async function playUrls(urls, channel) { + if (!channel) { + console.log('Channel error:', channel); + return; + } + + q.connection = getChannelConnection(channel); + return q.addArray(urls); +} + +export async function playStream(url, channel) { + if (!channel) { + console.log('Channel error:', channel); + return; + } + q.connection = getChannelConnection(channel); + q.add(createAudioResource(url, { inputType: StreamType.Opus })); +} + +export async function playOutro(url, channel) { + if (!channel) { + console.log('Channel error:', channel); + return; + } + q.connection = getChannelConnection(channel); + q.outro(url); +} + +export async function getChannel(interaction) { + const member = interaction.member; + if (!member) + return 'Please use this in your current server.'; + + const channel = member.voice.channel; + if (!channel) + return 'You\'re not in a voice channel.'; + + return channel; +} + +export async function stopMusic() { + return q.stop(); +} + +export async function skipMusic() { + return q.next(); +} + +export async function getQueue() { + return q.queue; +} + +export function clearQueue() { + return q.clear(); +}
A src/types.d.ts

@@ -0,0 +1,14 @@

+import { SlashCommandBuilder, Collection, AutocompleteInteraction, ChatInputCommandInteraction } from "discord.js" + +export interface SlashCommand { + command: SlashCommandBuilder, + execute: (interaction : ChatInputCommandInteraction) => void, + autocomplete?: (interaction: AutocompleteInteraction) => void, + cooldown?: number // in seconds +} + +declare module "discord.js" { + export interface Client { + slashCommands: Collection<string, SlashCommand> + } +}
A tsconfig.json

@@ -0,0 +1,11 @@

+{ + "compilerOptions": { + "outDir": "build", + "allowJs": true, + "target": "ESNext", + "moduleResolution": "NodeNext", + "module": "NodeNext", + "sourceMap": true + }, + "include": ["./src/**/*"] + }