diff --git a/.profile b/.profile deleted file mode 100644 index d89ea5a..0000000 --- a/.profile +++ /dev/null @@ -1,27 +0,0 @@ -# ~/.profile: executed by the command interpreter for login shells. -# This file is not read by bash(1), if ~/.bash_profile or ~/.bash_login -# exists. -# see /usr/share/doc/bash/examples/startup-files for examples. -# the files are located in the bash-doc package. - -# the default umask is set in /etc/profile; for setting the umask -# for ssh logins, install and configure the libpam-umask package. -#umask 022 - -# if running bash -if [ -n "$BASH_VERSION" ]; then - # include .bashrc if it exists - if [ -f "$HOME/.bashrc" ]; then - . "$HOME/.bashrc" - fi -fi - -# set PATH so it includes user's private bin if it exists -if [ -d "$HOME/bin" ] ; then - PATH="$HOME/bin:$PATH" -fi - -# set PATH so it includes user's private bin if it exists -if [ -d "$HOME/.local/bin" ] ; then - PATH="$HOME/.local/bin:$PATH" -fi diff --git a/commands.js b/commands.js index 2ea130e..646fb49 100644 --- a/commands.js +++ b/commands.js @@ -26,5 +26,9 @@ module.exports = [ .setName("loop") .setDescription("Makes the queue play from the start when it has finished all the songs.") .toJSON(), + new SlashCommandBuilder() + .setName("invite") + .setDescription("Invite me to your server.") + .toJSON(), -] \ No newline at end of file +] diff --git a/index.js b/index.js index 12b6d17..130c6bc 100644 --- a/index.js +++ b/index.js @@ -80,7 +80,7 @@ client.on("interactionCreate", async interaction => { interaction.followUp("An error has occured, name: " + e.name + ", details in console."); console.log(e); } - await interaction.reply({ content: `**${video.title}** playing now. (because you couldn't go to spotify)` }); + await interaction.reply({ content: `**[${video.title}](${video.url})** playing now. (because you couldn't go to spotify)` }); return; } else { serverQueue.songs.push({ title: video.title, url: video.url, requestedBy: interaction.member }); @@ -92,7 +92,7 @@ client.on("interactionCreate", async interaction => { console.log(e); } } - await interaction.reply({ content: `**${video.title}** has been added to the queue. it will play when **${serverQueue.songs[serverQueue.index].title}** is done. (Because someone clown came before you)` }); + await interaction.reply({ content: `**[${video.title}](${video.url})** has been added to the queue. it will play when **${serverQueue.songs[serverQueue.index].title}** is done. (Because someone clown came before you)` }); } } @@ -164,7 +164,9 @@ client.on("interactionCreate", async interaction => { await interaction.reply("YES FINALLY YOU RELIEVE ME FROM THIS ENDLESS PLAYING"); } } else await interaction.reply({ content: "Nothing is playing, are you an idiot or smth?" }); - } + } else if(interaction.commandName == "invite"){ + interaction.reply("To invite me to your server, click [here](https://discord.com/api/oauth2/authorize?client_id=914122883473739807&permissions=36716800&scope=bot%20applications.commands)."); + } } }); @@ -197,8 +199,8 @@ async function playMusic(serverQueue) { return; } try { - let stream = await ytdl(song.url, { filter: "audioonly" }); - let resource = createAudioResource(stream, { inputType: StreamType.WebmOpus }); + let stream = await ytdl(song.url, { filter: "audioonly", highWaterMark: 983554432 }); + let resource = createAudioResource(stream, { inputType: StreamType.Arbitrary }); try{ player.play(resource); } catch(e){ @@ -232,7 +234,7 @@ async function playMusic(serverQueue) { } else { //serverQueue.songs.shift(); serverQueue.index++; - serverQueue.textChannel.send({ content: `**${(serverQueue.songs[index+1] || serverQueue.songs[index]).title}** is now playing. (send help)` }); + if(serverQueue.songs[index+1]) serverQueue.textChannel.send({ content: `**${(serverQueue.songs[index+1]).title}** is now playing. (send help)` }); try{ playMusic(serverQueue); } catch(e){ diff --git a/node_modules/.package-lock.json b/node_modules/.package-lock.json index 4a6a38c..a79f382 100644 --- a/node_modules/.package-lock.json +++ b/node_modules/.package-lock.json @@ -1599,11 +1599,11 @@ } }, "node_modules/ytdl-core": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/ytdl-core/-/ytdl-core-4.9.1.tgz", - "integrity": "sha512-6Jbp5RDhUEozlaJQAR+l8oV8AHsx3WUXxSyPxzE6wOIAaLql7Hjiy0ZM58wZoyj1YEenlEPjEqcJIjKYKxvHtQ==", + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/ytdl-core/-/ytdl-core-4.10.0.tgz", + "integrity": "sha512-RCCoSVTmMeBPH5NFR1fh3nkDU9okvWM0ZdN6plw6I5+vBBZVUEpOt8vjbSgprLRMmGUsmrQZJhvG1CHOat4mLA==", "dependencies": { - "m3u8stream": "^0.8.3", + "m3u8stream": "^0.8.4", "miniget": "^4.0.0", "sax": "^1.1.3" }, diff --git a/node_modules/ytdl-core/lib/sig.js b/node_modules/ytdl-core/lib/sig.js index 2bdccbe..20575d0 100644 --- a/node_modules/ytdl-core/lib/sig.js +++ b/node_modules/ytdl-core/lib/sig.js @@ -1,228 +1,108 @@ const querystring = require('querystring'); const Cache = require('./cache'); const utils = require('./utils'); +const vm = require('vm'); - -// A shared cache to keep track of html5player.js tokens. +// A shared cache to keep track of html5player js functions. exports.cache = new Cache(); - /** - * Extract signature deciphering tokens from html5player file. + * Extract signature deciphering and n parameter transform functions from html5player file. * * @param {string} html5playerfile * @param {Object} options * @returns {Promise>} */ -exports.getTokens = (html5playerfile, options) => exports.cache.getOrSet(html5playerfile, async() => { +exports.getFunctions = (html5playerfile, options) => exports.cache.getOrSet(html5playerfile, async() => { const body = await utils.exposedMiniget(html5playerfile, options).text(); - const tokens = exports.extractActions(body); - if (!tokens || !tokens.length) { - throw Error('Could not extract signature deciphering actions'); + const functions = exports.extractFunctions(body); + if (!functions || !functions.length) { + throw Error('Could not extract functions'); } - exports.cache.set(html5playerfile, tokens); - return tokens; + exports.cache.set(html5playerfile, functions); + return functions; }); - /** - * Decipher a signature based on action tokens. - * - * @param {Array.} tokens - * @param {string} sig - * @returns {string} - */ -exports.decipher = (tokens, sig) => { - sig = sig.split(''); - for (let i = 0, len = tokens.length; i < len; i++) { - let token = tokens[i], pos; - switch (token[0]) { - case 'r': - sig = sig.reverse(); - break; - case 'w': - pos = ~~token.slice(1); - sig = swapHeadAndPosition(sig, pos); - break; - case 's': - pos = ~~token.slice(1); - sig = sig.slice(pos); - break; - case 'p': - pos = ~~token.slice(1); - sig.splice(0, pos); - break; - } - } - return sig.join(''); -}; - - -/** - * Swaps the first element of an array with one of given position. - * - * @param {Array.} arr - * @param {number} position - * @returns {Array.} - */ -const swapHeadAndPosition = (arr, position) => { - const first = arr[0]; - arr[0] = arr[position % arr.length]; - arr[position] = first; - return arr; -}; - - -const jsVarStr = '[a-zA-Z_\\$][a-zA-Z_0-9]*'; -const jsSingleQuoteStr = `'[^'\\\\]*(:?\\\\[\\s\\S][^'\\\\]*)*'`; -const jsDoubleQuoteStr = `"[^"\\\\]*(:?\\\\[\\s\\S][^"\\\\]*)*"`; -const jsQuoteStr = `(?:${jsSingleQuoteStr}|${jsDoubleQuoteStr})`; -const jsKeyStr = `(?:${jsVarStr}|${jsQuoteStr})`; -const jsPropStr = `(?:\\.${jsVarStr}|\\[${jsQuoteStr}\\])`; -const jsEmptyStr = `(?:''|"")`; -const reverseStr = ':function\\(a\\)\\{' + - '(?:return )?a\\.reverse\\(\\)' + -'\\}'; -const sliceStr = ':function\\(a,b\\)\\{' + - 'return a\\.slice\\(b\\)' + -'\\}'; -const spliceStr = ':function\\(a,b\\)\\{' + - 'a\\.splice\\(0,b\\)' + -'\\}'; -const swapStr = ':function\\(a,b\\)\\{' + - 'var c=a\\[0\\];a\\[0\\]=a\\[b(?:%a\\.length)?\\];a\\[b(?:%a\\.length)?\\]=c(?:;return a)?' + -'\\}'; -const actionsObjRegexp = new RegExp( - `var (${jsVarStr})=\\{((?:(?:${ - jsKeyStr}${reverseStr}|${ - jsKeyStr}${sliceStr}|${ - jsKeyStr}${spliceStr}|${ - jsKeyStr}${swapStr - }),?\\r?\\n?)+)\\};`); -const actionsFuncRegexp = new RegExp(`${`function(?: ${jsVarStr})?\\(a\\)\\{` + - `a=a\\.split\\(${jsEmptyStr}\\);\\s*` + - `((?:(?:a=)?${jsVarStr}`}${ - jsPropStr -}\\(a,\\d+\\);)+)` + - `return a\\.join\\(${jsEmptyStr}\\)` + - `\\}`); -const reverseRegexp = new RegExp(`(?:^|,)(${jsKeyStr})${reverseStr}`, 'm'); -const sliceRegexp = new RegExp(`(?:^|,)(${jsKeyStr})${sliceStr}`, 'm'); -const spliceRegexp = new RegExp(`(?:^|,)(${jsKeyStr})${spliceStr}`, 'm'); -const swapRegexp = new RegExp(`(?:^|,)(${jsKeyStr})${swapStr}`, 'm'); - - -/** - * Extracts the actions that should be taken to decipher a signature. - * - * This searches for a function that performs string manipulations on - * the signature. We already know what the 3 possible changes to a signature - * are in order to decipher it. There is - * - * * Reversing the string. - * * Removing a number of characters from the beginning. - * * Swapping the first character with another position. - * - * Note, `Array#slice()` used to be used instead of `Array#splice()`, - * it's kept in case we encounter any older html5player files. - * - * After retrieving the function that does this, we can see what actions - * it takes on a signature. + * Extracts the actions that should be taken to decipher a signature + * and tranform the n parameter * * @param {string} body * @returns {Array.} */ -exports.extractActions = body => { - const objResult = actionsObjRegexp.exec(body); - const funcResult = actionsFuncRegexp.exec(body); - if (!objResult || !funcResult) { return null; } - - const obj = objResult[1].replace(/\$/g, '\\$'); - const objBody = objResult[2].replace(/\$/g, '\\$'); - const funcBody = funcResult[1].replace(/\$/g, '\\$'); - - let result = reverseRegexp.exec(objBody); - const reverseKey = result && result[1] - .replace(/\$/g, '\\$') - .replace(/\$|^'|^"|'$|"$/g, ''); - result = sliceRegexp.exec(objBody); - const sliceKey = result && result[1] - .replace(/\$/g, '\\$') - .replace(/\$|^'|^"|'$|"$/g, ''); - result = spliceRegexp.exec(objBody); - const spliceKey = result && result[1] - .replace(/\$/g, '\\$') - .replace(/\$|^'|^"|'$|"$/g, ''); - result = swapRegexp.exec(objBody); - const swapKey = result && result[1] - .replace(/\$/g, '\\$') - .replace(/\$|^'|^"|'$|"$/g, ''); - - const keys = `(${[reverseKey, sliceKey, spliceKey, swapKey].join('|')})`; - const myreg = `(?:a=)?${obj - }(?:\\.${keys}|\\['${keys}'\\]|\\["${keys}"\\])` + - `\\(a,(\\d+)\\)`; - const tokenizeRegexp = new RegExp(myreg, 'g'); - const tokens = []; - while ((result = tokenizeRegexp.exec(funcBody)) !== null) { - let key = result[1] || result[2] || result[3]; - switch (key) { - case swapKey: - tokens.push(`w${result[4]}`); - break; - case reverseKey: - tokens.push('r'); - break; - case sliceKey: - tokens.push(`s${result[4]}`); - break; - case spliceKey: - tokens.push(`p${result[4]}`); - break; +exports.extractFunctions = body => { + const functions = []; + const extractManipulations = caller => { + const functionName = utils.between(caller, `a=a.split("");`, `.`); + if (!functionName) return ''; + const functionStart = `var ${functionName}={`; + const ndx = body.indexOf(functionStart); + if (ndx < 0) return ''; + const subBody = body.slice(ndx + functionStart.length - 1); + return `var ${functionName}=${utils.cutAfterJSON(subBody)}`; + }; + const extractDecipher = () => { + const functionName = utils.between(body, `a.set("alr","yes");c&&(c=`, `(decodeURIC`); + if (functionName && functionName.length) { + const functionStart = `${functionName}=function(a)`; + const ndx = body.indexOf(functionStart); + if (ndx >= 0) { + const subBody = body.slice(ndx + functionStart.length); + let functionBody = `var ${functionStart}${utils.cutAfterJSON(subBody)}`; + functionBody = `${extractManipulations(functionBody)};${functionBody};${functionName}(sig);`; + functions.push(functionBody); + } } - } - return tokens; + }; + const extractNCode = () => { + const functionName = utils.between(body, `&&(b=a.get("n"))&&(b=`, `(b)`); + if (functionName && functionName.length) { + const functionStart = `${functionName}=function(a)`; + const ndx = body.indexOf(functionStart); + if (ndx >= 0) { + const subBody = body.slice(ndx + functionStart.length); + const functionBody = `var ${functionStart}${utils.cutAfterJSON(subBody)};${functionName}(ncode);`; + functions.push(functionBody); + } + } + }; + extractDecipher(); + extractNCode(); + return functions; }; - /** + * Apply decipher and n-transform to individual format + * * @param {Object} format - * @param {string} sig + * @param {vm.Script} decipherScript + * @param {vm.Script} nTransformScript */ -exports.setDownloadURL = (format, sig) => { - let decodedUrl; - if (format.url) { - decodedUrl = format.url; - } else { - return; - } - - try { - decodedUrl = decodeURIComponent(decodedUrl); - } catch (err) { - return; - } - - // Make some adjustments to the final url. - const parsedUrl = new URL(decodedUrl); - - // This is needed for a speedier download. - // See https://github.com/fent/node-ytdl-core/issues/127 - parsedUrl.searchParams.set('ratebypass', 'yes'); - - if (sig) { - // When YouTube provides a `sp` parameter the signature `sig` must go - // into the parameter it specifies. - // See https://github.com/fent/node-ytdl-core/issues/417 - parsedUrl.searchParams.set(format.sp || 'signature', sig); - } - - format.url = parsedUrl.toString(); +exports.setDownloadURL = (format, decipherScript, nTransformScript) => { + const decipher = url => { + const args = querystring.parse(url); + if (!args.s || !decipherScript) return args.url; + const components = new URL(decodeURIComponent(args.url)); + components.searchParams.set(args.sp ? args.sp : 'signature', + decipherScript.runInNewContext({ sig: decodeURIComponent(args.s) })); + return components.toString(); + }; + const ncode = url => { + const components = new URL(decodeURIComponent(url)); + const n = components.searchParams.get('n'); + if (!n || !nTransformScript) return url; + components.searchParams.set('n', nTransformScript.runInNewContext({ ncode: n })); + return components.toString(); + }; + const cipher = !format.url; + const url = format.url || format.signatureCipher || format.cipher; + format.url = cipher ? ncode(decipher(url)) : ncode(url); + delete format.signatureCipher; + delete format.cipher; }; - /** - * Applies `sig.decipher()` to all format URL's. + * Applies decipher and n parameter transforms to all format URL's. * * @param {Array.} formats * @param {string} html5player @@ -230,16 +110,11 @@ exports.setDownloadURL = (format, sig) => { */ exports.decipherFormats = async(formats, html5player, options) => { let decipheredFormats = {}; - let tokens = await exports.getTokens(html5player, options); + let functions = await exports.getFunctions(html5player, options); + const decipherScript = functions.length ? new vm.Script(functions[0]) : null; + const nTransformScript = functions.length > 1 ? new vm.Script(functions[1]) : null; formats.forEach(format => { - let cipher = format.signatureCipher || format.cipher; - if (cipher) { - Object.assign(format, querystring.parse(cipher)); - delete format.signatureCipher; - delete format.cipher; - } - const sig = tokens && format.s ? exports.decipher(tokens, format.s) : null; - exports.setDownloadURL(format, sig); + exports.setDownloadURL(format, decipherScript, nTransformScript); decipheredFormats[format.url] = format; }); return decipheredFormats; diff --git a/node_modules/ytdl-core/package.json b/node_modules/ytdl-core/package.json index b1a15e3..6fb03c9 100644 --- a/node_modules/ytdl-core/package.json +++ b/node_modules/ytdl-core/package.json @@ -6,7 +6,7 @@ "video", "download" ], - "version": "4.9.1", + "version": "4.10.0", "repository": { "type": "git", "url": "git://github.com/fent/node-ytdl-core.git" @@ -35,7 +35,7 @@ "lint:typings:fix": "tslint --fix typings/index.d.ts" }, "dependencies": { - "m3u8stream": "^0.8.3", + "m3u8stream": "^0.8.4", "miniget": "^4.0.0", "sax": "^1.1.3" }, diff --git a/node_modules/ytdl-core/typings/index.d.ts b/node_modules/ytdl-core/typings/index.d.ts index 7bd5371..17d9f63 100644 --- a/node_modules/ytdl-core/typings/index.d.ts +++ b/node_modules/ytdl-core/typings/index.d.ts @@ -198,6 +198,7 @@ declare module 'ytdl-core' { liveBroadcastDetails?: { isLiveNow: boolean; startTimestamp: string; + endTimestamp?: string; }; uploadDate: string; } diff --git a/package-lock.json b/package-lock.json index 12279d6..ca31ec0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,7 +17,7 @@ "ffmpeg-static": "^4.4.0", "tweetnacl": "^1.0.3", "yt-search": "^2.10.2", - "ytdl-core": "^4.9.1" + "ytdl-core": "^4.10.0" } }, "node_modules/@derhuerst/http-basic": { @@ -1615,11 +1615,11 @@ } }, "node_modules/ytdl-core": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/ytdl-core/-/ytdl-core-4.9.1.tgz", - "integrity": "sha512-6Jbp5RDhUEozlaJQAR+l8oV8AHsx3WUXxSyPxzE6wOIAaLql7Hjiy0ZM58wZoyj1YEenlEPjEqcJIjKYKxvHtQ==", + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/ytdl-core/-/ytdl-core-4.10.0.tgz", + "integrity": "sha512-RCCoSVTmMeBPH5NFR1fh3nkDU9okvWM0ZdN6plw6I5+vBBZVUEpOt8vjbSgprLRMmGUsmrQZJhvG1CHOat4mLA==", "dependencies": { - "m3u8stream": "^0.8.3", + "m3u8stream": "^0.8.4", "miniget": "^4.0.0", "sax": "^1.1.3" }, @@ -2885,11 +2885,11 @@ } }, "ytdl-core": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/ytdl-core/-/ytdl-core-4.9.1.tgz", - "integrity": "sha512-6Jbp5RDhUEozlaJQAR+l8oV8AHsx3WUXxSyPxzE6wOIAaLql7Hjiy0ZM58wZoyj1YEenlEPjEqcJIjKYKxvHtQ==", + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/ytdl-core/-/ytdl-core-4.10.0.tgz", + "integrity": "sha512-RCCoSVTmMeBPH5NFR1fh3nkDU9okvWM0ZdN6plw6I5+vBBZVUEpOt8vjbSgprLRMmGUsmrQZJhvG1CHOat4mLA==", "requires": { - "m3u8stream": "^0.8.3", + "m3u8stream": "^0.8.4", "miniget": "^4.0.0", "sax": "^1.1.3" } diff --git a/package.json b/package.json index 11ac79c..92c22fd 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,6 @@ "ffmpeg-static": "^4.4.0", "tweetnacl": "^1.0.3", "yt-search": "^2.10.2", - "ytdl-core": "^4.9.1" + "ytdl-core": "^4.10.0" } }