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} ', - 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} ', - 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 ( - - - Add to Playlist - closeModal()}/> - - -

Where do you want to save this very nice tune?

-
- Spotify Cover -
- - {this.props.track.name} - - - {Messages.USER_ACTIVITY_LISTENING_ARTISTS.format({ - artists: this.props.track.artists, - artistsHook: t => t - })} - -
-
- -
- {!this.props.loaded - ? - : Object.keys(this.props.playlists).map(p => this.renderItem(p))} -
-
- - - -
- ); - } - - renderItem (playlistId) { - const playlist = this.props.playlists[playlistId]; - if (!playlist.editable) { - return null; - } - - return ( - - Spotify Cover -
- - {playlist.name} - - - {playlist.tracksLoaded - ? `${Object.keys(playlist.tracks).length} tracks` - : Messages.DEFAULT_INPUT_PLACEHOLDER} - -
- {playlist.tracksLoaded - ? ( - - ) - : ( - - - - )} -
- ); - } - - handleAddToPlaylist (playlistId) { - closeModal(); - const playlist = this.props.playlists[playlistId]; - if (playlist.tracks[this.props.track.id]) { - openModal(() => ( - { - SpotifyAPI.addToPlaylist(playlistId, this.props.track.uri); - closeModal(); - }} - onCancel={closeModal} - > -
- This item is already in this playlist. Do you want to add it anyway? -
-
- )); - } 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 ( - - {isPremium && this.renderDevices()} - {isPremium && this.renderSongs()} - {isPremium && this.renderPlaybackSettings()} - {isPremium && this.renderVolume()} - {isPremium && this.renderSave()} - {this.renderActions()} - - ); - } - - renderDevices () { - return ( - - - {this.props.devices.sort(d => -Number(d.is_active)).map((device, i) => ( - <> - - {i === 0 && } - - ))} - - - ); - } - - renderSongs () { - const hasCoolFeatures = powercord.account && powercord.account.accounts.spotify; - - return ( - - - {this.props.playlistsLoaded - ? this._renderList(this.props.playlists) - : null} - - {hasCoolFeatures && - {this.props.albumsLoaded - ? this._renderList(this.props.albums) - : null} - } - {hasCoolFeatures && - {this.props.topSongsLoaded - ? this._renderSongs(this.props.topSongs) - : null} - } - {hasCoolFeatures && - {this.props.songsLoaded - ? this._renderSongs(this.props.songs) - : null} - } - - ); - } - - _renderList (list) { - return Object.entries(list).map(([ id, item ]) => ( - setTimeout(() => SpotifyAPI.play({ context_uri: item.uri }), 10)} - > - {item.tracksLoaded - ? this._renderSongs(item.tracks, item.uri) - : null} - - )); - } - - _renderSongs (list, uri) { - return Object.entries(list).map(([ id, item ]) => ( - 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 ( - - - SpotifyAPI.setRepeatState('off')} - disabled={isOff} - /> - SpotifyAPI.setRepeatState('context')} - disabled={isContext || !this.props.playerState.canRepeat} - /> - SpotifyAPI.setRepeatState('track')} - disabled={isTrack || !this.props.playerState.canRepeatOne} - /> - - SpotifyAPI.setShuffleState(!this.props.playerState.shuffle)} - disabled={!this.props.playerState.canShuffle} - /> - - ); - } - - renderVolume () { - const Slider = getModule(m => m.render && m.render.toString().includes('sliderContainer'), false); - return ( - - ( - - )} - /> - - ); - } - - renderSave () { - if (!powercord.account || !powercord.account.accounts.spotify) { - return null; - } - - return ( - - {this.props.currentLibraryState === playerStore.LibraryState.IN_LIBRARY - ? SpotifyAPI.removeSong(this.props.currentTrack.id)} - disabled={[ playerStore.LibraryState.UNKNOWN, playerStore.LibraryState.LOCAL_SONG ].includes(this.props.currentLibraryState)} - /> - : SpotifyAPI.addSong(this.props.currentTrack.id)} - disabled={[ playerStore.LibraryState.UNKNOWN, playerStore.LibraryState.LOCAL_SONG ].includes(this.props.currentLibraryState)} - />} - openModal(() => React.createElement(AddToPlaylist, { track: this.props.currentTrack }))} - /> - - ); - } - - renderActions () { - return ( - - { - const protocol = getModule([ 'isProtocolRegistered', '_dispatchToken' ], false).isProtocolRegistered(); - shell.openExternal(protocol ? this.props.currentTrack.uri : this.props.currentTrack.urls.track); - }} - /> - messages.sendMessage( - channels.getChannelId(), - { content: this.props.currentTrack.urls.album } - )} - /> - messages.sendMessage( - channels.getChannelId(), - { content: this.props.currentTrack.urls.track } - )} - /> - clipboard.writeText(this.props.currentTrack.urls.album)} - /> - clipboard.writeText(this.props.currentTrack.urls.track)} - /> - - ); - } -} - -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/Modal.jsx b/src/Powercord/plugins/pc-spotify/components/Modal.jsx deleted file mode 100644 index c62bece..0000000 --- a/src/Powercord/plugins/pc-spotify/components/Modal.jsx +++ /dev/null @@ -1,274 +0,0 @@ -const { shell } = require('electron'); -const { React, Flux, getModule, getModuleByDisplayName, contextMenu, i18n: { Messages } } = require('powercord/webpack'); -const { AsyncComponent, Icon, Icons: { FontAwesome } } = require('powercord/components'); -const { open: openModal } = require('powercord/modal'); - -const { SPOTIFY_DEFAULT_IMAGE } = require('../constants'); -const SpotifyAPI = require('../SpotifyAPI'); -const playerStore = require('../playerStore/store'); -const playerStoreActions = require('../playerStore/actions'); -const AddToPlaylist = require('./AddToPlaylist'); -const ContextMenu = require('./ContextMenu'); -const SeekBar = require('./SeekBar'); -const PayUp = require('./PayUp'); - -const PanelSubtext = AsyncComponent.from(getModuleByDisplayName('PanelSubtext')); -const Tooltip = AsyncComponent.from(getModuleByDisplayName('Tooltip')); - -class Modal extends React.PureComponent { - constructor (props) { - super(props); - this._rerenderScheduled = false; - this.state = { - hover: false - }; - } - - render () { - if (this.props.devices.length === 0 || !this.props.currentTrack) { - return null; - } - - const isPremium = getModule([ 'isSpotifyPremium' ], false).isSpotifyPremium(); - if (isPremium === null && !this._rerenderScheduled) { - this._rerenderScheduled = true; - setTimeout(() => this.forceUpdate(), 1e3); - } - - return ( -
this.setState({ hover: true })} - onMouseLeave={() => this.setState({ hover: false })} - > - {this.renderFromBase(isPremium)} - {isPremium && this.renderExtraControls()} - this.setState({ seeking })} - onDurationOverflow={() => { - const playerState = playerStore.getPlayerState(); - playerStoreActions.updatePlayerState({ - ...playerState, - playing: false - }); - }} - /> -
- ); - } - - renderFromBase (isPremium) { - const { avatar, avatarWrapper } = getModule([ 'container', 'usernameContainer' ], false); - - return { - ...this.props.base, - props: { - ...this.props.base.props, - onMouseEnter: () => void 0, - onMouseLeave: () => void 0, - onContextMenu: e => contextMenu.openContextMenu(e, () => React.createElement(ContextMenu)), - className: `${this.props.base.props.className || ''}`, - children: [ - ( -
{ - const protocol = getModule([ 'isProtocolRegistered', '_dispatchToken' ], false).isProtocolRegistered(); - shell.openExternal(protocol ? this.props.currentTrack.uri : this.props.currentTrack.urls.track); - }} - > - - {(props) => ( - Spotify cover - )} - -
- ), - ( - - {(tooltipProps) => this.renderNameComponent(tooltipProps)} - - ), - { - ...this.props.base.props.children[1], - props: { - ...this.props.base.props.children[1].props, - className: `${this.props.base.props.children[1].props.className || ''} spotify-buttons`.trim(), - children: isPremium - ? [ - this.renderButton(() => Messages.PAGINATION_PREVIOUS, 'backward', () => SpotifyAPI.prev()), - this.props.playerState.playing - ? this.renderButton(() => Messages.PAUSE, 'pause', () => SpotifyAPI.pause()) - : this.renderButton(() => Messages.PLAY, 'play', () => SpotifyAPI.play()), - this.renderButton(() => Messages.NEXT, 'forward', () => SpotifyAPI.next()) - ] - : this.renderInfoPremium() - } - } - ] - } - }; - } - - renderNameComponent (props = {}) { - const nameComponent = this.props.base.props.children[0].props.children[1].props.children({}); - delete nameComponent.props.onMouseLeave; - delete nameComponent.props.onMouseEnter; - delete nameComponent.props.onClick; - - // [ nameComponent.props.className ] = nameComponent.props.className.split(' '); - Object.assign(nameComponent.props, props); - nameComponent.props.children.props.children[0].props.className = 'spotify-title'; - nameComponent.props.children.props.children[0].props.children.props.children = this.props.currentTrack.name; - nameComponent.props.children.props.children[1] = ( - - {Messages.USER_ACTIVITY_LISTENING_ARTISTS.format({ - artists: this.props.currentTrack.artists, - artistsHook: t => t - })} - - ); - return nameComponent; - } - - renderExtraControls () { - if (!this.props.getSetting('showControls', true)) { - return null; - } - - const hasCoolFeatures = powercord.account && powercord.account.accounts.spotify; - return ( -
- {hasCoolFeatures && this.renderAddToLibrary()} - {this.renderShuffle()} - {this.renderRepeat()} - {hasCoolFeatures && this.renderAddToPlaylist()} -
- ); - } - - renderAddToLibrary () { - switch (this.props.currentLibraryState) { - case playerStore.LibraryState.LOCAL_SONG: - return this.renderButton(() => Messages.SPOTIFY_CANT_LIKE_LOCAL, 'heart', () => - SpotifyAPI.removeSong(this.props.currentTrack.id), false, 'active'); - case playerStore.LibraryState.IN_LIBRARY: - return this.renderButton(() => Messages.SPOTIFY_REMOVE_LIKED_SONGS, 'heart', () => - SpotifyAPI.removeSong(this.props.currentTrack.id), false, 'active'); - case playerStore.LibraryState.NOT_IN_LIBRARY: - return this.renderButton(() => Messages.SPOTIFY_ADD_LIKED_SONGS, 'heart-regular', () => - SpotifyAPI.addSong(this.props.currentTrack.id)); - default: - return this.renderButton(() => Messages.DEFAULT_INPUT_PLACEHOLDER, 'heart', () => void 0, true); - } - } - - renderShuffle () { - if (!this.props.playerState.canShuffle) { - return this.renderButton(() => 'Cannot shuffle right now', 'random', () => void 0, true); - } - const { shuffle } = this.props.playerState; - return this.renderButton(() => 'Shuffle', 'random', () => - SpotifyAPI.setShuffleState(!shuffle), false, shuffle ? 'active' : ''); - } - - renderRepeat () { - if (!this.props.playerState.canRepeat && !this.props.playerState.canRepeatOne) { - return this.renderButton(() => 'Cannot repeat right now', 'sync', () => void 0, true); - } - - switch (this.props.playerState.repeat) { - case playerStore.RepeatState.NO_REPEAT: - return this.renderButton(() => 'Repeat', 'sync', () => this.handleSetRepeat(), false); - case playerStore.RepeatState.REPEAT_CONTEXT: - return this.renderButton(() => 'Repeat Track', 'sync', () => this.handleSetRepeat(), false, 'active'); - case playerStore.RepeatState.REPEAT_TRACK: - return this.renderButton(() => 'No Repeat', 'undo', () => this.handleSetRepeat(), false, 'active'); - } - } - - renderAddToPlaylist () { - return this.renderButton(() => 'Save to Playlist', 'plus-circle', () => this.handleAddToPlaylist()); - } - - renderButton (tooltipText, icon, onClick, disabled, className) { - return { - ...this.props.base.props.children[1].props.children[0], - props: { - ...this.props.base.props.children[1].props.children[0].props, - icon: () => React.createElement(FontAwesome, { - className, - icon - }), - tooltipText: tooltipText(), - disabled, - onClick - } - }; - } - - renderInfoPremium () { - return { - ...this.props.base.props.children[1].props.children[0], - props: { - ...this.props.base.props.children[1].props.children[0].props, - tooltipText: 'Not seeing controls?', - icon: () => React.createElement(Icon, { - name: 'Info', - width: 20, - height: 20, - style: { color: 'var(--interactive-normal)' } - }), - onClick: () => openModal(() => React.createElement(PayUp)) - } - }; - } - - handleSetRepeat () { - const possibleStates = [ - playerStore.RepeatState.NO_REPEAT, - this.props.playerState.canRepeat && playerStore.RepeatState.REPEAT_CONTEXT, - this.props.playerState.canRepeatOne && playerStore.RepeatState.REPEAT_TRACK - ].filter(Boolean); - const currentIndex = possibleStates.indexOf(this.props.playerState.repeat); - const nextState = possibleStates[(currentIndex + 1) % possibleStates.length]; - switch (nextState) { - case playerStore.RepeatState.NO_REPEAT: - SpotifyAPI.setRepeatState('off'); - break; - case playerStore.RepeatState.REPEAT_CONTEXT: - SpotifyAPI.setRepeatState('context'); - break; - case playerStore.RepeatState.REPEAT_TRACK: - SpotifyAPI.setRepeatState('track'); - break; - } - } - - handleAddToPlaylist () { - openModal(() => React.createElement(AddToPlaylist, { track: this.props.currentTrack })); - } -} - -module.exports = Flux.connectStores( - [ playerStore, powercord.api.settings.store ], - (props) => ({ - ...playerStore.getStore(), - ...powercord.api.settings._fluxProps(props.entityID) - }) -)(Modal); 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 ( - - - Spotify Premium Required - closeModal()}/> - - -
- 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. -
-
- 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. -
-
- 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) -
-
- - - - -
- ); - } -); 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 ( -
-
- - {formatTime(progress)} - - - {formatTime(this.props.duration)} - -
-
this.startSeek(e)}> - - -
-
- ); - } -} - -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 }) => ( -
- toggleSetting('squareCovers')} - > - Squared covers - - - toggleSetting('showControls')} - > - Show advanced controls - - - { - patch(getSetting('noAutoPause', true)); - toggleSetting('noAutoPause'); - }} - > - No auto pause - -
- ) -); 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] - ? cover - : cover; - return ( -
this.handleClick(this.props.item)}> - {image} - {this.props.item.name} -
- ); - } -} - -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(); - }); - return ( - - - Multiple tracks found - "{this.props.query}" - closeModal()}/> - - - - Please select the track that you wish to share to the current channel. - - Available tracks -
- {trackList} -
-
-
- ); - } -}; 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); - } -}