7 Commits
1.4.0 ... main

Author SHA1 Message Date
1ac096ca52 Made it so you can configure everything
You can configure the following settings:
- Placeholder artwork URL
- Paused timeout (in seconds, minutes, or hours)
- Metadata fetch interval (in seconds, minutes, or hours)

I also added helping notes to the title of the inputs to make it clearer what each setting does.

Also meow meow meow meow meow meow meow meow meow meow meow meow meow meow
2025-06-27 12:00:56 -07:00
575bf35e7f fix the repeat requests for the same URL 2025-06-25 17:13:46 -07:00
a599a5b246 forgot to bump version 2025-06-23 15:55:39 -07:00
89c26b2fd1 Changed up redundant code, and fixed the pause bug
The issue with the 5 min pause bug was that the pause function was not properly resetting the state of the artwork, So it would say its not playing, while showing the artwork as if it was playing.

And the redundant code was just a few lines that were repeated in multiple places, so I consolidated them into a single function to improve maintainability.

Who made Javascript? Why is it so hard. 😭
2025-06-23 13:57:41 -07:00
4d267d2568 Changed default artwork handling, and added some more functions to make my life better 2025-06-22 21:41:26 -07:00
acd2b7c244 missed a NOTHING_PLAYING_ARTWORK_BASE64 function, and removed some stuff from artworkep that wasnt needed 2025-06-22 13:34:59 -07:00
65b5dfd6f1 make the placeholder artwork a function 2025-06-22 12:51:41 -07:00
4 changed files with 231 additions and 79 deletions

View File

@ -4,6 +4,24 @@ let artworkEndpoint = null;
let token = null; let token = null;
let intervalId = null; let intervalId = null;
let lastSentArtworkKey = null; let lastSentArtworkKey = null;
let placeholderArtwork = null;
let pausedTimeout = 300000;
let metadataFetchInterval = 1000;
// log credits to console
if (!window._nowPlayingExtensionLogged) {
console.log('Apple Music Now Playing Extension made by Sophia Atkinson with help from PreMiD contributors');
window._nowPlayingExtensionLogged = true;
}
// Cache for artwork base64 by URL
const artworkBase64Cache = {
lastArtworkUrl: null,
lastArtworkBase64: null,
defaultArtworkBase64: null
};
async function imageUrlToBase64(url) { async function imageUrlToBase64(url) {
try { try {
@ -21,74 +39,83 @@ async function imageUrlToBase64(url) {
} }
} }
// Cache for artwork base64 by URL
const artworkBase64Cache = {
lastArtworkUrl: null,
lastArtworkBase64: null
};
async function getNowPlayingData() { async function getNowPlayingData() {
const audio = document.querySelector('audio#apple-music-player'); const audio = document.querySelector('audio#apple-music-player');
// From PreMiD Apple Music Presence // From PreMiD Apple Music Presence
const video = document.querySelector('apple-music-video-player')?.shadowRoot const video = document.querySelector('apple-music-video-player')?.shadowRoot
?.querySelector('amp-window-takeover > .container > amp-video-player-internal') ?.querySelector('amp-window-takeover > .container > amp-video-player-internal')?.shadowRoot
?.shadowRoot?.querySelector('amp-video-player')?.shadowRoot ?.querySelector('amp-video-player')?.shadowRoot
?.querySelector('div#video-container')?.querySelector('video#apple-music-video-player'); ?.querySelector('div#video-container > video#apple-music-video-player');
const media = video || audio; const media = video || audio;
const timestampInput = document.querySelector('amp-lcd.lcd.lcd__music')?.shadowRoot
?.querySelector('input#playback-progress[aria-valuenow][aria-valuemax]');
if (!artworkBase64Cache.defaultArtworkBase64 && placeholderArtwork) {
artworkBase64Cache.defaultArtworkBase64 = await imageUrlToBase64(placeholderArtwork);
}
const defaultArtworkBase64 = artworkBase64Cache.defaultArtworkBase64;
if (!media) { if (!media) {
pauseStartTime = null; pauseStartTime = null;
return { return {
details: "Nothing playing", details: "Nothing playing",
state: "", state: "",
artworkBase64: "data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22150%22%20height%3D%22150%22%20viewBox%3D%220%200%20150%20150%22%3E%3Crect%20width%3D%22100%25%22%20height%3D%22100%25%22%20fill%3D%22%231E1F22%22%2F%3E%3Cpath%20fill%3D%22%23FFF%22%20d%3D%22M31.66%2073.75h1.61q.59%200%201.03-.15t.73-.42q.29-.28.44-.68.14-.39.14-.88%200-.46-.14-.84-.15-.38-.43-.64-.29-.26-.73-.4t-1.04-.14h-1.61zm-2.15-5.82h3.76q1.16%200%202.01.27.84.28%201.4.76.55.49.82%201.17.26.68.26%201.49%200%20.84-.28%201.54t-.84%201.2q-.56.51-1.4.79t-1.97.28h-1.61v4.07h-2.15zm9.61-.32h1.98V79.5h-1.98zm8.29%209.77V76q-.85.04-1.44.14-.58.11-.93.28-.36.17-.51.39-.15.23-.15.49%200%20.52.31.74.31.23.8.23.61%200%201.05-.22.45-.22.87-.67m-4.17-4.31-.36-.63q1.42-1.3%203.41-1.3.72%200%201.29.24.57.23.96.65t.6%201.01q.2.58.2%201.28v5.18h-.9q-.28%200-.43-.08-.15-.09-.24-.34l-.17-.6q-.32.28-.61.5-.3.21-.62.35-.32.15-.68.22-.37.08-.81.08-.52%200-.96-.14t-.76-.42-.49-.7q-.18-.41-.18-.97%200-.31.11-.62.1-.3.34-.58.23-.28.61-.53.37-.25.92-.43.55-.19%201.28-.3.73-.12%201.66-.14v-.48q0-.83-.35-1.22-.35-.4-1.02-.4-.48%200-.79.11-.32.11-.56.25t-.43.26q-.2.11-.44.11-.21%200-.35-.11-.15-.11-.23-.25m13.83-1.78h1.58l-4.47%2010.47q-.09.2-.23.31t-.43.11h-1.47l1.54-3.3-3.32-7.59H52q.24%200%20.37.11.13.12.19.26l1.75%204.26q.09.21.15.43.06.21.11.44.07-.23.15-.45.07-.22.16-.43l1.64-4.25q.07-.16.22-.26.14-.11.33-.11m2.63%200h1.98v8.21H59.7zm2.26-2.39q0%20.26-.11.48-.1.22-.27.39-.18.17-.41.27t-.49.1-.49-.1-.39-.27q-.17-.17-.27-.39t-.1-.48.1-.5q.1-.23.27-.4.16-.16.39-.26t.49-.1.49.1.41.26q.17.17.27.4.11.24.11.5m3.45%202.75.14.65q.25-.25.52-.46.28-.21.59-.36.3-.15.66-.23.35-.09.76-.09.68%200%201.2.23t.86.64q.35.41.53.99.18.57.18%201.26v5.22h-1.97v-5.22q0-.76-.35-1.17-.34-.41-1.05-.41-.51%200-.96.23-.44.23-.84.63v5.94H63.7v-8.21h1.21q.38%200%20.5.36m10.31%203.55q.37%200%20.64-.1.28-.1.46-.28t.27-.42q.09-.25.09-.55%200-.61-.36-.96-.37-.36-1.1-.36-.72%200-1.09.36-.36.35-.36.96%200%20.29.09.54t.27.43.46.28q.27.1.63.1m2.24%204.67q0-.24-.15-.39-.14-.16-.39-.24t-.58-.12-.7-.06l-.77-.04q-.4-.02-.77-.06-.33.18-.54.43-.2.25-.2.57%200%20.22.11.41t.34.32q.24.14.61.21.38.08.92.08.56%200%20.96-.08.4-.09.66-.24.26-.14.38-.35.12-.2.12-.44m-.4-8.3h2.36v.74q0%20.35-.42.43l-.74.14q.17.42.17.92%200%20.61-.24%201.1-.25.5-.68.84t-1.02.53-1.27.19q-.24%200-.46-.02-.22-.03-.44-.07-.38.23-.38.52%200%20.25.22.37.23.11.61.16.37.05.85.06t.99.05q.5.04.98.14t.86.32q.37.21.6.59.23.37.23.95%200%20.55-.27%201.06t-.77.91q-.51.4-1.25.64-.74.25-1.69.25-.92%200-1.61-.18t-1.15-.48q-.45-.3-.68-.69-.22-.4-.22-.82%200-.58.35-.96.35-.39.95-.62-.32-.17-.52-.45-.19-.28-.19-.74%200-.18.07-.38.07-.19.2-.39.13-.19.33-.36t.47-.31q-.62-.33-.98-.89-.35-.56-.35-1.32%200-.6.24-1.1.25-.49.68-.84.44-.34%201.03-.53.6-.18%201.3-.18.53%200%201%20.11.46.1.84.31m15.67-3.64h1.89V79.5h-1.1q-.26%200-.43-.08-.17-.09-.33-.29l-6.04-7.71q.05.53.05.98v7.1h-1.9V67.93h1.13q.14%200%20.24.01.1.02.17.05.08.04.15.11.07.06.16.18l6.06%207.74-.04-.55q-.01-.27-.01-.51zm7.79%203.23q.91%200%201.66.3t1.28.84.82%201.33q.29.78.29%201.75%200%20.98-.29%201.76t-.82%201.34q-.53.55-1.28.84-.75.3-1.66.3-.92%200-1.67-.3-.75-.29-1.29-.84-.53-.56-.82-1.34t-.29-1.76q0-.97.29-1.75.29-.79.82-1.33.54-.54%201.29-.84t1.67-.3m0%206.94q1.02%200%201.52-.69.49-.69.49-2.01%200-1.33-.49-2.03-.5-.69-1.52-.69-1.04%200-1.54.7t-.5%202.02.5%202.01%201.54.69m10.66-6.81h1.97v8.21h-1.21q-.39%200-.49-.36l-.14-.66q-.5.52-1.11.83-.61.32-1.43.32-.67%200-1.19-.23t-.87-.64q-.35-.42-.53-.99t-.18-1.26v-5.22h1.98v5.22q0%20.75.34%201.16.35.41%201.05.41.51%200%20.96-.22.45-.23.85-.63zm6.66%208.34q-1.07%200-1.64-.61-.58-.6-.58-1.66v-4.59h-.84q-.16%200-.27-.1-.11-.11-.11-.31v-.79l1.32-.21.42-2.24q.04-.16.15-.25t.29-.09h1.02v2.58h2.19v1.41h-2.19v4.45q0%20.38.19.6t.51.22q.19%200%20.31-.05.13-.04.22-.09l.16-.09q.07-.05.15-.05t.14.05q.06.04.12.13l.59.96q-.43.36-.99.54-.56.19-1.16.19%22%2F%3E%3C%2Fsvg%3E", paused: true,
paused: true artworkBase64: defaultArtworkBase64
}; };
} }
// From PreMiD Apple Music Presence
const timestampInput = document.querySelector('amp-lcd.lcd.lcd__music')?.shadowRoot
?.querySelector('input#playback-progress[aria-valuenow][aria-valuemax]');
const paused = media.paused || media.readyState <= 2; const paused = media.paused || media.readyState <= 2;
const metadata = navigator.mediaSession?.metadata || {}; const metadata = navigator.mediaSession?.metadata || {};
const title = metadata.title || media.title || "Unknown Title"; const title = metadata.title || media.title || "Unknown Title";
const album = metadata.album || "Unknown Album";
const artist = metadata.artist || "Unknown Artist"; const artist = metadata.artist || "Unknown Artist";
const artworkSrc = metadata.artwork?.[0]?.src || null; const album = metadata.album || "Unknown Album";
const artworkSrc = Array.isArray(metadata.artwork) && metadata.artwork[0]?.src
const largeImageKey = artworkSrc?.replace(/\d+x\d+[a-z]*/i, '150x150bb'); // More robust for Apple Music artwork URLs ? metadata.artwork[0].src
: null;
let artworkBase64 = null; let artworkBase64 = null;
if (artworkSrc) { const largeImageKey = artworkSrc?.replace(/\d+x\d+[a-z]*/i, '150x150bb'); // More robust for Apple Music artwork URLs
if (artworkBase64Cache.lastArtworkUrl === largeImageKey && artworkBase64Cache.lastArtworkBase64) {
artworkBase64 = artworkBase64Cache.lastArtworkBase64; if (largeImageKey) {
} else { if (artworkBase64Cache.lastArtworkUrl !== largeImageKey) {
artworkBase64 = await imageUrlToBase64(largeImageKey); artworkBase64 = await imageUrlToBase64(largeImageKey);
artworkBase64Cache.lastArtworkUrl = largeImageKey; artworkBase64Cache.lastArtworkUrl = largeImageKey;
artworkBase64Cache.lastArtworkBase64 = artworkBase64; artworkBase64Cache.lastArtworkBase64 = artworkBase64;
} else {
artworkBase64 = artworkBase64Cache.lastArtworkBase64;
} }
} }
const currentTime = timestampInput ? Number(timestampInput.getAttribute('aria-valuenow')) : media.currentTime;
const duration = timestampInput ? Number(timestampInput.getAttribute('aria-valuemax')) : media.duration;
const commonData={details:title,state:artist,album,smallImageKey:'play',smallImageText:'Playing',startTimestamp:Math.floor(Date.now()/1000)-Math.floor(currentTime),endTimestamp:Math.floor(Date.now()/1000)+Math.floor(duration-currentTime),paused,currentTime,duration};
if (paused) { if (paused) {
if (!pauseStartTime) pauseStartTime = Date.now(); pauseStartTime ??= Date.now();
if (Date.now() - pauseStartTime >= 300000) { if (Date.now() - pauseStartTime >= pausedTimeout) {
return { return {
details: "Not playing", details: "Nothing playing",
state: "", state: "",
paused: true paused: true,
artworkBase64: defaultArtworkBase64
}; };
} }
} else {
pauseStartTime = null;
} }
const currentTime = timestampInput ? Number(timestampInput.getAttribute('aria-valuenow')) : media.currentTime || 0;
const duration = timestampInput ? Number(timestampInput.getAttribute('aria-valuemax')) : media.duration || 0;
const nowUnix = Math.floor(Date.now() / 1000);
return { return {
...commonData, details: title,
state: artist,
album,
paused,
currentTime,
startTimestamp: nowUnix - Math.floor(currentTime),
endTimestamp: nowUnix + Math.floor(Math.max(0, duration - currentTime)),
duration,
artworkBase64 artworkBase64
}; };
} }
@ -112,11 +139,7 @@ async function sendNowPlaying() {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'Authorization': `Bearer ${token}` 'Authorization': `Bearer ${token}`
}, },
body: JSON.stringify({ body: JSON.stringify({ ...noArtData, details, state })
...noArtData,
details,
state
})
}); });
console.log('Now playing data sent:', details, '-', state); console.log('Now playing data sent:', details, '-', state);
} catch (e) { } catch (e) {
@ -125,11 +148,7 @@ async function sendNowPlaying() {
// Only send artwork when song changes // Only send artwork when song changes
const currentTrackKey = `${details} - ${state}`; const currentTrackKey = `${details} - ${state}`;
if ( if (artworkEndpoint && artworkBase64 && currentTrackKey !== lastSentArtworkKey) {
artworkEndpoint &&
artworkBase64 &&
currentTrackKey !== lastSentArtworkKey
) {
try { try {
await fetch(artworkEndpoint, { await fetch(artworkEndpoint, {
method: 'POST', method: 'POST',
@ -138,9 +157,7 @@ async function sendNowPlaying() {
'Authorization': `Bearer ${token}` 'Authorization': `Bearer ${token}`
}, },
body: JSON.stringify({ body: JSON.stringify({
image: artworkBase64, image: artworkBase64
details,
state
}) })
}); });
lastSentArtworkKey = currentTrackKey; lastSentArtworkKey = currentTrackKey;
@ -166,27 +183,23 @@ function safeCall(fn) {
} }
} }
function startNowPlayingLoop() { function startNowPlayingLoop() {
if (window._nowPlayingIntervalSet) return; if (!window._nowPlayingIntervalSet) {
window._nowPlayingIntervalSet = true;
window._nowPlayingIntervalSet = true; intervalId = setInterval(() => safeCall(sendNowPlaying), metadataFetchInterval);
}
intervalId = setInterval(() => safeCall(sendNowPlaying), 1000);
} }
if (!window._nowPlayingExtensionLogged) { if (chrome?.storage?.sync) {
console.log('Apple Music Now Playing Extension made by Sophia Atkinson with help from PreMiD contributors'); chrome.storage.sync.get(['endpoint', 'artworkEndpoint', 'token', 'placeholderArtwork', 'pausedTimeout', 'metadataFetchInterval'], result => {
window._nowPlayingExtensionLogged = true;
}
if (typeof chrome !== "undefined" && chrome.storage && chrome.storage.sync) {
chrome.storage.sync.get(['endpoint', 'artworkEndpoint', 'token'], (result) => {
if (chrome.runtime.lastError) { if (chrome.runtime.lastError) {
console.error("Failed to load settings:", chrome.runtime.lastError.message); console.error("Failed to load settings:", chrome.runtime.lastError.message);
return; return;
} }
endpoint = result.endpoint; endpoint = result.endpoint;
artworkEndpoint = result.artworkEndpoint; artworkEndpoint = result.artworkEndpoint;
token = result.token; token = result.token;
placeholderArtwork = result.placeholderArtwork || 'https://placehold.co/150/1E1F22/FFF?text=Nothing%20playing';
pausedTimeout = Number(result.pausedTimeout) || pausedTimeout;
metadataFetchInterval = Number(result.metadataFetchInterval) || metadataFetchInterval;
if (!endpoint || !token) { if (!endpoint || !token) {
console.error("No endpoint/token configured."); console.error("No endpoint/token configured.");
@ -209,9 +222,7 @@ new MutationObserver(() => {
window._nowPlayingIntervalSet = false; window._nowPlayingIntervalSet = false;
setTimeout(() => { setTimeout(() => {
if (!window._nowPlayingIntervalSet) { if (!window._nowPlayingIntervalSet) startNowPlayingLoop();
startNowPlayingLoop(); }, metadataFetchInterval);
}
}, 1000);
} }
}).observe(document, { subtree: true, childList: true }); }).observe(document, { subtree: true, childList: true });

View File

@ -1,7 +1,7 @@
{ {
"manifest_version": 3, "manifest_version": 3,
"name": "Apple Music Now Playing", "name": "Apple Music Now Playing",
"version": "1.4.0", "version": "1.4.3",
"description": "Easily display your currently playing Apple Music track on your website with this extension.", "description": "Easily display your currently playing Apple Music track on your website with this extension.",
"permissions": [ "permissions": [
"storage", "storage",

View File

@ -14,6 +14,8 @@
--button-bg: var(--accent); --button-bg: var(--accent);
--button-hover: #d67b8a; --button-hover: #d67b8a;
--shadow: rgba(245, 169, 184, 0.3); --shadow: rgba(245, 169, 184, 0.3);
--details-bg: #18181a;
--details-border: #2a2a2a;
} }
body { body {
margin: 0; margin: 0;
@ -83,18 +85,93 @@
background-color: var(--button-hover); background-color: var(--button-hover);
outline: none; outline: none;
} }
details {
margin-top: 2rem;
background: var(--details-bg);
border: 1.5px solid var(--details-border);
border-radius: 10px;
padding: 0.5rem 1rem;
max-width: 100%;
}
details summary {
cursor: pointer;
font-weight: 700;
color: var(--accent);
font-size: 1.05rem;
outline: none;
padding: 0.5rem 0;
user-select: none;
transition: color 0.2s;
}
details summary:hover,
details summary:focus {
color: #fff;
}
details > div {
margin-top: 1rem;
padding-bottom: 0.5rem;
border-top: 1px solid var(--details-border);
padding-top: 1rem;
overflow-x: hidden;
}
details label {
margin-top: 0;
font-size: 0.98rem;
font-weight: 500;
color: #ccc;
}
details input[type="text"] {
margin-top: 0.3rem;
font-size: 0.98rem;
background: #232326;
border-color: #333;
width: 100%;
box-sizing: border-box;
}
.help {
cursor: help;
color: var(--accent);
transition: color 0.2s ease;
}
label[for="endpoint"]:before,
label[for="artworkEndpoint"]:before,
label[for="token"]:before {
content: "* ";
color: red;
font-weight: bold;
}
#version {
margin-top: 1.5rem;
font-size: 0.9rem;
color: #aaa;
text-align: center;
}
</style> </style>
</head> </head>
<body> <body>
<div class="container"> <div class="container">
<h1><span title="Apple Music Now Playing">AmNP</span> Settings (Beta)</h1> <h1><span class="help" title="Apple Music Now Playing">AmNP</span> <span class="help" title="Why are you hovering over this, this is the settings page nothing special about it">Settings</span> <span class="help" title="Beta means that this extention is experimental and may not work as expected">(Beta)</span></h1>
<label for="endpoint">Now playing Endpoint:</label> <label for="endpoint">Now playing Endpoint:</label>
<input type="text" id="endpoint" placeholder="https://zorpzeep.blorp/wp-json/nowplaying/v1/update" autocomplete="off" spellcheck="false"/> <input title="For wp-amnp it uses the /wp-json/nowplaying/v1/update" type="text" id="endpoint" placeholder="https://zorpzeep.blorp/wp-json/nowplaying/v1/update" autocomplete="off" spellcheck="false" required/>
<label for="artworkEndpoint">Artwork Endpoint:</label> <label for="artworkEndpoint">Artwork Endpoint:</label>
<input type="text" id="artworkEndpoint" placeholder="https://zorpzeep.blorp/wp-json/nowplaying/v1/update/art" autocomplete="off" spellcheck="false"/> <input title="For wp-amnp it uses the /wp-json/nowplaying/v1/update/artwork" type="text" id="artworkEndpoint" placeholder="https://zorpzeep.blorp/wp-json/nowplaying/v1/update/artwork" autocomplete="off" spellcheck="false" required/>
<label for="token">Bearer Token:</label> <label for="token">Bearer Token:</label>
<input type="text" id="token" placeholder="73757065725f7365637572655f746f6b656e" autocomplete="off" spellcheck="false"/> <input title="For wp-amnp it can be found in Settings -> Now Playing API" type="text" id="token" placeholder="73757065725f7365637572655f746f6b656e" autocomplete="off" spellcheck="false" required/>
<details>
<summary>Extra Configurations</summary>
<div>
<label for="placeholderArtwork">Placeholder Artwork URL:</label>
<input title="You can type a url that host a placeholder image, any format supported, all are encoded to base64" type="text" id="placeholderArtwork" placeholder="https://placehold.co/150/1E1F22/FFF?text=Nothing%20playing" autocomplete="off" spellcheck="false"/>
<label for="pausedTimeout">Paused Timeout:</label>
<input title="You can type 5m, 5 minutes, 300 seconds, 1h, 1 hour, etc. Note that they are converted to ms" type="text" id="pausedTimeout" placeholder="5m" autocomplete="off" spellcheck="false"/>
<label for="metadataFetchInterval">Update Interval:</label>
<input title="You can type 5m, 5 minutes, 300 seconds, 1h, 1 hour, etc. Note that they are converted to ms" type="text" id="metadataFetchInterval" placeholder="1s" autocomplete="off" spellcheck="false"/>
</div>
</details>
<button id="save">Save</button> <button id="save">Save</button>
<footer id="version">You are running version: (unavailable)</footer>
</div> </div>
<script src="options.js"></script> <script src="options.js"></script>
</body> </body>

View File

@ -1,17 +1,81 @@
/* global chrome */ /* global chrome */
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
chrome.storage.sync.get(['endpoint', 'token', 'artworkEndpoint'], (data) => { const keys = [
document.getElementById('endpoint').value = data.endpoint || ''; 'endpoint',
document.getElementById('token').value = data.token || ''; 'token',
document.getElementById('artworkEndpoint').value = data.artworkEndpoint || ''; 'artworkEndpoint',
}); 'placeholderArtwork',
'pausedTimeout',
'metadataFetchInterval'
];
function parseTimeToMs(input) {
if (!input) return 0;
const str = input.trim().toLowerCase();
const match = str.match(/^(\d+(\.\d+)?)(\s*(ms|milliseconds?|s|sec|seconds?|m|min|minutes?|h|hr|hrs|hour|hours?))?$/);
if (!match) return Number(str) || 0;
const value = parseFloat(match[1]);
const unit = match[4] || '';
switch (unit) {
case 'ms':
case 'millisecond':
case 'milliseconds':
return value;
case 's':
case 'sec':
case 'second':
case 'seconds':
return value * 1000;
case 'm':
case 'min':
case 'minute':
case 'minutes':
return value * 60 * 1000;
case 'h':
case 'hr':
case 'hrs':
case 'hour':
case 'hours':
return value * 60 * 60 * 1000;
default:
return value;
}
}
document.getElementById('save').addEventListener('click', () => { chrome.storage.sync.get(keys, (data) => {
const endpoint = document.getElementById('endpoint').value; for (const key of keys) {
const token = document.getElementById('token').value; const elem = document.getElementById(key);
const artworkEndpoint = document.getElementById('artworkEndpoint').value; if (elem) {
chrome.storage.sync.set({ endpoint, token, artworkEndpoint }, () => { elem.value = data[key] != null ? String(data[key]) : '';
alert('Settings saved.'); } else {
}); console.warn(`Element with id "${key}" not found in DOM`);
}
}
const saveBtn = document.getElementById('save');
if (saveBtn) {
saveBtn.addEventListener('click', () => {
const valuesToSave = {};
for (const key of keys) {
const elem = document.getElementById(key);
let value = elem ? elem.value.trim() : '';
// Parse time fields to ms
if (key === 'pausedTimeout' || key === 'metadataFetchInterval') {
value = parseTimeToMs(value);
}
valuesToSave[key] = value;
}
chrome.storage.sync.set(valuesToSave, () => {
alert('Settings saved.');
});
});
} else {
console.error('Save button not found in DOM');
}
}); });
}); });
if (typeof chrome !== "undefined" && chrome.runtime && chrome.runtime.getManifest) {
document.getElementById('version').textContent = 'You are running Version: ' + chrome.runtime.getManifest().version;
} else {
document.getElementById('version').textContent = 'You are running Version: (unavailable)';
}