Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 143 additions & 0 deletions extension/background-scripts/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -1757,6 +1757,7 @@
imageCompression.getDataUrlFromFile = getDataUrlFromFile, imageCompression.getFilefromDataUrl = getFilefromDataUrl, imageCompression.loadImage = loadImage, imageCompression.drawImageInCanvas = drawImageInCanvas, imageCompression.drawFileInCanvas = drawFileInCanvas, imageCompression.canvasToFile = canvasToFile, imageCompression.getExifOrientation = getExifOrientation, imageCompression.handleMaxWidthOrHeight = handleMaxWidthOrHeight, imageCompression.followExifOrientation = followExifOrientation, imageCompression.cleanupCanvasMemory = cleanupCanvasMemory, imageCompression.isAutoOrientationInBrowser = isAutoOrientationInBrowser, imageCompression.approximateBelowMaximumCanvasSizeOfBrowser = approximateBelowMaximumCanvasSizeOfBrowser, imageCompression.copyExifWithoutOrientation = copyExifWithoutOrientation, imageCompression.getBrowserName = getBrowserName, imageCompression.version = "2.0.2";

// src/common/utils.ts
var DEBUG = false;
var browser;
if (browser == void 0) {
browser = chrome;
Expand Down Expand Up @@ -1799,6 +1800,11 @@
}
return object;
}
function sendDebug(...messages) {
if (DEBUG) {
console.log(...messages);
}
}
var FNV_PRIME = 0x0100000001b3n;
var FNV_OFFSET = 0xcbf29ce484222325n;
function fnv1aHash(byteArray) {
Expand Down Expand Up @@ -2329,6 +2335,140 @@
}
}

// src/background-scripts/spotify.ts
async function spotifyController(command) {
const delay = (ms) => new Promise((r2) => setTimeout(r2, ms));
const nowPlayingRoot = () => document.querySelector('aside[data-testid="now-playing-bar"]');
const getButton = (testId2) => document.querySelector(`button[data-testid="${testId2}"]`);
const getText = (testId2) => {
const el = document.querySelector(`[data-testid="${testId2}"]`);
const t2 = el?.innerText?.trim();
return t2 ? t2 : null;
};
const inferIsPlayingFromLabel = (label) => {
if (!label) return null;
const l2 = label.toLowerCase();
if (l2.includes("pause") || l2.includes("pauze") || l2.includes("pausa")) {
return true;
}
if (l2.includes("play") || l2.includes("afspelen") || l2.includes("lecture")) {
return false;
}
return null;
};
const getState = () => {
const root = nowPlayingRoot();
if (!root) {
return {
ok: false,
humanError: "Spotify player not found on this page."
};
}
const title = (root.querySelector('[data-testid="context-item-info-title"]')?.textContent || "").trim() || null;
const artist = (root.querySelector('[data-testid="context-item-info-subtitles"]')?.textContent || "").trim() || null;
const cover = root.querySelector(
'img[data-testid="cover-art-image"]'
)?.src || null;
const nextBtn = getButton("control-button-skip-forward");
const prevBtn = getButton("control-button-skip-back");
const playPauseBtn = getButton("control-button-playpause");
const playPauseLabel = playPauseBtn?.getAttribute("aria-label") || null;
return {
ok: true,
title,
artist,
cover,
canNext: nextBtn ? !nextBtn.disabled : null,
canPrevious: prevBtn ? !prevBtn.disabled : null,
playPauseLabel,
isPlaying: inferIsPlayingFromLabel(playPauseLabel),
positionText: getText("playback-position"),
durationText: getText("playback-duration")
};
};
if (!command) {
return getState();
}
const before = getState();
if (!before || before.ok === false) {
return before;
}
const testId = command === "next" ? "control-button-skip-forward" : command === "previous" ? "control-button-skip-back" : "control-button-playpause";
const button = getButton(testId);
if (!button) {
return {
ok: false,
humanError: `Spotify control button not found (${command}).`
};
}
if (button.disabled) {
return {
ok: false,
humanError: `Spotify control button is disabled (${command}).`
};
}
button.click();
const changed = (after) => {
if (!after || after.ok === false) return false;
if (command === "playPause") {
if (before.isPlaying !== null && after.isPlaying !== null) {
return after.isPlaying !== before.isPlaying;
}
return after.playPauseLabel !== before.playPauseLabel;
}
return after.title !== before.title || after.cover !== before.cover;
};
for (let i2 = 0; i2 < 10; i2++) {
await delay(150);
const after = getState();
if (changed(after)) {
return after;
}
}
return getState();
}
async function runOnSpotifyTab(command) {
const tabs = await browser.tabs.query({ url: ["*://open.spotify.com/*"] });
if (!tabs || tabs.length === 0) {
return {
ok: false,
humanError: "No Spotify tab found. Open https://open.spotify.com in another tab."
};
}
const targetTab = tabs.find((t2) => t2.audible) || tabs.find((t2) => t2.active) || tabs[0];
try {
const results = await browser.scripting.executeScript({
target: { tabId: targetTab.id },
func: spotifyController,
args: [command]
});
return results?.[0]?.result ?? { ok: false, humanError: "Spotify returned no data." };
} catch (e2) {
sendDebug("[smpp][spotify] executeScript failed", e2);
return {
ok: false,
humanError: "Couldn't run the Spotify controller. Make sure the Spotify tab is open on open.spotify.com."
};
}
}
async function handleSpotifyMessage(message, sendResponse) {
if (message.action === "spotifyGetState") {
const state = await runOnSpotifyTab(null);
sendResponse(state);
return true;
}
if (message.action === "spotifyCommand") {
const state = await runOnSpotifyTab(message.command);
if (!state || state.ok === false) {
sendResponse(state);
return true;
}
sendResponse({ ok: true, state });
return true;
}
return false;
}

// src/background-scripts/index.ts
browser.runtime.onMessage.addListener(
(message, _sender, sendResponse) => {
Expand Down Expand Up @@ -2504,6 +2644,9 @@
await browser.storage.local.set(data);
sendResponse({ success: true });
}
if (await handleSpotifyMessage(message, sendResponse)) {
return;
}
if (message.action === "getDataVersion") {
let dataVersion = await browser.storage.local.get("dataVersion");
console.log(dataVersion);
Expand Down
205 changes: 205 additions & 0 deletions extension/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -1962,6 +1962,211 @@ svg .pencil {
fill: var(--color-accent);
}

/* src/styles/smpp-styles/spotify-widget.css */
.smpp-spotify-widget {
display: flex;
flex-direction: column;
gap: 0.75rem;
color: var(--color-text);
}
.smpp-spotify-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.75rem;
}
.smpp-spotify-header-left {
display: flex;
align-items: center;
gap: 0.6rem;
}
.smpp-spotify-title {
font-weight: 700;
font-size: 1.1rem;
}
.smpp-spotify-icon {
display: grid;
place-items: center;
}
.smpp-spotify-logo {
display: block;
width: 100%;
height: 100%;
}
.smpp-spotify-icon-small {
width: 28px;
height: 28px;
}
.smpp-spotify-icon-large {
width: 128px;
height: 128px;
margin-bottom: 0.5rem;
filter: drop-shadow(0px 2px 10px var(--color-shadow));
}
.smpp-spotify-body {
display: grid;
grid-template-columns: 104px 1fr;
gap: 0.75rem;
align-items: center;
}
.smpp-spotify-widget.smpp-spotify-disconnected .smpp-spotify-body {
grid-template-columns: 1fr;
}
.smpp-spotify-widget.smpp-spotify-disconnected .smpp-spotify-cover {
display: none;
}
.smpp-spotify-widget.smpp-spotify-disconnected .smpp-spotify-time {
display: none;
}
.smpp-spotify-widget.smpp-spotify-disconnected .smpp-spotify-progress-wrap {
display: none;
}
.smpp-spotify-cover {
width: 104px;
height: 104px;
border-radius: 1.25rem;
object-fit: cover;
background: var(--color-base02);
visibility: hidden;
}
.smpp-spotify-meta {
min-width: 0;
display: flex;
flex-direction: column;
gap: 0.15rem;
}
.smpp-spotify-track {
font-weight: 650;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.smpp-spotify-widget.smpp-spotify-disconnected .smpp-spotify-track {
white-space: normal;
overflow: visible;
text-overflow: unset;
}
.smpp-spotify-artist,
.smpp-spotify-time {
font-size: 0.9rem;
color: var(--color-text-subdued, rgba(255, 255, 255, 0.7));
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.smpp-spotify-help {
display: none;
margin-top: 0.25rem;
padding: 0.65rem 0.8rem;
border-radius: 1rem;
border: 2px solid var(--color-base02);
background: var(--color-base01);
color: var(--color-text-subdued, rgba(255, 255, 255, 0.7));
font-size: 0.9rem;
}
.smpp-spotify-help.show {
display: block;
}
.smpp-spotify-help-title {
font-weight: 700;
color: var(--color-text);
margin-bottom: 0.25rem;
}
.smpp-spotify-help-list {
margin: 0;
padding-left: 1.1rem;
}
.smpp-spotify-help-mono {
font-family:
ui-monospace,
SFMono-Regular,
Menlo,
Monaco,
Consolas,
"Liberation Mono",
"Courier New",
monospace;
}
.smpp-spotify-progress-wrap {
margin-top: 0.35rem;
}
.smpp-spotify-progress {
height: 10px;
border-radius: 9999px;
background: var(--color-base02);
border: 2px solid var(--color-base03);
overflow: hidden;
}
.smpp-spotify-progress-fill {
height: 100%;
width: 100%;
background: var(--color-accent);
border-radius: 9999px;
transform: scaleX(0);
transform-origin: left center;
}
.smpp-spotify-progress.smpp-spotify-progress-unknown {
opacity: 0.5;
}
.smpp-spotify-controls {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 0.5rem;
align-items: center;
}
.smpp-spotify-btn {
padding: 0.45rem 0.6rem;
border-radius: 0.9rem;
border: 3px solid var(--color-base03);
background: var(--color-base02);
color: var(--color-text);
cursor: pointer;
font-size: 1.1rem;
line-height: 1;
box-shadow: 0px 2px 10px 0px var(--color-shadow);
}
.smpp-spotify-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.enableAnimations .smpp-spotify-btn {
transition:
transform 0.15s ease,
filter 0.15s ease,
border-color 0.15s ease,
background-color 0.15s ease;
}
.smpp-spotify-btn:hover {
filter: brightness(115%);
border-color: var(--color-base04);
}
.smpp-spotify-btn:active {
scale: 0.96;
}
.smpp-spotify-btn-primary {
background: var(--color-accent);
border-color: var(--color-base03);
color: var(--color-black);
}
.smpp-spotify-open {
grid-column: 1 / -1;
padding: 0.5rem 0.75rem;
border-radius: 0.9rem;
border: 3px dashed var(--color-base03);
background: var(--color-base01);
color: var(--color-text);
cursor: pointer;
box-shadow: 0px 2px 10px 0px var(--color-shadow);
}
.smpp-spotify-preview {
gap: 0.25rem;
}
.smpp-spotify-preview-title {
font-size: 2rem;
font-weight: 700;
color: var(--color-base05);
}

/* src/styles/smpp-styles/tutorial-widget.css */
.lang-buttons {
flex-direction: row;
Expand Down
Loading