// Include Discordjs library // http://github.com/discordjs/ const { Client, VoiceChannel, Intents } = require('discord.js'); // Include fs, for reading and writing files const fs = require('fs'); // Include ytdl, so we can play youtube videos over voice const ytdl = require('ytdl-core-discord'); // Include our local auth key, so that we don't leak keys const auth = require('./auth.json'); // Volume for playback (fraction) var volume = 0.6; // **** PROGRAM START **** // This program is 2 lines of code, the rest is callback function definition, wherein the bot magic happens // Initialize a new Discord Client, and tell Discord what updates we are interested in recieving // https://discord.com/developers/docs/topics/gateway#list-of-intents const client = new Client({ intents: [Intents.FLAGS.GUILD_MESSAGES, Intents.FLAGS.GUILD_MESSAGE_REACTIONS] }); // Define Sigtrap Callback for clean shutdowns function signalExit(signal) { console.log("\tCaught ${signal}"); // Kills voice connections, and logs the client out client.destroy(); // Exit with return code 0 process.exit(0); } // Subscribe to a few signal events process.on('SIGINT', signalExit); // For Ctrl-C when running in a TTY process.on('SIGTERM', signalExit); // for systemd's stoppping of services // Define Several Discord Related Callbacks // This one only triggers when the connection to Discord is sucessful. Usually this is once at the start, // But if the connection to Discord hiccups for any reason, this will trigger again. client.on('ready', () => { console.log(`Logged in as ${client.user.tag}!`); }); // Here's the brains of the operation. This triggers every time a message is sent in any server the bot is in. // the async modifier really means async and concurently. Consider thread safety when interacting with globals. // The Message Object is our keyhole into Discord, where we have lots of fields and objects to work with. // https://discord.js.org/#/docs/main/v12/class/Message client.on('message', async message => { // Filter Section // In the event of a DM... if (message.guild == null) { switch (message.author.id) { case "262406433201586177": // Weegee case "137358406821347328": // moosecrap case "144317130026778624": // Piroglith case "137293315883139072": // tks_ftw case "172538370440953867": // Brightland case "183009699300507648": // spamminn case "209154502412992514": // NariNaju case "198278682677084160": // DrunkZombie case "137349560610586624": // craze case "305491553198145539": // Sam case "174356437550497792": // NoobWantsHacks case "308412988329558017": // Revanite // TODO: Recieve sound files from trusted parties? return; default: console.log(`Ignoring DM from ${message.author.tag}: ${message.content}`); return; } } // Filter Authorized Guilds switch (message.guild.id) { case "860344888301846538": // ZeSkypeBot Incubator case "305204527152365581": // Goldar Squad break; default: console.log(`Message from Unauthorized Guild ${message.guild.name}!`); return; } // Pre-Work // Do a few things that may be helpful const d = new Date(); // Process Section // Check for the shutup message if (message.content === "~") { console.log(`!stop issued by ${message.author.tag}`); if (!message.member.voice.channel) { message.channel.send('you can\'t stop the party if you\'re not here'); } else { message.member.voice.channel.leave(); } return; } // Try to find a sound file that matches the message if (!message.content.match(/[ \/;]/)) { // we had to add '/' because moose tried to 'play' ../../../../etc/shadow // Construct the expected sound path const soundPath = `./sounds/${message.content}.ogg`; if (message.author.id === "172538370440953867") { switch(message.content) { case "butthash": message.react('859687015247511561'); default: } } if (message.author.id === "144317130026778624") { switch(message.content) { case "daytona": case "e": if ([0,5,6].includes(d.getDay()) && Math.random() < 0.8) return; default: } } // TODO: this try block optional? try { // Test if there is a file at the expected path if (fs.existsSync(soundPath) && message.member.voice.channel) { console.log(`Sound '${message.content}' issued by ${message.author.tag} ${message.author.id}`); // Join the Voice Channel of the author const connection = await message.member.voice.channel.join(); // TODO: Switch to fs-based file read? // Play the sound file as requested const dispatcher = connection.play(soundPath, { volume: 0.60 }); dispatcher.on('start', () => { console.log(`Playing ${soundPath}`); }); dispatcher.on('finish', () => { console.log(`Finished ${soundPath}`); }); dispatcher.on('error', console.error); return; } // else no sound file, fall through past catch block } catch(err) { console.error(err); return; } } // Otherwise process commands if (message.content === "!help" ) { console.log(`!help issued by ${message.author.tag} ${message.author.id}`); var response = 'ZeSkypeBot v6.0.5 \'black pepper\'\n'; response += 'Original by moosecrap#5953 for Skype, Remade by Weegee#6402 for Discord\n'; response += 'Changelog:\n'; response += 'v6.0.5 Added feedback for cases where ytdl crashes\n'; response += 'v6.0.3 Changed !play regex per user feedback and counterexamples\n'; response += 'v6.0.2 Removed response to voiceless sound commands, for being too spammy\n'; response += 'v6.0.1 Added !play for youtube videos\n'; response += 'v6.0.0 Public release, plays sounds from Goldar Squad\'s collection of micspam audio files. No list will be provided\n'; //response += 'v6.0.0 Changed version numbers to match moosecrap\'s, removed sound file extension requirement\n'; //response += 'v0.2.0 Added !help command and printout\n'; //response += 'v0.1.0 Initial Release - Can play sounds from file\n'; response += 'Commands:\n'; response += '~ stop any sounds in progress and disconnect from voice\n'; response += '!help print this message\n'; response += 'To play Sounds, first join a voice channel then send a message in the text chat with the name of the sound.\n'; response += '!play [youtube url] play a youtube video over chat\n'; message.channel.send(response); return; } // Check for youtube play command const ytmatch = message.content.match(/(?<=!play (https?:\/\/)(www\.)?youtu((be\.com\/watch\?v=)|(\.be\/)))[\w-]{11}/); if (ytmatch && message.member.voice.channel) { const timematch = message.content.match(/(?<=t=)(\d{1,2}h)?(\d{1,2}m)?\d{1,4}s?(?=$)/); var timestamp = '0'; if (timematch) { timestamp = timematch[0]; } console.log(`!play issued for ${ytmatch[0]} by ${message.author.tag} ${message.author.id}`); // Reconstruct a youtu.be url for the supplied video const yturl = `https://youtu.be/${ytmatch[0]}`; //TODO: Factor out common code with the soundbyte play? const connection = await message.member.voice.channel.join(); // Play the video as requested try { //TODO: process the timestamp with ffmpeg or whatever const dispatcher = connection.play(await ytdl(yturl), { type: 'opus', volume: 0.60, begin: timestamp}); dispatcher.on('start', () => { console.log(`Playing ${yturl}`); }); dispatcher.on('finish', () => { console.log(`Finished ${yturl}`); }); dispatcher.on('error', console.error); } catch (error) { console.error(error); message.channel.send("Bro youtube says this shit is porn, i aint playing that"); const dispatcher = connection.play('./sounds/cantlet.ogg'); } return; } // Generic message responses if (message.content.match(/https:\/\/ootbingo\.github\.io/)) { var response = 'ooh its bingo time, i call '; if (Math.floor(Math.random()*2)) { response += 'over'; } else { response += 'under'; } response += ' on gateskip'; message.channel.send(response); return; } }); // Now our client is prepared, we can log in. Callbacks defined previously client.login(auth.token);