[READ-ONLY] Mirror of https://github.com/FoxxMD/multi-scrobbler. Scrobble plays from multiple sources to multiple clients docs.multi-scrobbler.app
deezer docker jellyfin koito lastfm listenbrainz maloja mopidy mpris music music-assistant plex scrobble self-hosted spotify subsonic tautulli youtube-music
0

Configure Feed

Select the types of activity you want to include in your feed.

fxi: Fix linting rules related to functions and error causes

FoxxMD (Jul 9, 2026, 7:52 PM UTC) fbdd5044 612a90d7

+13 -9
+1 -1
src/backend/common/database/drizzle/repositories/BaseRepository.ts
··· 46 46 } 47 47 48 48 async updateById(id: number, data: Partial<typeof this.table.$inferInsert>): Promise<void> { 49 - assert(id !== null && id !== undefined, `${typeof id === null ? 'null' : 'undefined'} given for entity id`); 49 + assert(id !== null && id !== undefined, `${id === null ? 'null' : 'undefined'} given for entity id`); 50 50 await this.db.update(this.table).set(data).where(eq(this.table.id, id)); 51 51 } 52 52
+2 -1
src/backend/common/vendor/LastfmApiClient.ts
··· 237 237 return true; 238 238 } catch (e) { 239 239 const hint = e.error?.cause?.message ?? undefined; 240 + // eslint-disable-next-line preserve-caught-error 240 241 throw new Error(`Could not connect to ${this.upstreamName} API server${hint !== undefined ? ` (${hint})` : ''}`, { cause: e.error ?? e }); 241 242 } 242 243 } ··· 385 386 } = resp; 386 387 const correctedResp: Writeable<LastFMUserGetRecentTracksResponse> = { 387 388 ...restResp, 388 - // @ts-expect-error 389 + // @ts-expect-error this is fine 389 390 recenttracks: { 390 391 ...rest, 391 392 track: [],
+1
src/backend/scrobblers/AbstractScrobbleClient.ts
··· 1614 1614 if(sourcePlayerData === undefined) { 1615 1615 return; 1616 1616 } 1617 + // eslint-disable-next-line prefer-const 1617 1618 let [shouldUpdate, npUpdateTopReason] = this.shouldUpdatePlayingNow(sourcePlayerData); 1618 1619 let clientReason: string | undefined; 1619 1620 if(!shouldUpdate) {
+3
src/backend/sources/MPRISSource.ts
··· 183 183 // microseconds 184 184 return dayjs.duration({milliseconds: Number(pos / 1000)}).asSeconds(); 185 185 } catch(e) { 186 + // eslint-disable-next-line preserve-caught-error 186 187 throw new Error('Could not get player Position', {cause: convertDBusExceptionToError(e)}); 187 188 } 188 189 } ··· 192 193 const status = await props['PlaybackStatus']; 193 194 return status as PlaybackStatus; 194 195 } catch (e) { 196 + // eslint-disable-next-line preserve-caught-error 195 197 throw new Error('Could not get player PlaybackStatus', {cause: convertDBusExceptionToError(e)}) 196 198 } 197 199 } ··· 201 203 const metadata = await props['Metadata']; 202 204 return this.metadataToPlain(metadata); 203 205 } catch(e) { 206 + // eslint-disable-next-line preserve-caught-error 204 207 throw new Error('Could not get player Metadata', {cause: convertDBusExceptionToError(e)}); 205 208 } 206 209 }
+2 -1
src/backend/sources/SpotifySource.ts
··· 40 40 import { MemoryPositionalSource } from "./MemoryPositionalSource.ts"; 41 41 import { baseFormatPlayObj } from "../utils/PlayTransformUtils.ts"; 42 42 import { createGetScrobblesForTimeRangeFunc } from "../utils/ListenFetchUtils.ts"; 43 + import { SimpleError } from "../common/errors/MSErrors.ts"; 43 44 44 45 const scopes = ['user-read-recently-played', 'user-read-currently-playing', 'user-read-playback-state', 'user-read-playback-position']; 45 46 const state = 'random'; ··· 550 551 const spotifyError = new UpstreamError('Spotify API call failed', {cause: e}); 551 552 if (e.statusCode === 401 && !hasApiPermissionError(e)) { 552 553 if (this.spotifyApi.getRefreshToken() === undefined) { 553 - throw new Error('Access token was not valid and no refresh token was present') 554 + throw new SimpleError('Access token was not valid and no refresh token was present', {cause: e}); 554 555 } 555 556 this.logger.debug('Access token was not valid, attempting to refresh'); 556 557
+1 -3
src/backend/tests/database/drizzle.test.ts
··· 15 15 import { formatNumber, generateArray } from '../../../core/DataUtils.ts'; 16 16 import { objectsEqual } from '../../utils/DataUtils.ts'; 17 17 import { eq, sql } from 'drizzle-orm'; 18 - import { type PlaySelect } from '../../common/database/drizzle/drizzleTypes.ts'; 19 18 import { loggerDebug } from '@foxxmd/logging'; 20 19 import { transientDb } from '../utils/TransientTestUtils.ts'; 21 20 import { getRoot } from '../../ioc.ts'; ··· 531 530 fixtureCreatePlay({ componentId: component[0].id, play: generatePlay({}, {source: 'test2'}) }) 532 531 ]).returning(); 533 532 534 - let result: PlaySelect[]; 535 533 // https://github.com/drizzle-team/drizzle-orm/discussions/938#discussioncomment-6542336 536 - result = await db.query.plays.findMany({ 534 + const result = await db.query.plays.findMany({ 537 535 where: { 538 536 AND: [ 539 537 {
+1 -1
src/backend/utils/ValidationUtils.ts
··· 96 96 const module = await import(resolve(projectDir, schemaFuncPath)) 97 97 schemaFetchFunc = module.getSchemaForType; 98 98 } catch (e) { 99 - throw new Error(`Could not load module from path: ${schemaFuncPath}`); 99 + throw new Error(`Could not load module from path: ${schemaFuncPath}`, {cause: e}); 100 100 } 101 101 return schemaFetchFunc; 102 102 }
+2 -2
src/client/logs/LogsSection.tsx
··· 26 26 interface LogLevelButtonProps { 27 27 name: string, 28 28 active: boolean, 29 - onClick: Function 29 + onClick: (val: number) => void 30 30 } 31 31 const LogLevelButton = (props: LogLevelButtonProps) => { 32 32 const {name, active, onClick} = props; ··· 34 34 const className = active ? "mx-1" : "capitalize underline cursor-pointer mx-1"; 35 35 return <span onClick={click} className={className}>{name.toUpperCase()}</span>; 36 36 } 37 - const LogLimitButton = (props: {val: number, active: boolean, onClick: Function}) => { 37 + const LogLimitButton = (props: {val: number, active: boolean, onClick: (val: number) => void}) => { 38 38 const {val, active, onClick} = props; 39 39 const click = useCallback(() => active ? null : onClick(val), [onClick, val, active]); 40 40 const className = active ? "mx-1" : "capitalize underline cursor-pointer mx-1";