From 2459625f55adfe9fb7acee615db6e8b2e3f357e2 Mon Sep 17 00:00:00 2001 From: davipatury Date: Thu, 27 Aug 2020 01:11:09 -0300 Subject: [PATCH] Improves TuneInProvider --- package-lock.json | 137 +++++++++++++++++- package.json | 1 + src/apis/TuneIn.js | 12 ++ src/index.js | 4 +- src/structures/lavacord/Song.js | 3 +- .../providers/spotify/SpotifySong.js | 2 +- .../providers/tunein/TuneInProvider.js | 13 +- src/structures/providers/tunein/TuneInSong.js | 23 +++ 8 files changed, 185 insertions(+), 10 deletions(-) create mode 100644 src/structures/providers/tunein/TuneInSong.js diff --git a/package-lock.json b/package-lock.json index 24e7985..4dd1cc3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -172,6 +172,11 @@ "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", "dev": true }, + "@types/node": { + "version": "14.6.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.6.0.tgz", + "integrity": "sha512-mikldZQitV94akrc4sCcSjtJfsTKt4p+e/s0AGscVA6XArQ9kFclP+ZiYUMnq987rc6QlYxXv/EivqlfSLxpKA==" + }, "abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -364,6 +369,11 @@ } } }, + "boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=" + }, "boxen": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/boxen/-/boxen-4.2.0.tgz", @@ -481,6 +491,19 @@ "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", "dev": true }, + "cheerio": { + "version": "1.0.0-rc.3", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.3.tgz", + "integrity": "sha512-0td5ijfUPuubwLUu0OBoe98gZj8C/AA+RW3v67GPlGOrvxWjZmBXiBCRU+I8VEiNyJzjth40POfHiz2RB3gImA==", + "requires": { + "css-select": "~1.2.0", + "dom-serializer": "~0.1.1", + "entities": "~1.1.1", + "htmlparser2": "^3.9.1", + "lodash": "^4.15.0", + "parse5": "^3.0.1" + } + }, "chokidar": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.3.1.tgz", @@ -625,6 +648,22 @@ "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", "dev": true }, + "css-select": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", + "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", + "requires": { + "boolbase": "~1.0.0", + "css-what": "2.1", + "domutils": "1.5.1", + "nth-check": "~1.0.1" + } + }, + "css-what": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz", + "integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==" + }, "debug": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", @@ -716,6 +755,37 @@ "esutils": "^2.0.2" } }, + "dom-serializer": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.1.tgz", + "integrity": "sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==", + "requires": { + "domelementtype": "^1.3.0", + "entities": "^1.1.1" + } + }, + "domelementtype": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==" + }, + "domhandler": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", + "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", + "requires": { + "domelementtype": "1" + } + }, + "domutils": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", + "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", + "requires": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, "dot-prop": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.2.0.tgz", @@ -762,6 +832,11 @@ "once": "^1.4.0" } }, + "entities": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==" + }, "erlpack": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/erlpack/-/erlpack-0.1.3.tgz", @@ -1532,6 +1607,19 @@ "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==", "dev": true }, + "htmlparser2": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", + "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", + "requires": { + "domelementtype": "^1.3.1", + "domhandler": "^2.3.0", + "domutils": "^1.5.1", + "entities": "^1.1.1", + "inherits": "^2.0.1", + "readable-stream": "^3.1.1" + } + }, "http-cache-semantics": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", @@ -1916,8 +2004,7 @@ "lodash": { "version": "4.17.19", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", - "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==", - "dev": true + "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==" }, "loose-envify": { "version": "1.4.0", @@ -2125,6 +2212,14 @@ "integrity": "sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==", "dev": true }, + "nth-check": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", + "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", + "requires": { + "boolbase": "~1.0.0" + } + }, "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -2304,6 +2399,14 @@ "error-ex": "^1.2.0" } }, + "parse5": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-3.0.3.tgz", + "integrity": "sha512-rgO9Zg5LLLkfJF9E6CCmXlSE4UVceloys8JrFqCcHloC3usd/kJCyPDwH2SOlzix2j3xaP9sUX3e8+kvkuleAA==", + "requires": { + "@types/node": "*" + } + }, "parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -2598,6 +2701,16 @@ "read-pkg": "^2.0.0" } }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, "readdirp": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.3.0.tgz", @@ -2998,6 +3111,21 @@ "es-abstract": "^1.17.5" } }, + "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==", + "requires": { + "safe-buffer": "~5.2.0" + }, + "dependencies": { + "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==" + } + } + }, "strip-ansi": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", @@ -3232,6 +3360,11 @@ "prepend-http": "^2.0.0" } }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, "utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", diff --git a/package.json b/package.json index c07e517..0daa389 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,7 @@ "main": "src/index.js", "dependencies": { "@sentry/node": "^5.15.4", + "cheerio": "^1.0.0-rc.3", "cors": "^2.8.5", "erlpack": "^0.1.3", "express": "^4.17.1", diff --git a/src/apis/TuneIn.js b/src/apis/TuneIn.js index 025392f..ead46a0 100644 --- a/src/apis/TuneIn.js +++ b/src/apis/TuneIn.js @@ -1,12 +1,24 @@ const fetch = require('node-fetch') +const cheerio = require('cheerio') +const fs = require('fs') const API_URL = 'https://opml.radiotime.com' +const TUNEIN_URL = 'https://tunein.com/radio/' class TuneInAPI { static get (id) { return this.request('/Tune.ashx', { id }).then(({ body: [result] }) => result) } + static async fetch (path, code) { + const res = await fetch(TUNEIN_URL + path).then(res => res.text()) + const $ = cheerio.load(res) + const scriptContent = $('#initialStateEl').get([0]).children[0].data.trim() + const json = JSON.parse(scriptContent.replace('window.INITIAL_STATE=', '').replace(/;$/, '')) + const { title, description, actions: { share: { shareUrl: shortUrl, logoUrl: image } } } = json.profiles[code] + return { title, description, image, shortUrl } + } + // Internal static async request (endpoint, queryParams = {}) { const qParams = new URLSearchParams({ ...queryParams, render: 'json', formats: 'mp3,aac,ogg,flash,html,hls' }) diff --git a/src/index.js b/src/index.js index e756218..bb31462 100644 --- a/src/index.js +++ b/src/index.js @@ -5,7 +5,7 @@ const MusicManager = require('./structures/MusicManager') const manager = new MusicManager() manager.connect().then(async () => { - const song = await manager.songProvider.get('https://tunein.com/radio/Rdio-Clube-FM-(Braslia)-1055-s126960/') // ytsearch:girassol da cor do seu cabelo + const song = await manager.songProvider.get('https://tunein.com/radio/Rdio-Executiva-927-s97450/') // ytsearch:girassol da cor do seu cabelo const player = await manager.lavalink.join({ guild: '445203868624748555', channel: '701928171519344801', @@ -13,7 +13,7 @@ manager.connect().then(async () => { }, { selfdeaf: true }) console.log('Connected') - console.log(song) + console.log(song.title) if (song) { await player.play(song) diff --git a/src/structures/lavacord/Song.js b/src/structures/lavacord/Song.js index cffbb4b..4c6fdf4 100644 --- a/src/structures/lavacord/Song.js +++ b/src/structures/lavacord/Song.js @@ -7,13 +7,14 @@ const TRACK_INFO_VERSION = 2 class Song { constructor (track, info, provider) { this.track = track - this.info = info + this.info = info || {} this.provider = provider try { if (track) { const decodedInfo = this.constructor.decodeTrack(track) console.log(info, decodedInfo) + // TODO: Fix length/position in live streams Utils.compareProperties(info, decodedInfo, { ignoreExtraKeys: true }) this.info = { ...info, ...decodedInfo } } diff --git a/src/structures/providers/spotify/SpotifySong.js b/src/structures/providers/spotify/SpotifySong.js index 6fae039..a703dba 100644 --- a/src/structures/providers/spotify/SpotifySong.js +++ b/src/structures/providers/spotify/SpotifySong.js @@ -17,7 +17,7 @@ class SpotifySong extends Song { } get length () { - return this.info.length + return super.length || this.spotifyTrack.duration_ms } get identifier () { diff --git a/src/structures/providers/tunein/TuneInProvider.js b/src/structures/providers/tunein/TuneInProvider.js index 90e1ebb..e97fcda 100644 --- a/src/structures/providers/tunein/TuneInProvider.js +++ b/src/structures/providers/tunein/TuneInProvider.js @@ -1,14 +1,19 @@ const TuneInAPI = require('../../../apis/TuneIn') +const TuneInSong = require('./TuneInSong') -const TUNEIN_REGEX = /tunein\.com\/radio\/[0-9A-Za-z-_()]*(s\d+)/g +const TUNEIN_REGEX = /^(?:https?:\/\/|)?(?:www\.)?tunein\.com\/radio\/([0-9A-Za-z-_()]*(s\d+))/g class TuneInProvider { static async get (provider, identifier) { const regexResult = TUNEIN_REGEX.exec(identifier) if (regexResult) { - const [, id] = regexResult - const { url } = await TuneInAPI.get(id) - return provider.get(url, true) + const [, path, id] = regexResult + const [{ url }, radioInfo] = await Promise.all([TuneInAPI.get(id), TuneInAPI.fetch(path, id)]) + const { tracks } = await provider.loadTracks(url, 1) + if (tracks && tracks.length) { + const [{ track, info }] = tracks + return new TuneInSong(radioInfo, track, info, provider) + } } } } diff --git a/src/structures/providers/tunein/TuneInSong.js b/src/structures/providers/tunein/TuneInSong.js new file mode 100644 index 0000000..2fad6b9 --- /dev/null +++ b/src/structures/providers/tunein/TuneInSong.js @@ -0,0 +1,23 @@ +const TuneInAPI = require('../../../apis/TuneIn') +const Song = require('../../lavacord/Song') + +class TuneInSong extends Song { + constructor (radioInfo, track, info, provider) { + super(track, info, provider) + this.radioInfo = radioInfo + } + + get title () { + return this.radioInfo.title + } + + get uri () { + return this.radioInfo.shortUrl + } + + get source () { + return 'tunein' + } +} + +module.exports = TuneInSong