From 4b8d32a169ab1ba27edcff4411e22b4adf75590a Mon Sep 17 00:00:00 2001 From: Dragon Fire Date: Fri, 12 Feb 2021 18:01:05 -0500 Subject: [PATCH] Add basic music player --- .gitignore | 1 - Xiao.js | 1 + commands/music/play.js | 84 ++++++++++++++++++++++++++++++++++++++++++ package.json | 2 +- 4 files changed, 86 insertions(+), 2 deletions(-) create mode 100644 commands/music/play.js diff --git a/.gitignore b/.gitignore index 02b69e37..bbb3670c 100644 --- a/.gitignore +++ b/.gitignore @@ -17,4 +17,3 @@ command-last-run.json *.traineddata # In-Development Commands -commands/music/ diff --git a/Xiao.js b/Xiao.js index 7f106bf1..0b521b38 100644 --- a/Xiao.js +++ b/Xiao.js @@ -37,6 +37,7 @@ client.registry ['edit-text', 'Text Manipulation'], ['edit-number', 'Number Manipulation'], ['voice', 'Play Audio'], + ['music', 'Music'], ['remind', 'Reminders'], ['phone', 'Phone'], ['code', 'Coding Tools'], diff --git a/commands/music/play.js b/commands/music/play.js new file mode 100644 index 00000000..236e22eb --- /dev/null +++ b/commands/music/play.js @@ -0,0 +1,84 @@ +const Command = require('../../structures/Command'); +const request = require('node-superfetch'); +const ytdl = require('ytdl-core'); +const { reactIfAble } = require('../../util/Util'); +const { GOOGLE_KEY } = process.env; + +module.exports = class PlayCommand extends Command { + constructor(client) { + super(client, { + name: 'play', + aliases: ['play-music', 'music'], + group: 'music', + memberName: 'play', + description: 'Plays a YouTube video in your voice channel.', + guildOnly: true, + throttling: { + usages: 1, + duration: 10 + }, + userPermissions: ['CONNECT', 'SPEAK'], + credit: [ + { + name: 'Google', + url: 'https://www.google.com/', + reason: 'YouTube Data API', + reasonURL: 'https://developers.google.com/youtube/v3/' + } + ], + args: [ + { + key: 'query', + prompt: 'What video would you like to play? Either a URL or search query.', + type: 'string' + } + ] + }); + + this.dispatchers = new Map(); + } + + async run(msg, { query }) { + const connection = this.client.voice.connections.get(msg.guild.id); + if (!connection) { + const usage = this.client.registry.commands.get('join').usage(); + return msg.reply(`I am not in a voice channel. Use ${usage} to fix that!`); + } + if (this.dispatchers.has(msg.guild.id)) return msg.reply('I am already playing music in this server.'); + const result = await this.searchForVideo(query); + if (!result) return msg.say('Could not find any results for your query.'); + const canPlay = await this.canUseVideo(result, msg.channel.nsfw || false); + if (!canPlay) return msg.say('I cannot play this video.'); + const dispatcher = connection.play(ytdl(result, { filter: 'audioonly', quality: 'lowest' })); + this.dispatchers.set(msg.guild.id, dispatcher); + dispatcher.once('finish', () => this.dispatchers.delete(msg.guild.id)); + dispatcher.once('error', () => this.dispatchers.delete(msg.guild.id)); + await reactIfAble(msg, this.client.user, '🔉'); + return null; + } + + async searchForVideo(query) { + if (ytdl.validateURL(query)) return ytdl.getURLVideoID(query); + if (ytdl.validateID(query)) return query; + const { body } = await request + .get('https://www.googleapis.com/youtube/v3/search') + .query({ + part: 'snippet', + type: 'video', + maxResults: 1, + q: query, + safeSearch: msg.channel.nsfw ? 'none' : 'strict', + key: GOOGLE_KEY + }); + if (!body.items.length) return null; + const data = body.items[0]; + return data.id.videoId; + } + + async canUseVideo(id, nsfw) { + const data = await ytdl.getInfo(id); + if (data.videoDetails.isPrivate || data.videoDetails.isLiveContent) return false; + if (data.videoDetails.age_restricted && nsfw) return false; + return true; + } +}; diff --git a/package.json b/package.json index 3864c275..9892e514 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "xiao", - "version": "129.5.1", + "version": "129.6.0", "description": "Your personal server companion.", "main": "Xiao.js", "scripts": {