From a62fe705f91876af0ab637e074e205db115c83b3 Mon Sep 17 00:00:00 2001 From: FoxxMD Date: Mon, 19 Feb 2024 13:02:28 -0500 Subject: [PATCH] fix: Fix lint errors --- eslint.config.js | 4 +- src/backend/common/infrastructure/Atomic.ts | 2 +- .../typings/lastfm-node-client.d.ts | 1 - src/backend/common/logging.ts | 35 +++++----------- src/backend/common/vendor/JRiverApiClient.ts | 2 +- src/backend/common/vendor/KodiApiClient.ts | 8 ++-- src/backend/common/vendor/LastfmApiClient.ts | 3 +- .../common/vendor/ListenbrainzApiClient.ts | 4 +- .../scrobblers/AbstractScrobbleClient.ts | 26 ++++-------- src/backend/scrobblers/LastfmScrobbler.ts | 6 +-- .../scrobblers/ListenbrainzScrobbler.ts | 4 +- src/backend/scrobblers/MalojaScrobbler.ts | 6 +-- src/backend/scrobblers/ScrobbleClients.ts | 18 +++----- src/backend/server/api.ts | 30 +++++++------ src/backend/server/auth.ts | 6 +-- src/backend/server/deezerRoutes.ts | 6 +-- src/backend/server/jellyfinRoutes.ts | 8 ++-- src/backend/server/plexRoutes.ts | 6 +-- src/backend/server/tautulliRoutes.ts | 2 +- src/backend/server/webscrobblerRoutes.ts | 4 +- src/backend/sources/AbstractSource.ts | 37 +++++----------- src/backend/sources/ChromecastSource.ts | 15 +++---- src/backend/sources/DeezerSource.ts | 18 +++----- src/backend/sources/JRiverSource.ts | 4 +- src/backend/sources/JellyfinSource.ts | 8 +--- src/backend/sources/KodiSource.ts | 2 +- src/backend/sources/LastfmSource.ts | 4 +- src/backend/sources/ListenbrainzSource.ts | 4 +- src/backend/sources/MPRISSource.ts | 14 +++---- src/backend/sources/MemorySource.ts | 16 +++---- src/backend/sources/MopidySource.ts | 2 +- .../PlayerState/AbstractPlayerState.ts | 6 +-- src/backend/sources/PlexSource.ts | 15 +++---- src/backend/sources/ScrobbleSources.ts | 21 ++++------ src/backend/sources/SpotifySource.ts | 42 ++++--------------- src/backend/sources/SubsonicSource.ts | 10 ++--- src/backend/sources/TautulliSource.ts | 9 +--- src/backend/sources/WebScrobblerSource.ts | 4 +- src/backend/sources/YTMusicSource.ts | 15 ++----- .../ingressNotifiers/TautulliNotifier.ts | 2 +- .../tests/listenbrainz/listenbrainz.test.ts | 2 +- src/backend/tests/utils/interfaces.ts | 2 +- src/backend/utils.ts | 19 ++++----- src/backend/utils/MDNSUtils.ts | 4 +- src/backend/utils/StringUtils.ts | 20 ++++----- src/backend/utils/TimeUtils.ts | 10 +++-- 46 files changed, 178 insertions(+), 308 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index 573ddeb9..7b649147 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -11,6 +11,7 @@ export default tsEslint.config( ...tsEslint.configs.recommended, ], files: ['src/backend/**/*.ts'], + ignores: ['eslint.config.js'], plugins: { "prefer-arrow-functions": arrow }, @@ -27,7 +28,8 @@ export default tsEslint.config( "singleReturnOnly": false } ], - "arrow-body-style": ["warn", "as-needed"] + "arrow-body-style": ["warn", "as-needed"], + "@typescript-eslint/no-explicit-any": "warn" } } ); diff --git a/src/backend/common/infrastructure/Atomic.ts b/src/backend/common/infrastructure/Atomic.ts index 06195c53..0909f857 100644 --- a/src/backend/common/infrastructure/Atomic.ts +++ b/src/backend/common/infrastructure/Atomic.ts @@ -205,7 +205,7 @@ export interface numberFormatOptions { } } -export const DELIMITERS = [',','&','\/','\\']; +export const DELIMITERS = [',','&','/','\\']; export const ARTIST_WEIGHT = 0.3; export const TITLE_WEIGHT = 0.4; diff --git a/src/backend/common/infrastructure/typings/lastfm-node-client.d.ts b/src/backend/common/infrastructure/typings/lastfm-node-client.d.ts index 3f0a7304..8ee66f5c 100644 --- a/src/backend/common/infrastructure/typings/lastfm-node-client.d.ts +++ b/src/backend/common/infrastructure/typings/lastfm-node-client.d.ts @@ -103,7 +103,6 @@ declare module 'lastfm-node-client' { }, duration: number, date?: { - // @ts-ignore uts: number, }, '@attr'?: { diff --git a/src/backend/common/logging.ts b/src/backend/common/logging.ts index 36d26604..346840cd 100644 --- a/src/backend/common/logging.ts +++ b/src/backend/common/logging.ts @@ -60,7 +60,7 @@ export const getLogger = (config: LogConfig = {}, name = 'app'): winstonNs.Logge const myTransports: TransportStream[] = [ new DuplexTransport({ stream: { - transform(chunk, e, cb) { + transform: (chunk, e, cb) => { cb(null, chunk); }, objectMode: true, @@ -90,10 +90,9 @@ export const getLogger = (config: LogConfig = {}, name = 'app'): winstonNs.Logge try { fileOrDirectoryIsWriteable(logPath); - // @ts-ignore myTransports.push(rotateTransport); } catch (e: any) { - let msg = 'WILL NOT write logs to rotating file due to an error while trying to access the specified logging directory'; + const msg = 'WILL NOT write logs to rotating file due to an error while trying to access the specified logging directory'; errors.push(new ErrorWithCause(msg, {cause: e})); } } @@ -156,7 +155,7 @@ export const defaultFormat = (defaultLabel = 'App') => printf(({ ...rest }) => { const keys = Object.keys(rest); - let stringifyValue = keys.length > 0 && !keys.every(x => causeKeys.some(y => y == x)) ? stringify.default(rest) : ''; + const stringifyValue = keys.length > 0 && !keys.every(x => causeKeys.some(y => y == x)) ? stringify.default(rest) : ''; let msg = message; let stackMsg = ''; if (stack !== undefined) { @@ -174,7 +173,7 @@ export const defaultFormat = (defaultLabel = 'App') => printf(({ } } - let nodes = Array.isArray(labels) ? labels : [labels]; + const nodes = Array.isArray(labels) ? labels : [labels]; if (leaf !== null && leaf !== undefined && !nodes.includes(leaf)) { nodes.push(leaf); } @@ -221,7 +220,6 @@ export const logLevels = { export const LOG_LEVEL_REGEX: RegExp = /\s*(debug|warn|info|error|verbose)\s*:/i export const isLogLineMinLevel = (log: string | LogInfo, minLevelText: LogLevel): boolean => { - // @ts-ignore const minLevel = logLevels[minLevelText]; let level: number; @@ -230,19 +228,15 @@ export const isLogLineMinLevel = (log: string | LogInfo, minLevelText: LogLevel) if (lineLevelMatch === null) { return false; } - // @ts-ignore level = logLevels[lineLevelMatch[1]]; } else { const lineLevelMatch = log.level; - // @ts-ignore level = logLevels[lineLevelMatch]; } return level <= minLevel; } -export const isLogLevelMinLevel = (levelStr: LogLevel, minLevelStr: LogLevel): boolean => { - return logLevels[levelStr] <= logLevels[minLevelStr]; -} +export const isLogLevelMinLevel = (levelStr: LogLevel, minLevelStr: LogLevel): boolean => logLevels[levelStr] <= logLevels[minLevelStr] const isProbablyError = (val: any, explicitErrorName?: string) => { if(typeof val !== 'object' || val === null) { @@ -281,12 +275,10 @@ const errorAwareFormat = { if (isProbablyError(einfo)) { const tinfo = transformError(einfo); info = Object.assign({}, tinfo, { - // @ts-ignore level: einfo.level, - // @ts-ignore [LEVEL]: einfo[LEVEL] || einfo.level, message: tinfo.message, - // @ts-ignore + [MESSAGE]: tinfo[MESSAGE] || tinfo.message }); if(includeStack) { @@ -294,20 +286,17 @@ const errorAwareFormat = { const dummyErr = new ErrorWithCause(''); const names = Object.getOwnPropertyNames(tinfo); for(const k of names) { + // eslint-disable-next-line no-prototype-builtins if(dummyErr.hasOwnProperty(k) || k === 'cause') { - // @ts-ignore dummyErr[k] = tinfo[k]; } } - // @ts-ignore info.stack = stackWithCauses(dummyErr); } } else { const err = transformError(einfo.message); info = Object.assign({}, einfo, err); - // @ts-ignore info.message = err.message; - // @ts-ignore info[MESSAGE] = err.message; if(includeStack) { @@ -316,12 +305,11 @@ const errorAwareFormat = { // https://stackoverflow.com/a/18278145/1469797 const names = Object.getOwnPropertyNames(err); for(const k of names) { + // eslint-disable-next-line no-prototype-builtins if(dummyErr.hasOwnProperty(k) || k === 'cause') { - // @ts-ignore dummyErr[k] = err[k]; } } - // @ts-ignore info.stack = stackWithCauses(dummyErr); } } @@ -350,14 +338,13 @@ const _transformError = (err: Error, seen: Set) => { try { - // @ts-ignore - let mOpts = err.matchOptions ?? matchOptions; + // @ts-expect-error type missing expected props + const mOpts = err.matchOptions ?? matchOptions; - // @ts-ignore const cause = err.cause as unknown; if (cause !== undefined && cause instanceof Error) { - // @ts-ignore + // @ts-expect-error type missing expected props err.cause = _transformError(cause, seen, mOpts); } diff --git a/src/backend/common/vendor/JRiverApiClient.ts b/src/backend/common/vendor/JRiverApiClient.ts index 06ec919f..848a5420 100644 --- a/src/backend/common/vendor/JRiverApiClient.ts +++ b/src/backend/common/vendor/JRiverApiClient.ts @@ -140,7 +140,7 @@ export class JRiverApiClient extends AbstractApiClient { testAuth = async () => { try { - let req = request.get(`${this.url}Authenticate`); + const req = request.get(`${this.url}Authenticate`); if (this.config.username !== undefined) { req.auth(this.config.username, this.config.password); } diff --git a/src/backend/common/vendor/KodiApiClient.ts b/src/backend/common/vendor/KodiApiClient.ts index b4d5f701..acde4e7e 100644 --- a/src/backend/common/vendor/KodiApiClient.ts +++ b/src/backend/common/vendor/KodiApiClient.ts @@ -99,8 +99,8 @@ export class KodiApiClient extends AbstractApiClient { playerid, } = obj; - let artists = artistVal === null || artistVal === undefined ? [] : artistVal; - let album = albumVal === null || albumVal === '' ? undefined : albumVal; + const artists = artistVal === null || artistVal === undefined ? [] : artistVal; + const album = albumVal === null || albumVal === '' ? undefined : albumVal; const trackProgressPosition = time !== undefined ? Math.round(dayjs.duration(time).asSeconds()) : undefined; return { @@ -150,14 +150,14 @@ export class KodiApiClient extends AbstractApiClient { getPlayerInfo = async (id: number): Promise => { // https://kodi.wiki/view/JSON-RPC_API/v12#Player.GetProperties - // @ts-ignore + // @ts-expect-error types are wrong const playerInfo = await this.client.Player.GetProperties(0, ["position","type","time","totaltime"]) return playerInfo; } getPlayerItem = async (id: number): Promise<{item: PlayerItem}> => { // https://kodi.wiki/view/JSON-RPC_API/v12#Player.GetItem - // @ts-ignore + // @ts-expect-error types are wrong const itemInfo = await this.client.Player.GetItem(0, ["title","artist","album","albumartist","starttime","endtime","duration","streamdetails","uniqueid"]); return itemInfo as {item: PlayerItem}; } diff --git a/src/backend/common/vendor/LastfmApiClient.ts b/src/backend/common/vendor/LastfmApiClient.ts index 0803fd47..75b13c3a 100644 --- a/src/backend/common/vendor/LastfmApiClient.ts +++ b/src/backend/common/vendor/LastfmApiClient.ts @@ -61,7 +61,6 @@ export default class LastfmApiClient extends AbstractApiClient { }, duration, date: { - // @ts-ignore uts: time, } = {}, '@attr': { @@ -71,7 +70,7 @@ export default class LastfmApiClient extends AbstractApiClient { mbid, } = obj; // arbitrary decision yikes - let artistStrings = splitByFirstFound(artists, [','], [artistName]); + const artistStrings = splitByFirstFound(artists, [','], [artistName]); return { data: { artists: [...new Set(artistStrings)] as string[], diff --git a/src/backend/common/vendor/ListenbrainzApiClient.ts b/src/backend/common/vendor/ListenbrainzApiClient.ts index 2888f0bf..65752229 100644 --- a/src/backend/common/vendor/ListenbrainzApiClient.ts +++ b/src/backend/common/vendor/ListenbrainzApiClient.ts @@ -150,7 +150,7 @@ export class ListenbrainzApiClient extends AbstractApiClient { if(status !== undefined) { const msgParts = [`(HTTP Status ${status})`]; // if the response is 400 then its likely there was an issue with the data we sent rather than an error with the service - let showStopper = status !== 400; + const showStopper = status !== 400; if(body !== undefined) { if(typeof body === 'object') { if('code' in body) { @@ -392,7 +392,7 @@ export class ListenbrainzApiClient extends AbstractApiClient { } // now try to extract any remaining artists from filtered artist/name values - let parsedArtists = parseArtistCredits(filteredSubmittedArtistName); + const parsedArtists = parseArtistCredits(filteredSubmittedArtistName); if (parsedArtists !== undefined) { if (parsedArtists.primary !== undefined) { artistsFromUserValues.push(parsedArtists.primary); diff --git a/src/backend/scrobblers/AbstractScrobbleClient.ts b/src/backend/scrobblers/AbstractScrobbleClient.ts index a31dbf7a..36a55c9c 100644 --- a/src/backend/scrobblers/AbstractScrobbleClient.ts +++ b/src/backend/scrobblers/AbstractScrobbleClient.ts @@ -172,17 +172,11 @@ export default abstract class AbstractScrobbleClient implements Authenticatable return true; } - authGated = () => { - return this.requiresAuth && !this.authed; - } + authGated = () => this.requiresAuth && !this.authed - canTryAuth = () => { - return this.authGated() && this.authFailure !== true; - } + canTryAuth = () => this.authGated() && this.authFailure !== true - protected doAuthentication = async (): Promise => { - return this.authed; - } + protected doAuthentication = async (): Promise => this.authed // default init function, should be overridden if auth stage is required testAuth = async () => { @@ -197,18 +191,14 @@ export default abstract class AbstractScrobbleClient implements Authenticatable } } - isReady = async () => { - return this.initialized && !this.authGated(); - } + isReady = async () => this.initialized && !this.authGated() refreshScrobbles = async () => { this.logger.debug('Scrobbler does not have refresh function implemented!'); } public abstract alreadyScrobbled(playObj: PlayObject, log?: boolean): Promise; - scrobblesLastCheckedAt = () => { - return this.lastScrobbleCheck; - } + scrobblesLastCheckedAt = () => this.lastScrobbleCheck formatPlayObj = (obj: any, options: FormatPlayObjectOptions = {}) => { this.logger.warn('formatPlayObj should be defined by concrete class!'); @@ -246,9 +236,7 @@ export default abstract class AbstractScrobbleClient implements Authenticatable this.scrobbledPlayObjs = new FixedSizeList(this.MAX_STORED_SCROBBLES, this.scrobbledPlayObjs.data.filter(x => this.timeFrameIsValid(x.play)[0])) ; } - getScrobbledPlays = () => { - return this.scrobbledPlayObjs.data.map(x => x.scrobble); - } + getScrobbledPlays = () => this.scrobbledPlayObjs.data.map(x => x.scrobble) findExistingSubmittedPlayObj = (playObj: PlayObject): ([undefined, undefined] | [ScrobbledPlayObject, ScrobbledPlayObject[]]) => { const { @@ -392,7 +380,7 @@ export default abstract class AbstractScrobbleClient implements Authenticatable artistBreakdown = `Artist: (${artistMatch.toFixed(2)} + Whole Match Bonus ${artistWholeMatchBonus.toFixed(2)}) * (${ARTIST_WEIGHT} + Whole Match Bonus 0.05) = ${artistScore.toFixed(2)}`; } - let scoreBreakdowns = [ + const scoreBreakdowns = [ //`Reference: ${(referenceMatch ? 1 : 0)} * ${REFERENCE_WEIGHT} = ${referenceScore.toFixed(2)}`, artistBreakdown, `Title: ${titleMatch.toFixed(2)} * ${TITLE_WEIGHT} = ${titleScore.toFixed(2)}`, diff --git a/src/backend/scrobblers/LastfmScrobbler.ts b/src/backend/scrobblers/LastfmScrobbler.ts index e0444596..87538e4f 100644 --- a/src/backend/scrobblers/LastfmScrobbler.ts +++ b/src/backend/scrobblers/LastfmScrobbler.ts @@ -32,7 +32,7 @@ export default class LastfmScrobbler extends AbstractScrobbleClient { constructor(name: any, config: LastfmClientConfig, options = {}, notifier: Notifiers, emitter: EventEmitter, logger: Logger) { super('lastfm', name, config, notifier, emitter, logger); - // @ts-ignore + // @ts-expect-error sloppy data structure assign this.api = new LastfmApiClient(name, config.data, options) } @@ -131,9 +131,7 @@ export default class LastfmScrobbler extends AbstractScrobbleClient { return track.toLocaleLowerCase().trim(); } - alreadyScrobbled = async (playObj: PlayObject, log = false) => { - return (await this.existingScrobble(playObj)) !== undefined; - } + alreadyScrobbled = async (playObj: PlayObject, log = false) => (await this.existingScrobble(playObj)) !== undefined public playToClientPayload(playObject: PlayObject): object { return this.api.playToClientPayload(playObject); diff --git a/src/backend/scrobblers/ListenbrainzScrobbler.ts b/src/backend/scrobblers/ListenbrainzScrobbler.ts index a284822b..d323e996 100644 --- a/src/backend/scrobblers/ListenbrainzScrobbler.ts +++ b/src/backend/scrobblers/ListenbrainzScrobbler.ts @@ -77,9 +77,7 @@ export default class ListenbrainzScrobbler extends AbstractScrobbleClient { this.lastScrobbleCheck = dayjs(); } - alreadyScrobbled = async (playObj: PlayObject, log = false) => { - return (await this.existingScrobble(playObj)) !== undefined; - } + alreadyScrobbled = async (playObj: PlayObject, log = false) => (await this.existingScrobble(playObj)) !== undefined public playToClientPayload(playObj: PlayObject): ListenPayload { return ListenbrainzApiClient.playToListenPayload(playObj); diff --git a/src/backend/scrobblers/MalojaScrobbler.ts b/src/backend/scrobblers/MalojaScrobbler.ts index f6755c3f..c0ead014 100644 --- a/src/backend/scrobblers/MalojaScrobbler.ts +++ b/src/backend/scrobblers/MalojaScrobbler.ts @@ -106,7 +106,7 @@ export default class MalojaScrobbler extends AbstractScrobbleClient { duration = mDuration; time = mTime; } - let artistStrings = artists.reduce((acc: any, curr: any) => { + const artistStrings = artists.reduce((acc: any, curr: any) => { let aString; if (typeof curr === 'string') { aString = curr; @@ -375,9 +375,7 @@ export default class MalojaScrobbler extends AbstractScrobbleClient { return lowerTitle; } - alreadyScrobbled = async (playObj: any, log = false) => { - return (await this.existingScrobble(playObj)) !== undefined; - } + alreadyScrobbled = async (playObj: any, log = false) => (await this.existingScrobble(playObj)) !== undefined public playToClientPayload(playObj: PlayObject): MalojaScrobbleRequestData { diff --git a/src/backend/scrobblers/ScrobbleClients.ts b/src/backend/scrobblers/ScrobbleClients.ts index 5011058e..b597d984 100644 --- a/src/backend/scrobblers/ScrobbleClients.ts +++ b/src/backend/scrobblers/ScrobbleClients.ts @@ -1,3 +1,4 @@ +/* eslint-disable no-case-declarations */ import dayjs, {Dayjs} from "dayjs"; import { createAjvFactory, @@ -55,13 +56,9 @@ export default class ScrobbleClients { }); } - getByName = (name: any) => { - return this.clients.find(x => x.name === name); - } + getByName = (name: any) => this.clients.find(x => x.name === name) - getByType = (type: any) => { - return this.clients.filter(x => x.type === type); - } + getByType = (type: any) => this.clients.filter(x => x.type === type) async getStatusSummary(type?: string, name?: string): Promise<[boolean, string[]]> { let clients: AbstractScrobbleClient[]; @@ -88,7 +85,7 @@ export default class ScrobbleClients { } buildClientsFromConfig = async (notifier: Notifiers) => { - let configs: ParsedConfig[] = []; + const configs: ParsedConfig[] = []; let configFile; try { @@ -127,7 +124,7 @@ export default class ScrobbleClients { } for (const clientType of clientTypes) { - let defaultConfigureAs = 'client'; + const defaultConfigureAs = 'client'; switch (clientType) { case 'maloja': // env builder for single user mode @@ -142,7 +139,6 @@ export default class ScrobbleClients { configureAs: 'client', data: { url, - // @ts-ignore apiKey } }) @@ -162,7 +158,6 @@ export default class ScrobbleClients { source: 'ENV', mode: 'single', configureAs: 'client', - // @ts-ignore data: {...lfm, redirectUri: lfm.redirectUri ?? `${this.localUrl}/lastfm/callback`} }) } @@ -180,7 +175,6 @@ export default class ScrobbleClients { source: 'ENV', mode: 'single', configureAs: 'client', - // @ts-ignore data: lz }) } @@ -211,7 +205,7 @@ export default class ScrobbleClients { for(const [i,rawConf] of rawClientConfigs.entries()) { try { const validConfig = validateJson(rawConf, clientSchema, this.logger); - // @ts-ignore + // @ts-expect-error configureAs should exist const {configureAs = defaultConfigureAs} = validConfig; if (configureAs === 'client') { const parsedConfig: ParsedConfig = { diff --git a/src/backend/server/api.ts b/src/backend/server/api.ts index b9b7d838..27e64dfe 100644 --- a/src/backend/server/api.ts +++ b/src/backend/server/api.ts @@ -81,7 +81,7 @@ export const setupApi = (app: ExpressWithAsync, logger: Logger, initialLogOutput let logObjectStream: Transform; try { logObjectStream = new Transform({ - transform(chunk, e, cb) { + transform: (chunk, e, cb) => { cb(null, chunk) }, objectMode: true, @@ -109,13 +109,13 @@ export const setupApi = (app: ExpressWithAsync, logger: Logger, initialLogOutput const sourceRequiredMiddle = sourceMiddleFunc(true); const setLogWebSettings: ExpressHandler = async (req, res, next) => { - // @ts-ignore + // @ts-expect-error logLevel not part of session const sessionLevel: LogLevel | undefined = req.session.logLevel as LogLevel | undefined; if(sessionLevel !== undefined && logConfig.level !== sessionLevel) { logConfig.level = sessionLevel; } - // @ts-ignore - const sessionLimit: number | undefined = req.session.limit as Number | undefined; + // @ts-expect-error limit not part of session + const sessionLimit: number | undefined = req.session.limit as number | undefined; if(sessionLimit !== undefined && logConfig.limit !== sessionLimit) { logConfig.limit = sessionLimit; } @@ -137,9 +137,9 @@ export const setupApi = (app: ExpressWithAsync, logger: Logger, initialLogOutput logConfig.level = req.body.level as LogLevel | undefined ?? logConfig.level; logConfig.limit = req.body.limit ?? logConfig.limit; const slicedLog = getLogs(logConfig.level, logConfig.limit + 1, logConfig.sort === 'ascending' ? 'asc' : 'desc'); - // @ts-ignore + // @ts-expect-error logLevel not part of session req.session.logLevel = logConfig.level; - // @ts-ignore + // @ts-expect-error limit not part of session req.session.limit = logConfig.limit; const jsonLogs: LogInfoJson[] = slicedLog.map(x => ({...x, formattedMessage: x[MESSAGE]})); return res.json({data: jsonLogs, settings: logConfig}); @@ -295,7 +295,7 @@ export const setupApi = (app: ExpressWithAsync, logger: Logger, initialLogOutput scrobbleClient: client, } = req; - let result: DeadLetterScrobble[] = (client as AbstractScrobbleClient).deadLetterScrobbles; + const result: DeadLetterScrobble[] = (client as AbstractScrobbleClient).deadLetterScrobbles; return res.json(result); }); @@ -310,7 +310,7 @@ export const setupApi = (app: ExpressWithAsync, logger: Logger, initialLogOutput await (client as AbstractScrobbleClient).processDeadLetterQueue(1000); - let result: DeadLetterScrobble[] = (client as AbstractScrobbleClient).deadLetterScrobbles; + const result: DeadLetterScrobble[] = (client as AbstractScrobbleClient).deadLetterScrobbles; return res.json(result); }); @@ -383,7 +383,7 @@ export const setupApi = (app: ExpressWithAsync, logger: Logger, initialLogOutput app.getAsync('/api/scrobbled', clientMiddleFunc(false), async (req, res, next) => { const { - // @ts-ignore + // @ts-expect-error scrobbleClient not part of req scrobbleClient: client, } = req; @@ -396,7 +396,7 @@ export const setupApi = (app: ExpressWithAsync, logger: Logger, initialLogOutput }); app.use('/api/poll', sourceRequiredMiddle); - app.getAsync('/api/poll', async function (req, res) { + app.getAsync('/api/poll', async (req, res) => { // @ts-expect-error TS(2339): Property 'scrobbleSource' does not exist on type '... Remove this comment to see the full error message const source = req.scrobbleSource as AbstractSource; source.logger.debug('User requested (re)start via API call'); @@ -419,7 +419,7 @@ export const setupApi = (app: ExpressWithAsync, logger: Logger, initialLogOutput }); app.use('/api/client/init', clientRequiredMiddle); - app.postAsync('/api/client/init', async function (req, res) { + app.postAsync('/api/client/init', async (req, res) => { // @ts-expect-error TS(2339): Property 'scrobbleSource' does not exist on type '... Remove this comment to see the full error message const client = req.scrobbleClient as AbstractScrobbleClient; client.logger.debug('User requested (re)start via API call'); @@ -434,10 +434,8 @@ export const setupApi = (app: ExpressWithAsync, logger: Logger, initialLogOutput res.status(200).send('OK'); }); - app.getAsync('/health', async function(req, res) { - return res.redirect(307, `/api/${req.url.slice(1)}`); - }); - app.getAsync('/api/health', async function (req, res) { + app.getAsync('/health', async (req, res) => res.redirect(307, `/api/${req.url.slice(1)}`)); + app.getAsync('/api/health', async (req, res) => { const { type, name @@ -450,7 +448,7 @@ export const setupApi = (app: ExpressWithAsync, logger: Logger, initialLogOutput return res.status((clientsReady && sourcesReady) ? 200 : 500).json({messages: sourceMessages.concat(clientMessages)}); }); - app.useAsync('/api/*', async function (req, res) { + app.useAsync('/api/*', async (req, res) => { const remote = req.connection.remoteAddress; const proxyRemote = req.headers["x-forwarded-for"]; const ua = req.headers["user-agent"]; diff --git a/src/backend/server/auth.ts b/src/backend/server/auth.ts index fd2e2fc0..724cb99b 100644 --- a/src/backend/server/auth.ts +++ b/src/backend/server/auth.ts @@ -10,7 +10,7 @@ import SpotifySource from "../sources/SpotifySource.js"; export const setupAuthRoutes = (app: ExpressWithAsync, logger: Logger, sourceMiddle: ExpressHandler, clientMiddle: ExpressHandler, scrobbleSources: ScrobbleSources, scrobbleClients: ScrobbleClients) => { app.use('/api/client/auth', clientMiddle); - app.getAsync('/api/client/auth', async function (req, res) { + app.getAsync('/api/client/auth', async (req, res) => { const { scrobbleClient, } = req as any; @@ -25,7 +25,7 @@ export const setupAuthRoutes = (app: ExpressWithAsync, logger: Logger, sourceMid }); app.use('/api/source/auth', sourceMiddle); - app.getAsync('/api/source/auth', async function (req, res, next) { + app.getAsync('/api/source/auth', async (req, res, next) => { const { // @ts-expect-error TS(2339): Property 'scrobbleSource' does not exist on type '... Remove this comment to see the full error message scrobbleSource: source, @@ -54,7 +54,7 @@ export const setupAuthRoutes = (app: ExpressWithAsync, logger: Logger, sourceMid } }); - app.getAsync(/.*callback$/, async function (req, res, next) { + app.getAsync(/.*callback$/, async (req, res, next) => { if(req.url.indexOf('/api') !== 0) { return res.redirect(307, `/api${req.url}`); } diff --git a/src/backend/server/deezerRoutes.ts b/src/backend/server/deezerRoutes.ts index 647794df..d4b4ae81 100644 --- a/src/backend/server/deezerRoutes.ts +++ b/src/backend/server/deezerRoutes.ts @@ -18,7 +18,7 @@ export const setupDeezerRoutes = (app: ExpressWithAsync, logger: Logger, scrobbl // something about the deezer passport strategy makes express continue with the response even though it should wait for accesstoken callback and userprofile fetching // so to get around this add an additional middleware that loops/sleeps until we should have fetched everything ¯\_(ツ)_/¯ - app.getAsync(/.*deezer\/callback*$/, function (req, res, next) { + app.getAsync(/.*deezer\/callback*$/, (req, res, next) => { if(req.url.indexOf('/api') !== 0) { return res.redirect(307, `/api${req.url}`); } @@ -26,9 +26,9 @@ export const setupDeezerRoutes = (app: ExpressWithAsync, logger: Logger, scrobbl const entity = scrobbleSources.getByName(req.session.deezerSource as string); const passportFunc = passport.authenticate(`deezer-${entity.name}`, {session: false}); return passportFunc(req, res, next); - }, async function (req, res) { + }, async (req, res) => { // @ts-expect-error TS(2339): Property 'deezerSource' does not exist on type 'Se... Remove this comment to see the full error message - let entity = scrobbleSources.getByName(req.session.deezerSource as string) as DeezerSource; + const entity = scrobbleSources.getByName(req.session.deezerSource as string) as DeezerSource; for(let i = 0; i < 3; i++) { if(entity.error !== undefined) { return res.send('Error with deezer credentials storage'); diff --git a/src/backend/server/jellyfinRoutes.ts b/src/backend/server/jellyfinRoutes.ts index f33939ac..8ae3b81a 100644 --- a/src/backend/server/jellyfinRoutes.ts +++ b/src/backend/server/jellyfinRoutes.ts @@ -18,17 +18,17 @@ export const setupJellyfinRoutes = (app: ExpressWithAsync, logger: Logger, scrob // } }); const jellyIngress = new JellyfinNotifier(); - app.postAsync('/jellyfin', async function(req, res) { + app.postAsync('/jellyfin', async (req, res) => { res.redirect(307, '/api/jellyfin/ingress'); }); app.postAsync('/api/jellyfin/ingress', - async function (req, res, next) { + async (req, res, next) => { // track request before parsing body to ensure we at least log that something is happening // (in the event body parsing does not work or request is not POST/PATCH) jellyIngress.trackIngress(req, true); next(); }, - jellyfinJsonParser, async function (req, res) { + jellyfinJsonParser, async (req, res) => { jellyIngress.trackIngress(req, false); res.send('OK'); @@ -59,8 +59,6 @@ export const setupJellyfinRoutes = (app: ExpressWithAsync, logger: Logger, scrob } const logPayload = pSources.some(x => { const { - data: { - } = {}, options: { logPayload = parseBool(process.env.DEBUG_MODE) } = {} diff --git a/src/backend/server/plexRoutes.ts b/src/backend/server/plexRoutes.ts index f0fe6624..0ef26f99 100644 --- a/src/backend/server/plexRoutes.ts +++ b/src/backend/server/plexRoutes.ts @@ -11,13 +11,13 @@ export const setupPlexRoutes = (app: ExpressWithAsync, logger: Logger, scrobbleS const plexMiddle = plexRequestMiddle(); const plexLog = logger.child({labels: ['Plex Request']}, mergeArr); const plexIngress = new PlexNotifier(); - const plexIngressMiddle: ExpressHandler = async function (req, res, next) { + const plexIngressMiddle: ExpressHandler = async (req, res, next) => { // track request before parsing body to ensure we at least log that something is happening // (in the event body parsing does not work or request is not POST/PATCH) plexIngress.trackIngress(req, true); next(); }; - const plexIngressRoute: ExpressHandler = async function (req, res) { + const plexIngressRoute: ExpressHandler = async (req, res) => { plexIngress.trackIngress(req, false); const {payload} = req as any; @@ -36,7 +36,7 @@ export const setupPlexRoutes = (app: ExpressWithAsync, logger: Logger, scrobbleS res.send('OK'); }; - app.postAsync('/plex', async function (req, res) { + app.postAsync('/plex', async (req, res) => { res.redirect(307, '/api/plex/ingress'); }); app.postAsync('/api/plex/ingress', plexIngressMiddle, plexMiddle, plexIngressRoute); diff --git a/src/backend/server/tautulliRoutes.ts b/src/backend/server/tautulliRoutes.ts index 9dedf245..01d32e06 100644 --- a/src/backend/server/tautulliRoutes.ts +++ b/src/backend/server/tautulliRoutes.ts @@ -43,7 +43,7 @@ export const setupTautulliRoutes = (app: ExpressWithAsync, logger: Logger, scrob res.send('OK'); }; - app.postAsync('/tautulli', async function(req, res) { + app.postAsync('/tautulli', async (req, res) => { res.redirect(307, '/api/tautulli/ingress'); }); app.postAsync('/api/tautulli/ingress', tautulliIngressRoute); diff --git a/src/backend/server/webscrobblerRoutes.ts b/src/backend/server/webscrobblerRoutes.ts index 815e86e0..174a81c2 100644 --- a/src/backend/server/webscrobblerRoutes.ts +++ b/src/backend/server/webscrobblerRoutes.ts @@ -23,13 +23,13 @@ export const setupWebscrobblerRoutes = (app: ExpressWithAsync, parentLogger: Log }); const webhookIngress = new WebhookNotifier(); app.postAsync('/api/webscrobbler*', - async function (req, res, next) { + async (req, res, next) => { // track request before parsing body to ensure we at least log that something is happening // (in the event body parsing does not work or request is not POST/PATCH) webhookIngress.trackIngress(req, true); next(); }, - webScrobblerJsonParser, nonEmptyBody(logger, 'WebScrobbler Extension'), async function (req, res) { + webScrobblerJsonParser, nonEmptyBody(logger, 'WebScrobbler Extension'), async (req, res) => { webhookIngress.trackIngress(req, false); res.sendStatus(200); diff --git a/src/backend/sources/AbstractSource.ts b/src/backend/sources/AbstractSource.ts index e8c0cc96..032f40ee 100644 --- a/src/backend/sources/AbstractSource.ts +++ b/src/backend/sources/AbstractSource.ts @@ -182,17 +182,11 @@ export default abstract class AbstractSource implements Authenticatable { return; } - authGated = () => { - return this.requiresAuth && !this.authed; - } + authGated = () => this.requiresAuth && !this.authed - canTryAuth = () => { - return this.authGated() && this.authFailure !== true; - } + canTryAuth = () => this.authGated() && this.authFailure !== true - protected doAuthentication = async (): Promise => { - return this.authed; - } + protected doAuthentication = async (): Promise => this.authed testAuth = async () => { if(!this.requiresAuth) { @@ -218,9 +212,7 @@ export default abstract class AbstractSource implements Authenticatable { && !this.authGated(); } - getRecentlyPlayed = async (options: RecentlyPlayedOptions = {}): Promise => { - return []; - } + getRecentlyPlayed = async (options: RecentlyPlayedOptions = {}): Promise => [] getUpstreamRecentlyPlayed = async (options: RecentlyPlayedOptions = {}): Promise => { throw new Error('Not implemented'); @@ -233,9 +225,7 @@ export default abstract class AbstractSource implements Authenticatable { // by default if the track was recently played it is valid // this is useful for sources where the track doesn't have complete information like Subsonic // TODO make this more descriptive? or move it elsewhere - recentlyPlayedTrackIsValid = (playObj: PlayObject) => { - return true; - } + recentlyPlayedTrackIsValid = (playObj: PlayObject) => true protected addPlayToDiscovered = (play: PlayObject) => { const platformId = this.multiPlatform ? genGroupId(play) : SINGLE_USER_PLATFORM_ID; @@ -247,10 +237,9 @@ export default abstract class AbstractSource implements Authenticatable { this.emitEvent('discovered', {play}); } - getFlatRecentlyDiscoveredPlays = (): PlayObject[] => { - // @ts-ignore - return Array.from(this.recentDiscoveredPlays.values()).map(x => x.data).flat(3).sort(sortByNewestPlayDate); - } + getFlatRecentlyDiscoveredPlays = (): PlayObject[] => + Array.from(this.recentDiscoveredPlays.values()).map(x => x.data).flat(3).sort(sortByNewestPlayDate) + getRecentlyDiscoveredPlaysByPlatform = (platformId: PlayPlatformId): PlayObject[] => { const list = this.recentDiscoveredPlays.get(platformId); @@ -263,7 +252,7 @@ export default abstract class AbstractSource implements Authenticatable { } existingDiscovered = (play: PlayObject, opts: {checkAll?: boolean} = {}): PlayObject | undefined => { - let lists: PlayObject[][] = []; + const lists: PlayObject[][] = []; if(opts.checkAll !== true) { lists.push(this.getRecentlyDiscoveredPlaysByPlatform(this.multiPlatform ? genGroupId(play) : SINGLE_USER_PLATFORM_ID)); } else { @@ -366,13 +355,9 @@ export default abstract class AbstractSource implements Authenticatable { this.emitter.emit('notify', payload); } - onPollPreAuthCheck = async (): Promise => { - return true; - } + onPollPreAuthCheck = async (): Promise => true - onPollPostAuthCheck = async (): Promise => { - return true; - } + onPollPostAuthCheck = async (): Promise => true poll = async () => { if(!(await this.onPollPreAuthCheck())) { diff --git a/src/backend/sources/ChromecastSource.ts b/src/backend/sources/ChromecastSource.ts index 333fc9eb..620c25e6 100644 --- a/src/backend/sources/ChromecastSource.ts +++ b/src/backend/sources/ChromecastSource.ts @@ -246,11 +246,11 @@ export class ChromecastSource extends MemorySource { protected initializeClientPlatform = async (device: MdnsDeviceInfo): Promise<[CastClient, PersistentClient, PlatformType]> => { - let index = 0; + const index = 0; for(const address of device.addresses) { - let castClient = new CastClient; - let client: PersistentClient = new PersistentClient({host: address, client: castClient}); + const castClient = new CastClient; + const client: PersistentClient = new PersistentClient({host: address, client: castClient}); client.on('connect', () => this.handleCastClientEvent(device.name, 'connect')); client.on('reconnect', () => this.handleCastClientEvent(device.name, 'reconnect')); client.on('reconnecting', () => this.handleCastClientEvent(device.name, 'reconnecting')); @@ -351,7 +351,7 @@ export class ChromecastSource extends MemorySource { let storedApp = v.applications.get(a.transportId); if(!storedApp) { const appName = a.displayName; - let found = `Found Application '${appName}-${a.transportId.substring(0, 4)}'`; + const found = `Found Application '${appName}-${a.transportId.substring(0, 4)}'`; const appLowerName = appName.toLocaleLowerCase(); let filtered = false; let valid = true; @@ -489,7 +489,7 @@ export class ChromecastSource extends MemorySource { } getRecentlyPlayed = async (options: RecentlyPlayedOptions = {}) => { - let plays: SourceData[] = []; + const plays: SourceData[] = []; try { await this.refreshApplications(); @@ -667,10 +667,11 @@ export class ChromecastSource extends MemorySource { let artists: string[] = [], albumArtists: string[] = [], - track: string = (title ?? songName) as string, - album: string = (albumNorm ?? albumName) as string, mediaType: string = 'unknown'; + const track: string = (title ?? songName) as string; + const album: string = (albumNorm ?? albumName) as string; + if(artist !== undefined) { artists = [artist as string]; } else if (artistName !== undefined) { diff --git a/src/backend/sources/DeezerSource.ts b/src/backend/sources/DeezerSource.ts index f65391f3..9aa941a0 100644 --- a/src/backend/sources/DeezerSource.ts +++ b/src/backend/sources/DeezerSource.ts @@ -42,7 +42,7 @@ export default class DeezerSource extends AbstractSource { this.logger.warn('Interval should be above 30 seconds...😬'); } - // @ts-ignore + // @ts-expect-error not correct structure this.config.data = { ...rest, interval, @@ -138,9 +138,7 @@ export default class DeezerSource extends AbstractSource { } } - getUpstreamRecentlyPlayed = async (options: RecentlyPlayedOptions = {}): Promise => { - return this.getRecentlyPlayed(options); - } + getUpstreamRecentlyPlayed = async (options: RecentlyPlayedOptions = {}): Promise => this.getRecentlyPlayed(options) getRecentlyPlayed = async (options: RecentlyPlayedOptions = {}) => { const resp = await this.callApi(request.get(`${this.baseUrl}/user/me/history?limit=20`)); @@ -208,15 +206,14 @@ export default class DeezerSource extends AbstractSource { } = {}, response, } = e; - let msg = response !== undefined ? `API Call failed: Server Response => ${ssMessage}` : `API Call failed: ${message}`; + const msg = response !== undefined ? `API Call failed: Server Response => ${ssMessage}` : `API Call failed: ${message}`; const responseMeta = ssResp ?? text; this.logger.error(msg, {status, response: responseMeta}); throw e; } } - generatePassportStrategy = () => { - return new DeezerStrategy({ + generatePassportStrategy = () => new DeezerStrategy({ clientID: this.config.data.clientId, clientSecret: this.config.data.clientSecret, callbackURL: this.redirectUri, @@ -237,8 +234,7 @@ export default class DeezerSource extends AbstractSource { } return done(r); }); - }); - } + }) handleAuthCodeCallback = async (res: any) => { const {error, accessToken, id, displayName} = res; @@ -259,7 +255,5 @@ export default class DeezerSource extends AbstractSource { } } - protected getBackloggedPlays = async () => { - return await this.getRecentlyPlayed({formatted: true}); - } + protected getBackloggedPlays = async () => await this.getRecentlyPlayed({formatted: true}) } diff --git a/src/backend/sources/JRiverSource.ts b/src/backend/sources/JRiverSource.ts index b8de9ef7..39ea66cf 100644 --- a/src/backend/sources/JRiverSource.ts +++ b/src/backend/sources/JRiverSource.ts @@ -91,8 +91,8 @@ export class JRiverSource extends MemorySource { ZoneName, } = obj; - let artists = Artist === null || Artist === undefined ? [] : [Artist]; - let album = Album === null || Album === '' ? undefined : Album; + const artists = Artist === null || Artist === undefined ? [] : [Artist]; + const album = Album === null || Album === '' ? undefined : Album; const length = Number.parseInt(DurationMS.toString()) / 1000; return { diff --git a/src/backend/sources/JellyfinSource.ts b/src/backend/sources/JellyfinSource.ts index ad066037..6b4c4562 100644 --- a/src/backend/sources/JellyfinSource.ts +++ b/src/backend/sources/JellyfinSource.ts @@ -251,9 +251,7 @@ export default class JellyfinSource extends MemorySource { return true; } - getRecentlyPlayed = async (options = {}) => { - return this.getFlatRecentlyDiscoveredPlays(); - } + getRecentlyPlayed = async (options = {}) => this.getFlatRecentlyDiscoveredPlays() handle = async (playObj: PlayObject) => { if (!this.isValidEvent(playObj)) { @@ -366,7 +364,5 @@ export default class JellyfinSource extends MemorySource { } } - getNewPlayer = (logger: Logger, id: PlayPlatformId, opts: PlayerStateOptions) => { - return new JellyfinPlayerState(logger, id, opts); - } + getNewPlayer = (logger: Logger, id: PlayPlatformId, opts: PlayerStateOptions) => new JellyfinPlayerState(logger, id, opts) } diff --git a/src/backend/sources/KodiSource.ts b/src/backend/sources/KodiSource.ts index a7fc0353..b63441ab 100644 --- a/src/backend/sources/KodiSource.ts +++ b/src/backend/sources/KodiSource.ts @@ -53,7 +53,7 @@ export class KodiSource extends MemorySource { return []; } - let play = await this.client.getRecentlyPlayed(options); + const play = await this.client.getRecentlyPlayed(options); return this.processRecentPlays(play); } diff --git a/src/backend/sources/LastfmSource.ts b/src/backend/sources/LastfmSource.ts index a6bf01cb..d5e8daaa 100644 --- a/src/backend/sources/LastfmSource.ts +++ b/src/backend/sources/LastfmSource.ts @@ -152,7 +152,5 @@ export default class LastfmSource extends MemorySource { } } - protected getBackloggedPlays = async () => { - return await this.getRecentlyPlayed({formatted: true}); - } + protected getBackloggedPlays = async () => await this.getRecentlyPlayed({formatted: true}) } diff --git a/src/backend/sources/ListenbrainzSource.ts b/src/backend/sources/ListenbrainzSource.ts index 3946710c..94dc6528 100644 --- a/src/backend/sources/ListenbrainzSource.ts +++ b/src/backend/sources/ListenbrainzSource.ts @@ -78,7 +78,5 @@ export default class ListenbrainzSource extends MemorySource { } } - protected getBackloggedPlays = async () => { - return await this.getRecentlyPlayed({formatted: true}); - } + protected getBackloggedPlays = async () => await this.getRecentlyPlayed({formatted: true}) } diff --git a/src/backend/sources/MPRISSource.ts b/src/backend/sources/MPRISSource.ts index cd24d2f9..dff939b2 100644 --- a/src/backend/sources/MPRISSource.ts +++ b/src/backend/sources/MPRISSource.ts @@ -107,8 +107,8 @@ export class MPRISSource extends MemorySource { } protected listNew = async () => { - let iface = await this.getDBus(); - let names = (await iface.ListNames())[0]; + const iface = await this.getDBus(); + const names = (await iface.ListNames())[0]; return names.filter((n) => n.includes('org.mpris.MediaPlayer2')) } @@ -123,7 +123,7 @@ export class MPRISSource extends MemorySource { for (const playerName of newList) { const plainPlayerName = playerName.replace('org.mpris.MediaPlayer2.', ''); try { - let props = await busNew.getInterface(playerName, MPRIS_PATH, MPRIS_IFACE); + const props = await busNew.getInterface(playerName, MPRIS_PATH, MPRIS_IFACE); // may not always have position available! can fallback to undefined for this let pos: number | undefined; try { @@ -181,9 +181,9 @@ export class MPRISSource extends MemorySource { } metadataToPlain = (metadataVariant): MPRISMetadata => { - let metadataPlain = {}; - for (let k of Object.keys(metadataVariant)) { - let value = metadataVariant[k]; + const metadataPlain = {}; + for (const k of Object.keys(metadataVariant)) { + const value = metadataVariant[k]; if (value === undefined || value === null) { //logging.warn(`ignoring a null metadata value for key ${k}`); continue; @@ -201,7 +201,7 @@ export class MPRISSource extends MemorySource { getRecentlyPlayed = async (options: RecentlyPlayedOptions = {}) => { const infos = await this.getPlayersInfo(); - let plays: PlayObject[] = []; + const plays: PlayObject[] = []; for(const info of infos) { const lowerName = info.name.toLocaleLowerCase(); if(this.whitelist.length > 0) { diff --git a/src/backend/sources/MemorySource.ts b/src/backend/sources/MemorySource.ts index 374e735f..1dc2a252 100644 --- a/src/backend/sources/MemorySource.ts +++ b/src/backend/sources/MemorySource.ts @@ -70,7 +70,6 @@ export default class MemorySource extends AbstractSource { deadPlatformIds.push([player.platformIdStr, `Removed after being orphaned for ${dayjs.duration(player.stateIntervalOptions.orphanedInterval, 'seconds').asMinutes()} minutes`]); } else if (isStale) { const state = player.getApiState(); - // @ts-ignore const stateHash = objectHash.sha1(state); if(stateHash !== this.playerState.get(key)) { this.playerState.set(key, stateHash); @@ -97,9 +96,7 @@ export default class MemorySource extends AbstractSource { return record; } - getNewPlayer = (logger: Logger, id: PlayPlatformId, opts: PlayerStateOptions) => { - return new GenericPlayerState(logger, id, opts); - } + getNewPlayer = (logger: Logger, id: PlayPlatformId, opts: PlayerStateOptions) => new GenericPlayerState(logger, id, opts) setNewPlayer = (idStr: string, logger: Logger, id: PlayPlatformId, opts: PlayerStateOptions = {}) => { this.players.set(idStr, this.getNewPlayer(this.logger, id, { @@ -173,7 +170,7 @@ export default class MemorySource extends AbstractSource { // wait to discover play until it is stale or current play has changed // so that our discovered track has an accurate "listenedFor" count if (candidate !== undefined && (playChanged || player.isUpdateStale())) { - let stPrefix = `${buildTrackString(candidate, {include: ['trackId', 'artist', 'track']})}`; + const stPrefix = `${buildTrackString(candidate, {include: ['trackId', 'artist', 'track']})}`; const thresholdResults = timePassesScrobbleThreshold(scrobbleThresholds, candidate.data.listenedFor, candidate.data.duration); if (thresholdResults.passes) { @@ -215,7 +212,6 @@ export default class MemorySource extends AbstractSource { player.logSummary(); } const apiState = player.getApiState(); - // @ts-ignore this.playerState.set(key, objectHash.sha1(apiState)) this.emitEvent('playerUpdate', apiState); } @@ -224,9 +220,7 @@ export default class MemorySource extends AbstractSource { return newStatefulPlays; } - recentlyPlayedTrackIsValid = (playObj: any) => { - return playObj.data.playDate.isBefore(dayjs().subtract(30, 's')); - } + recentlyPlayedTrackIsValid = (playObj: any) => playObj.data.playDate.isBefore(dayjs().subtract(30, 's')) protected getInterval(): number { /** @@ -270,7 +264,7 @@ export default class MemorySource extends AbstractSource { } } -function sortByPlayDate(a: ProgressAwarePlayObject, b: ProgressAwarePlayObject): number { +const sortByPlayDate = (a: ProgressAwarePlayObject, b: ProgressAwarePlayObject): number => { throw new Error("Function not implemented."); -} +}; diff --git a/src/backend/sources/MopidySource.ts b/src/backend/sources/MopidySource.ts index 11576f4c..486f01e6 100644 --- a/src/backend/sources/MopidySource.ts +++ b/src/backend/sources/MopidySource.ts @@ -57,7 +57,7 @@ export class MopidySource extends MemorySource { this.client = new Mopidy({ autoConnect: false, webSocketUrl: this.url.toString(), - // @ts-ignore + // @ts-expect-error logger satisfies but is missing types not used console: winston.loggers.get('noop') }); this.client.on('state:offline', () => { diff --git a/src/backend/sources/PlayerState/AbstractPlayerState.ts b/src/backend/sources/PlayerState/AbstractPlayerState.ts index 8736f340..3fb936ed 100644 --- a/src/backend/sources/PlayerState/AbstractPlayerState.ts +++ b/src/backend/sources/PlayerState/AbstractPlayerState.ts @@ -205,7 +205,7 @@ export abstract class AbstractPlayerState { public getPlayedObject(completed: boolean = false): PlayObject | undefined { if(this.currentPlay !== undefined) { - let ranges = [...this.listenRanges]; + const ranges = [...this.listenRanges]; if (this.currentListenRange !== undefined) { ranges.push(this.currentListenRange); } @@ -228,7 +228,7 @@ export abstract class AbstractPlayerState { public getListenDuration(): Second{ let listenDur: number = 0; - let ranges = [...this.listenRanges]; + const ranges = [...this.listenRanges]; if (this.currentListenRange !== undefined) { ranges.push(this.currentListenRange); } @@ -375,7 +375,7 @@ export abstract class AbstractPlayerState { } public textSummary() { - let parts = ['']; + const parts = ['']; let play: string; if (this.currentPlay !== undefined) { parts.push(`${buildTrackString(this.currentPlay, {include: ['trackId', 'artist', 'track']})} @ ${this.playFirstSeenAt.toISOString()}`); diff --git a/src/backend/sources/PlexSource.ts b/src/backend/sources/PlexSource.ts index 294ffc07..458788db 100644 --- a/src/backend/sources/PlexSource.ts +++ b/src/backend/sources/PlexSource.ts @@ -91,8 +91,7 @@ export default class PlexSource extends AbstractSource { librarySectionTitle: library, // plex returns the track artist as originalTitle (when there is an album artist) // otherwise this is undefined - // @ts-expect-error - originalTitle: trackArtist + originalTitle: trackArtist = undefined } = {}, Server: { // @ts-expect-error TS(2525): Initializer provides no value for this binding ele... Remove this comment to see the full error message @@ -104,8 +103,8 @@ export default class PlexSource extends AbstractSource { } } = obj; - let artists: string[] = []; - let albumArtists: string[] = []; + const artists: string[] = []; + const albumArtists: string[] = []; if(trackArtist !== undefined) { artists.push(trackArtist); albumArtists.push(artist); @@ -241,13 +240,9 @@ export const plexRequestMiddle = () => { const form = formidable({ allowEmptyFiles: true, multiples: true, - // issue with typings https://github.com/node-formidable/formidable/issues/821 - // @ts-ignore - fileWriteStreamHandler: (file: any) => { - return concatStream((data: any) => { + fileWriteStreamHandler: (file: any) => concatStream((data: any) => { file.buffer = data; - }); - } + }) }); form.on('progress', (received: any, expected: any) => { plexLog.debug(`Received ${received} bytes of expected ${expected}`); diff --git a/src/backend/sources/ScrobbleSources.ts b/src/backend/sources/ScrobbleSources.ts index e643a8ac..84075278 100644 --- a/src/backend/sources/ScrobbleSources.ts +++ b/src/backend/sources/ScrobbleSources.ts @@ -1,3 +1,4 @@ +/* eslint-disable no-case-declarations */ import { mergeArr, parseBool, readJson, validateJson } from "../utils.js"; import SpotifySource from "./SpotifySource.js"; import PlexSource from "./PlexSource.js"; @@ -61,22 +62,16 @@ export default class ScrobbleSources { this.logger = winston.loggers.get('app').child({labels: ['Sources']}, mergeArr); } - getByName = (name: any) => { - return this.sources.find(x => x.name === name); - } + getByName = (name: any) => this.sources.find(x => x.name === name) - getByType = (type: any) => { - return this.sources.filter(x => x.type === type); - } + getByType = (type: any) => this.sources.filter(x => x.type === type) - getByNameAndType = (name: string, type: SourceType) => { - return this.sources.find(x => x.name === name && x.type === type); - } + getByNameAndType = (name: string, type: SourceType) => this.sources.find(x => x.name === name && x.type === type) async getStatusSummary(type?: string, name?: string): Promise<[boolean, string[]]> { let sources: AbstractSource[] let sourcesReady = true; - let messages: string[] = []; + const messages: string[] = []; if(type !== undefined) { sources = this.getByType(type); @@ -101,7 +96,7 @@ export default class ScrobbleSources { } buildSourcesFromConfig = async (additionalConfigs: ParsedConfig[] = []) => { - let configs: ParsedConfig[] = additionalConfigs; + const configs: ParsedConfig[] = additionalConfigs; let configFile; try { @@ -138,7 +133,7 @@ export default class ScrobbleSources { } } - for (let sourceType of sourceTypes) { + for (const sourceType of sourceTypes) { let defaultConfigureAs = 'source'; // env builder for single user mode switch (sourceType) { @@ -376,7 +371,7 @@ export default class ScrobbleSources { try { const validConfig = validateJson(rawConf, sourceSchema, this.logger); - // @ts-ignore + // @ts-expect-error will eventually have all info (lazy) const parsedConfig: ParsedConfig = { ...rawConf, source: `${sourceType}.json`, diff --git a/src/backend/sources/SpotifySource.ts b/src/backend/sources/SpotifySource.ts index f6dcc84d..dd4098e5 100644 --- a/src/backend/sources/SpotifySource.ts +++ b/src/backend/sources/SpotifySource.ts @@ -233,7 +233,7 @@ export default class SpotifySource extends MemorySource { } if (validationErrors.length !== 0) { - this.logger.warn(`Configuration was not valid:\*${validationErrors.join('\n')}`); + this.logger.warn(`Configuration was not valid: *${validationErrors.join('\n')}`); throw new Error('Failed to initialize a Spotify source'); } @@ -284,9 +284,7 @@ export default class SpotifySource extends MemorySource { } } - createAuthUrl = () => { - return this.spotifyApi.createAuthorizeURL(scopes, this.name); - } + createAuthUrl = () => this.spotifyApi.createAuthorizeURL(scopes, this.name) handleAuthCodeCallback = async ({ error, @@ -508,39 +506,17 @@ export default class SpotifySource extends MemorySource { return true; } - protected getBackloggedPlays = async () => { - return await this.getPlayHistory({formatted: true}); - } + protected getBackloggedPlays = async () => await this.getPlayHistory({formatted: true}) } -const asPlayHistoryObject = (obj: object): obj is PlayHistoryObject => { - return 'played_at' in obj; -} +const asPlayHistoryObject = (obj: object): obj is PlayHistoryObject => 'played_at' in obj -const asCurrentlyPlayingObject = (obj: object): obj is CurrentlyPlayingObject => { - return 'is_playing' in obj; -} +const asCurrentlyPlayingObject = (obj: object): obj is CurrentlyPlayingObject => 'is_playing' in obj -const hasApiPermissionError = (e: Error): boolean => { - return findCauseByFunc(e, (err) => { - return err.message.includes('Permissions missing'); - }) !== undefined; -} +const hasApiPermissionError = (e: Error): boolean => findCauseByFunc(e, (err) => err.message.includes('Permissions missing')) !== undefined -const hasApiAuthError = (e: Error): boolean => { - return findCauseByFunc(e, (err) => { - return err.message.includes('An authentication error occurred'); - }) !== undefined; -} +const hasApiAuthError = (e: Error): boolean => findCauseByFunc(e, (err) => err.message.includes('An authentication error occurred')) !== undefined -const hasApiTimeoutError = (e: Error): boolean => { - return findCauseByFunc(e, (err) => { - return err.message.includes('A timeout occurred'); - }) !== undefined; -} +const hasApiTimeoutError = (e: Error): boolean => findCauseByFunc(e, (err) => err.message.includes('A timeout occurred')) !== undefined -const hasApiError = (e: Error): boolean => { - return findCauseByFunc(e, (err) => { - return err.message.includes('while communicating with Spotify\'s Web API.'); - }) !== undefined; -} +const hasApiError = (e: Error): boolean => findCauseByFunc(e, (err) => err.message.includes('while communicating with Spotify\'s Web API.')) !== undefined diff --git a/src/backend/sources/SubsonicSource.ts b/src/backend/sources/SubsonicSource.ts index 2e90b8d6..b08f1e19 100644 --- a/src/backend/sources/SubsonicSource.ts +++ b/src/backend/sources/SubsonicSource.ts @@ -88,7 +88,7 @@ export class SubsonicSource extends MemorySource { //queryOpts.p = password; queryOpts.p = `enc:${Buffer.from(password).toString('hex')}` } else { - const salt = await crypto.randomBytes(10).toString('hex'); + const salt = crypto.randomBytes(10).toString('hex'); const hash = crypto.createHash('md5').update(`${password}${salt}`).digest('hex') queryOpts.t = hash; queryOpts.s = salt; @@ -152,7 +152,7 @@ export class SubsonicSource extends MemorySource { } } - // @ts-ignore + // @ts-expect-error it is assignable to T idk return ssResp; } catch (e) { if(e instanceof UpstreamError) { @@ -247,14 +247,12 @@ export class SubsonicSource extends MemorySource { } } -export const getSubsonicResponseFromError = (error: unknown): UpstreamError => { - return findCauseByFunc(error, (err) => { +export const getSubsonicResponseFromError = (error: unknown): UpstreamError => findCauseByFunc(error, (err) => { if(err instanceof UpstreamError && err.response !== undefined) { return getSubsonicResponse(err.response) !== undefined; } return false; - }) as UpstreamError | undefined; -} + }) as UpstreamError | undefined export const parseApiResponseErrorToThrowable = (resp: SubsonicResponse) => { const { diff --git a/src/backend/sources/TautulliSource.ts b/src/backend/sources/TautulliSource.ts index 25ea38cb..ec2ffedb 100644 --- a/src/backend/sources/TautulliSource.ts +++ b/src/backend/sources/TautulliSource.ts @@ -40,19 +40,14 @@ export default class TautulliSource extends PlexSource { player, } = {} } = obj; - let artists: string[] = []; - let albumArtists: string[] = []; + const artists: string[] = []; + const albumArtists: string[] = []; if (track_artist !== undefined && track_artist !== artist_name) { artists.push(track_artist); albumArtists.push(artist_name); } else { artists.push(artist_name); } - if(action === undefined) { - //TODO why does TS think logger doesn't exist? - // @ts-ignore - this.logger.warn(`Payload did contain property 'action', assuming it should be 'watched'`); - } return { data: { artists, diff --git a/src/backend/sources/WebScrobblerSource.ts b/src/backend/sources/WebScrobblerSource.ts index 0e7d229c..c22d39ea 100644 --- a/src/backend/sources/WebScrobblerSource.ts +++ b/src/backend/sources/WebScrobblerSource.ts @@ -137,9 +137,7 @@ export class WebScrobblerSource extends MemorySource { } } - getRecentlyPlayed = async (options = {}) => { - return this.getFlatRecentlyDiscoveredPlays(); - } + getRecentlyPlayed = async (options = {}) => this.getFlatRecentlyDiscoveredPlays() isValidScrobble = (playObj: PlayObject) => { if (playObj.meta?.scrobbleAllowed === false) { diff --git a/src/backend/sources/YTMusicSource.ts b/src/backend/sources/YTMusicSource.ts index 79481a8e..0a44f16b 100644 --- a/src/backend/sources/YTMusicSource.ts +++ b/src/backend/sources/YTMusicSource.ts @@ -1,12 +1,9 @@ import YouTubeMusic from "youtube-music-ts-api"; - import AbstractSource, { RecentlyPlayedOptions } from "./AbstractSource.js"; import { FormatPlayObjectOptions, InternalConfig } from "../common/infrastructure/Atomic.js"; -// @ts-ignore import {IYouTubeMusicAuthenticated} from "youtube-music-ts-api/interfaces-primary"; import dayjs from "dayjs"; import { parseDurationFromTimestamp, playObjDataMatch } from "../utils.js"; -// @ts-ignore import {IPlaylistDetail, ITrackDetail} from "youtube-music-ts-api/interfaces-supplementary"; import { YTMusicSourceConfig } from "../common/infrastructure/config/source/ytmusic.js"; import EventEmitter from "events"; @@ -71,15 +68,13 @@ export default class YTMusicSource extends AbstractSource { } } - recentlyPlayedTrackIsValid = (playObj: PlayObject) => { - return playObj.meta.newFromSource; - } + recentlyPlayedTrackIsValid = (playObj: PlayObject) => playObj.meta.newFromSource api = async (): Promise => { if(this.apiInstance !== undefined) { return this.apiInstance; } - // @ts-ignore + // @ts-expect-error default does exist const ytm = new YouTubeMusic.default() as YouTubeMusic; try { this.apiInstance = await ytm.authenticate(this.config.data.cookie, this.config.data.authUser); @@ -152,8 +147,7 @@ export default class YTMusicSource extends AbstractSource { } if(newPlays.length > 0) { - newPlays = newPlays.map((x) => { - return { + newPlays = newPlays.map((x) => ({ data: { ...x.data, playDate: dayjs().startOf('minute') @@ -162,8 +156,7 @@ export default class YTMusicSource extends AbstractSource { ...x.meta, newFromSource: true } - } - }); + })); this.recentlyPlayed = newPlays.concat(this.recentlyPlayed).slice(0, 20); } } diff --git a/src/backend/sources/ingressNotifiers/TautulliNotifier.ts b/src/backend/sources/ingressNotifiers/TautulliNotifier.ts index 1c565214..ef12b2de 100644 --- a/src/backend/sources/ingressNotifiers/TautulliNotifier.ts +++ b/src/backend/sources/ingressNotifiers/TautulliNotifier.ts @@ -16,7 +16,7 @@ export class TautulliNotifier extends IngressNotifier { if(!this.seenServers.includes(playObj.meta.server)) { this.seenServers.push(playObj.meta.server); - let msg = [`Received data from server ${playObj.meta.server} for the first time.`]; + const msg = [`Received data from server ${playObj.meta.server} for the first time.`]; if(req.body === undefined) { msg.push('WARNING: Payload was empty.'); } diff --git a/src/backend/tests/listenbrainz/listenbrainz.test.ts b/src/backend/tests/listenbrainz/listenbrainz.test.ts index 643e2be3..1e0d4ded 100644 --- a/src/backend/tests/listenbrainz/listenbrainz.test.ts +++ b/src/backend/tests/listenbrainz/listenbrainz.test.ts @@ -130,7 +130,7 @@ describe('Listenbrainz Response Behavior', function() { playDate: dayjs(), meta: { brainz: { - // @ts-expect-error + // @ts-expect-error wrong on purpose artist: 'fad8967c-a327-4af5-a64a-d4de66ece652;100846a7-06f6-4129-97ce-4409b9a9a311', album: '2eb6a8fb-14f6-436e-9bdf-2f9d0d8cbae0', track: '677862e0-3603-4120-8c44-ee9a70893647', diff --git a/src/backend/tests/utils/interfaces.ts b/src/backend/tests/utils/interfaces.ts index 90791a1c..d0cec580 100644 --- a/src/backend/tests/utils/interfaces.ts +++ b/src/backend/tests/utils/interfaces.ts @@ -1,5 +1,5 @@ export interface ExpectedResults { artists: string[] track: string - album?: String + album?: string } diff --git a/src/backend/utils.ts b/src/backend/utils.ts index 85b9e1f4..a713408e 100644 --- a/src/backend/utils.ts +++ b/src/backend/utils.ts @@ -1,5 +1,5 @@ import {accessSync, constants, promises} from "fs"; -import dayjs from 'dayjs'; +import dayjs, {Dayjs} from 'dayjs'; import utc from 'dayjs/plugin/utc.js'; import {Logger} from '@foxxmd/winston'; import JSON5 from 'json5'; @@ -135,8 +135,8 @@ export const sortByNewestPlayDate = (a: PlayObject, b: PlayObject) => { }; export const setIntersection = (setA: any, setB: any) => { - let _intersection = new Set() - for (let elem of setB) { + const _intersection = new Set() + for (const elem of setB) { if (setA.has(elem)) { _intersection.add(elem) } @@ -252,12 +252,11 @@ export const parseRetryAfterSecsFromObj = (err: any) => { } // first try to parse as float - let retryAfter = Number.parseFloat(raVal); + let retryAfter: number | Dayjs = Number.parseFloat(raVal); if (!isNaN(retryAfter)) { return retryAfter; // got a number! } // try to parse as date - // @ts-ignore retryAfter = dayjs(retryAfter); if (!dayjs.isDayjs(retryAfter)) { return undefined; // could not parse string if not in ISO 8601 format @@ -278,7 +277,7 @@ export const spreadDelay = (retries: any, multiplier: any) => { return []; } let r; - let s = []; + const s = []; for(r = 0; r < retries; r++) { s.push(((r+1) * multiplier) * 1000); } @@ -286,7 +285,7 @@ export const spreadDelay = (retries: any, multiplier: any) => { } export const removeUndefinedKeys = >(obj: T): T | undefined => { - let newObj: any = {}; + const newObj: any = {}; Object.keys(obj).forEach((key) => { if(Array.isArray(obj[key])) { newObj[key] = obj[key]; @@ -366,7 +365,7 @@ export const validateJson = (config: object, schema: Schema, logger: Logger): logger.error('Json config was not valid. Please use schema to check validity.', {leaf: 'Config'}); if (Array.isArray(ajv.errors)) { for (const err of ajv.errors) { - let parts = [ + const parts = [ `At: ${err.instancePath}`, ]; let data; @@ -379,9 +378,7 @@ export const validateJson = (config: object, schema: Schema, logger: Logger): parts.push(`Data: ${data}`); } let suffix = ''; - // @ts-ignore if (err.params.allowedValues !== undefined) { - // @ts-ignore suffix = err.params.allowedValues.join(', '); suffix = ` [${suffix}]`; } @@ -727,7 +724,7 @@ export const durationToHuman = (dur: Duration): string => { return parts.join(' '); } export const getAddress = (host = '0.0.0.0', logger?: Logger): { v4?: string, v6?: string, host: string } => { - const local = host = '0.0.0.0' || host === '::' ? 'localhost' : host; + const local = host === '0.0.0.0' || host === '::' ? 'localhost' : host; let v4: string, v6: string; try { diff --git a/src/backend/utils/MDNSUtils.ts b/src/backend/utils/MDNSUtils.ts index 053d37a0..4f05e73a 100644 --- a/src/backend/utils/MDNSUtils.ts +++ b/src/backend/utils/MDNSUtils.ts @@ -39,7 +39,7 @@ export const discoveryAvahi = async (service: string, options?: DiscoveryOptions maybeLogger.debug(`Starting mDNS discovery with Avahi => Listening for ${(duration / 1000).toFixed(2)}s`); let anyDiscovered = false; - let services = new Map(); + const services = new Map(); const triggerDiscovery = () => { for(const [k,v] of services.entries()) { @@ -114,7 +114,7 @@ export const discoveryNative = async (service: string, options?: DiscoveryOption maybeLogger.debug(`Starting mDNS discovery => Listening for ${(duration / 1000).toFixed(2)}s`); if (sanity) { - let services: ServiceType[] = []; + const services: ServiceType[] = []; const testBrowser = new Browser(ServiceType.all()) .on('serviceUp', (service: ServiceType) => { services.push(service) diff --git a/src/backend/utils/StringUtils.ts b/src/backend/utils/StringUtils.ts index e88b005f..bb468a98 100644 --- a/src/backend/utils/StringUtils.ts +++ b/src/backend/utils/StringUtils.ts @@ -7,19 +7,17 @@ import {strategies} from '@foxxmd/string-sameness'; const {levenStrategy, diceStrategy} = strategies; // cant use [^\w\s] because this also catches non-english characters -export const SYMBOLS_WHITESPACE_REGEX = new RegExp(/[`=(){}<>;'’,.~!@#$%^&*_+|:"?\-\\\[\]\/\s]/g); -export const SYMBOLS_REGEX = new RegExp(/[`=(){}<>;'’,.~!@#$%^&*_+|:"?\-\\\[\]\/]/g); +export const SYMBOLS_WHITESPACE_REGEX = new RegExp(/[`=(){}<>;'’,.~!@#$%^&*_+|:"?\-\\[\]/\s]/g); +export const SYMBOLS_REGEX = new RegExp(/[`=(){}<>;'’,.~!@#$%^&*_+|:"?\-\\[\]/]/g); export const MULTI_WHITESPACE_REGEX = new RegExp(/\s{2,}/g); -export const uniqueNormalizedStrArr = (arr: string[]): string[] => { - return arr.reduce((acc: string[], curr) => { +export const uniqueNormalizedStrArr = (arr: string[]): string[] => arr.reduce((acc: string[], curr) => { const normalizedCurr = normalizeStr(curr) if (!acc.some(x => normalizeStr(x) === normalizedCurr)) { return acc.concat(curr); } return acc; - }, []); -} + }, []) // https://stackoverflow.com/a/37511463/1469797 export const normalizeStr = (str: string, options?: {keepSingleWhitespace?: boolean}): string => { const {keepSingleWhitespace = false} = options || {}; @@ -46,7 +44,7 @@ export interface PlayCredits { * * * */ -export const SECONDARY_CAPTURED_REGEX = new RegExp(/[(\[]\s*(?ft\.?\W|feat\.?\W|featuring|vs\.?\W)\s*(?.*)[)\]](?.*)/i); +export const SECONDARY_CAPTURED_REGEX = new RegExp(/[([]\s*(?ft\.?\W|feat\.?\W|featuring|vs\.?\W)\s*(?.*)[)\]](?.*)/i); /** @@ -63,7 +61,7 @@ export const SECONDARY_CAPTURED_REGEX = new RegExp(/[(\[]\s*(?ft\.?\W|fe * !!!! ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ******* * * */ -export const SECONDARY_FREE_REGEX = new RegExp(/^\s*(?ft\.?\W|feat\.?\W|featuring|vs\.?\W)\s*(?(?:.+?(?= - |\s*[(\[]))|(?:.*))(?.*)/i); +export const SECONDARY_FREE_REGEX = new RegExp(/^\s*(?ft\.?\W|feat\.?\W|featuring|vs\.?\W)\s*(?(?:.+?(?= - |\s*[([]))|(?:.*))(?.*)/i); const SECONDARY_REGEX_STRATS: RegExp[] = [SECONDARY_CAPTURED_REGEX, SECONDARY_FREE_REGEX]; @@ -78,7 +76,7 @@ const SECONDARY_REGEX_STRATS: RegExp[] = [SECONDARY_CAPTURED_REGEX, SECONDARY_FR * ^^^^^^^^^^^^^^********************************************** * * */ -export const PRIMARY_SECONDARY_SECTIONS_REGEX = new RegExp(/^(?.+?)(?(?:[(\[]?(?:\Wft\.?|\Wfeat\.?|featuring|\Wvs\.)).*)/i); +export const PRIMARY_SECONDARY_SECTIONS_REGEX = new RegExp(/^(?.+?)(?(?:[([]?(?:\Wft\.?|\Wfeat\.?|featuring|\Wvs\.)).*)/i); /** * For matching the most common track/artist pattern that has a joiner @@ -184,9 +182,7 @@ export const parseStringList = (str: string, delimiters: string[] = [',', '&', ' return explodedStrings.flat(1); }, [str]).map(x => x.trim()); } -export const containsDelimiters = (str: string) => { - return null !== str.match(/[,&\/\\]+/i); -} +export const containsDelimiters = (str: string) => null !== str.match(/[,&/\\]+/i) export const findDelimiters = (str: string) => { const found: string[] = []; for (const d of DELIMITERS) { diff --git a/src/backend/utils/TimeUtils.ts b/src/backend/utils/TimeUtils.ts index 11d29e70..5857e4b3 100644 --- a/src/backend/utils/TimeUtils.ts +++ b/src/backend/utils/TimeUtils.ts @@ -106,10 +106,10 @@ export const comparePlayTemporally = (existingPlay: PlayObject, candidatePlay: P const referenceDuration = newDuration ?? existingDuration; const referenceListenedFor = newListenedFor ?? existingListenedFor; - let playDiffThreshold = diffThreshold; + const playDiffThreshold = diffThreshold; // check if existing play time is same as new play date - let scrobblePlayDiff = Math.abs(existingTsSOCDate.unix() - candidateTsSOCDate.unix()); + const scrobblePlayDiff = Math.abs(existingTsSOCDate.unix() - candidateTsSOCDate.unix()); result.date = { threshold: diffThreshold, diff: scrobblePlayDiff @@ -161,11 +161,13 @@ export const comparePlayTemporally = (existingPlay: PlayObject, candidatePlay: P } export const timePassesScrobbleThreshold = (thresholds: ScrobbleThresholds, secondsTracked: number, playDuration?: number): ScrobbleThresholdResult => { let durationPasses = undefined, - durationThreshold: number | null = thresholds.duration ?? DEFAULT_SCROBBLE_DURATION_THRESHOLD, percentPasses = undefined, - percentThreshold: number | null = thresholds.percent ?? DEFAULT_SCROBBLE_PERCENT_THRESHOLD, percent: number | undefined; + const durationThreshold: number | null = thresholds.duration ?? DEFAULT_SCROBBLE_DURATION_THRESHOLD, + percentThreshold: number | null = thresholds.percent ?? DEFAULT_SCROBBLE_PERCENT_THRESHOLD; + + if (percentThreshold !== null && playDuration !== undefined && playDuration !== 0) { percent = Math.round(((secondsTracked / playDuration) * 100)); percentPasses = percent >= percentThreshold; -- 2.51.2