universe/subflakes/powercord/patches/remove-spotify.patch

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);
- }
-}