diff --git a/worker/package.json b/worker/package.json index ae9b0a6..20a4e97 100644 --- a/worker/package.json +++ b/worker/package.json @@ -22,6 +22,7 @@ "@googleapis/youtube": "^2.0.0", "@octokit/rest": "^18.12.0", "@octokit/webhooks": "^9.22.0", + "@types/spotify-web-api-node": "^5.0.7", "discord-api-types": "^0.27.2", "discord.js": "^13.6.0", "express": "^4.17.2", @@ -30,7 +31,7 @@ "graphql-request": "^4.0.0", "node-fetch": "^3.2.0", "rxjs": "^7.5.2", - "spotify-web-api-js": "^1.5.2", + "spotify-web-api-node": "^5.0.2", "twitter-api-v2": "^1.10.0" }, "devDependencies": { diff --git a/worker/src/actions.ts b/worker/src/actions.ts index c2ec8fa..af672ab 100644 --- a/worker/src/actions.ts +++ b/worker/src/actions.ts @@ -32,6 +32,7 @@ export class Manager { } catch (err) { this.handlePipelineError(x, err); } + console.log(`Pipeline finished ${x.name}`) }), )); } @@ -55,7 +56,10 @@ export class Manager { console.error(`Unhandled exception while trying to listen for the pipeline ${pipeline.name} (type: ${pipeline.type?.toString()}).`, error) fetch(`${process.env["WORKER_API_URL"]}/error/${pipeline.id}?WORKER_API_KEY=${process.env["WORKER_API_KEY"]}`, { method: "POST", - body: JSON.stringify({error}), + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({error: error.toString()}), }); return NEVER; } diff --git a/worker/src/services/discord.ts b/worker/src/services/discord.ts index 62d487f..e1b9f9e 100644 --- a/worker/src/services/discord.ts +++ b/worker/src/services/discord.ts @@ -110,7 +110,7 @@ export class Discord extends BaseService { @reaction(ReactionType.LeaveDiscordServer, ['server_id']) async leaveServer(params :any): Promise { let guild = await this._client.guilds.fetch(params['server_id']); - guild.leave(); + await guild.leave(); return { SERVER_NAME: guild.name, SERVER_ID: guild.id, diff --git a/worker/src/services/spotify.ts b/worker/src/services/spotify.ts index e1120ae..8be3082 100644 --- a/worker/src/services/spotify.ts +++ b/worker/src/services/spotify.ts @@ -1,4 +1,4 @@ -import SpotifyWebApi from 'spotify-web-api-js'; +import SpotifyWebApi from 'spotify-web-api-node'; import { action, BaseService, reaction, service } from "../models/base-service"; import { Observable } from 'rxjs'; import { Utils } from '../utils'; @@ -9,17 +9,28 @@ export class Spotify extends BaseService { private _spotify; - constructor(_: Pipeline) { + constructor(pipeline: Pipeline) { super(); - // TODO load credentials - this._spotify = new SpotifyWebApi(); + if (!("Spotify" in pipeline.userData)) + throw new Error("User is not authenticated via Spotify"); + this._spotify = new SpotifyWebApi({ + accessToken: pipeline.userData["Spotify"].accessToken, + refreshToken: pipeline.userData["Spotify"].refreshToken, + clientId: process.env["SPOTIFY_CLIENT_ID"], + clientSecret: process.env["SPOTIFY_SECRET"], + }); + } + + private async _refreshIfNeeded(): Promise { + } @action(PipelineType.OnSpotifyAddToPlaylist, ["playlistId"]) listenAddToPlaylist(params: any): Observable { return Utils.longPulling(async since => { + await this._refreshIfNeeded(); let ret = await this._spotify.getPlaylistTracks(params.playlistId); - return ret.items + return ret.body.items .filter(x => new Date(x.added_at) >= since) .map(x => ({ ID: x.track.id, @@ -31,8 +42,9 @@ export class Spotify extends BaseService { @action(PipelineType.OnSpotifySaveToLibrary, []) listenSaveToLibrary(_: any): Observable { return Utils.longPulling(async since => { + await this._refreshIfNeeded(); let ret = await this._spotify.getMySavedTracks(); - return ret.items + return ret.body.items .filter(x => new Date(x.added_at) >= since) .map(x => ({ ID: x.track.id, @@ -42,23 +54,26 @@ export class Spotify extends BaseService { } private async _searchTrack(artistName: string, trackName: string) { + await this._refreshIfNeeded(); let searchResult = await this._spotify.searchTracks(`artist=${artistName}&track=${trackName}`); - if (searchResult.tracks.total == 0) + if (searchResult.body.tracks.total == 0) throw new Error(`Spotify API: '${trackName}' by ${artistName}: no such track`); - return searchResult.tracks.items[0]; + return searchResult.body.tracks.items[0]; } private async _searchPlaylist(playlistName: string) { + await this._refreshIfNeeded(); let searchResult = await this._spotify.searchPlaylists(`name=${playlistName}&type=playlist`); - if (searchResult.playlists.total == 0) + if (searchResult.body.playlists.total == 0) throw new Error(`Spotify API: '${playlistName}': no such playlist`); - return searchResult.playlists.items[0]; + return searchResult.body.playlists.items[0]; } @reaction(ReactionType.PlayTrack, ['artist', 'track']) async playTrack(params: any): Promise { + await this._refreshIfNeeded(); let track = await this._searchTrack(params['artist'], params['track']); - this._spotify.play({uris: [track.uri]}); + await this._spotify.play({uris: [track.uri]}); return { URL: track.uri, ARTIST: track.artists?.[0].name, @@ -68,14 +83,16 @@ export class Spotify extends BaseService { @reaction(ReactionType.Pause, []) async pause(params: any): Promise { - this._spotify.pause(); + await this._refreshIfNeeded(); + await this._spotify.pause(); return {}; } @reaction(ReactionType.AddTrackToLibrary, ['artist', 'track']) async addTrackToLibrary(params: any): Promise { + await this._refreshIfNeeded(); let track = await this._searchTrack(params['artist'], params['track']); - this._spotify.addToMySavedTracks([track.id]); + await this._spotify.addToMySavedTracks([track.id]); return { URL: track.uri, ARTIST: track.artists?.[0].name, @@ -85,9 +102,10 @@ export class Spotify extends BaseService { @reaction(ReactionType.AddToPlaylist, ['artist', 'track', 'playlist']) async addToPlaylist(params: any): Promise { + await this._refreshIfNeeded(); let playlist = await this._searchPlaylist( params['playlist']); let track = await this._searchTrack(params['artist'], params['track']); - this._spotify.addTracksToPlaylist(playlist.id, [track.uri]); + await this._spotify.addTracksToPlaylist(playlist.id, [track.uri]); return { URL: track.uri, ARTIST: track.artists?.[0].name, diff --git a/worker/yarn.lock b/worker/yarn.lock index 9e0c70e..85fbc2f 100644 --- a/worker/yarn.lock +++ b/worker/yarn.lock @@ -244,6 +244,18 @@ "@types/mime" "^1" "@types/node" "*" +"@types/spotify-api@*": + version "0.0.13" + resolved "https://registry.yarnpkg.com/@types/spotify-api/-/spotify-api-0.0.13.tgz#66adb9123ad1a7365b31408422bd171d0fedf60c" + integrity sha512-rHmG4soR13N3jDCu0QL9cv6q3jMM/AU/0w5w7ukTt89e23rv5Vz86BNqXP/34wEzYWgiTNSG+f+xGT1rjABj4g== + +"@types/spotify-web-api-node@^5.0.7": + version "5.0.7" + resolved "https://registry.yarnpkg.com/@types/spotify-web-api-node/-/spotify-web-api-node-5.0.7.tgz#de5cb576781e0aaa38568744dfb891c86a7938ee" + integrity sha512-8ajd4xS3+l4Zau1OyggPv7DjeSFEIGYvG5Q8PbbBMKiaRFD53IkcvU4Bx4Ijyzw+l+Kc09L5L+MXRj0wyVLx9Q== + dependencies: + "@types/spotify-api" "*" + "@types/ws@^8.2.2": version "8.5.1" resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.1.tgz#79136958b48bc73d5165f286707ceb9f04471599" @@ -405,6 +417,11 @@ combined-stream@^1.0.8: dependencies: delayed-stream "~1.0.0" +component-emitter@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" + integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== + concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" @@ -432,6 +449,11 @@ cookie@0.4.2: resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432" integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA== +cookiejar@^2.1.2: + version "2.1.3" + resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.3.tgz#fc7a6216e408e74414b90230050842dacda75acc" + integrity sha512-JxbCBUdrfr6AQjOXrxoTvAMJO4HBTUIlBzslcJPAz+/KT8yk53fXun51u+RenNYvad/+Vc2DIz5o9UxlCDymFQ== + cross-fetch@^3.0.6: version "3.1.5" resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.5.tgz#e1389f44d9e7ba767907f7af8454787952ab534f" @@ -451,7 +473,7 @@ debug@2.6.9: dependencies: ms "2.0.0" -debug@4: +debug@4, debug@^4.1.1: version "4.3.3" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664" integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q== @@ -596,6 +618,11 @@ extract-files@^9.0.0: resolved "https://registry.yarnpkg.com/extract-files/-/extract-files-9.0.0.tgz#8a7744f2437f81f5ed3250ed9f1550de902fe54a" integrity sha512-CvdFfHkC95B4bBBk36hcEmvdR2awOdhhVUYH6S/zrVj3477zven/fJMYg7121h4T1xHZC+tetUpubpAhxwI7hQ== +fast-safe-stringify@^2.0.7: + version "2.1.1" + resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz#c406a83b6e70d9e35ce3b30a81141df30aeba884" + integrity sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA== + fast-text-encoding@^1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/fast-text-encoding/-/fast-text-encoding-1.0.3.tgz#ec02ac8e01ab8a319af182dae2681213cfe9ce53" @@ -654,6 +681,11 @@ formdata-polyfill@^4.0.10: dependencies: fetch-blob "^3.1.2" +formidable@^1.2.2: + version "1.2.6" + resolved "https://registry.yarnpkg.com/formidable/-/formidable-1.2.6.tgz#d2a51d60162bbc9b4a055d8457a7c75315d1a168" + integrity sha512-KcpbcpuLNOwrEjnbpMC0gS+X8ciDoZE1kkqzat4a8vrprf+s9pKNQ/QIwWfbfs4ltgmFl3MD177SNTkve3BwGQ== + forwarded@0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" @@ -840,7 +872,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.4: +inherits@2, inherits@2.0.4, inherits@^2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -913,7 +945,7 @@ merge-descriptors@1.0.1: resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E= -methods@~1.1.2: +methods@^1.1.2, methods@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= @@ -935,6 +967,11 @@ mime@1.6.0: resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== +mime@^2.4.6: + version "2.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-2.6.0.tgz#a2a682a95cd4d0cb1d6257e28f83da7e35800367" + integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg== + "minimatch@2 || 3", minimatch@^3.0.4: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" @@ -1053,7 +1090,7 @@ qs@6.9.7: resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.7.tgz#4610846871485e1e048f44ae3b94033f0e675afe" integrity sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw== -qs@^6.7.0: +qs@^6.7.0, qs@^6.9.4: version "6.10.3" resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.3.tgz#d6cde1b2ffca87b5aa57889816c5f81535e22e8e" integrity sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ== @@ -1075,6 +1112,15 @@ raw-body@2.4.3: iconv-lite "0.4.24" unpipe "1.0.0" +readable-stream@^3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" + integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + resolve@^1.1.7: version "1.22.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.0.tgz#5e0b8c67c15df57a89bdbabe603a002f21731198" @@ -1091,7 +1137,7 @@ rxjs@^7.5.2: dependencies: tslib "^2.1.0" -safe-buffer@5.2.1, safe-buffer@^5.0.1: +safe-buffer@5.2.1, safe-buffer@^5.0.1, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== @@ -1101,6 +1147,13 @@ safe-buffer@5.2.1, safe-buffer@^5.0.1: resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== +semver@^7.3.2: + version "7.3.5" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" + integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== + dependencies: + lru-cache "^6.0.0" + send@0.17.2: version "0.17.2" resolved "https://registry.yarnpkg.com/send/-/send-0.17.2.tgz#926622f76601c41808012c8bf1688fe3906f7820" @@ -1144,16 +1197,25 @@ side-channel@^1.0.4: get-intrinsic "^1.0.2" object-inspect "^1.9.0" -spotify-web-api-js@^1.5.2: - version "1.5.2" - resolved "https://registry.yarnpkg.com/spotify-web-api-js/-/spotify-web-api-js-1.5.2.tgz#ab69cab0809e357061c5684d6215bcf107822e0c" - integrity sha512-ie1gbg1wCabfobIkXTIBLUMyULS/hMCpF44Cdx2pAO0/+FrjhNSDjlDzcwCEDy+ZIo3Fscs+Gkg/GTeQ/ijo+Q== +spotify-web-api-node@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/spotify-web-api-node/-/spotify-web-api-node-5.0.2.tgz#683669b3ccc046a5a357300f151df93a2b3539fe" + integrity sha512-r82dRWU9PMimHvHEzL0DwEJrzFk+SMCVfq249SLt3I7EFez7R+jeoKQd+M1//QcnjqlXPs2am4DFsGk8/GCsrA== + dependencies: + superagent "^6.1.0" "statuses@>= 1.5.0 < 2", statuses@~1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + strip-ansi@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" @@ -1161,6 +1223,23 @@ strip-ansi@^3.0.0: dependencies: ansi-regex "^2.0.0" +superagent@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/superagent/-/superagent-6.1.0.tgz#09f08807bc41108ef164cfb4be293cebd480f4a6" + integrity sha512-OUDHEssirmplo3F+1HWKUrUjvnQuA+nZI6i/JJBdXb5eq9IyEQwPyPpqND+SSsxf6TygpBEkUjISVRN4/VOpeg== + dependencies: + component-emitter "^1.3.0" + cookiejar "^2.1.2" + debug "^4.1.1" + fast-safe-stringify "^2.0.7" + form-data "^3.0.0" + formidable "^1.2.2" + methods "^1.1.2" + mime "^2.4.6" + qs "^6.9.4" + readable-stream "^3.6.0" + semver "^7.3.2" + supports-color@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" @@ -1243,6 +1322,11 @@ url-template@^2.0.8: resolved "https://registry.yarnpkg.com/url-template/-/url-template-2.0.8.tgz#fc565a3cccbff7730c775f5641f9555791439f21" integrity sha1-/FZaPMy/93MMd19WQflVV5FDnyE= +util-deprecate@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= + utils-merge@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"