From 51f417b916a000b7679b0e99456a2e8c4ef3a2d5 Mon Sep 17 00:00:00 2001 From: Chris Ham Date: Wed, 14 Nov 2018 09:05:45 -0800 Subject: [PATCH 1/7] wip --- fgfm.js | 8 +++ lib/spotify.js | 77 ++++++++++++++++++++++++ package-lock.json | 143 ++++++++++++++++++++++++++++++++++++++++++++ package.json | 3 +- spotify-auth-url.js | 19 ++++++ 5 files changed, 249 insertions(+), 1 deletion(-) create mode 100755 lib/spotify.js create mode 100755 spotify-auth-url.js diff --git a/fgfm.js b/fgfm.js index 8bb2204..81fe2f5 100755 --- a/fgfm.js +++ b/fgfm.js @@ -13,6 +13,7 @@ const GHOBS = require('./lib/ghobs'); const FGFM = require('./lib/fgfm'); const cooldowns = require('./lib/cooldowns'); const util = require('./lib/util'); +const Spotify = require('./lib/spotify'); // Read internal configuration let config = require('./config.json'); @@ -96,6 +97,13 @@ const streamInit = (config, twitch) => { director.on('CREDITS_SHOWN', (secondsUntilEnd) => { twitch.editorChat.say(config.twitch.channel, `Thanks to everyone for watching and lurking! Have a wonderful night and stay comfy. greenhComfy`); }); + + // Spotify integration + const spotify = new Spotify(config.spotify); + spotify.init() + .then(() => { + spotify.getPlaybackState(); + }); // Chat commands const commands = { diff --git a/lib/spotify.js b/lib/spotify.js new file mode 100755 index 0000000..03fe8c9 --- /dev/null +++ b/lib/spotify.js @@ -0,0 +1,77 @@ +var SpotifyWebApi = require('spotify-web-api-node'); + +function Spotify(config) { + // Set up initial state + this.config = config; + + this.credentials = { + clientId: this.config.clientId, + clientSecret: this.config.clientSecret, + redirectUri: this.config.redirectUri + }; + + const spotifyApi = new SpotifyWebApi(this.credentials); + + // The code that's returned as a query parameter to the redirect URI + const code = this.config.userCode; + + this.init = () => { + return new Promise((resolve, reject) => { + // Retrieve an access token and a refresh token + spotifyApi.authorizationCodeGrant(code).then( + function(data) { + console.log('The token expires in ' + data.body['expires_in']); + console.log('The access token is ' + data.body['access_token']); + console.log('The refresh token is ' + data.body['refresh_token']); + + // Set the access token on the API object to use it in later calls + spotifyApi.setAccessToken(data.body['access_token']); + spotifyApi.setRefreshToken(data.body['refresh_token']); + + // clientId, clientSecret and refreshToken has been set on the api object previous to this call. + setInterval(() => { + spotifyApi.refreshAccessToken().then( + function(data) { + console.log('The access token has been refreshed!'); + + // Save the access token so that it's used in future calls + spotifyApi.setAccessToken(data.body['access_token']); + }, + function(err) { + console.log('Could not refresh access token', err); + } + ); + }, data.body['expires_in']*1000); + + resolve(); + }, + function(err) { + console.log('Something went wrong!', JSON.stringify(err)); + reject(err); + } + ); + }); + }; + + this.getMe = () => { + spotifyApi.getMe() + .then(function(data) { + console.log('Some information about the authenticated user', data.body); + }, function(err) { + console.log('Something went wrong!', err); + }); + }; + + this.getPlaybackState = () => { + spotifyApi.getMyCurrentPlaybackState({ + }) + .then(function(data) { + // Output items + console.log("Now Playing: ",data.body); + }, function(err) { + console.log('Something went wrong!', err); + }); + } +} + +module.exports = Spotify; diff --git a/package-lock.json b/package-lock.json index 504393d..551f275 100644 --- a/package-lock.json +++ b/package-lock.json @@ -116,11 +116,29 @@ "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" }, + "combined-stream": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz", + "integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==", + "requires": { + "delayed-stream": "1.0.0" + } + }, "commander": { "version": "2.11.0", "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz", "integrity": "sha1-FXFS/R56bI2YpbcVzzdt+SgARWM=" }, + "component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=" + }, + "cookiejar": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.2.tgz", + "integrity": "sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA==" + }, "core-js": { "version": "2.5.7", "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.7.tgz", @@ -203,6 +221,11 @@ "safer-buffer": "2.1.2" } }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, "extsprintf": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", @@ -237,6 +260,21 @@ "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "requires": { + "asynckit": "0.4.0", + "combined-stream": "1.0.7", + "mime-types": "2.1.21" + } + }, + "formidable": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.1.tgz", + "integrity": "sha512-Fs9VRguL0gqGHkXS5GQiMCr1VhZBxz0JnJs4JmMp/2jL18Fmbzvv7vOFRU+U8TBkHEE/CX1qDXzJplVULgsLeg==" + }, "getpass": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", @@ -316,6 +354,11 @@ "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -388,6 +431,29 @@ "resolved": "https://registry.npmjs.org/memcache/-/memcache-0.3.0.tgz", "integrity": "sha1-vbuXjqS+4P3TFmmXsYg9KX4IWdw=" }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + }, + "mime-db": { + "version": "1.37.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.37.0.tgz", + "integrity": "sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg==" + }, + "mime-types": { + "version": "2.1.21", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.21.tgz", + "integrity": "sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==", + "requires": { + "mime-db": "1.37.0" + } + }, "moment": { "version": "2.22.2", "resolved": "https://registry.npmjs.org/moment/-/moment-2.22.2.tgz", @@ -508,6 +574,11 @@ "resolved": "https://registry.npmjs.org/prism-media/-/prism-media-0.0.3.tgz", "integrity": "sha512-c9KkNifSMU/iXT8FFTaBwBMr+rdVcN+H/uNv1o+CuFeTThNZNTOrQ+RgXA1yL/DeLk098duAeRPP3QNPNbhxYQ==" }, + "process-nextick-args": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" + }, "psl": { "version": "1.1.29", "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.29.tgz", @@ -518,6 +589,25 @@ "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.0", + "safe-buffer": "5.1.2", + "string_decoder": "1.1.1", + "util-deprecate": "1.0.2" + } + }, "ref": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/ref/-/ref-1.3.5.tgz", @@ -643,6 +733,14 @@ "resolved": "https://registry.npmjs.org/sorted-array-functions/-/sorted-array-functions-1.2.0.tgz", "integrity": "sha512-sWpjPhIZJtqO77GN+LD8dDsDKcWZ9GCOJNqKzi1tvtjGIzwfoyuRH8S0psunmc6Z5P+qfDqztSbwYR5X/e1UTg==" }, + "spotify-web-api-node": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/spotify-web-api-node/-/spotify-web-api-node-4.0.0.tgz", + "integrity": "sha512-FQAX4qiP9xfjmJpkSfF5PEVr7RVorUZiLvcdVTlhVFLYAmQ8VSsZlyb0yTK0GExKhAcgJy9GfWxqjSB2r9SrjA==", + "requires": { + "superagent": "3.8.3" + } + }, "sshpk": { "version": "1.14.2", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.2.tgz", @@ -659,6 +757,46 @@ "tweetnacl": "0.14.5" } }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "5.1.2" + } + }, + "superagent": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-3.8.3.tgz", + "integrity": "sha512-GLQtLMCoEIK4eDv6OGtkOoSMt3D+oq0y3dsxMuYuDvaNUvuT8eFBuLmfR0iYYzHC1e8hpzC6ZsxbuP6DIalMFA==", + "requires": { + "component-emitter": "1.2.1", + "cookiejar": "2.1.2", + "debug": "3.2.6", + "extend": "3.0.2", + "form-data": "2.3.3", + "formidable": "1.2.1", + "methods": "1.1.2", + "mime": "1.6.0", + "qs": "6.5.2", + "readable-stream": "2.3.6" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "requires": { + "ms": "2.1.1" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + } + } + }, "tough-cookie": { "version": "2.4.3", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", @@ -682,6 +820,11 @@ "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", "optional": true }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, "uuid": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", diff --git a/package.json b/package.json index 166e1e7..97fa8d8 100755 --- a/package.json +++ b/package.json @@ -16,7 +16,8 @@ "node-opus": "^0.2.9", "node-schedule": "^1.3.0", "obs-websocket-js": "^1.2.0", - "request": "^2.88.0" + "request": "^2.88.0", + "spotify-web-api-node": "^4.0.0" }, "devDependencies": {}, "scripts": { diff --git a/spotify-auth-url.js b/spotify-auth-url.js new file mode 100755 index 0000000..021d642 --- /dev/null +++ b/spotify-auth-url.js @@ -0,0 +1,19 @@ +var SpotifyWebApi = require('spotify-web-api-node'); +let config = require('./config.json'); + +var scopes = ['streaming', 'app-remote-control', 'user-read-currently-playing', 'user-read-playback-state', 'user-modify-playback-state', 'user-read-recently-played', 'playlist-read-collaborative', 'playlist-modify-private', 'playlist-modify-public', 'playlist-read-private'], + redirectUri = 'http://forevergrind.fm/spotify', + clientId = config.spotify.clientId, + state = 'some-state-of-my-choice'; + +// Setting credentials can be done in the wrapper's constructor, or using the API object's setters. +var spotifyApi = new SpotifyWebApi({ + redirectUri: redirectUri, + clientId: clientId +}); + +// Create the authorization URL +var authorizeURL = spotifyApi.createAuthorizeURL(scopes, state); + +// https://accounts.spotify.com:443/authorize?client_id=5fe01282e44241328a84e7c5cc169165&response_type=code&redirect_uri=https://example.com/callback&scope=user-read-private%20user-read-email&state=some-state-of-my-choice +console.log(authorizeURL); \ No newline at end of file From 6683d7f68a00b517163bbf2a2feb5dc4e597e827 Mon Sep 17 00:00:00 2001 From: Chris Ham Date: Fri, 16 Nov 2018 08:51:18 -0800 Subject: [PATCH 2/7] todo --- fgfm.TODO | 1 + 1 file changed, 1 insertion(+) diff --git a/fgfm.TODO b/fgfm.TODO index de95f2d..5cfbf60 100755 --- a/fgfm.TODO +++ b/fgfm.TODO @@ -1,4 +1,5 @@ TODO: + ☐ Don't re-create queue on start if it already exists ☐ Handle socket disconnect ☐ Start/stop stream automation ☐ Support scheduled start/stop From 1603f26aeb87a282763cc514c02c8f44b2f45bcd Mon Sep 17 00:00:00 2001 From: Chris Ham Date: Tue, 4 Dec 2018 09:57:33 -0800 Subject: [PATCH 3/7] todo updates --- fgfm.TODO | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/fgfm.TODO b/fgfm.TODO index 5cfbf60..4134ff3 100755 --- a/fgfm.TODO +++ b/fgfm.TODO @@ -1,11 +1,14 @@ TODO: + ☐ Spotify integration + ☐ !song -- display current song + link + ☐ !skip -- mods auto-skip, voting for regs + ☐ !volume -- volume adjustment + ☐ !pause / !resume + ☐ Web interface for viewers to issue commands + ☐ Admin panel on website for control + ☐ Add support for a command to mute/unmute audio sources ☐ Don't re-create queue on start if it already exists ☐ Handle socket disconnect - ☐ Start/stop stream automation - ☐ Support scheduled start/stop - - instead of countdown for X seconds, use datetime argument - ☐ Start - - Countdown for X minutes is triggered and shown ☐ Decouple twitch chat from GHOBS ☐ Move anything that calls director.state from app into fgfm lib ☐ Restrict # of requests a user can have in the queue at once @@ -25,9 +28,11 @@ TODO: - !weeb ☐ Fix commercial playing issue (switches back to scene early) ☐ Change $auw and $meme to queue up the videos just like the others (don't switch scenes) + ☐ Stats tracking for games + ☐ Most won/lost gambling + ☐ Most trivia answered Ideas: - ☐ Web interface for viewers to issue commands -- twitch extension?! ☐ Support songrequests -- play through discord? StreamWebRemote: From 11655fac86c03e59a215016f6df878063b83c412 Mon Sep 17 00:00:00 2001 From: Chris Ham Date: Tue, 4 Dec 2018 11:55:33 -0800 Subject: [PATCH 4/7] spotify control --- fgfm.TODO | 7 +++--- fgfm.js | 49 +++++++++++++++++++++++++++++++++------ lib/spotify.js | 63 ++++++++++++++++++++++++++++++++++++++++++-------- 3 files changed, 100 insertions(+), 19 deletions(-) diff --git a/fgfm.TODO b/fgfm.TODO index 4134ff3..5f0417f 100755 --- a/fgfm.TODO +++ b/fgfm.TODO @@ -1,9 +1,10 @@ TODO: ☐ Spotify integration - ☐ !song -- display current song + link - ☐ !skip -- mods auto-skip, voting for regs + ✔ !song -- display current song + link @done (18-12-04 11:24) + ✔ !playlist -- display current context / album @done (18-12-04 11:24) + ✔ !skip -- mods auto-skip @done (18-12-04 11:47) ☐ !volume -- volume adjustment - ☐ !pause / !resume + ✔ !pause / !resume @done (18-12-04 11:49) ☐ Web interface for viewers to issue commands ☐ Admin panel on website for control ☐ Add support for a command to mute/unmute audio sources diff --git a/fgfm.js b/fgfm.js index 81fe2f5..a536731 100755 --- a/fgfm.js +++ b/fgfm.js @@ -82,12 +82,16 @@ const streamInit = (config, twitch) => { // Handle show events from the director director.on('SHOW_STARTED', () => { - // Enable vrmode timer + manageTimer('vr', 'on'); + }); + director.on('SHOW_PAUSED', () => { + manageTimer('vr', 'off'); + }); + director.on('SHOW_RESUMED', () => { manageTimer('vr', 'on'); }); director.on('SHOW_ENDING', (secondsUntilCredits) => { - // Disable vrmode timer manageTimer('vr', 'off'); // Let the chat know the stream is ending soon @@ -100,10 +104,7 @@ const streamInit = (config, twitch) => { // Spotify integration const spotify = new Spotify(config.spotify); - spotify.init() - .then(() => { - spotify.getPlaybackState(); - }); + spotify.init(); // Chat commands const commands = { @@ -298,6 +299,25 @@ const streamInit = (config, twitch) => { }, + songskip: (cmd) => { + spotify.skip(); + }, + + songpause: (cmd) => { + spotify.pause(); + }, + + songresume: (cmd) => { + spotify.resume(); + }, + + songvol: (cmd) => { + let volume = parseInt(cmd.args[1]) || 100; + spotify.setVolume(volume) + .then(res => twitch.botChat.say(cmd.to, `Volume set to ${volume}`)) + .catch(err => twitch.botChat.say(cmd.to, `Error setting spotify volume: ${JSON.stringify(err)}`)); + }, + reboot: (cmd) => { console.log('Received request from admin to reboot...'); twitch.botChat.say(cmd.to, 'Rebooting...'); @@ -436,7 +456,6 @@ const streamInit = (config, twitch) => { twitch.botChat.say(cmd.to, snesGames.sort(util.randSort).slice(0, 10).join(' | ')); }, - // voting to skip current video skip: (cmd) => { // check if there is an existing vote to skip for the director.state.currentVideo @@ -453,6 +472,22 @@ const streamInit = (config, twitch) => { skipVote.target = null; } }, + + song: async (cmd) => { + spotify.getCurrentSong() + .then(async song => { + let artists = []; + await util.asyncForEach(song.artists, async (artist) => artists.push(artist.name)); + twitch.botChat.say(cmd.to, `Current Song: ${artists.join(',')} - ${song.name} | ${song.url}`); + }) + .catch(err => twitch.botChat.say(cmd.to, `Error retrieving current song: ${JSON.stringify(err)}`)); + }, + + playlist: (cmd) => { + spotify.getCurrentPlaylist() + .then(playlist => twitch.botChat.say(cmd.to, `Current Playlist: ${playlist}`)) + .catch(err => twitch.botChat.say(cmd.to, `Error retrieving current playlist: ${JSON.stringify(err)}`)); + } } }; diff --git a/lib/spotify.js b/lib/spotify.js index 03fe8c9..b76b8e0 100755 --- a/lib/spotify.js +++ b/lib/spotify.js @@ -62,16 +62,61 @@ function Spotify(config) { }); }; - this.getPlaybackState = () => { - spotifyApi.getMyCurrentPlaybackState({ - }) - .then(function(data) { - // Output items - console.log("Now Playing: ",data.body); - }, function(err) { - console.log('Something went wrong!', err); + this.getCurrentSong = () => { + return new Promise((resolve, reject) => { + spotifyApi.getMyCurrentPlaybackState({}, (err, data) => { + if (err) { + reject(err); + return; + } + + let state = data.body; + resolve({ + artists: state.item.artists, + name: state.item.name, + album: state.item.album.name, + url: state.item.external_urls.spotify + }); + }); }); - } + }; + + this.getCurrentPlaylist = () => { + return new Promise((resolve, reject) => { + spotifyApi.getMyCurrentPlaybackState({}, (err, data) => { + if (err) { + reject(err); + return; + } + + let state = data.body; + if (state.context) { + resolve(state.context.external_urls.spotify); + } else { + resolve(state.item.album.external_urls.spotify); + } + }); + }); + }; + + this.skip = () => { + return spotifyApi.skipToNext(); + }; + + this.pause = () => { + return spotifyApi.pause(); + }; + + this.resume = () => { + return spotifyApi.play(); + }; + + this.setVolume = (volume) => { + volume = parseInt(volume); + if (volume < 0) volume = 0; + if (volume > 100) volume = 100; + return spotifyApi.setVolume(volume); + }; } module.exports = Spotify; From 4b6bb38192582a0787a550e2bad852a2c9975e3d Mon Sep 17 00:00:00 2001 From: Chris Ham Date: Tue, 11 Dec 2018 11:25:15 -0800 Subject: [PATCH 5/7] support playlist changing, shuffle, and repeat mode control --- fgfm.TODO | 18 ++++++++----- fgfm.js | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++ lib/spotify.js | 12 +++++++++ 3 files changed, 93 insertions(+), 7 deletions(-) diff --git a/fgfm.TODO b/fgfm.TODO index 5f0417f..6babb46 100755 --- a/fgfm.TODO +++ b/fgfm.TODO @@ -1,11 +1,15 @@ TODO: ☐ Spotify integration - ✔ !song -- display current song + link @done (18-12-04 11:24) - ✔ !playlist -- display current context / album @done (18-12-04 11:24) - ✔ !skip -- mods auto-skip @done (18-12-04 11:47) - ☐ !volume -- volume adjustment - ✔ !pause / !resume @done (18-12-04 11:49) + ✔ song -- display current song + link @done (18-12-04 11:24) + ✔ playlist -- display current context / album @done (18-12-04 11:24) + ✔ skip -- mods auto-skip @done (18-12-04 11:47) + ✔ volume -- volume adjustment @done (18-12-04 19:49) + ✔ pause / resume @done (18-12-04 11:49) + ✔ ability to change the playlist (setplaylist ) @done (18-12-11 10:52) + ✔ shuffle on/off @done (18-12-11 11:22) + ✔ repeat mode control @done (18-12-11 11:22) ☐ Web interface for viewers to issue commands + ☐ Organized room / video list, one-click add-to-queue ☐ Admin panel on website for control ☐ Add support for a command to mute/unmute audio sources ☐ Don't re-create queue on start if it already exists @@ -32,9 +36,9 @@ TODO: ☐ Stats tracking for games ☐ Most won/lost gambling ☐ Most trivia answered + ☐ Allow %'s for !gamble -Ideas: - ☐ Support songrequests -- play through discord? +Ideas: StreamWebRemote: - OBS Websocket Connection Mgmt diff --git a/fgfm.js b/fgfm.js index a536731..af8a10c 100755 --- a/fgfm.js +++ b/fgfm.js @@ -318,6 +318,76 @@ const streamInit = (config, twitch) => { .catch(err => twitch.botChat.say(cmd.to, `Error setting spotify volume: ${JSON.stringify(err)}`)); }, + songplay: (cmd) => { + let url = cmd.args[1] || false; + if (url === false) { + return twitch.botChat.say(cmd.to, `You must provide a link to a spotify playlist or album!`); + } + + // parse+validate url + let spotifyUri = false; + + // check for native spotify URI first + if (url.includes('spotify:')) { + let parsedUrl = url.match(/spotify:(playlist|album):([A-Za-z0-9]{22})/); + if (parsedUrl !== null) { + spotifyUri = parsedUrl[0]; + } + } else if (url.includes('spotify.com')) { + // determine if it's an album or playlist + if (!url.includes('/playlist/') && !url.includes('/album/')) { + return twitch.botChat.say(cmd.to, `Spotify URL must be a playlist or album!`); + } + + // parse the URL to get the resource type and ID + let parsedUrl = url.match(/(playlist|album)\/([A-Za-z0-9]{22})/); + if (parsedUrl !== null) { + spotifyUri = `spotify:${parsedUrl[1]}:${parsedUrl[2]}`; + } else { + return twitch.botChat.say(cmd.to, `Unable to parse spotify URL!`); + } + } else { + return twitch.botChat.say(cmd.to, `Invalid spotify URL!`); + } + + if (spotifyUri !== false) { + spotify.playContext(spotifyUri) + .then(res => twitch.botChat.say(cmd.to, `Changed playlist!`)) + .catch(err => twitch.botChat.say(cmd.to, `Error changing playlist: ${JSON.stringify(err)}`)); + } else { + return twitch.botChat.say(cmd.to, `Unable to parse Spotify URL!`); + } + }, + + songshuffle: (cmd) => { + let state = cmd.args[1] || true; + + if (state === 'off' || state === 'false') { + state = false; + } else { + state = true; + } + + spotify.shuffle(state) + .then(res => twitch.botChat.say(cmd.to, `Updated shuffle state!`)) + .catch(err => twitch.botChat.say(cmd.to, `Error changing shuffle state: ${JSON.stringify(err)}`)) + }, + + songrepeat: (cmd) => { + let state = cmd.args[1] || false; + if (state === false) { + return twitch.botChat.say(cmd.to, `You must provide a repeat mode (track, context, or off)!`); + } + + if (!['track', 'context', 'off'].includes(state)) { + return twitch.botChat.say(cmd.to, `You must provide a valid repeat mode (track, context, or off)!`); + } + + spotify.repeat(state) + .then(res => twitch.botChat.say(cmd.to, `Updated repeat mode!`)) + .catch(err => twitch.botChat.say(cmd.to, `Error changing repeat mode: ${JSON.stringify(err)}`)) + }, + reboot: (cmd) => { console.log('Received request from admin to reboot...'); twitch.botChat.say(cmd.to, 'Rebooting...'); diff --git a/lib/spotify.js b/lib/spotify.js index b76b8e0..031817e 100755 --- a/lib/spotify.js +++ b/lib/spotify.js @@ -99,6 +99,10 @@ function Spotify(config) { }); }; + this.playContext = (uri) => { + return spotifyApi.play({"context_uri": uri}); + }; + this.skip = () => { return spotifyApi.skipToNext(); }; @@ -117,6 +121,14 @@ function Spotify(config) { if (volume > 100) volume = 100; return spotifyApi.setVolume(volume); }; + + this.shuffle = (state) => { + return spotifyApi.setShuffle({"state": state}); + }; + + this.repeat = (state) => { + return spotifyApi.setRepeat({"state": state}); + }; } module.exports = Spotify; From fd6ea2f926cb5e477de8c358e9734134695edeb2 Mon Sep 17 00:00:00 2001 From: Chris Ham Date: Tue, 11 Dec 2018 11:29:36 -0800 Subject: [PATCH 6/7] remove vod config from repo, local only now --- conf/vods.json | 720 ------------------------------------------------- 1 file changed, 720 deletions(-) delete mode 100755 conf/vods.json diff --git a/conf/vods.json b/conf/vods.json deleted file mode 100755 index 79f9075..0000000 --- a/conf/vods.json +++ /dev/null @@ -1,720 +0,0 @@ -{ - "alttp": [ - { - "id": "ttas", - "category": "Optimal TTAS", - "label": "Any% NMG Optimal Theory TAS [1:20:38.72]", - "name": "OTTAS Full", - "chatName": "OTTAS Full", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\Risky\\full-risky-raw.mp4", - "sceneItem": "16x9ph", - "length": 4843, - "includeInShuffle": true - }, - { - "id": "ot-seg-escape", - "category": "Optimal TTAS Segment", - "label": false, - "name": "Escape (OTTAS Seg)", - "chatName": "Escape (OTTAS Seg)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\Risky\\final-versions-timed\\01-escape.mp4", - "sceneItem": "4x3ph", - "length": 352, - "includeInShuffle": true - }, - { - "id": "ot-seg-eastern", - "category": "Optimal TTAS Segment", - "label": false, - "name": "Eastern (OTTAS Seg)", - "chatName": "Eastern (OTTAS Seg)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\Risky\\final-versions-timed\\02-eastern.mp4", - "sceneItem": "4x3ph", - "length": 277, - "includeInShuffle": true - }, - { - "id": "ot-seg-desert", - "category": "Optimal TTAS Segment", - "label": false, - "name": "Desert (OTTAS Seg)", - "chatName": "Desert (OTTAS Seg)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\Risky\\final-versions-timed\\03-desert.mp4", - "sceneItem": "4x3ph", - "length": 345, - "includeInShuffle": true - }, - { - "id": "ot-seg-hera", - "category": "Optimal TTAS Segment", - "label": false, - "name": "Hera (OTTAS Seg)", - "chatName": "Hera (OTTAS Seg)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\Risky\\final-versions-timed\\04-hera.mp4", - "sceneItem": "4x3ph", - "length": 299, - "includeInShuffle": true - }, - { - "id": "ot-seg-atower", - "category": "Optimal TTAS Segment", - "label": false, - "name": "ATower (OTTAS Seg)", - "chatName": "ATower (OTTAS Seg)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\Risky\\final-versions-timed\\05-atower.mp4", - "sceneItem": "4x3ph", - "length": 352, - "includeInShuffle": true - }, - { - "id": "ot-seg-pod", - "category": "Optimal TTAS Segment", - "label": false, - "name": "PoD (OTTAS Seg)", - "chatName": "PoD (OTTAS Seg)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\Risky\\final-versions-timed\\06-pod.mp4", - "sceneItem": "4x3ph", - "length": 309, - "includeInShuffle": true - }, - { - "id": "ot-seg-thieves", - "category": "Optimal TTAS Segment", - "label": false, - "name": "Thieves (OTTAS Seg)", - "chatName": "Thieves (OTTAS Seg)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\Risky\\final-versions-timed\\07-thieves.mp4", - "sceneItem": "4x3ph", - "length": 377, - "includeInShuffle": true - }, - { - "id": "ot-seg-skull", - "category": "Optimal TTAS Segment", - "label": false, - "name": "Skull (OTTAS Seg)", - "chatName": "Skull (OTTAS Seg)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\Risky\\final-versions-timed\\08-skull.mp4", - "sceneItem": "4x3ph", - "length": 267, - "includeInShuffle": true - }, - { - "id": "ot-seg-ice", - "category": "Optimal TTAS Segment", - "label": false, - "name": "Ice (OTTAS Seg)", - "chatName": "Ice (OTTAS Seg)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\Risky\\final-versions-timed\\09-ice.mp4", - "sceneItem": "4x3ph", - "length": 318, - "includeInShuffle": true - }, - { - "id": "ot-seg-swamp", - "category": "Optimal TTAS Segment", - "label": false, - "name": "Swamp (OTTAS Seg)", - "chatName": "Swamp (OTTAS Seg)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\Risky\\final-versions-timed\\10-swamp.mp4", - "sceneItem": "4x3ph", - "length": 345, - "includeInShuffle": true - }, - { - "id": "ot-seg-mire", - "category": "Optimal TTAS Segment", - "label": false, - "name": "Mire (OTTAS Seg)", - "chatName": "Mire (OTTAS Seg)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\Risky\\final-versions-timed\\11-mire.mp4", - "sceneItem": "4x3ph", - "length": 365, - "includeInShuffle": true - }, - { - "id": "ot-seg-trock", - "category": "Optimal TTAS Segment", - "label": false, - "name": "TRock (OTTAS Seg)", - "chatName": "TRock (OTTAS Seg)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\Risky\\final-versions-timed\\12-trock.mp4", - "sceneItem": "4x3ph", - "length": 361, - "includeInShuffle": true - }, - { - "id": "ot-seg-gtower", - "category": "Optimal TTAS Segment", - "label": false, - "name": "GTower (OTTAS Seg)", - "chatName": "GTower (OTTAS Seg)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\Risky\\final-versions-timed\\13-gtower.mp4", - "sceneItem": "4x3ph", - "length": 396, - "includeInShuffle": true - }, - { - "id": "ot-seg-ganon", - "category": "Optimal TTAS Segment", - "label": false, - "name": "Ganon (TTAS Seg)", - "chatName": "Ganon (TTAS Seg)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\Risky\\final-versions-timed\\14-ganon.mp4", - "sceneItem": "4x3ph", - "length": 108, - "includeInShuffle": true - }, - { - "id": "rta-ttas", - "category": "Safe TTAS", - "label": "Any% NMG Safe/RTA Theory TAS [1:21:26.52]", - "name": "STTAS Full", - "chatName": "STTAS Full", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\RTA\\full-rta-raw.mp4", - "sceneItem": "16x9ph", - "length": 4892, - "includeInShuffle": true - }, - { - "id": "st-seg-escape", - "category": "Safe TTAS Segment", - "label": false, - "name": "Escape (STTAS Seg)", - "chatName": "Escape (STTAS Seg)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\RTA\\final-versions-timed\\01-escape.mp4", - "sceneItem": "4x3ph", - "length": 354, - "includeInShuffle": true - }, - { - "id": "st-seg-eastern", - "category": "Safe TTAS Segment", - "label": false, - "name": "Eastern (STTAS Seg)", - "chatName": "Eastern (STTAS Seg)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\RTA\\final-versions-timed\\02-eastern.mp4", - "sceneItem": "4x3ph", - "length": 281, - "includeInShuffle": true - }, - { - "id": "st-seg-desert", - "category": "Safe TTAS Segment", - "label": false, - "name": "Desert (STTAS Seg)", - "chatName": "Desert (STTAS Seg)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\RTA\\final-versions-timed\\03-desert.mp4", - "sceneItem": "4x3ph", - "length": 347, - "includeInShuffle": true - }, - { - "id": "st-seg-hera", - "category": "Safe TTAS Segment", - "label": false, - "name": "Hera (STTAS Seg)", - "chatName": "Hera (STTAS Seg)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\RTA\\final-versions-timed\\04-hera.mp4", - "sceneItem": "4x3ph", - "length": 303, - "includeInShuffle": true - }, - { - "id": "st-seg-atower", - "category": "Safe TTAS Segment", - "label": false, - "name": "Agah Tower (STTAS Seg)", - "chatName": "Agah Tower (STTAS Seg)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\RTA\\final-versions-timed\\05-atower.mp4", - "sceneItem": "4x3ph", - "length": 354, - "includeInShuffle": true - }, - { - "id": "st-seg-pod", - "category": "Safe TTAS Segment", - "label": false, - "name": "PoD (STTAS Seg)", - "chatName": "PoD (STTAS Seg)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\RTA\\final-versions-timed\\06-pod.mp4", - "sceneItem": "4x3ph", - "length": 310, - "includeInShuffle": true - }, - { - "id": "st-seg-thieves", - "category": "Safe TTAS Segment", - "label": false, - "name": "Thieves (STTAS Seg)", - "chatName": "Thieves (STTAS Seg)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\RTA\\final-versions-timed\\07-thieves.mp4", - "sceneItem": "4x3ph", - "length": 379, - "includeInShuffle": true - }, - { - "id": "st-seg-skull", - "category": "Safe TTAS Segment", - "label": false, - "name": "Skull (STTAS Seg)", - "chatName": "Skull (STTAS Seg)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\RTA\\final-versions-timed\\08-skull.mp4", - "sceneItem": "4x3ph", - "length": 270, - "includeInShuffle": true - }, - { - "id": "st-seg-ice", - "category": "Safe TTAS Segment", - "label": false, - "name": "Ice (STTAS Seg)", - "chatName": "Ice (STTAS Seg)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\RTA\\final-versions-timed\\09-ice.mp4", - "sceneItem": "4x3ph", - "length": 321, - "includeInShuffle": true - }, - { - "id": "st-seg-swamp", - "category": "Safe TTAS Segment", - "label": false, - "name": "Swamp (STTAS Seg)", - "chatName": "Swamp (STTAS Seg)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\RTA\\final-versions-timed\\10-swamp.mp4", - "sceneItem": "4x3ph", - "length": 351, - "includeInShuffle": true - }, - { - "id": "st-seg-mire", - "category": "Safe TTAS Segment", - "label": false, - "name": "Mire (STTAS Seg)", - "chatName": "Mire (STTAS Seg)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\RTA\\final-versions-timed\\11-mire.mp4", - "sceneItem": "4x3ph", - "length": 370, - "includeInShuffle": true - }, - { - "id": "st-seg-trock", - "category": "Safe TTAS Segment", - "label": false, - "name": "TRock (STTAS Seg)", - "chatName": "TRock (STTAS Seg)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\RTA\\final-versions-timed\\12-trock.mp4", - "sceneItem": "4x3ph", - "length": 364, - "includeInShuffle": true - }, - { - "id": "st-seg-gtower", - "category": "Safe TTAS Segment", - "label": false, - "name": "GTower (STTAS Seg)", - "chatName": "GTower (STTAS Seg)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\RTA\\final-versions-timed\\13-gtower.mp4", - "sceneItem": "4x3ph", - "length": 408, - "includeInShuffle": true - }, - { - "id": "nmg-gold-escape", - "category": "Any% NMG Gold Segment", - "label": "Any% NMG Gold Segment: Escape (5:52.43) [2018-09-19]", - "name": "Escape (NMG Gold)", - "chatName": "Escape (NMG Gold)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\gold-segments\\any%-nmg-nsq\\720p\\01-[552.43]-2018-09-19-escape.mp4", - "sceneItem": "4x3ph", - "length": 360, - "includeInShuffle": true - }, - { - "id": "nmg-gold-eastern", - "category": "Any% NMG Gold Segment", - "label": "Any% NMG Gold Segment: Eastern (5:00.12) [2018-06-25]", - "name": "Eastern (NMG Gold)", - "chatName": "Eastern (NMG Gold)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\gold-segments\\any%-nmg-nsq\\720p\\timed\\02-[500.12]-2018-06-25-eastern.mp4", - "sceneItem": "4x3ph", - "length": 302, - "includeInShuffle": true - }, - { - "id": "nmg-gold-desert", - "category": "Any% NMG Gold Segment", - "label": "Any% NMG Gold Segment: Desert (6:10.32) [2018-06-02]", - "name": "Desert (NMG Gold)", - "chatName": "Desert (NMG Gold)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\gold-segments\\any%-nmg-nsq\\720p\\timed\\03-[610.32]-2018-06-02-desert.mp4", - "sceneItem": "4x3ph", - "length": 374, - "includeInShuffle": true - }, - { - "id": "nmg-gold-hera", - "category": "Any% NMG Gold Segment", - "label": "Any% NMG Gold Segment: Hera (5:28.83) [2018-06-01]", - "name": "Hera (NMG Gold)", - "chatName": "Hera (NMG Gold)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\gold-segments\\any%-nmg-nsq\\720p\\timed\\04-[528.83]-2018-06-01-hera.mp4", - "sceneItem": "4x3ph", - "length": 332, - "includeInShuffle": true - }, - { - "id": "nmg-gold-atower", - "category": "Any% NMG Gold Segment", - "label": "Any% NMG Gold Segment: Agah Tower (5:13.45) [2018-09-07]", - "name": "Agah Tower (NMG Gold)", - "chatName": "Agah Tower (NMG Gold)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\gold-segments\\any%-nmg-nsq\\720p\\timed\\05a-[513.45]-2018-09-07-atower.mp4", - "sceneItem": "4x3ph", - "length": 314, - "includeInShuffle": true - }, - { - "id": "nmg-gold-pod", - "category": "Any% NMG Gold Segment", - "label": "Any% NMG Gold Segment: Palace of Darkness (6:11.15) [2018-05-27]", - "name": "PoD (NMG Gold)", - "chatName": "PoD (NMG Gold)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\gold-segments\\any%-nmg-nsq\\720p\\timed\\06-[611.15]-2018-05-27-pod.mp4", - "sceneItem": "4x3ph", - "length": 376, - "includeInShuffle": true - }, - { - "id": "nmg-gold-thieves", - "category": "Any% NMG Gold Segment", - "label": "Any% NMG Gold Segment: Thieves Town (7:08.37) [2018-07-01]", - "name": "Thieves (NMG Gold)", - "chatName": "Thieves (NMG Gold)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\gold-segments\\any%-nmg-nsq\\720p\\timed\\07-[708.37]-2018-07-01-thieves.mp4", - "sceneItem": "4x3ph", - "length": 432, - "includeInShuffle": true - }, - { - "id": "nmg-gold-skull", - "category": "Any% NMG Gold Segment", - "label": "Any% NMG Gold Segment: Skull Woods (5:24.28) [2018-06-02]", - "name": "Skull (NMG Gold)", - "chatName": "Skull (NMG Gold)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\gold-segments\\any%-nmg-nsq\\720p\\timed\\08-[524.28]-2018-06-02-skull.mp4", - "sceneItem": "4x3ph", - "length": 328, - "includeInShuffle": true - }, - { - "id": "nmg-gold-ice", - "category": "Any% NMG Gold Segment", - "label": "Any% NMG Gold Segment: Ice Palace (6:18.13) [2018-09-08]", - "name": "Ice (NMG Gold)", - "chatName": "Ice (NMG Gold)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\gold-segments\\any%-nmg-nsq\\720p\\timed\\09-[618.13]-2018-09-08-ice.mp4", - "sceneItem": "4x3ph", - "length": 379, - "includeInShuffle": true - }, - { - "id": "nmg-gold-swamp", - "category": "Any% NMG Gold Segment", - "label": "Any% NMG Gold Segment: Swamp Palace (6:51.87) [2018-06-10]", - "name": "Swamp (NMG Gold)", - "chatName": "Swamp (NMG Gold)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\gold-segments\\any%-nmg-nsq\\720p\\timed\\10-[651.87]-2018-06-10-swamp.mp4", - "sceneItem": "4x3ph", - "length": 418, - "includeInShuffle": true - }, - { - "id": "nmg-gold-mire", - "category": "Any% NMG Gold Segment", - "label": "Any% NMG Gold Segment: Misery Mire (7:06.58) [2018-10-21]", - "name": "Mire (NMG Gold)", - "chatName": "Mire (NMG Gold)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\gold-segments\\any%-nmg-nsq\\720p\\11-[706.58]-2018-10-21-mire.mp4", - "sceneItem": "4x3ph", - "length": 430, - "includeInShuffle": true - }, - { - "id": "nmg-gold-trock", - "category": "Any% NMG Gold Segment", - "label": "Any% NMG Gold Segment: Turtle Rock (7:07.34) [2018-06-25]", - "name": "TRock (NMG Gold)", - "chatName": "TRock (NMG Gold)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\gold-segments\\any%-nmg-nsq\\720p\\timed\\12-[707.34]-2018-06-25-trock.mp4", - "sceneItem": "4x3ph", - "length": 434, - "includeInShuffle": true - }, - { - "id": "nmg-gold-gtower", - "category": "Any% NMG Gold Segment", - "label": "Any% NMG Gold Segment: Ganon's Tower (7:09.85) [2018-08-19]", - "name": "GTower (NMG Gold)", - "chatName": "GTower (NMG Gold)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\gold-segments\\any%-nmg-nsq\\720p\\timed\\13-[709.85]-2018-08-19-gtower.mp4", - "sceneItem": "4x3ph", - "length": 433, - "includeInShuffle": true - }, - { - "id": "nmg-gold-ganon", - "category": "Any% NMG Gold Segment", - "label": "Any% NMG Gold Segment: Ganon (1:43.06) [2018-05-30]", - "name": "Ganon (NMG Gold)", - "chatName": "Ganon (NMG Gold)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\gold-segments\\any%-nmg-nsq\\720p\\timed\\14-[143.06]-2018-05-30-ganon.mp4", - "sceneItem": "4x3ph", - "length": 117, - "includeInShuffle": true - }, - { - "id": "pb-100-ahp", - "category": "Personal Best", - "label": "Personal Best: 100% All Heart Pieces (1:19:12) [2017-12-22]", - "name": "100% MG AHP (PB)", - "chatName": "100% MG AHP (PB)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\personal-bests\\100%-mg-ahp\\2017-12-22-100mg-11912.mp4", - "sceneItem": "16x9ph", - "length": 4786, - "includeInShuffle": true - }, - { - "id": "pb-ab", - "category": "Personal Best", - "label": "Personal Best: All Bosses No EG (1:09:23) [2017-11-20]", - "name": "All Bosses (PB)", - "chatName": "All Bosses (PB)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\personal-bests\\all-bosses\\2017-11-20-ab-10923.mp4", - "sceneItem": "16x9ph", - "length": 4200, - "includeInShuffle": true - }, - { - "id": "pb-ad", - "category": "Personal Best", - "label": "Personal Best: All Dungeons No EG/DG/WW (1:14:59) [2017-11-19]", - "name": "All Dungeons (PB)", - "chatName": "All Dungeons (PB)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\personal-bests\\all-dungeons\\2017-11-19-ad-11459.mp4", - "sceneItem": "16x9ph", - "length": 4555, - "includeInShuffle": true - }, - { - "id": "pb-any-nmg", - "category": "Personal Best", - "label": "Personal Best: Any% NMG No S+Q (1:26:24) [2018-05-27]", - "name": "Any% NMG (PB)", - "chatName": "Any% NMG (PB)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\personal-bests\\any%-nmg-nsq\\2018-05-27-nmg-12624.mp4", - "sceneItem": "16x9ph", - "length": 5190, - "includeInShuffle": true - }, - { - "id": "pb-any-no-eg", - "category": "Personal Best", - "label": "Personal Best: Any% No EG (29:05) [2018-10-14]", - "name": "Any% No EG (PB)", - "chatName": "Any% No EG (PB)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\personal-bests\\any%-no-eg\\2018-10-14-no-eg-2905.mp4", - "sceneItem": "16x9ph", - "length": 1777, - "includeInShuffle": true - }, - { - "id": "pb-master-sword", - "category": "Personal Best", - "label": "Personal Best: Master Sword NMG (22:23) [2018-08-23]", - "name": "Master Sword (PB)", - "chatName": "Master Sword (PB)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\personal-bests\\master-sword\\2018-08-23-master-sword-2223.mp4", - "sceneItem": "16x9ph", - "length": 1409, - "includeInShuffle": true - }, - { - "id": "pb-ms", - "category": "Personal Best", - "label": "Personal Best: Mirror Shield NMG (50:32) [2017-06-20]", - "name": "Mirror Shield (PB)", - "chatName": "Mirror Shield (PB)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\personal-bests\\mirror-shield\\2017-06-20-mirror-shield-5032.mp4", - "sceneItem": "16x9ph", - "length": 3068, - "includeInShuffle": true - }, - { - "id": "pb-ms-no-eg", - "category": "Personal Best", - "label": "Personal Best: Mirror Shield No EG (11:38) [2017-07-02]", - "name": "Mirror Shield No EG (PB)", - "chatName": "Mirror Shield No EG (PB)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\personal-bests\\mirror-shield-no-eg\\2017-07-02-mirror-shield-no-eg-1138-resized.mp4", - "sceneItem": "4x3ph", - "length": 738, - "includeInShuffle": true - }, - { - "id": "pb-rbo", - "category": "Personal Best", - "label": "Personal Best: Reverse Boss Order (1:18:13) [2017-12-01]", - "name": "Reverse Boss Order (PB)", - "chatName": "Reverse Boss Order (PB)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\personal-bests\\rbo\\2017-12-01-rbo-11813.mp4", - "sceneItem": "16x9ph", - "length": 4725, - "includeInShuffle": true - }, - { - "id": "pb-100-nmg", - "category": "Personal Best", - "label": "Personal Best: 100% NMG (1:49:30) [2017-01-31]", - "name": "100% NMG (PB)", - "chatName": "100% NMG (PB)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\personal-bests\\100%\\2017-01-31-hundo-14930.mp4", - "sceneItem": "legacyph", - "length": 6598, - "includeInShuffle": false - }, - { - "id": "pb-dg", - "category": "Personal Best", - "label": "Personal Best: Defeat Ganon (13:41) [2017-02-20]", - "name": "Defeat Ganon (PB)", - "chatName": "Defeat Ganon (PB)", - "filePath": "Y:\\media\\videos\\ALttP\\my-vids\\personal-bests\\defeat-ganon\\2017-02-20_12-55-40-defeat-ganon-1341.mp4", - "sceneItem": "legacyph", - "length": 868, - "includeInShuffle": true - } - ], - "memes": [ - { - "id": "auw", - "name": "auw", - "filePath": "Y:\\media\\videos\\black-box-everybody-wow.mp4", - "sceneItem": "everybody-wow", - "length": 247, - "includeInShuffle": true - }, - { - "id": "archery", - "name": "archery", - "filePath": "Y:\\media\\videos\\ALttP\\memes\\archery-contest.mp4", - "sceneItem": "meme1", - "length": 27, - "includeInShuffle": true - }, - { - "id": "69blazeit", - "name": "69blazeit", - "filePath": "Y:\\media\\videos\\ALttP\\memes\\69BlazeIt.mp4", - "sceneItem": "meme1", - "length": 92, - "includeInShuffle": true - }, - { - "id": "rpgfarm", - "name": "rpgfarm", - "filePath": "Y:\\media\\videos\\ALttP\\memes\\2016-07-24-1424-06-rpg-race-sniped.mp4", - "sceneItem": "meme1", - "length": 144, - "includeInShuffle": true - }, - { - "id": "emmapeg", - "name": "emmapeg", - "filePath": "Y:\\media\\videos\\ALttP\\memes\\emma-pegging.mp4", - "sceneItem": "meme1", - "length": 8, - "includeInShuffle": true - }, - { - "id": "handy", - "name": "handy", - "filePath": "Y:\\media\\videos\\ALttP\\memes\\handy-in-the-mothhole.mp4", - "sceneItem": "meme1", - "length": 39, - "includeInShuffle": true - }, - { - "id": "bodyguard", - "name": "bodyguard", - "filePath": "Y:\\media\\videos\\ALttP\\memes\\heroic-popo.mp4", - "sceneItem": "meme1", - "length": 14, - "includeInShuffle": true - }, - { - "id": "whowillitbe", - "name": "whowillitbe", - "filePath": "Y:\\media\\videos\\ALttP\\memes\\its-gonna-be-may.mp4", - "sceneItem": "meme1", - "length": 30, - "includeInShuffle": true - }, - { - "id": "mindblown", - "name": "mindblown", - "filePath": "Y:\\media\\videos\\ALttP\\memes\\mindblowing-and-lifechanging.mp4", - "sceneItem": "meme1", - "length": 55, - "includeInShuffle": true - }, - { - "id": "nerd-nookie", - "name": "nerd-nookie", - "filePath": "Y:\\media\\videos\\ALttP\\memes\\nerd-bizkit.mp4", - "sceneItem": "meme1", - "length": 27, - "includeInShuffle": true - }, - { - "id": "curling", - "name": "curling", - "filePath": "Y:\\media\\videos\\ALttP\\curling-bored-janitors.mp4", - "sceneItem": "meme1", - "length": 16, - "includeInShuffle": true - }, - { - "id": "airplane", - "name": "airplane", - "filePath": "Y:\\media\\videos\\ALttP\\emetaPlane.mp4", - "sceneItem": "meme1", - "length": 5, - "includeInShuffle": true - }, - { - "id": "hard-things", - "name": "hard-things", - "filePath": "Y:\\media\\videos\\ALttP\\questions-about-hard-things.mp4", - "sceneItem": "meme1", - "length": 39, - "includeInShuffle": true - }, - { - "id": "18arrows", - "name": "18arrows", - "filePath": "Y:\\media\\videos\\ALttP\\screevo-18-arrows-fine.mp4", - "sceneItem": "meme1", - "length": 41, - "includeInShuffle": true - }, - { - "id": "quake", - "name": "quake", - "filePath": "Y:\\media\\videos\\ALttP\\trock-indoor-quake.mp4", - "sceneItem": "meme1", - "length": 17, - "includeInShuffle": true - } - ] -} \ No newline at end of file From 22eba12c0c95f0eb3591c552b13cde994f1cb4ad Mon Sep 17 00:00:00 2001 From: Chris Ham Date: Tue, 11 Dec 2018 11:30:24 -0800 Subject: [PATCH 7/7] ignore vod config --- .gitignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index ba99aff..fb6916d 100755 --- a/.gitignore +++ b/.gitignore @@ -53,4 +53,6 @@ logs/* start.bat tokens.json -config.json \ No newline at end of file +config.json + +conf/vods.json \ No newline at end of file