2271 lines
69 KiB
Diff
2271 lines
69 KiB
Diff
diff --git a/src/Powercord/plugins/pc-spotify/SpotifyAPI.js b/src/Powercord/plugins/pc-spotify/SpotifyAPI.js
|
|
deleted file mode 100644
|
|
index 35bbc83..0000000
|
|
--- a/src/Powercord/plugins/pc-spotify/SpotifyAPI.js
|
|
+++ /dev/null
|
|
@@ -1,245 +0,0 @@
|
|
-const { WEBSITE } = require('powercord/constants');
|
|
-const { shell: { openExternal } } = require('electron');
|
|
-const { get, put, post, del } = require('powercord/http');
|
|
-const { getModule, http, spotify, constants: { Endpoints } } = require('powercord/webpack');
|
|
-const { SPOTIFY_BASE_URL, SPOTIFY_PLAYER_URL } = require('./constants');
|
|
-const playerStore = require('./playerStore/store');
|
|
-
|
|
-const revokedMessages = {
|
|
- SCOPES_UPDATED: 'Your Spotify account needs to be relinked to your Powercord account due to new authorizations required.',
|
|
- ACCESS_DENIED: 'Powercord is no longer able to connect to your Spotify account. Therefore, it has been automatically unlinked.'
|
|
-};
|
|
-
|
|
-let usedCached = false;
|
|
-
|
|
-module.exports = {
|
|
- accessToken: null,
|
|
-
|
|
- async getAccessToken () {
|
|
- if (!powercord.account) {
|
|
- await powercord.fetchAccount();
|
|
- }
|
|
-
|
|
- if (powercord.account && powercord.account.accounts.spotify) {
|
|
- const baseUrl = powercord.settings.get('backendURL', WEBSITE);
|
|
- const resp = await get(`${baseUrl}/api/v2/users/@me/spotify`)
|
|
- .set('Authorization', powercord.account.token)
|
|
- .then(r => r.body);
|
|
-
|
|
- if (resp.revoked) {
|
|
- powercord.api.notices.sendAnnouncement('spotify-revoked', {
|
|
- color: 'orange',
|
|
- message: revokedMessages[resp.revoked],
|
|
- button: {
|
|
- text: 'Relink Spotify',
|
|
- onClick: () => openExternal(`${baseUrl}/api/v2/oauth/spotify`)
|
|
- }
|
|
- });
|
|
- } else if (resp.token) {
|
|
- return resp.token;
|
|
- }
|
|
- }
|
|
-
|
|
- console.debug('%c[SpotifyAPI]', 'color: #1ed860', 'No Spotify account linked to Powercord; Falling back to Discord\'s token');
|
|
- if (!usedCached) {
|
|
- const spotifyMdl = await getModule([ 'getActiveSocketAndDevice' ]);
|
|
- const active = spotifyMdl.getActiveSocketAndDevice();
|
|
- if (active && active.socket && active.socket.accessToken) {
|
|
- usedCached = true;
|
|
- return active.socket.accessToken;
|
|
- }
|
|
- }
|
|
-
|
|
- usedCached = false;
|
|
- const spotifyUserID = await http.get(Endpoints.CONNECTIONS)
|
|
- .then(res =>
|
|
- res.body.find(connection =>
|
|
- connection.type === 'spotify'
|
|
- ).id
|
|
- );
|
|
-
|
|
- return spotify.getAccessToken(spotifyUserID)
|
|
- .then(r => r.body.access_token);
|
|
- },
|
|
-
|
|
- genericRequest (request, isConnectWeb) {
|
|
- request.set('Authorization', `Bearer ${this.accessToken}`);
|
|
- if (isConnectWeb) {
|
|
- const currentDeviceId = playerStore.getLastActiveDeviceId();
|
|
- if (currentDeviceId) {
|
|
- request.query('device_id', currentDeviceId);
|
|
- }
|
|
- }
|
|
- return request
|
|
- .catch(async (err) => {
|
|
- if (err) {
|
|
- if (err.statusCode === 401) {
|
|
- this.accessToken = await this.getAccessToken();
|
|
- delete request._res;
|
|
- return this.genericRequest(request);
|
|
- }
|
|
- console.error(err.body, request.opts);
|
|
- throw err;
|
|
- }
|
|
- });
|
|
- },
|
|
-
|
|
- getTrack (trackId) {
|
|
- return this.genericRequest(
|
|
- get(`${SPOTIFY_BASE_URL}/tracks/${trackId}`)
|
|
- ).then(r => r.body);
|
|
- },
|
|
-
|
|
- getPlaylists (limit = 50, offset = 0) {
|
|
- return this._fetchAll(`${SPOTIFY_BASE_URL}/me/playlists`, limit, offset);
|
|
- },
|
|
-
|
|
- getPlaylistTracks (playlistId, limit = 100, offset = 0) {
|
|
- return this._fetchAll(`${SPOTIFY_BASE_URL}/playlists/${playlistId}/tracks`, limit, offset);
|
|
- },
|
|
-
|
|
- addToPlaylist (playlistID, songURI) {
|
|
- return this.genericRequest(
|
|
- post(`${SPOTIFY_BASE_URL}/playlists/${playlistID}/tracks`)
|
|
- .query('uris', songURI)
|
|
- ).then(r => r.body);
|
|
- },
|
|
-
|
|
- getAlbums (limit = 50, offset = 0) {
|
|
- return this._fetchAll(`${SPOTIFY_BASE_URL}/me/albums`, limit, offset);
|
|
- },
|
|
-
|
|
- getAlbumTracks (albumId, limit = 100, offset = 0) {
|
|
- return this._fetchAll(`${SPOTIFY_BASE_URL}/albums/${albumId}/tracks`, limit, offset);
|
|
- },
|
|
-
|
|
- getTopSongs () {
|
|
- return this.genericRequest(
|
|
- get(`${SPOTIFY_BASE_URL}/me/top/tracks`)
|
|
- .query('limit', 50)
|
|
- ).then(r => r.body);
|
|
- },
|
|
-
|
|
- getSongs (limit = 50, offset = 0) {
|
|
- return this._fetchAll(`${SPOTIFY_BASE_URL}/me/tracks`, limit, offset);
|
|
- },
|
|
-
|
|
- search (query, type = 'track', limit = 20) {
|
|
- return this.genericRequest(
|
|
- get(`${SPOTIFY_BASE_URL}/search`)
|
|
- .query('q', query)
|
|
- .query('type', type)
|
|
- .query('limit', limit)
|
|
- ).then(r => r.body);
|
|
- },
|
|
-
|
|
- play (data) {
|
|
- return this.genericRequest(
|
|
- put(`${SPOTIFY_PLAYER_URL}/play`).send(data), true
|
|
- );
|
|
- },
|
|
-
|
|
- pause () {
|
|
- return this.genericRequest(
|
|
- put(`${SPOTIFY_PLAYER_URL}/pause`), true
|
|
- );
|
|
- },
|
|
-
|
|
- seek (position) {
|
|
- return this.genericRequest(
|
|
- put(`${SPOTIFY_PLAYER_URL}/seek`).query('position_ms', position), true
|
|
- );
|
|
- },
|
|
-
|
|
- next () {
|
|
- return this.genericRequest(
|
|
- post(`${SPOTIFY_PLAYER_URL}/next`), true
|
|
- );
|
|
- },
|
|
-
|
|
- prev () {
|
|
- return this.genericRequest(
|
|
- post(`${SPOTIFY_PLAYER_URL}/previous`), true
|
|
- );
|
|
- },
|
|
-
|
|
- getPlayer () {
|
|
- return this.genericRequest(
|
|
- get(SPOTIFY_PLAYER_URL)
|
|
- ).then(r => r.body);
|
|
- },
|
|
-
|
|
- getDevices () {
|
|
- return this.genericRequest(
|
|
- get(`${SPOTIFY_PLAYER_URL}/devices`)
|
|
- ).then(r => r.body);
|
|
- },
|
|
-
|
|
- setVolume (volume) {
|
|
- return this.genericRequest(
|
|
- put(`${SPOTIFY_PLAYER_URL}/volume`).query('volume_percent', volume), true
|
|
- );
|
|
- },
|
|
-
|
|
- setActiveDevice (deviceID) {
|
|
- return this.genericRequest(
|
|
- put(SPOTIFY_PLAYER_URL)
|
|
- .send({
|
|
- device_ids: [ deviceID ],
|
|
- play: true
|
|
- })
|
|
- );
|
|
- },
|
|
-
|
|
- setRepeatState (state) {
|
|
- return this.genericRequest(
|
|
- put(`${SPOTIFY_PLAYER_URL}/repeat`).query('state', state), true
|
|
- );
|
|
- },
|
|
-
|
|
- setShuffleState (state) {
|
|
- return this.genericRequest(
|
|
- put(`${SPOTIFY_PLAYER_URL}/shuffle`).query('state', state), true
|
|
- );
|
|
- },
|
|
-
|
|
- addSong (songID) {
|
|
- return this.genericRequest(
|
|
- put(`${SPOTIFY_BASE_URL}/me/tracks`)
|
|
- .query('ids', songID)
|
|
- );
|
|
- },
|
|
-
|
|
- removeSong (songID) {
|
|
- return this.genericRequest(
|
|
- del(`${SPOTIFY_BASE_URL}/me/tracks`)
|
|
- .query('ids', songID)
|
|
- );
|
|
- },
|
|
-
|
|
- checkLibrary (songID) {
|
|
- return this.genericRequest(
|
|
- get(`${SPOTIFY_BASE_URL}/me/tracks/contains`)
|
|
- .query('ids', songID)
|
|
- );
|
|
- },
|
|
-
|
|
- async _fetchAll (url, limit, offset) {
|
|
- const items = [];
|
|
- while (url) {
|
|
- const req = get(url);
|
|
- if (limit) {
|
|
- req.query('limit', limit);
|
|
- limit = 0;
|
|
- }
|
|
- if (offset) {
|
|
- req.query('offset', offset);
|
|
- offset = 0;
|
|
- }
|
|
- const res = await this.genericRequest(req).then(r => r.body);
|
|
- items.push(...res.items);
|
|
- url = res.next;
|
|
- }
|
|
- return items;
|
|
- }
|
|
-};
|
|
diff --git a/src/Powercord/plugins/pc-spotify/commands/album.js b/src/Powercord/plugins/pc-spotify/commands/album.js
|
|
deleted file mode 100644
|
|
index 0d5e298..0000000
|
|
--- a/src/Powercord/plugins/pc-spotify/commands/album.js
|
|
+++ /dev/null
|
|
@@ -1,26 +0,0 @@
|
|
-const playerStore = require('../playerStore/store');
|
|
-
|
|
-module.exports = {
|
|
- command: 'album',
|
|
- description: 'Send album of current playing song to selected channel',
|
|
- category: 'Spotify',
|
|
- executor () {
|
|
- const currentTrack = playerStore.getCurrentTrack();
|
|
- if (!currentTrack) {
|
|
- return {
|
|
- send: false,
|
|
- result: 'You are not currently listening to anything.'
|
|
- };
|
|
- }
|
|
- if (!currentTrack.urls.album) {
|
|
- return {
|
|
- send: false,
|
|
- result: 'The track you\'re listening to doesn\'t belong to an album.'
|
|
- };
|
|
- }
|
|
- return {
|
|
- send: true,
|
|
- result: currentTrack.urls.album
|
|
- };
|
|
- }
|
|
-};
|
|
diff --git a/src/Powercord/plugins/pc-spotify/commands/find.js b/src/Powercord/plugins/pc-spotify/commands/find.js
|
|
deleted file mode 100644
|
|
index f5d6fe9..0000000
|
|
--- a/src/Powercord/plugins/pc-spotify/commands/find.js
|
|
+++ /dev/null
|
|
@@ -1,39 +0,0 @@
|
|
-const { getModule } = require('powercord/webpack');
|
|
-const SpotifyAPI = require('../SpotifyAPI');
|
|
-
|
|
-module.exports = {
|
|
- command: 'find',
|
|
- description: 'Searches for a song and plays it!',
|
|
- usage: '{c} {song}',
|
|
- category: 'Spotify',
|
|
- executor (args) {
|
|
- const isPremium = getModule([ 'isSpotifyPremium' ], false).isSpotifyPremium();
|
|
- if (!isPremium) {
|
|
- return {
|
|
- send: false,
|
|
- result: 'Oops, it looks like you are not a Spotify Premium member. Unfortunately, this feature isn\'t available to you as per Spotify\'s requirements.'
|
|
- };
|
|
- }
|
|
- if (!args[0]) {
|
|
- return {
|
|
- send: false,
|
|
- result: 'You need to specify a song to search for and play!'
|
|
- };
|
|
- }
|
|
-
|
|
- return SpotifyAPI.search(args.join(' '), 'track', 1).then((body) => {
|
|
- const tracksArray = body.tracks.items;
|
|
- if (tracksArray.length > 0) {
|
|
- const trackURL = tracksArray[0].uri;
|
|
- SpotifyAPI.play({
|
|
- uris: [ trackURL ]
|
|
- });
|
|
- return;
|
|
- }
|
|
- return {
|
|
- send: false,
|
|
- result: 'Couldn\'t find a song!'
|
|
- };
|
|
- });
|
|
- }
|
|
-};
|
|
diff --git a/src/Powercord/plugins/pc-spotify/commands/index.js b/src/Powercord/plugins/pc-spotify/commands/index.js
|
|
deleted file mode 100644
|
|
index d59ce4c..0000000
|
|
--- a/src/Powercord/plugins/pc-spotify/commands/index.js
|
|
+++ /dev/null
|
|
@@ -1,7 +0,0 @@
|
|
-require('fs')
|
|
- .readdirSync(__dirname)
|
|
- .filter((file) => file !== 'index.js' && file !== '.DS_Store')
|
|
- .forEach(filename => {
|
|
- const moduleName = filename.split('.')[0];
|
|
- exports[moduleName] = require(`${__dirname}/${filename}`);
|
|
- });
|
|
diff --git a/src/Powercord/plugins/pc-spotify/commands/like.js b/src/Powercord/plugins/pc-spotify/commands/like.js
|
|
deleted file mode 100644
|
|
index 3840aac..0000000
|
|
--- a/src/Powercord/plugins/pc-spotify/commands/like.js
|
|
+++ /dev/null
|
|
@@ -1,29 +0,0 @@
|
|
-const playerStore = require('../playerStore/store');
|
|
-const SpotifyAPI = require('../SpotifyAPI');
|
|
-
|
|
-module.exports = {
|
|
- command: 'like',
|
|
- description: 'Like the current playing song',
|
|
- category: 'Spotify',
|
|
- async executor () {
|
|
- if (!powercord.account || !powercord.account.accounts.spotify) {
|
|
- return {
|
|
- send: false,
|
|
- result: 'You need a Powercord account and connected Spotify account to use this feature!'
|
|
- };
|
|
- }
|
|
- const currentTrack = playerStore.getCurrentTrack();
|
|
- if (!currentTrack) {
|
|
- return {
|
|
- send: false,
|
|
- result: 'You are not currently listening to anything.'
|
|
- };
|
|
- }
|
|
- const { body } = await SpotifyAPI.checkLibrary(currentTrack.id);
|
|
- SpotifyAPI[body[0] ? 'removeSong' : 'addSong'](currentTrack.id);
|
|
- return {
|
|
- send: false,
|
|
- result: `You ${body[0] ? 'removed' : 'added'} **${currentTrack.name}** by **${currentTrack.artists}** ${body[0] ? 'from' : 'to'} your Liked Songs.`
|
|
- };
|
|
- }
|
|
-};
|
|
diff --git a/src/Powercord/plugins/pc-spotify/commands/next.js b/src/Powercord/plugins/pc-spotify/commands/next.js
|
|
deleted file mode 100644
|
|
index 730c4db..0000000
|
|
--- a/src/Powercord/plugins/pc-spotify/commands/next.js
|
|
+++ /dev/null
|
|
@@ -1,20 +0,0 @@
|
|
-const { getModule } = require('powercord/webpack');
|
|
-const SpotifyAPI = require('../SpotifyAPI');
|
|
-
|
|
-module.exports = {
|
|
- command: 'next',
|
|
- aliases: [ 'skip' ],
|
|
- description: 'Skip Spotify song',
|
|
- usage: '{c}',
|
|
- category: 'Spotify',
|
|
- executor () {
|
|
- const isPremium = getModule([ 'isSpotifyPremium' ], false).isSpotifyPremium();
|
|
- if (!isPremium) {
|
|
- return {
|
|
- send: false,
|
|
- result: 'Oops, it looks like you are not a Spotify Premium member. Unfortunately, this feature isn\'t available to you as per Spotify\'s requirements.'
|
|
- };
|
|
- }
|
|
- return SpotifyAPI.next();
|
|
- }
|
|
-};
|
|
diff --git a/src/Powercord/plugins/pc-spotify/commands/pause.js b/src/Powercord/plugins/pc-spotify/commands/pause.js
|
|
deleted file mode 100644
|
|
index 4d576d7..0000000
|
|
--- a/src/Powercord/plugins/pc-spotify/commands/pause.js
|
|
+++ /dev/null
|
|
@@ -1,19 +0,0 @@
|
|
-const { getModule } = require('powercord/webpack');
|
|
-const SpotifyAPI = require('../SpotifyAPI');
|
|
-
|
|
-module.exports = {
|
|
- command: 'pause',
|
|
- description: 'Pause Spotify playback',
|
|
- usage: '{c}',
|
|
- category: 'Spotify',
|
|
- executor () {
|
|
- const isPremium = getModule([ 'isSpotifyPremium' ], false).isSpotifyPremium();
|
|
- if (!isPremium) {
|
|
- return {
|
|
- send: false,
|
|
- result: 'Oops, it looks like you are not a Spotify Premium member. Unfortunately, this feature isn\'t available to you as per Spotify\'s requirements.'
|
|
- };
|
|
- }
|
|
- return SpotifyAPI.pause();
|
|
- }
|
|
-};
|
|
diff --git a/src/Powercord/plugins/pc-spotify/commands/play.js b/src/Powercord/plugins/pc-spotify/commands/play.js
|
|
deleted file mode 100644
|
|
index a857325..0000000
|
|
--- a/src/Powercord/plugins/pc-spotify/commands/play.js
|
|
+++ /dev/null
|
|
@@ -1,38 +0,0 @@
|
|
-const { getModule } = require('powercord/webpack');
|
|
-const SpotifyAPI = require('../SpotifyAPI');
|
|
-const urlRegex = /\/track\/([A-z0-9]*)/;
|
|
-
|
|
-module.exports = {
|
|
- command: 'play',
|
|
- description: 'Play a Spotify URL',
|
|
- usage: '{c} <URL>',
|
|
- category: 'Spotify',
|
|
- executor ([ url ]) {
|
|
- const isPremium = getModule([ 'isSpotifyPremium' ], false).isSpotifyPremium();
|
|
- if (!isPremium) {
|
|
- return {
|
|
- send: false,
|
|
- result: 'Oops, it looks like you are not a Spotify Premium member. Unfortunately, this feature isn\'t available to you as per Spotify\'s requirements.'
|
|
- };
|
|
- }
|
|
-
|
|
- if (!url) {
|
|
- const spotifyModals = document.querySelectorAll('.embedSpotify-tvxDCr');
|
|
- const spotifyModal = spotifyModals[spotifyModals.length - 1];
|
|
- url = spotifyModal && spotifyModal.children[0].src;
|
|
-
|
|
- if (!url) {
|
|
- return {
|
|
- send: false,
|
|
- result: 'No URL specified.'
|
|
- };
|
|
- }
|
|
- }
|
|
-
|
|
- SpotifyAPI.play({
|
|
- uris: [
|
|
- `spotify:track:${urlRegex.exec(url)[1]}`
|
|
- ]
|
|
- });
|
|
- }
|
|
-};
|
|
diff --git a/src/Powercord/plugins/pc-spotify/commands/previous.js b/src/Powercord/plugins/pc-spotify/commands/previous.js
|
|
deleted file mode 100644
|
|
index 8cb704d..0000000
|
|
--- a/src/Powercord/plugins/pc-spotify/commands/previous.js
|
|
+++ /dev/null
|
|
@@ -1,20 +0,0 @@
|
|
-const { getModule } = require('powercord/webpack');
|
|
-const SpotifyAPI = require('../SpotifyAPI');
|
|
-
|
|
-module.exports = {
|
|
- command: 'previous',
|
|
- aliases: [ 'prev' ],
|
|
- description: 'Go back one Spotify song',
|
|
- usage: '{c}',
|
|
- category: 'Spotify',
|
|
- executor () {
|
|
- const isPremium = getModule([ 'isSpotifyPremium' ], false).isSpotifyPremium();
|
|
- if (!isPremium) {
|
|
- return {
|
|
- send: false,
|
|
- result: 'Oops, it looks like you are not a Spotify Premium member. Unfortunately, this feature isn\'t available to you as per Spotify\'s requirements.'
|
|
- };
|
|
- }
|
|
- return SpotifyAPI.prev();
|
|
- }
|
|
-};
|
|
diff --git a/src/Powercord/plugins/pc-spotify/commands/resume.js b/src/Powercord/plugins/pc-spotify/commands/resume.js
|
|
deleted file mode 100644
|
|
index da6486c..0000000
|
|
--- a/src/Powercord/plugins/pc-spotify/commands/resume.js
|
|
+++ /dev/null
|
|
@@ -1,19 +0,0 @@
|
|
-const { getModule } = require('powercord/webpack');
|
|
-const SpotifyAPI = require('../SpotifyAPI');
|
|
-
|
|
-module.exports = {
|
|
- command: 'resume',
|
|
- description: 'Resume Spotify playback',
|
|
- usage: '{c}',
|
|
- category: 'Spotify',
|
|
- executor () {
|
|
- const isPremium = getModule([ 'isSpotifyPremium' ], false).isSpotifyPremium();
|
|
- if (!isPremium) {
|
|
- return {
|
|
- send: false,
|
|
- result: 'Oops, it looks like you are not a Spotify Premium member. Unfortunately, this feature isn\'t available to you as per Spotify\'s requirements.'
|
|
- };
|
|
- }
|
|
- return SpotifyAPI.play();
|
|
- }
|
|
-};
|
|
diff --git a/src/Powercord/plugins/pc-spotify/commands/share.js b/src/Powercord/plugins/pc-spotify/commands/share.js
|
|
deleted file mode 100644
|
|
index b5d71c5..0000000
|
|
--- a/src/Powercord/plugins/pc-spotify/commands/share.js
|
|
+++ /dev/null
|
|
@@ -1,50 +0,0 @@
|
|
-const { open: openModal } = require('powercord/modal');
|
|
-const { React } = require('powercord/webpack');
|
|
-const SpotifyAPI = require('../SpotifyAPI');
|
|
-const playerStore = require('../playerStore/store');
|
|
-const ShareModal = require('../components/ShareModal');
|
|
-
|
|
-module.exports = {
|
|
- command: 'share',
|
|
- description: 'Send specified or current playing song to selected channel',
|
|
- usage: '{c} {song name/artist}',
|
|
- category: 'Spotify',
|
|
- async executor (query) {
|
|
- query = query.join(' ');
|
|
-
|
|
- if (query.length > 0) {
|
|
- const result = await SpotifyAPI.search(query, 'track', 14);
|
|
- const closestTrack = result.tracks.items[0];
|
|
-
|
|
- if (result.tracks.items.length > 1) {
|
|
- return openModal(() => React.createElement(ShareModal, {
|
|
- tracks: result.tracks,
|
|
- query
|
|
- }));
|
|
- } else if (closestTrack) {
|
|
- return {
|
|
- send: true,
|
|
- result: closestTrack.external_urls.spotify
|
|
- };
|
|
- }
|
|
-
|
|
- return {
|
|
- send: false,
|
|
- result: `Couldn't find "\`${query}\`". Try searching again using a different spelling or keyword.`
|
|
- };
|
|
- }
|
|
-
|
|
- const currentTrack = playerStore.getCurrentTrack();
|
|
- if (!currentTrack) {
|
|
- return {
|
|
- send: false,
|
|
- result: 'You are not currently listening to anything.'
|
|
- };
|
|
- }
|
|
-
|
|
- return {
|
|
- send: true,
|
|
- result: currentTrack.urls.track
|
|
- };
|
|
- }
|
|
-};
|
|
diff --git a/src/Powercord/plugins/pc-spotify/commands/volume.js b/src/Powercord/plugins/pc-spotify/commands/volume.js
|
|
deleted file mode 100644
|
|
index 83fd046..0000000
|
|
--- a/src/Powercord/plugins/pc-spotify/commands/volume.js
|
|
+++ /dev/null
|
|
@@ -1,20 +0,0 @@
|
|
-const { getModule } = require('powercord/webpack');
|
|
-const SpotifyAPI = require('../SpotifyAPI');
|
|
-
|
|
-module.exports = {
|
|
- command: 'volume',
|
|
- aliases: [ 'vol' ],
|
|
- description: 'Change Spotify volume',
|
|
- usage: '{c} <number between 0-100>',
|
|
- category: 'Spotify',
|
|
- executor ([ args ]) {
|
|
- const isPremium = getModule([ 'isSpotifyPremium' ], false).isSpotifyPremium();
|
|
- if (!isPremium) {
|
|
- return {
|
|
- send: false,
|
|
- result: 'Oops, it looks like you are not a Spotify Premium member. Unfortunately, this feature isn\'t available to you as per Spotify\'s requirements.'
|
|
- };
|
|
- }
|
|
- return SpotifyAPI.setVolume(args);
|
|
- }
|
|
-};
|
|
diff --git a/src/Powercord/plugins/pc-spotify/components/AddToPlaylist.jsx b/src/Powercord/plugins/pc-spotify/components/AddToPlaylist.jsx
|
|
deleted file mode 100644
|
|
index 77cd1af..0000000
|
|
--- a/src/Powercord/plugins/pc-spotify/components/AddToPlaylist.jsx
|
|
+++ /dev/null
|
|
@@ -1,124 +0,0 @@
|
|
-const { React, Flux, i18n: { Messages } } = require('powercord/webpack');
|
|
-const { FormTitle, Button, Divider, Spinner, Card, Tooltip } = require('powercord/components');
|
|
-const { Modal, Confirm } = require('powercord/components/modal');
|
|
-const { open: openModal, close: closeModal } = require('powercord/modal');
|
|
-
|
|
-const { SPOTIFY_DEFAULT_IMAGE } = require('../constants');
|
|
-const songsStore = require('../songsStore/store');
|
|
-const songsStoreActions = require('../songsStore/actions');
|
|
-const SpotifyAPI = require('../SpotifyAPI');
|
|
-
|
|
-class AddToPlaylist extends React.PureComponent {
|
|
- componentDidMount () {
|
|
- if (!this.props.loaded) {
|
|
- songsStoreActions.loadPlaylists();
|
|
- }
|
|
- }
|
|
-
|
|
- render () {
|
|
- return (
|
|
- <Modal className='powercord-text spotify-add-to-playlist' size={Modal.Sizes.MEDIUM}>
|
|
- <Modal.Header>
|
|
- <FormTitle tag='h4'>Add to Playlist</FormTitle>
|
|
- <Modal.CloseButton onClick={() => closeModal()}/>
|
|
- </Modal.Header>
|
|
- <Modal.Content>
|
|
- <p>Where do you want to save this very nice tune?</p>
|
|
- <div className='track'>
|
|
- <img src={this.props.track.cover || SPOTIFY_DEFAULT_IMAGE} alt='Spotify Cover'/>
|
|
- <div className='details'>
|
|
- <span className='title'>
|
|
- {this.props.track.name}
|
|
- </span>
|
|
- <span className='sub'>
|
|
- {Messages.USER_ACTIVITY_LISTENING_ARTISTS.format({
|
|
- artists: this.props.track.artists,
|
|
- artistsHook: t => t
|
|
- })}
|
|
- </span>
|
|
- </div>
|
|
- </div>
|
|
- <Divider/>
|
|
- <div className='playlists'>
|
|
- {!this.props.loaded
|
|
- ? <Spinner/>
|
|
- : Object.keys(this.props.playlists).map(p => this.renderItem(p))}
|
|
- </div>
|
|
- </Modal.Content>
|
|
- <Modal.Footer>
|
|
- <Button onClick={() => closeModal()} look={Button.Looks.LINK} color={Button.Colors.TRANSPARENT}>
|
|
- {Messages.USER_ACTIVITY_NEVER_MIND}
|
|
- </Button>
|
|
- </Modal.Footer>
|
|
- </Modal>
|
|
- );
|
|
- }
|
|
-
|
|
- renderItem (playlistId) {
|
|
- const playlist = this.props.playlists[playlistId];
|
|
- if (!playlist.editable) {
|
|
- return null;
|
|
- }
|
|
-
|
|
- return (
|
|
- <Card className='playlist' key={playlistId}>
|
|
- <img src={playlist.icon || SPOTIFY_DEFAULT_IMAGE} alt='Spotify Cover'/>
|
|
- <div className='details'>
|
|
- <span className='title'>
|
|
- {playlist.name}
|
|
- </span>
|
|
- <span className='sub'>
|
|
- {playlist.tracksLoaded
|
|
- ? `${Object.keys(playlist.tracks).length} tracks`
|
|
- : Messages.DEFAULT_INPUT_PLACEHOLDER}
|
|
- </span>
|
|
- </div>
|
|
- {playlist.tracksLoaded
|
|
- ? (
|
|
- <Button onClick={() => this.handleAddToPlaylist(playlistId)}>
|
|
- Add to Playlist
|
|
- </Button>
|
|
- )
|
|
- : (
|
|
- <Tooltip text='Please wait for the playlist to load'>
|
|
- <Button disabled>Add to Playlist</Button>
|
|
- </Tooltip>
|
|
- )}
|
|
- </Card>
|
|
- );
|
|
- }
|
|
-
|
|
- handleAddToPlaylist (playlistId) {
|
|
- closeModal();
|
|
- const playlist = this.props.playlists[playlistId];
|
|
- if (playlist.tracks[this.props.track.id]) {
|
|
- openModal(() => (
|
|
- <Confirm
|
|
- header='Duplicate detected'
|
|
- confirmText='Add anyway'
|
|
- cancelText={Messages.USER_ACTIVITY_NEVER_MIND}
|
|
- onConfirm={() => {
|
|
- SpotifyAPI.addToPlaylist(playlistId, this.props.track.uri);
|
|
- closeModal();
|
|
- }}
|
|
- onCancel={closeModal}
|
|
- >
|
|
- <div className='powercord-text'>
|
|
- This item is already in this playlist. Do you want to add it anyway?
|
|
- </div>
|
|
- </Confirm>
|
|
- ));
|
|
- } else {
|
|
- SpotifyAPI.addToPlaylist(playlistId, this.props.track.uri);
|
|
- }
|
|
- console.log(playlistId, this.props.playlists[playlistId]);
|
|
- }
|
|
-}
|
|
-
|
|
-module.exports = Flux.connectStores(
|
|
- [ songsStore ],
|
|
- () => ({
|
|
- loaded: songsStore.getPlaylistsLoaded(),
|
|
- playlists: songsStore.getPlaylists()
|
|
- })
|
|
-)(AddToPlaylist);
|
|
diff --git a/src/Powercord/plugins/pc-spotify/components/ContextMenu.jsx b/src/Powercord/plugins/pc-spotify/components/ContextMenu.jsx
|
|
deleted file mode 100644
|
|
index 6d27bc1..0000000
|
|
--- a/src/Powercord/plugins/pc-spotify/components/ContextMenu.jsx
|
|
+++ /dev/null
|
|
@@ -1,285 +0,0 @@
|
|
-const { clipboard, shell } = require('electron');
|
|
-const { React, Flux, getModule, messages, channels, contextMenu: { closeContextMenu }, i18n: { Messages } } = require('powercord/webpack');
|
|
-const { open: openModal } = require('powercord/modal');
|
|
-const { Menu } = require('powercord/components');
|
|
-const { formatTime } = require('powercord/util');
|
|
-
|
|
-const songsStore = require('../songsStore/store');
|
|
-const songsStoreActions = require('../songsStore/actions');
|
|
-const playerStore = require('../playerStore/store');
|
|
-const SpotifyAPI = require('../SpotifyAPI');
|
|
-const AddToPlaylist = require('./AddToPlaylist');
|
|
-
|
|
-class ContextMenu extends React.PureComponent {
|
|
- constructor (props) {
|
|
- super(props);
|
|
- this.handleVolumeSlide = global._.debounce(this.handleVolumeSlide.bind(this), 200);
|
|
- }
|
|
-
|
|
- handleVolumeSlide (volume) {
|
|
- SpotifyAPI.setVolume(Math.round(volume));
|
|
- }
|
|
-
|
|
- componentDidMount () {
|
|
- if (powercord.account && powercord.account.accounts.spotify) {
|
|
- if (!this.props.songsLoaded) {
|
|
- songsStoreActions.loadSongs();
|
|
- }
|
|
- if (!this.props.topSongsLoaded) {
|
|
- songsStoreActions.loadTopSongs();
|
|
- }
|
|
- if (!this.props.albumsLoaded) {
|
|
- songsStoreActions.loadAlbums();
|
|
- }
|
|
- }
|
|
- if (!this.props.playlistsLoaded) {
|
|
- songsStoreActions.loadPlaylists();
|
|
- }
|
|
- }
|
|
-
|
|
- render () {
|
|
- const isPremium = getModule([ 'isSpotifyPremium' ], false).isSpotifyPremium();
|
|
-
|
|
- return (
|
|
- <Menu.Menu navId='powercord-spotify-menu' onClose={closeContextMenu}>
|
|
- {isPremium && this.renderDevices()}
|
|
- {isPremium && this.renderSongs()}
|
|
- {isPremium && this.renderPlaybackSettings()}
|
|
- {isPremium && this.renderVolume()}
|
|
- {isPremium && this.renderSave()}
|
|
- {this.renderActions()}
|
|
- </Menu.Menu>
|
|
- );
|
|
- }
|
|
-
|
|
- renderDevices () {
|
|
- return (
|
|
- <Menu.MenuGroup>
|
|
- <Menu.MenuItem id='devices' label='Devices'>
|
|
- {this.props.devices.sort(d => -Number(d.is_active)).map((device, i) => (
|
|
- <>
|
|
- <Menu.MenuItem
|
|
- id={device.id}
|
|
- label={device.name}
|
|
- hint={device.type}
|
|
- disabled={i === 0}
|
|
- />
|
|
- {i === 0 && <Menu.MenuSeparator/>}
|
|
- </>
|
|
- ))}
|
|
- </Menu.MenuItem>
|
|
- </Menu.MenuGroup>
|
|
- );
|
|
- }
|
|
-
|
|
- renderSongs () {
|
|
- const hasCoolFeatures = powercord.account && powercord.account.accounts.spotify;
|
|
-
|
|
- return (
|
|
- <Menu.MenuGroup>
|
|
- <Menu.MenuItem id='playlists' label='Playlists' disabled={!this.props.playlistsLoaded}>
|
|
- {this.props.playlistsLoaded
|
|
- ? this._renderList(this.props.playlists)
|
|
- : null}
|
|
- </Menu.MenuItem>
|
|
- {hasCoolFeatures && <Menu.MenuItem id='albums' label='Albums' disabled={!this.props.albumsLoaded}>
|
|
- {this.props.albumsLoaded
|
|
- ? this._renderList(this.props.albums)
|
|
- : null}
|
|
- </Menu.MenuItem>}
|
|
- {hasCoolFeatures && <Menu.MenuItem id='top-songs' label='Top Songs' disabled={!this.props.topSongsLoaded}>
|
|
- {this.props.topSongsLoaded
|
|
- ? this._renderSongs(this.props.topSongs)
|
|
- : null}
|
|
- </Menu.MenuItem>}
|
|
- {hasCoolFeatures && <Menu.MenuItem id='songs' label='Songs' disabled={!this.props.songsLoaded}>
|
|
- {this.props.songsLoaded
|
|
- ? this._renderSongs(this.props.songs)
|
|
- : null}
|
|
- </Menu.MenuItem>}
|
|
- </Menu.MenuGroup>
|
|
- );
|
|
- }
|
|
-
|
|
- _renderList (list) {
|
|
- return Object.entries(list).map(([ id, item ]) => (
|
|
- <Menu.MenuItem
|
|
- id={id}
|
|
- label={item.name}
|
|
- hint={item.tracksLoaded ? `${Object.keys(item.tracks).length} tracks` : Messages.DEFAULT_INPUT_PLACEHOLDER}
|
|
- action={() => setTimeout(() => SpotifyAPI.play({ context_uri: item.uri }), 10)}
|
|
- >
|
|
- {item.tracksLoaded
|
|
- ? this._renderSongs(item.tracks, item.uri)
|
|
- : null}
|
|
- </Menu.MenuItem>
|
|
- ));
|
|
- }
|
|
-
|
|
- _renderSongs (list, uri) {
|
|
- return Object.entries(list).map(([ id, item ]) => (
|
|
- <Menu.MenuItem
|
|
- id={id}
|
|
- label={item.name}
|
|
- hint={formatTime(item.duration)}
|
|
- action={() => setTimeout(() => {
|
|
- if (uri) {
|
|
- SpotifyAPI.play({
|
|
- context_uri: uri,
|
|
- offset: { uri: item.uri }
|
|
- });
|
|
- } else {
|
|
- SpotifyAPI.play({
|
|
- uris: [ item.uri ]
|
|
- });
|
|
- }
|
|
- }, 10)}
|
|
- />
|
|
- ));
|
|
- }
|
|
-
|
|
- renderPlaybackSettings () {
|
|
- if (!powercord.account || !powercord.account.accounts.spotify) {
|
|
- return null;
|
|
- }
|
|
-
|
|
- const cannotAll = !this.props.playerState.canRepeat && !this.props.playerState.canRepeatOne;
|
|
- const isOff = this.props.playerState.repeat === playerStore.RepeatState.NO_REPEAT;
|
|
- const isContext = this.props.playerState.repeat === playerStore.RepeatState.REPEAT_CONTEXT;
|
|
- const isTrack = this.props.playerState.repeat === playerStore.RepeatState.REPEAT_TRACK;
|
|
-
|
|
- return (
|
|
- <Menu.MenuGroup>
|
|
- <Menu.MenuItem id='repeat' label='Repeat Mode' disabled={cannotAll}>
|
|
- <Menu.MenuItem
|
|
- id={`off${isOff ? '-active' : ''}`}
|
|
- label='No Repeat'
|
|
- action={() => SpotifyAPI.setRepeatState('off')}
|
|
- disabled={isOff}
|
|
- />
|
|
- <Menu.MenuItem
|
|
- id={`context${isContext ? '-active' : ''}`}
|
|
- label='Repeat'
|
|
- action={() => SpotifyAPI.setRepeatState('context')}
|
|
- disabled={isContext || !this.props.playerState.canRepeat}
|
|
- />
|
|
- <Menu.MenuItem
|
|
- id={`track${isTrack ? '-active' : ''}`}
|
|
- label='Repeat Track'
|
|
- action={() => SpotifyAPI.setRepeatState('track')}
|
|
- disabled={isTrack || !this.props.playerState.canRepeatOne}
|
|
- />
|
|
- </Menu.MenuItem>
|
|
- <Menu.MenuCheckboxItem
|
|
- id='shuffle'
|
|
- label='Shuffle'
|
|
- checked={this.props.playerState.shuffle}
|
|
- action={() => SpotifyAPI.setShuffleState(!this.props.playerState.shuffle)}
|
|
- disabled={!this.props.playerState.canShuffle}
|
|
- />
|
|
- </Menu.MenuGroup>
|
|
- );
|
|
- }
|
|
-
|
|
- renderVolume () {
|
|
- const Slider = getModule(m => m.render && m.render.toString().includes('sliderContainer'), false);
|
|
- return (
|
|
- <Menu.MenuGroup>
|
|
- <Menu.MenuControlItem
|
|
- id='volume'
|
|
- label='Volume'
|
|
- control={(props, ref) => (
|
|
- <Slider
|
|
- mini
|
|
- ref={ref}
|
|
- value={this.props.playerState.volume}
|
|
- onChange={this.handleVolumeSlide.bind(this)}
|
|
- {...props}
|
|
- />
|
|
- )}
|
|
- />
|
|
- </Menu.MenuGroup>
|
|
- );
|
|
- }
|
|
-
|
|
- renderSave () {
|
|
- if (!powercord.account || !powercord.account.accounts.spotify) {
|
|
- return null;
|
|
- }
|
|
-
|
|
- return (
|
|
- <Menu.MenuGroup>
|
|
- {this.props.currentLibraryState === playerStore.LibraryState.IN_LIBRARY
|
|
- ? <Menu.MenuItem
|
|
- id='remove-liked'
|
|
- label={Messages.SPOTIFY_REMOVE_LIKED_SONGS}
|
|
- action={() => SpotifyAPI.removeSong(this.props.currentTrack.id)}
|
|
- disabled={[ playerStore.LibraryState.UNKNOWN, playerStore.LibraryState.LOCAL_SONG ].includes(this.props.currentLibraryState)}
|
|
- />
|
|
- : <Menu.MenuItem
|
|
- id='save-liked'
|
|
- label={Messages.SPOTIFY_ADD_LIKED_SONGS}
|
|
- action={() => SpotifyAPI.addSong(this.props.currentTrack.id)}
|
|
- disabled={[ playerStore.LibraryState.UNKNOWN, playerStore.LibraryState.LOCAL_SONG ].includes(this.props.currentLibraryState)}
|
|
- />}
|
|
- <Menu.MenuItem
|
|
- id='save-playlist'
|
|
- label='Save to Playlist'
|
|
- action={() => openModal(() => React.createElement(AddToPlaylist, { track: this.props.currentTrack }))}
|
|
- />
|
|
- </Menu.MenuGroup>
|
|
- );
|
|
- }
|
|
-
|
|
- renderActions () {
|
|
- return (
|
|
- <Menu.MenuGroup>
|
|
- <Menu.MenuItem
|
|
- id='open-spotify'
|
|
- label='Open in Spotify'
|
|
- action={() => {
|
|
- const protocol = getModule([ 'isProtocolRegistered', '_dispatchToken' ], false).isProtocolRegistered();
|
|
- shell.openExternal(protocol ? this.props.currentTrack.uri : this.props.currentTrack.urls.track);
|
|
- }}
|
|
- />
|
|
- <Menu.MenuItem
|
|
- id='send-album'
|
|
- disabled={!this.props.currentTrack.urls.album}
|
|
- label='Send Album URL to Channel'
|
|
- action={() => messages.sendMessage(
|
|
- channels.getChannelId(),
|
|
- { content: this.props.currentTrack.urls.album }
|
|
- )}
|
|
- />
|
|
- <Menu.MenuItem
|
|
- id='send-song'
|
|
- label='Send Song URL to Channel'
|
|
- action={() => messages.sendMessage(
|
|
- channels.getChannelId(),
|
|
- { content: this.props.currentTrack.urls.track }
|
|
- )}
|
|
- />
|
|
- <Menu.MenuItem
|
|
- id='copy-album'
|
|
- disabled={!this.props.currentTrack.urls.album}
|
|
- label='Copy Album URL'
|
|
- action={() => clipboard.writeText(this.props.currentTrack.urls.album)}
|
|
- />
|
|
- <Menu.MenuItem
|
|
- id='copy-song'
|
|
- label='Copy Song URL'
|
|
- action={() => clipboard.writeText(this.props.currentTrack.urls.track)}
|
|
- />
|
|
- </Menu.MenuGroup>
|
|
- );
|
|
- }
|
|
-}
|
|
-
|
|
-module.exports = Flux.connectStores(
|
|
- [ songsStore, playerStore, powercord.api.settings.store ],
|
|
- (props) => ({
|
|
- ...songsStore.getStore(),
|
|
- ...playerStore.getStore(),
|
|
- ...powercord.api.settings._fluxProps(props.entityID)
|
|
- })
|
|
-)(ContextMenu);
|
|
diff --git a/src/Powercord/plugins/pc-spotify/components/PayUp.jsx b/src/Powercord/plugins/pc-spotify/components/PayUp.jsx
|
|
deleted file mode 100644
|
|
index c5af79a..0000000
|
|
--- a/src/Powercord/plugins/pc-spotify/components/PayUp.jsx
|
|
+++ /dev/null
|
|
@@ -1,47 +0,0 @@
|
|
-const { React, getModule, constants: { SpotifyEndpoints }, i18n: { Messages } } = require('powercord/webpack');
|
|
-const { FormTitle, Button } = require('powercord/components');
|
|
-const { Modal } = require('powercord/components/modal');
|
|
-const { close: closeModal } = require('powercord/modal');
|
|
-
|
|
-module.exports = React.memo(
|
|
- () => {
|
|
- const { size16 } = getModule([ 'size16' ], false);
|
|
- const { marginBottom20 } = getModule([ 'marginBottom20' ], false);
|
|
-
|
|
- return (
|
|
- <Modal className='powercord-text'>
|
|
- <Modal.Header>
|
|
- <FormTitle tag='h4'>Spotify Premium Required</FormTitle>
|
|
- <Modal.CloseButton onClick={() => closeModal()}/>
|
|
- </Modal.Header>
|
|
- <Modal.Content>
|
|
- <div className={`${size16} ${marginBottom20}`}>
|
|
- To control your Spotify playback we use Spotify's "Connect Web" API, which is unfortunately locked to
|
|
- Spotify Premium users. In order for you to control Spotify's playback you'll need to get a Premium
|
|
- subscription.
|
|
- </div>
|
|
- <div className={`${size16} ${marginBottom20}`}>
|
|
- If you do happen to have a Spotify Premium subscription but you're still not seeing the buttons show up,
|
|
- it might happen that Spotify is reporting inaccurate data about your Premium status and alters the
|
|
- availability of the buttons. Try changing the playback in any way (play, pause, change track, ...) to
|
|
- trigger and update and let us get accurate data from Spotify.
|
|
- </div>
|
|
- <div className={`${size16} ${marginBottom20}`}>
|
|
- If this still did not fix it, make sure you're not in a private session as we've received a few reports
|
|
- saying this causes your Premium subscription status to not be properly sent to us. Also make sure you do
|
|
- have your Spotify account linked to your Discord account (You can tell Discord to not show it on your
|
|
- profile and not show the song you're listening to in your status, if you don't want to)
|
|
- </div>
|
|
- </Modal.Content>
|
|
- <Modal.Footer>
|
|
- <Button onClick={() => window.open(SpotifyEndpoints.PREMIUM_SITE)}>
|
|
- {Messages.SPOTIFY_PREMIUM_UPGRADE_BUTTON}
|
|
- </Button>
|
|
- <Button onClick={() => closeModal()} look={Button.Looks.LINK} color={Button.Colors.TRANSPARENT}>
|
|
- {Messages.PREMIUM_DOWNGRADE_DONE_BUTTON}
|
|
- </Button>
|
|
- </Modal.Footer>
|
|
- </Modal>
|
|
- );
|
|
- }
|
|
-);
|
|
diff --git a/src/Powercord/plugins/pc-spotify/components/SeekBar.jsx b/src/Powercord/plugins/pc-spotify/components/SeekBar.jsx
|
|
deleted file mode 100644
|
|
index 48b96cb..0000000
|
|
--- a/src/Powercord/plugins/pc-spotify/components/SeekBar.jsx
|
|
+++ /dev/null
|
|
@@ -1,121 +0,0 @@
|
|
-const { React } = require('powercord/webpack');
|
|
-const { formatTime } = require('powercord/util');
|
|
-const SpotifyAPI = require('../SpotifyAPI');
|
|
-
|
|
-class SeekBar extends React.PureComponent {
|
|
- constructor (props) {
|
|
- super(props);
|
|
-
|
|
- this.state = {
|
|
- seeking: false,
|
|
- progress: null,
|
|
- wasPlaying: false,
|
|
- listeners: {}
|
|
- };
|
|
-
|
|
- this._overflowFired = false;
|
|
- this.seek = this.seek.bind(this);
|
|
- this.endSeek = this.endSeek.bind(this, false);
|
|
- }
|
|
-
|
|
- componentDidMount () {
|
|
- this._renderInterval = setInterval(() => this.forceUpdate(), 500);
|
|
- }
|
|
-
|
|
- componentDidUpdate (prevProps) {
|
|
- if (!this.state.seeking && this.props.progress !== prevProps.progress) {
|
|
- this.setState({ progress: null });
|
|
- }
|
|
- }
|
|
-
|
|
- componentWillUnmount () {
|
|
- if (this.state.listeners.seek) {
|
|
- document.removeEventListener('mousemove', this.seek);
|
|
- }
|
|
-
|
|
- if (this.state.listeners.stop) {
|
|
- document.removeEventListener('mouseup', this.endSeek);
|
|
- }
|
|
-
|
|
- if (this._renderInterval) {
|
|
- clearInterval(this._renderInterval);
|
|
- }
|
|
- }
|
|
-
|
|
- async startSeek (e) {
|
|
- if (!this.props.isPremium) {
|
|
- return;
|
|
- }
|
|
-
|
|
- this.seek(e);
|
|
- document.addEventListener('mousemove', this.seek);
|
|
- document.addEventListener('mouseup', this.endSeek);
|
|
-
|
|
- this.props.onSeeking(true);
|
|
- this.setState({
|
|
- seeking: true,
|
|
- wasPlaying: this.props.isPlaying
|
|
- });
|
|
- if (this.props.isPlaying && !await SpotifyAPI.pause()) {
|
|
- await this.endSeek(true);
|
|
- }
|
|
- }
|
|
-
|
|
- seek ({ clientX: mouseX }) {
|
|
- const { x, width } = document.querySelector('.spotify-seek-bar').getBoundingClientRect();
|
|
- const delta = mouseX - x;
|
|
- const seek = delta / width;
|
|
- this.setState({ progress: Math.round(this.props.duration * Math.max(0, Math.min(seek, 1))) });
|
|
- }
|
|
-
|
|
- async endSeek (cancel) {
|
|
- document.removeEventListener('mousemove', this.seek);
|
|
- document.removeEventListener('mouseup', this.endSeek);
|
|
-
|
|
- this.props.onSeeking(false);
|
|
- this.setState({ seeking: false });
|
|
- if (cancel) {
|
|
- this.setState({ progress: false });
|
|
- } else {
|
|
- await SpotifyAPI.seek(this.state.progress);
|
|
- if (this.state.wasPlaying) {
|
|
- await SpotifyAPI.play();
|
|
- }
|
|
- }
|
|
- }
|
|
-
|
|
- render () {
|
|
- const rawProgress = this.state.progress || this.props.progress;
|
|
- const progress = (this.props.isPlaying && !this.state.seeking)
|
|
- ? rawProgress + (Date.now() - this.props.progressAt)
|
|
- : rawProgress;
|
|
- const trimmedProgress = Math.min(progress, this.props.duration);
|
|
- const current = trimmedProgress / this.props.duration * 100;
|
|
- const isOverflowing = progress - trimmedProgress > 2000;
|
|
- if (isOverflowing && !this._overflowFired) {
|
|
- this._overflowFired = true;
|
|
- this.props.onDurationOverflow();
|
|
- } else if (!isOverflowing && this._overflowFired) {
|
|
- this._overflowFired = false;
|
|
- }
|
|
-
|
|
- return (
|
|
- <div className={[ 'spotify-seek', !this.props.isPremium && 'no-premium' ].filter(Boolean).join(' ')}>
|
|
- <div className='spotify-seek-elements'>
|
|
- <span className='spotify-seek-duration'>
|
|
- {formatTime(progress)}
|
|
- </span>
|
|
- <span className='spotify-seek-duration'>
|
|
- {formatTime(this.props.duration)}
|
|
- </span>
|
|
- </div>
|
|
- <div className='spotify-seek-bar' onMouseDown={(e) => this.startSeek(e)}>
|
|
- <span className='spotify-seek-bar-progress' style={{ width: `${current}%` }}/>
|
|
- <span className='spotify-seek-bar-cursor' style={{ left: `${current}%` }}/>
|
|
- </div>
|
|
- </div>
|
|
- );
|
|
- }
|
|
-}
|
|
-
|
|
-module.exports = SeekBar;
|
|
diff --git a/src/Powercord/plugins/pc-spotify/components/Settings.jsx b/src/Powercord/plugins/pc-spotify/components/Settings.jsx
|
|
deleted file mode 100644
|
|
index 94ceb55..0000000
|
|
--- a/src/Powercord/plugins/pc-spotify/components/Settings.jsx
|
|
+++ /dev/null
|
|
@@ -1,35 +0,0 @@
|
|
-const { React } = require('powercord/webpack');
|
|
-const { SwitchItem } = require('powercord/components/settings');
|
|
-
|
|
-module.exports = React.memo(
|
|
- ({ getSetting, toggleSetting, patch }) => (
|
|
- <div>
|
|
- <SwitchItem
|
|
- note='Shows covers in a square shape instead of a rounded one.'
|
|
- value={getSetting('squareCovers', false)}
|
|
- onChange={() => toggleSetting('squareCovers')}
|
|
- >
|
|
- Squared covers
|
|
- </SwitchItem>
|
|
-
|
|
- <SwitchItem
|
|
- note='Adds shuffle, repeat and other controls to the Spotify modal. Increases the height if enabled, if not these controls are available in the context menu.'
|
|
- value={getSetting('showControls', true)}
|
|
- onChange={() => toggleSetting('showControls')}
|
|
- >
|
|
- Show advanced controls
|
|
- </SwitchItem>
|
|
-
|
|
- <SwitchItem
|
|
- note={'Prevents Discord from automatically pausing Spotify playback if you\'re sending voice for more than 30 seconds.'}
|
|
- value={getSetting('noAutoPause', true)}
|
|
- onChange={() => {
|
|
- patch(getSetting('noAutoPause', true));
|
|
- toggleSetting('noAutoPause');
|
|
- }}
|
|
- >
|
|
- No auto pause
|
|
- </SwitchItem>
|
|
- </div>
|
|
- )
|
|
-);
|
|
diff --git a/src/Powercord/plugins/pc-spotify/components/ShareModal.jsx b/src/Powercord/plugins/pc-spotify/components/ShareModal.jsx
|
|
deleted file mode 100644
|
|
index dbbfd8b..0000000
|
|
--- a/src/Powercord/plugins/pc-spotify/components/ShareModal.jsx
|
|
+++ /dev/null
|
|
@@ -1,65 +0,0 @@
|
|
-const { React, messages, channels } = require('powercord/webpack');
|
|
-const { FormTitle, Text } = require('powercord/components');
|
|
-const { Modal } = require('powercord/components/modal');
|
|
-const { close: closeModal } = require('powercord/modal');
|
|
-const { SPOTIFY_DEFAULT_IMAGE } = require('../constants');
|
|
-
|
|
-class Track extends React.PureComponent {
|
|
- handleClick (item) {
|
|
- return messages.sendMessage(
|
|
- channels.getChannelId(),
|
|
- { content: item.external_urls.spotify }
|
|
- ).then(() => closeModal());
|
|
- }
|
|
-
|
|
- render () {
|
|
- const image = this.props.item.album.images[0]
|
|
- ? <img className='image' alt='cover' src={this.props.item.album.images[0].url} height='50' width='50' />
|
|
- : <img className='image' alt='cover' src={SPOTIFY_DEFAULT_IMAGE} height='50' />;
|
|
- return (
|
|
- <div className='powercord-spotify-playlist' onClick={() => this.handleClick(this.props.item)}>
|
|
- {image}
|
|
- <span className='name'>{this.props.item.name}</span>
|
|
- </div>
|
|
- );
|
|
- }
|
|
-}
|
|
-
|
|
-module.exports = class ShareModal extends React.PureComponent {
|
|
- constructor () {
|
|
- super();
|
|
-
|
|
- this.state = {
|
|
- tracks: []
|
|
- };
|
|
- }
|
|
-
|
|
- async componentDidMount () {
|
|
- this.setState({ tracks: await this.props.tracks.items });
|
|
- }
|
|
-
|
|
- render () {
|
|
- const { tracks } = this.state;
|
|
- const trackList = [];
|
|
- tracks.forEach(track => {
|
|
- trackList.push(<Track className='powercord-spotify-playlist' item={track}/>);
|
|
- });
|
|
- return (
|
|
- <Modal size={Modal.Sizes.MEDIUM}>
|
|
- <Modal.Header>
|
|
- <FormTitle tag='h4'>Multiple tracks found - "{this.props.query}"</FormTitle>
|
|
- <Modal.CloseButton onClick={() => closeModal()}/>
|
|
- </Modal.Header>
|
|
- <Modal.Content>
|
|
- <Text color={Text.Colors.PRIMARY} size={Text.Sizes.MEDIUM}>
|
|
- Please select the track that you wish to share to the current channel.
|
|
- </Text>
|
|
- <FormTitle style={{ marginTop: '16px' }}>Available tracks</FormTitle>
|
|
- <div className='powercord-spotify-playlist-group'>
|
|
- {trackList}
|
|
- </div>
|
|
- </Modal.Content>
|
|
- </Modal>
|
|
- );
|
|
- }
|
|
-};
|
|
diff --git a/src/Powercord/plugins/pc-spotify/constants.js b/src/Powercord/plugins/pc-spotify/constants.js
|
|
deleted file mode 100644
|
|
index 4245866..0000000
|
|
--- a/src/Powercord/plugins/pc-spotify/constants.js
|
|
+++ /dev/null
|
|
@@ -1,21 +0,0 @@
|
|
-module.exports = Object.freeze({
|
|
- FluxActions: {
|
|
- DEVICES_FETCHED: 'SPOTIFY_DEVICES_FETCHED',
|
|
- CURRENT_TRACK_UPDATED: 'SPOTIFY_CURRENT_TRACK_UPDATED',
|
|
- PLAYER_STATE_UPDATED: 'SPOTIFY_PLAYER_STATE_UPDATED',
|
|
- LIBRARY_STATE_UPDATED: 'SPOTIFY_LIBRARY_STATE_UPDATED',
|
|
- SONGS_LOADED: 'SPOTIFY_SONGS_LOADED',
|
|
- TOP_SONGS_LOADED: 'SPOTIFY_TOP_SONGS_LOADED',
|
|
- ALBUMS_LOADED: 'SPOTIFY_ALBUMS_LOADED',
|
|
- ALBUM_TRACKS_LOADED: 'SPOTIFY_ALBUMS_TRACKS_LOADED',
|
|
- PLAYLISTS_LOADED: 'SPOTIFY_PLAYLISTS_LOADED',
|
|
- PLAYLIST_TRACKS_LOADED: 'SPOTIFY_PLAYLISTS_TRACKS_LOADED',
|
|
- PLAYLIST_TRACK_ADDED: 'SPOTIFY_PLAYLIST_TRACK_ADDED',
|
|
- PLAYLIST_TRACK_REMOVED: 'SPOTIFY_PLAYLIST_TRACK_REMOVED',
|
|
- PURGE_SONGS: 'SPOTIFY_PURGE_SONGS'
|
|
- },
|
|
- SPOTIFY_BASE_URL: 'https://api.spotify.com/v1',
|
|
- SPOTIFY_PLAYER_URL: 'https://api.spotify.com/v1/me/player',
|
|
- SPOTIFY_DEFAULT_IMAGE: 'https://www.scdn.co/i/_global/favicon.png',
|
|
- SPOTIFY_COLOR: '#1ed860'
|
|
-});
|
|
diff --git a/src/Powercord/plugins/pc-spotify/i18n/en-US.json b/src/Powercord/plugins/pc-spotify/i18n/en-US.json
|
|
deleted file mode 100644
|
|
index 8174978..0000000
|
|
--- a/src/Powercord/plugins/pc-spotify/i18n/en-US.json
|
|
+++ /dev/null
|
|
@@ -1,6 +0,0 @@
|
|
-{
|
|
- "SPOTIFY_NOT_PREMIUM": "You are not Spotify Premium.",
|
|
- "SPOTIFY_ADD_LIKED_SONGS": "Save to Liked Songs",
|
|
- "SPOTIFY_REMOVE_LIKED_SONGS": "Remove from Liked Songs",
|
|
- "SPOTIFY_CANT_LIKE_LOCAL": "You can't add a local song to your Liked Songs"
|
|
-}
|
|
diff --git a/src/Powercord/plugins/pc-spotify/i18n/index.js b/src/Powercord/plugins/pc-spotify/i18n/index.js
|
|
deleted file mode 100644
|
|
index d59ce4c..0000000
|
|
--- a/src/Powercord/plugins/pc-spotify/i18n/index.js
|
|
+++ /dev/null
|
|
@@ -1,7 +0,0 @@
|
|
-require('fs')
|
|
- .readdirSync(__dirname)
|
|
- .filter((file) => file !== 'index.js' && file !== '.DS_Store')
|
|
- .forEach(filename => {
|
|
- const moduleName = filename.split('.')[0];
|
|
- exports[moduleName] = require(`${__dirname}/${filename}`);
|
|
- });
|
|
diff --git a/src/Powercord/plugins/pc-spotify/index.js b/src/Powercord/plugins/pc-spotify/index.js
|
|
deleted file mode 100644
|
|
index 331f773..0000000
|
|
--- a/src/Powercord/plugins/pc-spotify/index.js
|
|
+++ /dev/null
|
|
@@ -1,260 +0,0 @@
|
|
-const { Plugin } = require('powercord/entities');
|
|
-const { React, getModule, spotify, spotifySocket } = require('powercord/webpack');
|
|
-const { inject, uninject } = require('powercord/injector');
|
|
-const { waitFor, getOwnerInstance, findInTree, sleep } = require('powercord/util');
|
|
-const playerStoreActions = require('./playerStore/actions');
|
|
-const playerStore = require('./playerStore/store');
|
|
-const songsStoreActions = require('./songsStore/actions');
|
|
-const songsStore = require('./songsStore/store');
|
|
-const i18n = require('./i18n');
|
|
-const commands = require('./commands');
|
|
-
|
|
-const SpotifyAPI = require('./SpotifyAPI');
|
|
-
|
|
-const Settings = require('./components/Settings');
|
|
-const Modal = require('./components/Modal');
|
|
-
|
|
-class Spotify extends Plugin {
|
|
- get color () {
|
|
- return '#1ed860';
|
|
- }
|
|
-
|
|
- get playerStore () {
|
|
- return playerStore;
|
|
- }
|
|
-
|
|
- get songsStore () {
|
|
- return songsStore;
|
|
- }
|
|
-
|
|
- get SpotifyAPI () {
|
|
- return SpotifyAPI;
|
|
- }
|
|
-
|
|
- startPlugin () {
|
|
- powercord.api.i18n.loadAllStrings(i18n);
|
|
- this.loadStylesheet('style.scss');
|
|
- this._injectSocket();
|
|
- this._injectModal();
|
|
- this._patchAutoPause();
|
|
- spotify.fetchIsSpotifyProtocolRegistered();
|
|
-
|
|
- SpotifyAPI.getPlayer()
|
|
- .then((player) => this._handlePlayerState(player))
|
|
- .catch((e) => this.error('Failed to get player', e));
|
|
-
|
|
- playerStoreActions.fetchDevices()
|
|
- .catch((e) => this.error('Failed to fetch devices', e));
|
|
-
|
|
- powercord.api.settings.registerSettings('pc-spotify', {
|
|
- category: this.entityID,
|
|
- label: 'Spotify',
|
|
- render: (props) =>
|
|
- React.createElement(Settings, {
|
|
- patch: this._patchAutoPause.bind(this),
|
|
- ...props
|
|
- })
|
|
- });
|
|
-
|
|
- Object.values(commands).forEach(cmd => powercord.api.commands.registerCommand(cmd));
|
|
- }
|
|
-
|
|
- pluginWillUnload () {
|
|
- uninject('pc-spotify-socket');
|
|
- uninject('pc-spotify-modal');
|
|
- // this._applySocketChanges();
|
|
- this._patchAutoPause(true);
|
|
- Object.values(commands).forEach(cmd => powercord.api.commands.unregisterCommand(cmd.command));
|
|
- powercord.api.settings.unregisterSettings('pc-spotify');
|
|
- spotifySocket.getActiveSocketAndDevice()?.socket.socket.close();
|
|
- songsStoreActions.purgeSongs();
|
|
-
|
|
- const { container } = getModule([ 'container', 'usernameContainer' ], false);
|
|
- const accountContainer = document.querySelector(`section > .${container}`);
|
|
- const instance = getOwnerInstance(accountContainer);
|
|
- instance.forceUpdate();
|
|
- }
|
|
-
|
|
- async _injectSocket () {
|
|
- const { SpotifySocket } = await getModule([ 'SpotifySocket' ]);
|
|
- inject('pc-spotify-socket', SpotifySocket.prototype, 'handleMessage', ([ e ]) => this._handleSpotifyMessage(e));
|
|
- spotifySocket.getActiveSocketAndDevice()?.socket.socket.close();
|
|
- }
|
|
-
|
|
- async _injectModal () {
|
|
- await sleep(1e3); // It ain't stupid if it works
|
|
- const { container } = await getModule([ 'container', 'usernameContainer' ]);
|
|
- const accountContainer = await waitFor(`section > .${container}`);
|
|
- const instance = getOwnerInstance(accountContainer);
|
|
- await inject('pc-spotify-modal', instance.__proto__, 'render', (_, res) => {
|
|
- const realRes = findInTree(res, t => t.props && t.props.className === container);
|
|
- return [
|
|
- React.createElement(Modal, {
|
|
- entityID: this.entityID,
|
|
- base: realRes
|
|
- }),
|
|
- res
|
|
- ];
|
|
- });
|
|
- instance.forceUpdate();
|
|
- }
|
|
-
|
|
- _patchAutoPause (revert) {
|
|
- if (this.settings.get('noAutoPause', true)) {
|
|
- const spotifyMdl = getModule([ 'initialize', 'wasAutoPaused' ], false);
|
|
- if (revert) {
|
|
- spotifyMdl.wasAutoPaused = spotifyMdl._wasAutoPaused;
|
|
- spotify.pause = spotify._pause;
|
|
- } else {
|
|
- spotifyMdl._wasAutoPaused = spotifyMdl.wasAutoPaused;
|
|
- spotifyMdl.wasAutoPaused = () => false;
|
|
- spotify._pause = spotify.pause;
|
|
- spotify.pause = () => void 0;
|
|
- }
|
|
- }
|
|
- }
|
|
-
|
|
- _handleSpotifyMessage (msg) {
|
|
- const data = JSON.parse(msg.data);
|
|
- if (!data.type === 'message' || !data.payloads) {
|
|
- return;
|
|
- }
|
|
-
|
|
- const collectionRegex = /hm:\/\/collection\/collection\/[\w\d]+\/json/i;
|
|
- const playlistRegex = /hm:\/\/playlist\/v2\/playlist\/[\w\d]+/i;
|
|
- switch (true) {
|
|
- case data.uri === 'wss://event':
|
|
- for (const payload of data.payloads || []) {
|
|
- for (const event of payload.events || []) {
|
|
- this._handleSpotifyEvent(event);
|
|
- }
|
|
- }
|
|
- break;
|
|
- case collectionRegex.test(data.uri): {
|
|
- const currentTrack = playerStore.getCurrentTrack();
|
|
- if (!currentTrack) {
|
|
- // Useless to further process the event
|
|
- return;
|
|
- }
|
|
-
|
|
- for (const rawPayload of data.payloads || []) {
|
|
- const payload = JSON.parse(rawPayload);
|
|
- for (const track of payload.items) {
|
|
- if (track.identifier === currentTrack.id) {
|
|
- playerStoreActions.updateCurrentLibraryState(
|
|
- track.removed ? playerStore.LibraryState.NOT_IN_LIBRARY : playerStore.LibraryState.IN_LIBRARY
|
|
- );
|
|
- }
|
|
- }
|
|
- }
|
|
- break;
|
|
- }
|
|
- case playlistRegex.test(data.uri):
|
|
- for (const hermes of data.payloads || []) {
|
|
- const payload = this._decodePlaylistHermes(hermes);
|
|
- if (payload.added) {
|
|
- songsStoreActions.addTrack(payload.playlistId, payload.trackId);
|
|
- } else {
|
|
- songsStoreActions.deleteTrack(payload.playlistId, payload.trackId);
|
|
- }
|
|
- }
|
|
- break;
|
|
- }
|
|
- }
|
|
-
|
|
- _handleSpotifyEvent (evt) {
|
|
- switch (evt.type) {
|
|
- case 'PLAYER_STATE_CHANGED':
|
|
- this._handlePlayerState(evt.event.state);
|
|
- break;
|
|
- case 'DEVICE_STATE_CHANGED':
|
|
- playerStoreActions.fetchDevices();
|
|
- break;
|
|
- }
|
|
- }
|
|
-
|
|
- _handlePlayerState (state) {
|
|
- if (!state.timestamp) {
|
|
- return;
|
|
- }
|
|
-
|
|
- // Handle track
|
|
- const currentTrack = playerStore.getCurrentTrack();
|
|
- if (!currentTrack || currentTrack.id !== state.item.id) {
|
|
- if (this._libraryTimeout) {
|
|
- clearTimeout(this._libraryTimeout);
|
|
- }
|
|
- if (!state.item.is_local && powercord.account && powercord.account.accounts.spotify) {
|
|
- this._libraryTimeout = setTimeout(() => {
|
|
- SpotifyAPI.checkLibrary(state.item.id).then(r => playerStoreActions.updateCurrentLibraryState(
|
|
- r.body[0]
|
|
- ? playerStore.LibraryState.IN_LIBRARY
|
|
- : playerStore.LibraryState.NOT_IN_LIBRARY
|
|
- ));
|
|
- }, 1500);
|
|
- } else if (state.item.is_local) {
|
|
- playerStoreActions.updateCurrentLibraryState(playerStore.LibraryState.LOCAL_SONG);
|
|
- }
|
|
- playerStoreActions.updateCurrentTrack({
|
|
- id: state.item.id,
|
|
- uri: state.item.uri,
|
|
- name: state.item.name,
|
|
- isLocal: state.item.is_local,
|
|
- duration: state.item.duration_ms,
|
|
- explicit: state.item.explicit,
|
|
- cover: state.item.album && state.item.album.images[0] ? state.item.album.images[0].url : null,
|
|
- artists: state.item.artists.map(a => a.name).join(', '),
|
|
- album: state.item.album ? state.item.album.name : null,
|
|
- urls: {
|
|
- track: state.item.external_urls.spotify,
|
|
- album: state.item.album ? state.item.album.external_urls.spotify : null
|
|
- }
|
|
- });
|
|
- }
|
|
-
|
|
- // Handle state
|
|
- playerStoreActions.updatePlayerState({
|
|
- repeat: state.repeat_state === 'track'
|
|
- ? playerStore.RepeatState.REPEAT_TRACK
|
|
- : state.repeat_state === 'context'
|
|
- ? playerStore.RepeatState.REPEAT_CONTEXT
|
|
- : playerStore.RepeatState.NO_REPEAT,
|
|
- shuffle: state.shuffle_state,
|
|
- canRepeat: !state.actions.disallows.toggling_repeat_context,
|
|
- canRepeatOne: !state.actions.disallows.toggling_repeat_track,
|
|
- canShuffle: !state.actions.disallows.toggling_shuffle,
|
|
- spotifyRecordedProgress: state.progress_ms,
|
|
- playing: state.is_playing,
|
|
- volume: state.device.volume_percent
|
|
- });
|
|
- }
|
|
-
|
|
- _decodePlaylistHermes (hermes) {
|
|
- const hex = Buffer.from(hermes, 'base64').toString('hex');
|
|
- const decoded = this._decodeHermes(hex);
|
|
- const trackDetails = this._decodeHermes(decoded[3].hex.substring(18));
|
|
- return {
|
|
- playlistId: decoded[0].utf8.split(':').pop(),
|
|
- trackId: trackDetails[0].utf8.replace(/[\n$]/g, '').split(':').pop(),
|
|
- added: trackDetails.length === 2
|
|
- };
|
|
- }
|
|
-
|
|
- _decodeHermes (hex) {
|
|
- const res = [];
|
|
- for (let i = 0; i < hex.length;) {
|
|
- const length = parseInt(hex.substring(i + 2, i + 4), 16);
|
|
- const rawStr = hex.substring(i + 4, i + 4 + (length * 2));
|
|
- i += (length * 2) + 4;
|
|
- res.push({
|
|
- hex: rawStr,
|
|
- get utf8 () {
|
|
- return Buffer.from(rawStr, 'hex').toString('utf8');
|
|
- }
|
|
- });
|
|
- }
|
|
- return res;
|
|
- }
|
|
-}
|
|
-
|
|
-module.exports = Spotify;
|
|
diff --git a/src/Powercord/plugins/pc-spotify/manifest.json b/src/Powercord/plugins/pc-spotify/manifest.json
|
|
deleted file mode 100644
|
|
index d3b4bf4..0000000
|
|
--- a/src/Powercord/plugins/pc-spotify/manifest.json
|
|
+++ /dev/null
|
|
@@ -1,7 +0,0 @@
|
|
-{
|
|
- "name": "Spotify Modal",
|
|
- "version": "2.0.0",
|
|
- "description": "Better Spotify integration in Discord",
|
|
- "author": "Powercord Team",
|
|
- "license": "MIT"
|
|
-}
|
|
diff --git a/src/Powercord/plugins/pc-spotify/playerStore/actions.js b/src/Powercord/plugins/pc-spotify/playerStore/actions.js
|
|
deleted file mode 100644
|
|
index efaa990..0000000
|
|
--- a/src/Powercord/plugins/pc-spotify/playerStore/actions.js
|
|
+++ /dev/null
|
|
@@ -1,37 +0,0 @@
|
|
-const { FluxDispatcher } = require('powercord/webpack');
|
|
-const { FluxActions } = require('../constants');
|
|
-const SpotifyAPI = require('../SpotifyAPI');
|
|
-
|
|
-module.exports = {
|
|
- fetchDevices: async () => {
|
|
- const { devices } = await SpotifyAPI.getDevices();
|
|
- FluxDispatcher.dirtyDispatch({
|
|
- type: FluxActions.DEVICES_FETCHED,
|
|
- devices
|
|
- });
|
|
- },
|
|
-
|
|
- updateCurrentTrack: (newTrack) => {
|
|
- FluxDispatcher.dirtyDispatch({
|
|
- type: FluxActions.CURRENT_TRACK_UPDATED,
|
|
- track: newTrack
|
|
- });
|
|
- },
|
|
-
|
|
- updatePlayerState: (newState) => {
|
|
- FluxDispatcher.dirtyDispatch({
|
|
- type: FluxActions.PLAYER_STATE_UPDATED,
|
|
- state: {
|
|
- ...newState,
|
|
- spotifyRecordedProgressAt: Date.now()
|
|
- }
|
|
- });
|
|
- },
|
|
-
|
|
- updateCurrentLibraryState: (newState) => {
|
|
- FluxDispatcher.dirtyDispatch({
|
|
- type: FluxActions.LIBRARY_STATE_UPDATED,
|
|
- state: newState
|
|
- });
|
|
- }
|
|
-};
|
|
diff --git a/src/Powercord/plugins/pc-spotify/playerStore/store.js b/src/Powercord/plugins/pc-spotify/playerStore/store.js
|
|
deleted file mode 100644
|
|
index 96bb284..0000000
|
|
--- a/src/Powercord/plugins/pc-spotify/playerStore/store.js
|
|
+++ /dev/null
|
|
@@ -1,98 +0,0 @@
|
|
-const { Flux, FluxDispatcher } = require('powercord/webpack');
|
|
-const { FluxActions } = require('../constants');
|
|
-
|
|
-const LibraryState = Object.freeze({
|
|
- UNKNOWN: 'UNKNOWN',
|
|
- IN_LIBRARY: 'IN_LIBRARY',
|
|
- NOT_IN_LIBRARY: 'NOT_IN_LIBRARY',
|
|
- LOCAL_SONG: 'LOCAL_SONG'
|
|
-});
|
|
-
|
|
-const RepeatState = Object.freeze({
|
|
- NO_REPEAT: 'NO_REPEAT',
|
|
- REPEAT_TRACK: 'REPEAT_TRACK',
|
|
- REPEAT_CONTEXT: 'REPEAT_CONTEXT'
|
|
-});
|
|
-
|
|
-let lastActiveDeviceId = null;
|
|
-let devices = [];
|
|
-let currentTrack = null;
|
|
-let currentLibraryState = LibraryState.UNKNOWN;
|
|
-let playerState = {
|
|
- repeat: RepeatState.NO_REPEAT,
|
|
- shuffle: false,
|
|
- canRepeat: true,
|
|
- canRepeatOne: true,
|
|
- canShuffle: true,
|
|
- spotifyRecordedProgress: 0,
|
|
- spotifyRecordedProgressAt: Date.now(),
|
|
- playing: false,
|
|
- volume: 100
|
|
-};
|
|
-
|
|
-function handleDevicesFetched (fetchedDevices) {
|
|
- devices = fetchedDevices;
|
|
- const activeDevice = devices.find(d => d.is_active);
|
|
- if (activeDevice) {
|
|
- lastActiveDeviceId = activeDevice.id;
|
|
- }
|
|
-}
|
|
-
|
|
-function handleCurrentTrackUpdated (track) {
|
|
- currentLibraryState = LibraryState.UNKNOWN;
|
|
- currentTrack = track;
|
|
-}
|
|
-
|
|
-function handlePlayerStateUpdated (state) {
|
|
- playerState = state;
|
|
-}
|
|
-
|
|
-function handleLibraryStateUpdated (state) {
|
|
- currentLibraryState = state;
|
|
-}
|
|
-
|
|
-class SpotifyPlayerStore extends Flux.Store {
|
|
- get LibraryState () {
|
|
- return LibraryState;
|
|
- }
|
|
-
|
|
- get RepeatState () {
|
|
- return RepeatState;
|
|
- }
|
|
-
|
|
- getStore () {
|
|
- return {
|
|
- devices,
|
|
- currentTrack,
|
|
- currentLibraryState,
|
|
- playerState
|
|
- };
|
|
- }
|
|
-
|
|
- getDevices () {
|
|
- return devices;
|
|
- }
|
|
-
|
|
- getLastActiveDeviceId () {
|
|
- return lastActiveDeviceId;
|
|
- }
|
|
-
|
|
- getCurrentTrack () {
|
|
- return currentTrack;
|
|
- }
|
|
-
|
|
- getCurrentLibraryState () {
|
|
- return currentLibraryState;
|
|
- }
|
|
-
|
|
- getPlayerState () {
|
|
- return playerState;
|
|
- }
|
|
-}
|
|
-
|
|
-module.exports = new SpotifyPlayerStore(FluxDispatcher, {
|
|
- [FluxActions.DEVICES_FETCHED]: ({ devices }) => handleDevicesFetched(devices),
|
|
- [FluxActions.CURRENT_TRACK_UPDATED]: ({ track }) => handleCurrentTrackUpdated(track),
|
|
- [FluxActions.PLAYER_STATE_UPDATED]: ({ state }) => handlePlayerStateUpdated(state),
|
|
- [FluxActions.LIBRARY_STATE_UPDATED]: ({ state }) => handleLibraryStateUpdated(state)
|
|
-});
|
|
diff --git a/src/Powercord/plugins/pc-spotify/songsStore/actions.js b/src/Powercord/plugins/pc-spotify/songsStore/actions.js
|
|
deleted file mode 100644
|
|
index 6ebeb6b..0000000
|
|
--- a/src/Powercord/plugins/pc-spotify/songsStore/actions.js
|
|
+++ /dev/null
|
|
@@ -1,115 +0,0 @@
|
|
-const { FluxDispatcher } = require('powercord/webpack');
|
|
-const { FluxActions } = require('../constants');
|
|
-const SpotifyAPI = require('../SpotifyAPI');
|
|
-
|
|
-function formatTracks (spotifyTracks) {
|
|
- return Object.fromEntries(
|
|
- spotifyTracks.map(t => [
|
|
- t.id || t.uri,
|
|
- {
|
|
- uri: t.uri,
|
|
- name: t.name,
|
|
- isLocal: t.is_local,
|
|
- duration: t.duration_ms,
|
|
- explicit: t.explicit
|
|
- }
|
|
- ])
|
|
- );
|
|
-}
|
|
-
|
|
-module.exports = {
|
|
- loadSongs: async () => {
|
|
- const songs = await SpotifyAPI.getSongs();
|
|
- FluxDispatcher.dirtyDispatch({
|
|
- type: FluxActions.SONGS_LOADED,
|
|
- songs: formatTracks(songs.map(s => s.track))
|
|
- });
|
|
- },
|
|
-
|
|
- loadTopSongs: async () => {
|
|
- const topSongs = await SpotifyAPI.getTopSongs();
|
|
- FluxDispatcher.dirtyDispatch({
|
|
- type: FluxActions.TOP_SONGS_LOADED,
|
|
- topSongs: formatTracks(topSongs.items)
|
|
- });
|
|
- },
|
|
-
|
|
- loadAlbums: async () => {
|
|
- const albums = await SpotifyAPI.getAlbums();
|
|
- FluxDispatcher.dirtyDispatch({
|
|
- type: FluxActions.ALBUMS_LOADED,
|
|
- albums: Object.fromEntries(
|
|
- albums.map(({ album }) => [
|
|
- album.id,
|
|
- {
|
|
- uri: album.uri,
|
|
- name: album.name,
|
|
- tracks: formatTracks(album.tracks.items),
|
|
- tracksLoaded: !album.tracks.next
|
|
- }
|
|
- ])
|
|
- )
|
|
- });
|
|
-
|
|
- albums.filter(({ album: { tracks: { next } } }) => !next).forEach(async ({ album: { id, tracks } }) => {
|
|
- const albumTracks = await SpotifyAPI.getAlbumTracks(id, tracks.limit, tracks.limit);
|
|
- FluxDispatcher.dirtyDispatch({
|
|
- type: FluxActions.ALBUM_TRACKS_LOADED,
|
|
- albumId: id,
|
|
- tracks: formatTracks(tracks.items.concat(albumTracks))
|
|
- });
|
|
- });
|
|
- },
|
|
-
|
|
- loadPlaylists: async () => {
|
|
- const playlists = await SpotifyAPI.getPlaylists();
|
|
- FluxDispatcher.dirtyDispatch({
|
|
- type: FluxActions.PLAYLISTS_LOADED,
|
|
- playlists: Object.fromEntries(
|
|
- playlists.map(playlist => [
|
|
- playlist.id,
|
|
- {
|
|
- uri: playlist.uri,
|
|
- name: playlist.name,
|
|
- icon: playlist.images[0] ? playlist.images[0].url : null,
|
|
- editable: playlist.owner.display_name === powercord.account.accounts.spotify || playlist.collaborative,
|
|
- tracksLoaded: false
|
|
- }
|
|
- ])
|
|
- )
|
|
- });
|
|
-
|
|
- playlists.forEach(async ({ id }) => {
|
|
- const playlistTracks = await SpotifyAPI.getPlaylistTracks(id);
|
|
- FluxDispatcher.dirtyDispatch({
|
|
- type: FluxActions.PLAYLIST_TRACKS_LOADED,
|
|
- playlistId: id,
|
|
- tracks: formatTracks(playlistTracks.map(pt => pt.track))
|
|
- });
|
|
- });
|
|
- },
|
|
-
|
|
- addTrack: async (playlistId, trackId) => {
|
|
- const track = await SpotifyAPI.getTrack(trackId);
|
|
- FluxDispatcher.dirtyDispatch({
|
|
- type: FluxActions.PLAYLIST_TRACK_ADDED,
|
|
- playlistId,
|
|
- trackId,
|
|
- trackDetails: {
|
|
- name: track.name,
|
|
- duration: track.duration_ms,
|
|
- explicit: track.explicit
|
|
- }
|
|
- });
|
|
- },
|
|
-
|
|
- deleteTrack: (playlistId, trackId) => {
|
|
- FluxDispatcher.dirtyDispatch({
|
|
- type: FluxActions.PLAYLIST_TRACK_REMOVED,
|
|
- playlistId,
|
|
- trackId
|
|
- });
|
|
- },
|
|
-
|
|
- purgeSongs: () => FluxDispatcher.dirtyDispatch({ type: FluxActions.PURGE_SONGS })
|
|
-};
|
|
diff --git a/src/Powercord/plugins/pc-spotify/songsStore/store.js b/src/Powercord/plugins/pc-spotify/songsStore/store.js
|
|
deleted file mode 100644
|
|
index 1c66283..0000000
|
|
--- a/src/Powercord/plugins/pc-spotify/songsStore/store.js
|
|
+++ /dev/null
|
|
@@ -1,161 +0,0 @@
|
|
-const { Flux, FluxDispatcher } = require('powercord/webpack');
|
|
-const { FluxActions } = require('../constants');
|
|
-
|
|
-let songsLoaded = false;
|
|
-let topSongsLoaded = false;
|
|
-let albumsLoaded = false;
|
|
-let playlistsLoaded = false;
|
|
-
|
|
-let songs = {};
|
|
-let topSongs = {};
|
|
-let albums = {};
|
|
-let playlists = {};
|
|
-
|
|
-function handleTopSongsLoaded (topSongsData) {
|
|
- topSongsLoaded = true;
|
|
- topSongs = topSongsData;
|
|
-}
|
|
-
|
|
-function handleSongsLoaded (songsData) {
|
|
- songsLoaded = true;
|
|
- songs = songsData;
|
|
-}
|
|
-
|
|
-function handleAlbumsLoaded (albumsData) {
|
|
- albumsLoaded = true;
|
|
- albums = albumsData;
|
|
-}
|
|
-
|
|
-function handleAlbumTracksLoaded (albumId, tracks) {
|
|
- albums = {
|
|
- ...albums,
|
|
- [albumId]: {
|
|
- ...albums[albumId],
|
|
- tracksLoaded: true,
|
|
- tracks
|
|
- }
|
|
- };
|
|
-}
|
|
-
|
|
-function handlePlaylistsLoaded (playlistsData) {
|
|
- playlistsLoaded = true;
|
|
- playlists = playlistsData;
|
|
-}
|
|
-
|
|
-function handlePlaylistTracksLoaded (playlistId, tracks) {
|
|
- playlists = {
|
|
- ...playlists,
|
|
- [playlistId]: {
|
|
- ...playlists[playlistId],
|
|
- tracksLoaded: true,
|
|
- tracks
|
|
- }
|
|
- };
|
|
-}
|
|
-
|
|
-function handlePlaylistTrackAdded (playlistId, trackId, trackDetails) {
|
|
- if (playlists[playlistId]) { // If the playlist doesn't exist it means it hasn't been loaded; Let's skip the event
|
|
- playlists = {
|
|
- ...playlists,
|
|
- [playlistId]: {
|
|
- ...playlists[playlistId],
|
|
- tracks: {
|
|
- ...playlists[playlistId].tracks,
|
|
- [trackId]: trackDetails
|
|
- }
|
|
- }
|
|
- };
|
|
- }
|
|
-}
|
|
-
|
|
-function handlePlaylistTrackRemoved (playlistId, trackId) {
|
|
- if (playlists[playlistId]) { // If the playlist doesn't exist it means it hasn't been loaded; Let's skip the event
|
|
- delete playlists[playlistId].tracks[trackId];
|
|
- playlists = global._.cloneDeep(playlists);
|
|
- }
|
|
-}
|
|
-
|
|
-function handlePurgeSongs () {
|
|
- songsLoaded = false;
|
|
- topSongsLoaded = false;
|
|
- albumsLoaded = false;
|
|
- playlistsLoaded = false;
|
|
- songs = {};
|
|
- topSongs = {};
|
|
- albums = {};
|
|
- playlists = {};
|
|
-}
|
|
-
|
|
-class SpotifyPlaylistsStore extends Flux.Store {
|
|
- getStore () {
|
|
- return {
|
|
- songsLoaded,
|
|
- topSongsLoaded,
|
|
- albumsLoaded,
|
|
- playlistsLoaded,
|
|
- songs,
|
|
- topSongs,
|
|
- albums,
|
|
- playlists
|
|
- };
|
|
- }
|
|
-
|
|
- getSongsLoaded () {
|
|
- return songsLoaded;
|
|
- }
|
|
-
|
|
- getSongs () {
|
|
- return songs;
|
|
- }
|
|
-
|
|
- getTopSongsLoaded () {
|
|
- return topSongsLoaded;
|
|
- }
|
|
-
|
|
- getTopSongs () {
|
|
- return topSongs;
|
|
- }
|
|
-
|
|
- getPlaylistsLoaded () {
|
|
- return playlistsLoaded;
|
|
- }
|
|
-
|
|
- getPlaylists () {
|
|
- return playlists;
|
|
- }
|
|
-
|
|
- getPlaylist (playlistId) {
|
|
- return playlists[playlistId];
|
|
- }
|
|
-
|
|
- isInPlaylist (playlistId, trackId) {
|
|
- if (!playlists[playlistId]) {
|
|
- return false;
|
|
- }
|
|
- return Object.keys(playlists[playlistId].tracks).includes(trackId);
|
|
- }
|
|
-
|
|
- getAlbumsLoaded () {
|
|
- return albumsLoaded;
|
|
- }
|
|
-
|
|
- getAlbumbs () {
|
|
- return albums;
|
|
- }
|
|
-
|
|
- getAlbum (albumId) {
|
|
- return albums[albumId];
|
|
- }
|
|
-}
|
|
-
|
|
-module.exports = new SpotifyPlaylistsStore(FluxDispatcher, {
|
|
- [FluxActions.SONGS_LOADED]: ({ songs }) => handleSongsLoaded(songs),
|
|
- [FluxActions.TOP_SONGS_LOADED]: ({ topSongs }) => handleTopSongsLoaded(topSongs),
|
|
- [FluxActions.ALBUMS_LOADED]: ({ albums }) => handleAlbumsLoaded(albums),
|
|
- [FluxActions.ALBUM_TRACKS_LOADED]: ({ albumId, tracks }) => handleAlbumTracksLoaded(albumId, tracks),
|
|
- [FluxActions.PLAYLISTS_LOADED]: ({ playlists }) => handlePlaylistsLoaded(playlists),
|
|
- [FluxActions.PLAYLIST_TRACKS_LOADED]: ({ playlistId, tracks }) => handlePlaylistTracksLoaded(playlistId, tracks),
|
|
- [FluxActions.PLAYLIST_TRACK_ADDED]: ({ playlistId, trackId, trackDetails }) => handlePlaylistTrackAdded(playlistId, trackId, trackDetails),
|
|
- [FluxActions.PLAYLIST_TRACK_REMOVED]: ({ playlistId, trackId }) => handlePlaylistTrackRemoved(playlistId, trackId),
|
|
- [FluxActions.PURGE_SONGS]: () => handlePurgeSongs()
|
|
-});
|
|
diff --git a/src/Powercord/plugins/pc-spotify/style.scss b/src/Powercord/plugins/pc-spotify/style.scss
|
|
deleted file mode 100644
|
|
index a4a742a..0000000
|
|
--- a/src/Powercord/plugins/pc-spotify/style.scss
|
|
+++ /dev/null
|
|
@@ -1,182 +0,0 @@
|
|
-:root {
|
|
- --spotify-color: #1ed860;
|
|
-}
|
|
-
|
|
-.powercord-spotify {
|
|
- img {
|
|
- margin-bottom: -4px;
|
|
- }
|
|
-
|
|
- .spotify-buttons button {
|
|
- color: var(--spotify-color) !important;
|
|
-
|
|
- > div {
|
|
- color: inherit;
|
|
- }
|
|
- }
|
|
-
|
|
- .spotify-extra-controls {
|
|
- pointer-events: none;
|
|
- display: flex;
|
|
- align-items: center;
|
|
- justify-content: center;
|
|
- margin-top: -10px;
|
|
- opacity: 0;
|
|
- height: 0;
|
|
- transition: height .2s, opacity .2s;
|
|
-
|
|
- .active {
|
|
- color: var(--spotify-color) !important;
|
|
- }
|
|
-
|
|
- + .spotify-seek {
|
|
- margin-top: 3px;
|
|
- }
|
|
- }
|
|
-
|
|
- .spotify-seek {
|
|
- margin-top: -6px;
|
|
- transition: margin-top .2s;
|
|
-
|
|
- &-elements {
|
|
- display: flex;
|
|
- justify-content: space-between;
|
|
- color: var(--text-normal);
|
|
- font-size: 12px;
|
|
- padding: 0 4px 4px;
|
|
- font-weight: 500;
|
|
- opacity: 0;
|
|
- height: 4px;
|
|
- transition: height .2s, opacity .2s;
|
|
- }
|
|
-
|
|
- &-bar {
|
|
- position: relative;
|
|
- height: 2px;
|
|
- transition: height .2s;
|
|
-
|
|
- &-progress {
|
|
- height: 100%;
|
|
- display: block;
|
|
- background-color: var(--spotify-color);
|
|
- }
|
|
-
|
|
- &-cursor {
|
|
- top: 50%;
|
|
- position: absolute;
|
|
- transform: translateY(-50%) translateX(-50%);
|
|
- width: 8px;
|
|
- height: 8px;
|
|
- border-radius: 50%;
|
|
- border: 2px #b9bbbe solid;
|
|
- background-color: #fff;
|
|
- opacity: 0;
|
|
- transition: opacity .2s;
|
|
- }
|
|
- }
|
|
-
|
|
- &:not(.no-premium) {
|
|
- .spotify-seek-bar-progress, .spotify-seek-bar-cursor {
|
|
- cursor: pointer;
|
|
- }
|
|
- }
|
|
- }
|
|
-
|
|
- &.hover {
|
|
- .spotify-extra-controls {
|
|
- pointer-events: initial;
|
|
- height: 32px;
|
|
- opacity: 1;
|
|
-
|
|
- + .spotify-seek {
|
|
- margin-top: -12px;
|
|
- }
|
|
- }
|
|
-
|
|
- .spotify-seek {
|
|
- &-elements {
|
|
- opacity: 1;
|
|
- height: 12px;
|
|
- }
|
|
-
|
|
- &:not(.no-premium) {
|
|
- .spotify-seek-bar {
|
|
- height: 4px;
|
|
-
|
|
- &-cursor {
|
|
- opacity: 1;
|
|
- }
|
|
- }
|
|
- }
|
|
- }
|
|
- }
|
|
-}
|
|
-
|
|
-.spotify-tooltip {
|
|
- max-width: none !important;
|
|
- white-space: nowrap
|
|
-}
|
|
-
|
|
-.spotify-add-to-playlist {
|
|
- .playlists {
|
|
- margin-top: 20px;
|
|
- }
|
|
-
|
|
- .track, .playlist {
|
|
- display: flex;
|
|
- align-items: center;
|
|
-
|
|
- img {
|
|
- width: 48px;
|
|
- height: 48px;
|
|
- margin-right: 16px;
|
|
- }
|
|
-
|
|
- .details {
|
|
- display: flex;
|
|
- flex-direction: column;
|
|
-
|
|
- .title {
|
|
- font-size: 18px;
|
|
- color: var(--header-primary);
|
|
- margin-bottom: 4px;
|
|
- }
|
|
-
|
|
- .artist {
|
|
- font-size: 16px;
|
|
- color: var(--header-secondary);
|
|
- }
|
|
- }
|
|
- }
|
|
-
|
|
- .playlist {
|
|
- display: flex;
|
|
- padding: 16px;
|
|
- margin-bottom: 16px;
|
|
-
|
|
- .details {
|
|
- margin-right: auto;
|
|
- }
|
|
- }
|
|
-}
|
|
-
|
|
-#powercord-spotify-menu {
|
|
- .disabled-1WRMNA[id^='powercord-spotify-menu-devices--'], #powercord-spotify-menu-repeat + div [id*='-active'] {
|
|
- color: var(--spotify-color) !important;
|
|
- opacity: 1 !important;
|
|
- }
|
|
-
|
|
- .colorDefault-2K3EoJ .checkbox-3s5GYZ {
|
|
- color: var(--spotify-color);
|
|
- }
|
|
-
|
|
- .barFill-23-gu- {
|
|
- background-color: var(--spotify-color);
|
|
- }
|
|
-}
|
|
-
|
|
-.theme-dark {
|
|
- .powercord-spotify img[src*='_global/favicon.png'], .spotify-add-to-playlist img[src*='_global/favicon.png'] {
|
|
- filter: invert(1);
|
|
- }
|
|
-}
|