From 43b755e64991a9644a7d57e7915352e8e84611b4 Mon Sep 17 00:00:00 2001 From: minish Date: Tue, 29 Aug 2023 12:37:11 -0400 Subject: [PATCH] initial commit --- .dockerignore | 2 + .gitignore | 130 +++++++++++++++++++++ Dockerfile | 16 +++ index.js | 311 ++++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 23 ++++ 5 files changed, 482 insertions(+) create mode 100644 .dockerignore create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 index.js create mode 100644 package.json diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..16ef814 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,2 @@ +/.git +/node_modules diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c6bba59 --- /dev/null +++ b/.gitignore @@ -0,0 +1,130 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp +.cache + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..3f1aa10 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,16 @@ +FROM node:18-alpine +ENV NODE_ENV=production + +RUN ["apk", "update"] +RUN ["apk", "add", "ffmpeg"] +RUN ["apk", "add", "yt-dlp"] + +WORKDIR /app + +COPY ["package.json", "./"] + +RUN npm install --omit=dev + +COPY . . + +CMD ["node", "."] diff --git a/index.js b/index.js new file mode 100644 index 0000000..7d01e72 --- /dev/null +++ b/index.js @@ -0,0 +1,311 @@ +const { Client, GatewayIntentBits } = require('discord.js'); +const { DisTube, defaultFilters } = require('distube'); +const { YtDlpPlugin } = require('@distube/yt-dlp'); +/* const { SoundCloudPlugin } = require('@distube/soundcloud'); +const { SpotifyPlugin } = require('@distube/spotify'); +const { DeezerPlugin } = require('@distube/deezer'); */ +const client = new Client({ + intents: [ + GatewayIntentBits.Guilds, + GatewayIntentBits.GuildVoiceStates, + GatewayIntentBits.MessageContent, + GatewayIntentBits.GuildMessages + ], +}); +const config = { + prefix: '!!', + token: process.env.DISCORD_TOKEN, +}; + +// Create a new DisTube +const distube = new DisTube(client, { + searchSongs: 0, // 5 + searchCooldown: 0, // 30 + leaveOnEmpty: false, + leaveOnFinish: false, + leaveOnStop: false, + + plugins: [ new YtDlpPlugin({ update: true }) ], + }); + +client.on('ready', client => { + console.log(`Logged in as ${client.user.tag}!`); +}); +// client.on("debug", console.log) + +const checkQueue = message => { + if (typeof distube.getQueue(message) === 'undefined') { + message.channel.send('There is no queue'); + return false; + } return true; +} + +let formattedTimeToSeconds = str => { + var p = str.split(':'), + s = 0, m = 1; + + while (p.length > 0) { + s += m * parseInt(p.pop(), 10); + m *= 60; + } + + return s; +} + +client.on('messageCreate', message => { + if (message.author.bot || !message.inGuild()) return; + if (!message.content.startsWith(config.prefix)) return; + const args = message.content + .slice(config.prefix.length) + .trim() + .split(/ +/g); + const command = args.shift().toLowerCase(); + if (command === 'play' || command === 'p') { + if (args.length < 1) { + message.channel.send('No song provided'); + return; + } + + const voiceChannel = message.member?.voice?.channel; + if (voiceChannel) { + distube.play(voiceChannel, args.join(' '), { + message, + textChannel: message.channel, + member: message.member, + }); + } else { + message.channel.send( + 'You must join a voice channel first.', + ); + } + } + + if (['repeat', 'loop'].includes(command)) { + let queue = distube.getQueue(message); + if (!checkQueue(message)) return; + const mode = distube.setRepeatMode(message); + message.channel.send( + `Set repeat mode to \`${mode + ? mode === 2 + ? 'All Queue' + : 'This Song' + : 'Off' + }\``, + ); + } + + if (command === 'stop') { + if (!checkQueue(message)) return; + distube.stop(message); + message.channel.send('Stopped the music!'); + } + + if (command === 'leave') { + distube.voices.get(message)?.leave(); + message.channel.send('Left the voice channel!'); + } + + if (['resume', 'unpause'].includes(command)) { + if (!checkQueue(message)) return; + let queue = distube.getQueue(message); + distube.resume(message); + message.channel.send(`Queue is now ${queue.paused ? 'paused' : 'unpaused'}`) + } + + if (command === 'pause') { + if (!checkQueue(message)) return; + let queue = distube.getQueue(message); + if (!queue.paused) + distube.pause(message); + else distube.resume(message); + message.channel.send(`Queue is now ${queue.paused ? 'paused' : 'unpaused'}`) + } + + if (['skip', 's'].includes(command)) { + if (!checkQueue(message)) return; + if (distube.getQueue(message).songs.length < 1) return message.channel.send('There is no song up next in queue'); + distube.skip(message); + } + + if (['queue', 'q'].includes(command)) { + const queue = distube.getQueue(message); + if (!queue) { + message.channel.send('Nothing playing right now!'); + } else { + message.channel.send( + `Current queue:\n${queue.songs + .map( + (song, id) => + `**${id ? id : 'Playing'}**. ${song.name + } - \`${song.formattedDuration}\``, + ) + .slice(0, 10) + .join('\n')}`, + ); + } + } + + if (['playing', 'nowplaying', 'np', 'current', 'cur'].includes(command)) { + if (!checkQueue(message)) return; + let queue = distube.getQueue(message); + let song = queue.songs[0]; + message.channel.send(`Currently playing \`${song.name}\`\nDuration: \`${queue.formattedCurrentTime}\` / \`${queue.formattedDuration}\``); + } + + if (['seek', 'jump', 'j'].includes(command)) { + if (!checkQueue(message)) return; + let queue = distube.getQueue(message); + if (args.length < 1) return message.channel.send('There is no time') + let seconds = formattedTimeToSeconds(args[0]); + if (isNaN(seconds)) return message.channel.send('Time is not valid') + queue.seek(seconds); + message.channel.send('Seeked'); + } + + if (command === 'status') { + if (!checkQueue(message)) return; + let queue = distube.getQueue(message); + message.channel.send(status(queue)); + } + + if (['volume', 'vol'].includes(command)) { + let volume = parseInt(args[0]); + + let queue = distube.getQueue(message); + + if (!checkQueue(message)) return; + + if (args.length < 1) { + message.channel.send(`Current volume: ${queue.volume}%`); + } else if (!isNaN(volume) && volume >= 0) { + queue.setVolume(volume); + message.channel.send(`New volume: ${queue.volume}%`); + } else { + message.channel.send('Invalid number'); + } + } + + if (command === 'filter') { + const filters = [ + 'off', + '3d', + 'bassboost', + 'echo', + 'karaoke', + 'nightcore', + 'vaporwave', + ]; + + if (args.length >= 1 && args.every(arg => filters.includes(arg))) { + let queue = distube.getQueue(message); + if (!checkQueue(message)) return; + + if (args.includes('off')) + queue.filters.set([]); + else + queue.filters.set(args); + + message.channel.send( + `Current queue filter: ${queue.filters.names.join(', ') || 'Off'}`, + ); + } else { + message.channel.send(`Available filters are ${filters.join(', ')}.`); + } + } + + if (['autoplay', 'ap', 'darrius', 'darriusmode'].includes(command)) { + let queue = distube.getQueue(message); + + if (!checkQueue(message)) return; + + queue.toggleAutoplay(); + + message.channel.send(`Autoplay is now ${queue.autoplay ? 'on' : 'off'}`); + } + + if (['restart'].includes(command)) { + if (false /*message.author.id !== '778441081883983893'*/) { + message.channel.send('no permission!') + } else { + message.channel.send('Restarting!!') + setTimeout(() => process.exit(1), 2000) + } + } +}); + +// Queue status template +const status = queue => + `Volume: \`${queue.volume}%\` | Filter: \`${queue.filters.names.join(' ') || 'Off' + }\` | Loop: \`${queue.repeatMode + ? queue.repeatMode === 2 + ? 'All Queue' + : 'This Song' + : 'Off' + }\` | Autoplay: \`${queue.autoplay ? 'On' : 'Off'}\``; + +// DisTube event listeners, more in the documentation page +distube + .on('playSong', (queue, song) => + queue.textChannel?.send( + `Playing \`${song.name}\` - \`${song.formattedDuration + }\`\nRequested by: ${song.user}\n${status(queue)}`, + ), + ) + .on('addSong', (queue, song) => { + if (queue.songs.length < 2) return; + queue.textChannel?.send( + `Added \`${song.name}\` - \`${song.formattedDuration}\` to the queue by ${song.user}`, + ) + } + ) + .on('addList', (queue, playlist) => + queue.textChannel?.send( + `Added \`${playlist.name}\` playlist (${playlist.songs.length + } songs) to queue\n${status(queue)}`, + ), + ) + .on('error', (textChannel, e) => { + console.error(e); + textChannel.send( + `An error encountered: ${e.message.slice(0, 2000)}`, + ); + }) + .on('finish', queue => queue.textChannel?.send('Finished queue!')) + .on('finishSong', queue => + queue.textChannel?.send('Finished song!'), + ) + .on('disconnect', queue => + queue.textChannel?.send('Disconnected!'), + ) + .on('empty', queue => + queue.textChannel?.send( + 'The voice channel is empty! Leaving the voice channel...', + ), + ) + // DisTubeOptions.searchSongs > 1 + .on('searchResult', (message, result) => { + let i = 0; + message.channel.send( + `**Choose an option from below**\n${result + .map( + song => + `**${++i}**. ${song.name} - \`${song.formattedDuration + }\``, + ) + .join( + '\n', + )}\n*Enter anything else or wait 30 seconds to cancel*`, + ); + }) + .on('searchCancel', message => + message.channel.send('Searching canceled'), + ) + .on('searchInvalidAnswer', message => + message.channel.send('Invalid number of result.'), + ) + .on('searchNoResult', message => + message.channel.send('No result found!'), + ) + .on('searchDone', () => { }); + +client.login(config.token); diff --git a/package.json b/package.json new file mode 100644 index 0000000..d370e91 --- /dev/null +++ b/package.json @@ -0,0 +1,23 @@ +{ + "name": "nodemusicbot", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "dependencies": { + "@discordjs/opus": "^0.9.0", + "@discordjs/voice": "^0.16.0", + "@distube/deezer": "^1.0.0", + "@distube/soundcloud": "^1.3.0", + "@distube/spotify": "^1.5.1", + "@distube/yt-dlp": "^1.1.3", + "@distube/ytdl-core": "^4.11.9", + "discord.js": "^14.9.0", + "distube": "^4.0.5", + "libsodium-wrappers": "^0.7.11" + } +}