diff --git a/src/sh.cider.streamdeck.sdPlugin/app.js b/src/sh.cider.streamdeck.sdPlugin/app.js
index 59c5b83..631affb 100644
--- a/src/sh.cider.streamdeck.sdPlugin/app.js
+++ b/src/sh.cider.streamdeck.sdPlugin/app.js
@@ -1,10 +1,34 @@
+// ==========================================================================
+// Cider Stream Deck Plugin - Main Application
+// ==========================================================================
+// This file contains the main logic for the Cider Stream Deck plugin.
+// It handles communication with the Stream Deck, manages actions,
+// and interacts with the Cider application.
+// ==========================================================================
+
///
///
-// RPC Authorization (Required for Cider 2.5.x and above)
+// ==========================================================================
+// Global State
+// ==========================================================================
+
+const AppState = {
+ STARTING_UP: 'starting_up',
+ READY: 'ready',
+ ERROR: 'error'
+};
+
+let currentAppState = AppState.STARTING_UP;
+let isAuthenticated = false;
+
+// ==========================================================================
+// Initialization and Setup
+// ==========================================================================
$SD.onConnected(() => {
console.debug('[DEBUG] [System] Stream Deck connected!');
+ currentAppState = AppState.STARTING_UP;
setDefaults();
$SD.getGlobalSettings();
});
@@ -25,6 +49,7 @@ const actions = {
ciderPlaybackAction: new Action('sh.cider.streamdeck.playback')
};
+// Global variables
let marqueeInterval, marqueePosition = 0, currentMarqueeText = '', isScrolling = false;
let MARQUEE_SPEED = 200, MARQUEE_STEP = 1, PAUSE_DURATION = 2000, DISPLAY_LENGTH = 15; lastMarqueeUpdateTime = 0;
let marqueeEnabled = true, tapBehavior = 'addToLibrary', volumeStep = 1, pressBehavior = 'togglePlay';
@@ -33,76 +58,10 @@ let useAdaptiveIcons = false, rpcKey = null;
// Ensure window.contexts is initialized
window.contexts = window.contexts || {};
-actions.ciderPlaybackAction.onTouchTap(() => {
- console.debug(`[DEBUG] [Action] ciderPlaybackAction action tapped.`);
- switch (tapBehavior) {
- case 'addToLibrary':
- addToLibrary();
- break;
- case 'favorite':
- setRating(1);
- break;
- default:
- addToLibrary();
- setRating(1);
- break;
- }
-});
-
-actions.ciderPlaybackAction.onDialDown(() => {
- console.debug(`[DEBUG] [Action] ciderPlaybackAction dial pressed`);
- switch (pressBehavior) {
- case 'togglePlay':
- comRPC("POST", "playpause");
- break;
- case 'toggleMute':
- muteVolume();
- break;
- default:
- comRPC("POST", "playpause");
- break;
- }
-});
-
-
-actions.ciderPlaybackAction.onDialRotate((jsonObj) => {
- setPreciseVolume(actions.ciderPlaybackAction, window.contexts.ciderPlaybackAction[0], jsonObj.payload, volumeStep);
-});
-
-// Populate the initial state of the plugin on page shift.
-
-actions.ciderPlaybackAction.onWillAppear(({ context }) => {
- if (window.contexts.ciderPlaybackAction[0] === context) {
- initialize();
- }
-});
-
-actions.albumArtAction.onWillAppear(({ context }) => {
- if (!window.contexts.ciderPlaybackAction[0]) {
- initialize();
- }
-});
-
-// Handle disappearing contexts
-
-actions.ciderPlaybackAction.onWillDisappear(() => {
- console.debug(`[DEBUG] [Action] ciderPlaybackAction action disappeared.`);
- clearMarquee();
- window.artworkCache = null;
- window.songCache = null;
-});
-
-actions.albumArtAction.onWillDisappear(() => {
- if (!window.contexts.ciderPlaybackAction[0]) {
- console.debug(`[DEBUG] [Action] ciderPlaybackAction action disappeared.`);
- clearMarquee();
- window.artworkCache = null;
- window.songCache = null;
- }
-});
-
+// ==========================================================================
+// Context Management
+// ==========================================================================
-// Action Initialization and Context Management
Object.keys(actions).forEach(actionKey => {
if (!window.contexts[actionKey]) {
window.contexts[actionKey] = [];
@@ -114,6 +73,11 @@ Object.keys(actions).forEach(actionKey => {
window.contexts[actionKey].push(context);
console.debug(`[DEBUG] [Context] Context added for ${actionKey}: ${context}`);
}
+ if (currentAppState === AppState.READY) {
+ if (actionKey === 'ciderPlaybackAction' || actionKey === 'albumArtAction' && currentAppState === AppState.READY) {
+ initialize();
+ }
+ }
});
action.onWillDisappear(({ context }) => {
@@ -122,9 +86,17 @@ Object.keys(actions).forEach(actionKey => {
window.contexts[actionKey].splice(index, 1);
console.debug(`[DEBUG] [Context] Context removed for ${actionKey}: ${context}`);
}
+
+ if (actionKey === 'ciderPlaybackAction' || actionKey === 'albumArtAction') {
+ if (!window.contexts.ciderPlaybackAction[0] && !window.contexts.albumArtAction[0]) {
+ console.debug(`[DEBUG] [Action] ciderPlaybackAction and albumArtAction disappeared.`);
+ clearMarquee();
+ window.artworkCache = null;
+ window.songCache = null;
+ }
+ }
});
- // Stream Deck Action Handlers
action.onKeyDown(() => {
console.debug(`[DEBUG] [Action] ${actionKey} action triggered.`);
switch (actionKey) {
@@ -150,121 +122,270 @@ Object.keys(actions).forEach(actionKey => {
addToLibrary();
break;
case 'volumeUpAction':
- setVolume("up");
+ handleVolumeChange(null, null, 'up');
break;
case 'volumeDownAction':
- setVolume("down");
+ handleVolumeChange(null, null, 'down');
break;
case 'ciderLogoAction':
- console.warn(`[DEBUG] [Action] User must be high, why you clicking the logo?`);
+ console.warn(`[DEBUG] [Action] User must be high, why you clicking the logo?`);
+ break;
+ case 'ciderPlaybackAction':
+ switch (tapBehavior) {
+ case 'addToLibrary':
+ addToLibrary();
+ break;
+ case 'favorite':
+ setRating(1);
+ break;
+ default:
+ addToLibrary();
+ setRating(1);
+ break;
+ }
break;
default:
console.warn(`[DEBUG] [Action] No handler for ${actionKey}`);
break;
}
});
-});
-// Receiving Global Settings
-$SD.onDidReceiveGlobalSettings(({ payload }) => {
- console.debug(`[DEBUG] [Settings] Global settings received: ${JSON.stringify(payload.settings)}`);
-
- // Set the settings based on the received global settings
- useAdaptiveIcons = payload.settings.iconSettings?.useAdaptiveIcons || true;
-
- if (payload.settings.marqueeSettings) {
- marqueeEnabled = payload.settings.marqueeSettings.enabled !== false;
- MARQUEE_SPEED = payload.settings.marqueeSettings.speed || 200;
- PAUSE_DURATION = payload.settings.marqueeSettings.delay || 2000;
- DISPLAY_LENGTH = payload.settings.marqueeSettings.length || 15;
- }
+ if (actionKey === 'ciderPlaybackAction') {
+ action.onDialDown(() => {
+ console.debug(`[DEBUG] [Action] ciderPlaybackAction dial pressed`);
+ switch (pressBehavior) {
+ case 'togglePlay':
+ comRPC("POST", "playpause");
+ break;
+ case 'toggleMute':
+ handleVolumeChange(null, window.contexts.ciderPlaybackAction[0], 'mute');
+ break;
+ default:
+ comRPC("POST", "playpause");
+ break;
+ }
+ });
- if (payload.settings.tapSettings) {
- tapBehavior = payload.settings.tapSettings.tapBehavior || 'addToLibrary';
+ action.onDialRotate((jsonObj) => {
+ handleVolumeChange(actions.ciderPlaybackAction, window.contexts.ciderPlaybackAction[0], null, jsonObj.payload);
+ });
}
+});
- if (payload.settings.knobSettings) {
- volumeStep = payload.settings.knobSettings.volumeStep || 1;
- pressBehavior = payload.settings.knobSettings.pressBehavior || 'togglePlay';
- }
+// ==========================================================================
+// Settings Management
+// ==========================================================================
+
+const defaultSettings = {
+ iconSettings: { useAdaptiveIcons: true },
+ marqueeSettings: {
+ enabled: true,
+ speed: 200,
+ delay: 2000,
+ length: 15
+ },
+ tapSettings: { tapBehavior: 'addToLibrary' },
+ knobSettings: {
+ volumeStep: 1,
+ pressBehavior: 'togglePlay'
+ },
+ authorization: { rpcKey: null }
+};
- console.debug(`[DEBUG] [Settings] Adaptive icons: ${useAdaptiveIcons}; Marquee enabled: ${marqueeEnabled}; Speed: ${MARQUEE_SPEED}; Length: ${DISPLAY_LENGTH}; Delay: ${PAUSE_DURATION}`);
+function updateSettings(settings) {
+ const mergedSettings = {...defaultSettings, ...settings};
- rpcKey = payload.settings.authorization?.rpcKey || null;
- window.token = rpcKey;
+ useAdaptiveIcons = mergedSettings.iconSettings.useAdaptiveIcons;
+ Object.assign(window, {
+ marqueeEnabled: mergedSettings.marqueeSettings.enabled,
+ MARQUEE_SPEED: mergedSettings.marqueeSettings.speed,
+ PAUSE_DURATION: mergedSettings.marqueeSettings.delay,
+ DISPLAY_LENGTH: mergedSettings.marqueeSettings.length,
+ tapBehavior: mergedSettings.tapSettings.tapBehavior,
+ volumeStep: mergedSettings.knobSettings.volumeStep,
+ pressBehavior: mergedSettings.knobSettings.pressBehavior,
+ token: mergedSettings.authorization.rpcKey
+ });
+
+ console.debug(`[DEBUG] [Settings] Updated settings:`, mergedSettings);
+
+ if (currentAppState === AppState.STARTING_UP) {
+ startupProcess();
+ }
+}
+
+$SD.onDidReceiveGlobalSettings(({ payload }) => {
+ console.debug(`[DEBUG] [Settings] Global settings received:`, payload.settings);
+ updateSettings(payload.settings);
checkAuthKey();
});
-async function checkAuthKey() {
+// ==========================================================================
+// Authentication and Connection
+// ==========================================================================
+
+const MAX_RETRY_ATTEMPTS = 5;
+const RETRY_DELAY = 5000; // 5 seconds
+let retryAttempts = 0;
+let reconnectTimeout;
+
+async function startupProcess() {
+ currentAppState = AppState.STARTING_UP;
+ console.log("[INFO] [Startup] Beginning startup process...");
+
if (!window.token) {
console.log("CiderDeck: Please enter your Cider authorization key in the plugin settings.");
+ alertContexts("No auth key");
+ currentAppState = AppState.ERROR;
return;
}
+
+ try {
+ await checkAuthKey();
+ await startWebSocket();
+ await initialize();
+ currentAppState = AppState.READY;
+ console.log("[INFO] [Startup] Startup process completed successfully.");
+ } catch (error) {
+ console.error("[ERROR] [Startup] Startup process failed:", error);
+ currentAppState = AppState.ERROR;
+ handleConnectionFailure();
+ }
+}
+
+async function checkAuthKey() {
try {
const data = await comRPC("GET", "active", true);
if (data.error) {
- alertContexts();
- $SD.getGlobalSettings();
- } else {
- startWebSocket();
+ throw new Error("Invalid response from Cider");
}
+ console.debug("[DEBUG] [Auth] Successfully authenticated with Cider");
+ isAuthenticated = true;
} catch (error) {
- alertContexts();
+ console.error("[ERROR] [Auth] Failed to authenticate:", error.message);
+ throw error;
+ }
+}
+
+function handleConnectionFailure() {
+ isAuthenticated = false;
+ alertContexts("Connection failed");
+
+ if (retryAttempts < MAX_RETRY_ATTEMPTS) {
+ retryAttempts++;
+ console.log(`[INFO] [Auth] Retrying connection (Attempt ${retryAttempts}/${MAX_RETRY_ATTEMPTS})...`);
+ reconnectTimeout = setTimeout(startupProcess, RETRY_DELAY);
+ } else {
+ console.error("[ERROR] [Auth] Max retry attempts reached. Please check your settings and Cider application status.");
$SD.getGlobalSettings();
}
}
+function startWebSocket() {
+ return new Promise((resolve, reject) => {
+ try {
+ const CiderApp = io('http://localhost:10767', {
+ reconnectionAttempts: MAX_RETRY_ATTEMPTS,
+ reconnectionDelay: RETRY_DELAY,
+ timeout: 10000 // 10 seconds timeout
+ });
+
+ CiderApp.on('connect', () => {
+ console.log("[INFO] [WebSocket] Connected to Cider");
+ resolve();
+ });
+
+ CiderApp.on("API:Playback", handlePlaybackEvent);
+
+ CiderApp.on('disconnect', (reason) => {
+ console.warn("[WARN] [WebSocket] Disconnected from Cider:", reason);
+ isAuthenticated = false;
+ currentAppState = AppState.ERROR;
+ if (reason === 'io server disconnect') {
+ CiderApp.connect();
+ }
+ });
+
+ CiderApp.on('error', (error) => {
+ console.error("[ERROR] [WebSocket] Connection error:", error);
+ handleConnectionFailure();
+ });
+
+ CiderApp.io.on('reconnect_attempt', (attemptNumber) => {
+ console.log(`[INFO] [WebSocket] Reconnection attempt ${attemptNumber}`);
+ });
+
+ CiderApp.io.on('reconnect_failed', () => {
+ console.error("[ERROR] [WebSocket] Failed to reconnect after all attempts");
+ handleConnectionFailure();
+ });
+ } catch (error) {
+ console.error("[ERROR] [WebSocket] Failed to initialize WebSocket:", error);
+ handleConnectionFailure();
+ }
+ });
+}
+
+function handlePlaybackEvent({ data, type }) {
+ if (!data && data !== 0) {
+ setDefaults();
+ return;
+ }
+
+ switch (type) {
+ case "playbackStatus.nowPlayingStatusDidChange":
+ setAdaptiveData(data);
+ break;
+ case "playbackStatus.nowPlayingItemDidChange":
+ setManualData(data);
+ break;
+ case "playbackStatus.playbackStateDidChange":
+ if (data) setData(data);
+ break;
+ case "playbackStatus.playbackTimeDidChange":
+ setPlaybackStatus(data.isPlaying);
+ if (window.contexts.ciderPlaybackAction[0]) {
+ setPlaybackTime(data.currentPlaybackTime, data.currentPlaybackDuration);
+ }
+ break;
+ case "playerStatus.volumeDidChange":
+ if (window.contexts.ciderPlaybackAction[0]) {
+ updateVolumeDisplay(window.contexts.ciderPlaybackAction[0], data);
+ }
+ break;
+ default:
+ console.warn("[WARN] [Playback] Unhandled event type:", type);
+ }
+}
+
async function initialize() {
- await comRPC("GET", "now-playing").then(data => {
+ if (!isAuthenticated) {
+ throw new Error("Attempted to initialize before authentication.");
+ }
+
+ try {
+ const data = await comRPC("GET", "now-playing");
if (data.status === "ok") {
setManualData(data.info);
setAdaptiveData(data.info);
if(window.contexts.ciderPlaybackAction[0]) {
initializeVolumeDisplay(actions.ciderPlaybackAction, window.contexts.ciderPlaybackAction[0]);
- };
- }
- }).catch(console.error);
-};
-
-async function startWebSocket() {
- try {
- const CiderApp = io('http://localhost:10767');
-
- await initialize();
-
- CiderApp.on("API:Playback", ({ data, type }) => {
- if (!data && data !== 0) return setDefaults();
- switch (type) {
- case "playbackStatus.nowPlayingStatusDidChange":
- setAdaptiveData(data);
- break;
- case "playbackStatus.nowPlayingItemDidChange":
- setManualData(data);
- break;
- case "playbackStatus.playbackStateDidChange":
- if (data) setData(data);
- break;
- case "playbackStatus.playbackTimeDidChange":
- setPlaybackStatus(data.isPlaying);
- if(window.contexts.ciderPlaybackAction[0]) { setPlaybackTime(data.currentPlaybackTime, data.currentPlaybackDuration); }
- break;
- case "playerStatus.volumeDidChange":
- if(window.contexts.ciderPlaybackAction[0]) { updateVolumeDisplay(window.contexts.ciderPlaybackAction[0], data); }
- break;
}
- });
-
- CiderApp.on("close", () => setTimeout(checkAuthKey, 5000));
- CiderApp.onerror = () => setTimeout(checkAuthKey, 5000);
-
+ } else {
+ throw new Error("Invalid response from now-playing endpoint");
+ }
} catch (error) {
- // Retry connection on failure.
- $SD.getGlobalSettings();
+ console.error("[ERROR] [Init] Failed to initialize:", error.message);
+ throw error;
}
}
+// ==========================================================================
+// Playback Control Functions
+// ==========================================================================
+
async function setDefaults() {
console.debug("[DEBUG] [Defaults] Setting default state.");
Object.keys(actions).forEach(actionKey => {
@@ -292,7 +413,6 @@ async function setAdaptiveData({ inLibrary, inFavorites }) {
}
}
-
async function setData({ state, attributes }) {
setPlaybackStatus(state);
@@ -361,6 +481,10 @@ async function setPlaybackTime(time, duration) {
$SD.setFeedback(window.contexts.ciderPlaybackAction[0], feedbackPayload);
}
+// ==========================================================================
+// Library and Rating Functions
+// ==========================================================================
+
async function addToLibrary() {
if (!window.addedToLibrary) {
await comRPC("POST", "add-to-library", true);
@@ -370,83 +494,67 @@ async function addToLibrary() {
}
}
-let isChangingVolume = false;
-let isMuted = false;
-let previousVolume;
+async function setRating(ratingValue) {
+ if (window.ratingCache !== ratingValue) {
+ await comRPC("POST", "set-rating", true, { rating: ratingValue });
-async function setVolume(direction) {
- if (isChangingVolume) return;
- if (isMuted) { muteVolume(!isMuted); return};
- isChangingVolume = true;
+ const likeIcon = ratingValue === 1 ? 'liked.png' : 'like.png';
+ const dislikeIcon = ratingValue === -1 ? 'disliked.png' : 'dislike.png';
- try {
- const { volume: audioData } = await comRPC("GET", "volume");
- const roundedVolume = Math.round(audioData * 20) / 20;
- const newVolume = direction === "up" ? Math.min(roundedVolume + 0.05, 1) : Math.max(roundedVolume - 0.05, 0);
- await comRPC("POST", "volume", true, { volume: newVolume });
- console.debug("[DEBUG] [Volume] Volume changed to:", newVolume);
- } catch (error) {
- console.error("Error changing volume:", error);
- } finally {
- isChangingVolume = false;
+ window.contexts.likeAction?.forEach(context => setImage(context, `actions/playback/assets/${likeIcon}`, 0));
+ window.contexts.dislikeAction?.forEach(context => setImage(context, `actions/playback/assets/${dislikeIcon}`, 0));
+
+ window.ratingCache = ratingValue;
+ console.debug("[DEBUG] [Rating] Updated rating to:", ratingValue);
}
}
-async function muteVolume() {
- if (isChangingVolume) return;
- isChangingVolume = true;
- if (!isMuted) {
- previousVolume = await comRPC("GET", "volume").then(data => data.volume);
- }
- isMuted = !isMuted;
+// ==========================================================================
+// Volume Control Functions
+// ==========================================================================
- try {
- const newVolume = isMuted ? 0 : previousVolume;
- await comRPC("POST", "volume", true, { volume: newVolume });
- console.debug("[DEBUG] [Volume] Volume changed to:", newVolume);
-
- // Update Stream Deck+ display
- if (window.contexts.ciderPlaybackAction[0]) {
- const feedbackPayload = {
- "indicator2": isMuted ? 0 : Math.round(previousVolume * 100),
- "icon2": "actions/playback/assets/volup"
- };
- $SD.setFeedback(window.contexts.ciderPlaybackAction[0], feedbackPayload);
- }
- } catch (error) {
- console.error("Error changing volume:", error);
- } finally {
- isChangingVolume = false;
- }
-}
+let isChangingVolume = false;
+let isMuted = false;
+let previousVolume;
-// Stream Deck + Exclusive Vol Control (Left/Right +1% and set progress bar on dial display)
-async function setPreciseVolume(action, context, payload, volumeStep) {
+async function handleVolumeChange(action, context, direction, payload) {
if (isChangingVolume) return;
- if (isMuted) { muteVolume(!isMuted); return};
isChangingVolume = true;
try {
- const { volume: currentVolume } = await comRPC("GET", "volume");
+ let { volume: currentVolume } = await comRPC("GET", "volume");
+ let currentVolumePercent = Math.round(currentVolume * 100);
+
+ const globalVolumeStep = window.volumeStep;
+
let newVolume;
- if (payload.ticks !== undefined) {
- // Dial rotation
- // multiply 0.01 by volumeStep to adjust sensitivity (1-10)
- newVolume = Math.max(0, Math.min(1, currentVolume + (payload.ticks * 0.01 * volumeStep)));
+ if (isMuted && direction !== 'mute') {
+ isMuted = false;
+ newVolume = previousVolume;
+ } else if (direction === 'mute') {
+ isMuted = !isMuted;
+ previousVolume = currentVolume;
+ newVolume = isMuted ? 0 : previousVolume;
+ } else if (direction === 'up' || direction === 'down') {
+ newVolume = direction === 'up'
+ ? Math.min(currentVolume + globalVolumeStep / 100, 1)
+ : Math.max(currentVolume - globalVolumeStep / 100, 0);
+ } else if (payload && payload.ticks !== undefined) {
+ newVolume = Math.max(0, Math.min(1, currentVolume + (payload.ticks * globalVolumeStep / 100)));
}
- await comRPC("POST", "volume", true, { volume: newVolume });
- console.debug("[DEBUG] [Volume] Volume changed to:", newVolume);
-
- // Update Stream Deck+ display
- const volumePercentage = Math.round(newVolume * 100);
- const feedbackPayload = {
- "indicator2": volumePercentage,
- "icon2": "actions/playback/assets/volup"
- };
- $SD.setFeedback(context, feedbackPayload);
+ if (newVolume !== undefined) {
+ let newVolumePercent = Math.round(newVolume * 100);
+
+ if (Math.abs(newVolumePercent - currentVolumePercent) < globalVolumeStep / 2) {
+ return;
+ }
+ await comRPC("POST", "volume", true, { volume: newVolume });
+ console.debug(`[DEBUG] [Volume] Volume changed from to ${newVolumePercent}%`);
+ updateVolumeDisplay(context, newVolume);
+ }
} catch (error) {
console.error("Error changing volume:", error);
} finally {
@@ -454,7 +562,7 @@ async function setPreciseVolume(action, context, payload, volumeStep) {
}
}
-async function updateVolumeDisplay(context, volume) {
+function updateVolumeDisplay(context, volume) {
const volumePercentage = Math.round(volume * 100);
const feedbackPayload = {
"indicator2": volumePercentage,
@@ -463,49 +571,37 @@ async function updateVolumeDisplay(context, volume) {
$SD.setFeedback(context, feedbackPayload);
}
-async function initializeVolumeDisplay(action, context, payload) {
+async function initializeVolumeDisplay(action, context) {
try {
const { volume: currentVolume } = await comRPC("GET", "volume");
- const volumePercentage = Math.round(currentVolume * 100);
-
- const feedbackPayload = {
- "indicator2": volumePercentage,
- "icon2": "actions/playback/assets/volup"
- };
- $SD.setFeedback(context, feedbackPayload);
-
- console.debug("[DEBUG] [Volume] Display initialized with volume:", volumePercentage);
+ updateVolumeDisplay(context, currentVolume);
+ console.debug("[DEBUG] [Volume] Display initialized with volume:", Math.round(currentVolume * 100));
} catch (error) {
console.error("Error initializing volume display:", error);
}
}
-
-
-async function setRating(ratingValue) {
- if (window.ratingCache !== ratingValue) {
- await comRPC("POST", "set-rating", true, { rating: ratingValue });
-
- const likeIcon = ratingValue === 1 ? 'liked.png' : 'like.png';
- const dislikeIcon = ratingValue === -1 ? 'disliked.png' : 'dislike.png';
- window.contexts.likeAction?.forEach(context => setImage(context, `actions/playback/assets/${likeIcon}`, 0));
- window.contexts.dislikeAction?.forEach(context => setImage(context, `actions/playback/assets/${dislikeIcon}`, 0));
-
- window.ratingCache = ratingValue;
- console.debug("[DEBUG] [Rating] Updated rating to:", ratingValue);
- }
-}
+// ==========================================================================
+// Utility Functions
+// ==========================================================================
-function alertContexts() {
- Object.keys(window.contexts).forEach(actionKey => {
- window.contexts[actionKey].forEach(context => {
- $SD.showAlert(context);
- console.debug(`[DEBUG] [Alert] Alert shown for context: ${context}`);
- });
+function alertContexts(message) {
+ Object.values(window.contexts).flat().forEach(context => {
+ $SD.showAlert(context);
+ console.debug(`[DEBUG] [Alert] Alert shown for context: ${context}`);
});
+ if (message) {
+ console.log(`[INFO] [Alert] ${message}`);
+ }
}
async function comRPC(method, request, noCheck, _body) {
+ // Check and make sure token is set before attempting to make a request.
+ if (!window.token) {
+ console.log("CiderDeck: Please enter your Cider authorization key in the plugin settings.");
+ return;
+ }
+
const fetchOptions = {
method,
headers: {
@@ -530,6 +626,26 @@ function setTitle(action, title, context) {
if (action && title && context !== null) $SD.setTitle(action, title, context);
}
+function getBase64Image(url) {
+ return new Promise((resolve, reject) => {
+ const image = new Image();
+ image.crossOrigin = 'anonymous';
+ image.onload = () => {
+ const canvas = document.createElement('canvas');
+ canvas.width = image.naturalWidth;
+ canvas.height = image.naturalHeight;
+ canvas.getContext('2d').drawImage(image, 0, 0);
+ resolve(canvas.toDataURL('image/png'));
+ };
+ image.onerror = () => reject(new Error('Failed to load image'));
+ image.src = url;
+ });
+}
+
+// ==========================================================================
+// Marquee Functions
+// ==========================================================================
+
function clearMarquee() {
if (marqueeInterval) {
clearInterval(marqueeInterval);
@@ -579,7 +695,7 @@ function updateMarqueeDisplay(context) {
return;
}
- let visibleText = currentMarqueeText.substr(marqueePosition, DISPLAY_LENGTH);
+ let visibleText = currentMarqueeText.substring(marqueePosition, marqueePosition + DISPLAY_LENGTH);
// Pad with spaces if we're near the end to avoid text wrapping
if (visibleText.length < DISPLAY_LENGTH) {
@@ -588,19 +704,3 @@ function updateMarqueeDisplay(context) {
$SD.setFeedback(context, { "title": visibleText });
}
-
-function getBase64Image(url) {
- return new Promise((resolve, reject) => {
- const image = new Image();
- image.crossOrigin = 'anonymous';
- image.onload = () => {
- const canvas = document.createElement('canvas');
- canvas.width = image.naturalWidth;
- canvas.height = image.naturalHeight;
- canvas.getContext('2d').drawImage(image, 0, 0);
- resolve(canvas.toDataURL('image/png'));
- };
- image.onerror = () => reject(new Error('Failed to load image'));
- image.src = url;
- });
-}