From ec22795775802010feec21afb1439a265dcb50cb Mon Sep 17 00:00:00 2001 From: charles Date: Tue, 28 May 2024 16:33:04 -0600 Subject: [PATCH] feat: re-introduce like/unlike ui (#214) --- src/actions/init.js | 5 + src/background.js | 63 ++++++--- src/components/icons/icon.js | 37 +++++- src/content.js | 4 +- src/models/heart-icon.js | 162 ++++++++++++++++++++++++ src/models/loop-icon.js | 14 +- src/models/now-playing-icons.js | 8 +- src/models/seek/seek-icon.js | 6 +- src/models/snip/current-snip.js | 4 +- src/models/snip/track-snip.js | 2 +- src/models/tracklist/heart-icon.js | 126 ++++++++++++++++++ src/models/tracklist/track-list.js | 111 ++++++++++++++-- src/models/tracklist/tracklist-icon.js | 4 +- src/observers/now-playing.js | 10 +- src/observers/song-tracker.js | 2 +- src/observers/track-list.js | 3 +- src/services/track.js | 30 +++++ src/stores/cache.js | 8 +- src/stores/data.js | 27 ++++ src/styles.css | 51 ++++---- src/utils/{higlight.js => highlight.js} | 20 ++- src/utils/request.js | 7 +- src/utils/selectors.js | 2 +- src/utils/song.js | 4 +- 24 files changed, 615 insertions(+), 95 deletions(-) create mode 100644 src/models/heart-icon.js create mode 100644 src/models/tracklist/heart-icon.js create mode 100644 src/services/track.js rename src/utils/{higlight.js => highlight.js} (58%) diff --git a/src/actions/init.js b/src/actions/init.js index f11859e0..b0c9a16f 100644 --- a/src/actions/init.js +++ b/src/actions/init.js @@ -33,6 +33,11 @@ async function load() { sessionStorage.setItem('connection_id', connection_id) }) + document.addEventListener('app.now-playing', async (e) => { + const now_playing = e.detail['now-playing'] + sessionStorage.setItem('now-playing', now_playing) + }) + document.addEventListener('app.auth_token', async (e) => { const { auth_token } = e.detail sessionStorage.setItem('auth_token', auth_token) diff --git a/src/background.js b/src/background.js index 8fc51e14..d0af3a91 100644 --- a/src/background.js +++ b/src/background.js @@ -5,19 +5,26 @@ import { activeOpenTab, sendMessage } from './utils/messaging.js' import { getQueueList, setQueueList } from './services/queue.js' import { createArtistDiscoPlaylist } from './services/artist-disco.js' import { playSharedTrack, seekTrackToPosition } from './services/player.js' +import { checkIfTracksInCollection, updateLikedTracks } from './services/track.js' let ENABLED = true let popupPort = null -async function getUIState({ selector, tabId }) { - const result = await chrome.scripting.executeScript({ - args: [selector], +async function getUIState({ selector, tabId, delay = 0 }) { + const [result] = await chrome.scripting.executeScript({ + args: [selector, delay], target: { tabId }, - func: (selector) => - document.querySelector(selector)?.getAttribute('aria-label').toLowerCase() + func: (selector, delay) => + new Promise((resolve) => { + setTimeout(() => { + resolve( + document.querySelector(selector)?.getAttribute('aria-label').toLowerCase() + ) + }, delay) + }) }) - return result?.at(0).result + return result?.result } async function getMediaControlsState(tabId) { @@ -36,8 +43,9 @@ async function getMediaControlsState(tabId) { const promises = selectors.map( (selector) => new Promise((resolve) => { - if (selector.search(/(loop)|(add-button)/g) < 0) + if (selector.search(/(loop)|(heart)/g) < 0) { return resolve(getUIState({ selector, tabId })) + } return setTimeout(() => resolve(getUIState({ selector, tabId })), 500) }) ) @@ -67,7 +75,10 @@ chrome.runtime.onConnect.addListener(async (port) => { if (message?.type !== 'controls') return const { selector, tabId } = await executeButtonClick({ command: message.key }) - const result = await getUIState({ selector, tabId }) + + const delay = selector.includes('heart') ? 250 : 0 + const result = await getUIState({ selector, tabId, delay }) + port.postMessage({ type: 'controls', data: { key: message.key, result } }) }) @@ -104,11 +115,12 @@ chrome.storage.onChanged.addListener(async (changes) => { updateBadgeState({ changes, changedKey }) if (changedKey == 'now-playing' && ENABLED) { - if (!popupPort) return - - const { active, tabId } = await activeOpenTab() - active && popupPort.postMessage({ type: changedKey, data: changes[changedKey].newValue }) - return await setMediaState({ active, tabId }) + if (popupPort) { + const { active, tabId } = await activeOpenTab() + active && + popupPort.postMessage({ type: changedKey, data: changes[changedKey].newValue }) + await setMediaState({ active, tabId }) + } } const messageValue = @@ -132,8 +144,25 @@ chrome.webRequest.onBeforeRequest.addListener( ['requestBody'] ) +function getTrackId(url) { + const query = new URL(url) + const params = new URLSearchParams(query.search) + const variables = params.get('variables') + const uris = JSON.parse(decodeURIComponent(variables)).uris.at(0) + return uris.split('track:').at(-1) +} + chrome.webRequest.onBeforeSendHeaders.addListener( - (details) => { + async (details) => { + if (details.url.includes('areEntitiesInLibrary')) { + const nowPlaying = await getState('now-playing') + if (!nowPlaying) return + if (nowPlaying?.trackId) return + + nowPlaying.trackId = getTrackId(details.url) + chrome.storage.local.set({ 'now-playing': nowPlaying }) + } + const authHeader = details?.requestHeaders?.find( (header) => header?.name == 'authorization' ) @@ -144,7 +173,8 @@ chrome.webRequest.onBeforeSendHeaders.addListener( { urls: [ 'https://api.spotify.com/*', - 'https://guc3-spclient.spotify.com/track-playback/v1/devices' + 'https://guc3-spclient.spotify.com/track-playback/v1/devices', + 'https://api-partner.spotify.com/pathfinder/v1/query?operationName=areEntitiesInLibrary*' ] }, ['requestHeaders'] @@ -162,8 +192,11 @@ chrome.runtime.onMessage.addListener(({ key, values }, _, sendResponse) => { 'queue.get': getQueueList, 'play.shared': playSharedTrack, 'play.seek': seekTrackToPosition, + 'tracks.update': updateLikedTracks, + 'tracks.liked': checkIfTracksInCollection, 'artist.disco': createArtistDiscoPlaylist } + const handlerFn = messageHandler[key] handlerFn ? promiseHandler(handlerFn(values), sendResponse) diff --git a/src/components/icons/icon.js b/src/components/icons/icon.js index 646e4380..e16f3f56 100644 --- a/src/components/icons/icon.js +++ b/src/components/icons/icon.js @@ -23,7 +23,24 @@ export const NOW_PLAYING_SKIP_ICON = { id: 'chorus-skip' } +export const TRACK_HEART = { + lw: 22, + role: 'heart', + ariaLabel: 'Like Song', + stroke: 'currentColor', + fill: 'none', + viewBox: '-5 -4 24 24' +} + +export const HEART_ICON = { + id: 'chorus-heart', + ...TRACK_HEART +} + const SVG_PATHS = { + heart: ` + + `, skip: ` { +export const createIcon = ({ + role, + viewBox, + id, + ariaLabel, + strokeWidth, + stroke, + fill = 'currentColor', + lw = '1.25rem' +}) => { const svgPath = SVG_PATHS[role] || SVG_PATHS.default const buttonStylesKey = id ? 'settings' : 'default' - const buttonStyles = BUTTON_STYLES[buttonStylesKey] + let buttonStyles = BUTTON_STYLES[buttonStylesKey] + if (role == 'skip' && !id) buttonStyles += 'padding-right: 4px;' return `