diff --git a/plugins/multi-scrobbler/src/constants.ts b/plugins/multi-scrobbler/src/constants.ts index 4d08944..0cd71ea 100644 --- a/plugins/multi-scrobbler/src/constants.ts +++ b/plugins/multi-scrobbler/src/constants.ts @@ -16,24 +16,28 @@ const Constants = { SERVICES: { lastfm: { name: "Last.fm", - baseUrl: "http://ws.audioscrobbler.com/2.0", + baseUrl: "https://ws.audioscrobbler.com/2.0", requiresApiKey: true, requiresToken: false, }, librefm: { name: "Libre.fm", - baseUrl: "http://libre.fm/api", + baseUrl: "https://libre.fm/api", requiresApiKey: true, requiresToken: false, }, listenbrainz: { name: "ListenBrainz", - baseUrl: "http://api.listenbrainz.org/1", + baseUrl: "https://api.listenbrainz.org/1", requiresApiKey: false, requiresToken: true, }, } as Record, + // Default request headers. Avoid setting a custom User-Agent here to prevent exposing + // environment or build details; the runtime should manage safe headers. + DEFAULT_HEADERS: {} as Record, + // Last.fm/Libre.fm use these hashes for their generic album covers DEFAULT_COVER_HASHES: [ "2a96cbd8b46e442fc41c2b86b821562f", diff --git a/plugins/multi-scrobbler/src/services/BaseService.ts b/plugins/multi-scrobbler/src/services/BaseService.ts index da49b80..3df5845 100644 --- a/plugins/multi-scrobbler/src/services/BaseService.ts +++ b/plugins/multi-scrobbler/src/services/BaseService.ts @@ -3,119 +3,119 @@ import Constants from "../constants"; import { incrementApiCall, recordServiceError } from "../utils/debug"; export abstract class BaseService implements ServiceClient { - protected retryCount = 0; - protected lastError = 0; + protected retryCount = 0; + protected lastError = 0; abstract fetchLatestScrobble(): Promise; abstract validateCredentials(): Promise; abstract getServiceName(): string; protected log(...args: any[]): void { - console.log(`[${this.getServiceName()}]`, ...args); + console.log(`[${this.getServiceName()}]`, ...args); } protected logError(...args: any[]): void { - console.error(`[${this.getServiceName()}] Error:`, ...args); + console.error(`[${this.getServiceName()}] Error:`, ...args); } protected logVerbose(...args: any[]): void { - // subclasses will check if verbose logging is enabled - console.log(`[${this.getServiceName()}] Verbose:`, ...args); + // subclasses will check if verbose logging is enabled + console.log(`[${this.getServiceName()}] Verbose:`, ...args); } protected async handleError(error: any): Promise { - this.lastError = error.error || 0; + this.lastError = error.error || 0; - const errorMessage = this.getErrorMessage(error); - this.logError(errorMessage); - recordServiceError( + const errorMessage = this.getErrorMessage(error); + this.logError(errorMessage); + recordServiceError( this.getServiceName().toLowerCase() as any, errorMessage, - ); + ); - throw new Error(`${this.getServiceName()} API Error: ${errorMessage}`); + throw new Error(`${this.getServiceName()} API Error: ${errorMessage}`); } protected getErrorMessage(error: any): string { - if (error.error && Constants.API_ERROR_CODES[error.error]) { - return Constants.API_ERROR_CODES[error.error]; - } - return error.message || error.toString() || "Unknown error"; + if (error.error && Constants.API_ERROR_CODES[error.error]) { + return Constants.API_ERROR_CODES[error.error]; + } + return error.message || error.toString() || "Unknown error"; } protected async makeRequest( - url: string, - options: RequestInit = {}, + url: string, + options: RequestInit = {}, ): Promise { - try { - this.logVerbose(`Making request to: ${url}`); - incrementApiCall(); + try { + this.logVerbose(`Making request to: ${url}`); + incrementApiCall(); - const response = await fetch(url, { - ...options, - headers: { - "User-Agent": "Vendetta Multi-Service Scrobbler/3.0.0", - ...options.headers, - }, - }); + const response = await fetch(url, { + ...options, + headers: { + "User-Agent": "Vendetta Multi-Service Scrobbler/3.0.0", + ...options.headers, + }, + }); - if (!response.ok) { - const error = new Error( - `HTTP ${response.status}: ${response.statusText}`, - ); - recordServiceError( + if (!response.ok) { + const error = new Error( + `HTTP ${response.status}: ${response.statusText}`, + ); + recordServiceError( this.getServiceName().toLowerCase() as any, error.message, - ); - throw error; - } + ); + throw error; + } - const data = await response.json(); + const data = await response.json(); - if (data.error) { - await this.handleError(data); - } + if (data.error) { + await this.handleError(data); + } - this.retryCount = 0; // worked, reset retry counter - return data; - } catch (error) { - this.retryCount++; - if (this.retryCount > Constants.MAX_RETRY_ATTEMPTS) { - this.retryCount = 0; - recordServiceError( + this.retryCount = 0; // worked, reset retry counter + return data; + } catch (error) { + this.retryCount++; + if (this.retryCount > Constants.MAX_RETRY_ATTEMPTS) { + this.retryCount = 0; + recordServiceError( this.getServiceName().toLowerCase() as any, `Max retries exceeded: ${(error as Error).message}`, - ); - throw error; - } - - this.logVerbose( - `Request failed, retrying (${this.retryCount}/${Constants.MAX_RETRY_ATTEMPTS})`, - ); - await new Promise((resolve) => - setTimeout(resolve, Constants.RETRY_DELAY), - ); - return this.makeRequest(url, options); + ); + throw error; } + + this.logVerbose( + `Request failed, retrying (${this.retryCount}/${Constants.MAX_RETRY_ATTEMPTS})`, + ); + await new Promise((resolve) => + setTimeout(resolve, Constants.RETRY_DELAY), + ); + return this.makeRequest(url, options); + } } protected isDefaultCover(cover?: string): boolean { - if (!cover) return true; - return Constants.DEFAULT_COVER_HASHES.some((hash) => cover.includes(hash)); + if (!cover) return true; + return Constants.DEFAULT_COVER_HASHES.some((hash) => cover.includes(hash)); } protected processAlbumArt(cover?: string): string | null { - if (!cover || this.isDefaultCover(cover)) { - return null; - } - return cover; + if (!cover || this.isDefaultCover(cover)) { + return null; + } + return cover; } public getLastError(): number { - return this.lastError; + return this.lastError; } public resetRetryCount(): void { - this.retryCount = 0; + this.retryCount = 0; } }