Something went wrong. Try again.
[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
Something went wrong. Try again.
4.8 kB · 145 lines
TSX
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145import type { Card, IconProps, HTMLChakraProps} from '@chakra-ui/react';import { Span } from '@chakra-ui/react';import type {PlayApiCommonDetailed, QueryPlaysOptsJson} from '../../core/Api';import { INGRESS_QUEUE, isPlayState, type LifecycleStep, QUEUE_STATUS_COMPLETED, QUEUE_STATUS_FAILED } from '../../core/Atomic';import { isErrorIsh, type ErrorIsh } from '../../core/ErrorUtils';import type { QueryPlaysOptsJsonRefreshable } from '../queries';
export const cardHeaderSeparator: Card.HeaderProps = { borderBottomWidth: "1px", paddingBottom: "2"};
export const timelineTextFormatting: HTMLChakraProps<"span"> = { textAlign: "left", textWrap: "balance" }
export const timelineIconProps: IconProps = { fontSize: 'md'}
export const TimelineItemSummaryText = (props: HTMLChakraProps<"span"> & {children: React.ReactNode}) => <Span {...timelineTextFormatting}>{props.children}</Span>
export const activityTransformHasIssue = (steps: LifecycleStep[]): 'warn' | 'error' | undefined => { for(const step of steps) { if(step.flowKnownState === 'prereq') { return 'warn'; } if(step.flowKnownState === 'skip') { continue; } if(step.error !== undefined && step.error !== null && Object.keys(step.error).length > 0) { return 'error'; } } return undefined;}
export const activityTimelineHasIssue = (activity: PlayApiCommonDetailed): 'warn' | 'error' | undefined => { const { queueStates = [], play: { lifecycle = [], scrobble: { error: scrobbleError, warnings: scrobbleWarnings = [] } = {}, } = {}, } = activity; const transformIssue = activityTransformHasIssue(lifecycle); if(transformIssue !== undefined) { return transformIssue; } if (scrobbleError !== undefined) { return 'error'; } if(scrobbleWarnings.length > 0) { return 'warn'; } if (queueStates.some(x => x.queueStatus === QUEUE_STATUS_FAILED) && !queueStates.some(x => x.queueStatus === QUEUE_STATUS_COMPLETED)) { return 'error'; }
return undefined;}
export interface ErrorData { name?: string code?: string message?: string stack?: string}
export const walkError = (err: ErrorIsh, errors: ErrorData[] = []): ErrorData[] => { const thisErr: ErrorData = { name: err.name, code: 'code' in err ? err.code : undefined, message: err.message, stack: err.stack }; errors.push(thisErr); if(isErrorIsh(err.cause)) { return walkError(err.cause, errors); } return errors;}
export const findAuthCheckError = (err: ErrorIsh): ErrorIsh | undefined => { if(err.name === 'Authentication Check') { return err; } if(err.cause === undefined || !isErrorIsh(err.cause)) { return undefined; } return findAuthCheckError(err.cause);}
export const findAuthError = (err: ErrorIsh): ErrorIsh | undefined => { if(err.name === 'Authentication Error') { return err; } if(err.cause === undefined || !isErrorIsh(err.cause)) { return undefined; } return findAuthError(err.cause);}
export const findAnyAuthError = (e: ErrorIsh): [ErrorIsh | undefined, boolean] => { const aError = findAuthError(e); if(aError !== undefined && 'unrecoverable' in aError && aError.unrecoverable === true) { return [aError, true]; } const aCheckError = findAuthCheckError(e); if(aCheckError !== undefined && 'unrecoverable' in aCheckError && aCheckError.unrecoverable === true) { return [aCheckError, true]; } if(aError !== undefined) { return [aError, false]; } if(aCheckError !== undefined) { return [aCheckError, false]; } return [undefined, false];}
export const queryPlayOptsRefreshableToJson = (query: QueryPlaysOptsJsonRefreshable): QueryPlaysOptsJson => { const { nonce, state, ...rest } = query; const derived: QueryPlaysOptsJson = rest; if (state !== undefined) { derived.state = state.filter(x => isPlayState(x));
if (state.includes('failed TBR') && state.includes('queued')) { derived.queues = [{ queueName: INGRESS_QUEUE, queueStatus: 'queued' }, { queueName: INGRESS_QUEUE, queueStatus: 'failed' }]; derived.state = Array.from(new Set([...derived.state, 'failed', 'queued'])); } else if (state.includes('failed TBR')) { derived.queues = [{ queueName: INGRESS_QUEUE, queueStatus: 'failed' }]; derived.state = Array.from(new Set([...derived.state, 'failed'])); } } return derived;}