[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.

refactor: Remove lifecycleless types

FoxxMD (Jun 10, 2026, 1:54 AM UTC) a194f752 4180ae0c

+82 -86
+4 -4
src/backend/common/infrastructure/Atomic.ts
··· 3 3 import { Request, Response } from "express"; 4 4 import { type NextFunction, type ParamsDictionary, type Query } from "express-serve-static-core"; 5 5 import { FixedSizeList } from 'fixed-size-list'; 6 - import { ErrorLike, isPlayObject, PlayMetaLifecycleless, PlayObject, PlayObjectLifecycleless, UnixTimestamp } from "../../../core/Atomic.js"; 6 + import { ErrorLike, isPlayObject, PlayMeta, PlayObject, PlayObjectMinimal, UnixTimestamp } from "../../../core/Atomic.js"; 7 7 import TupleMap from "../TupleMap.js"; 8 8 import { MusicBrainzApi } from 'musicbrainz-api'; 9 9 import { SourceType } from './config/source/sources.js'; ··· 109 109 [key: string]: any 110 110 } 111 111 112 - export interface ProgressAwarePlayObject extends PlayObjectLifecycleless { 113 - meta: PlayMetaLifecycleless & { 112 + export interface ProgressAwarePlayObject extends PlayObjectMinimal { 113 + meta: PlayMeta & { 114 114 initialTrackProgressPosition?: number 115 115 } 116 116 } ··· 131 131 132 132 export interface ScrobbledPlayObject { 133 133 play: PlayObject 134 - scrobble: PlayObjectLifecycleless 134 + scrobble: PlayObjectMinimal 135 135 } 136 136 137 137
+2 -2
src/backend/common/vendor/KodiApiClient.ts
··· 2 2 import { KodiClient } from 'kodi-api' 3 3 import normalizeUrl from "normalize-url"; 4 4 import { URL } from "url"; 5 - import { PlayObject, PlayObjectLifecycleless } from "../../../core/Atomic.js"; 5 + import { PlayObject, PlayObjectMinimal } from "../../../core/Atomic.js"; 6 6 import { RecentlyPlayedOptions } from "../../sources/AbstractSource.js"; 7 7 import { AbstractApiOptions, FormatPlayObjectOptions } from "../infrastructure/Atomic.js"; 8 8 import { KodiData } from "../infrastructure/config/source/kodi.js"; ··· 106 106 const album = albumVal === null || albumVal === '' ? undefined : albumVal; 107 107 const trackProgressPosition = time !== undefined ? Math.round(dayjs.duration(time).asSeconds()) : undefined; 108 108 109 - const play: PlayObjectLifecycleless = { 109 + const play: PlayObjectMinimal = { 110 110 data: { 111 111 track: title, 112 112 album: album,
+3 -3
src/backend/common/vendor/LastfmApiClient.ts
··· 1 1 import dayjs, { Dayjs, ManipulateType } from "dayjs"; 2 - import { BrainzMeta, PlayObject, PlayObjectLifecycleless, ScrobbleActionResult, UnixTimestamp, URLData, Writeable } from "../../../core/Atomic.js"; 2 + import { BrainzMeta, PlayObject, PlayObjectMinimal, ScrobbleActionResult, UnixTimestamp, URLData, Writeable } from "../../../core/Atomic.js"; 3 3 import { artistNamesToCredits, artistNameToCredit, nonEmptyStringOrDefault, splitByFirstFound } from "../../../core/StringUtils.js"; 4 4 import { removeUndefinedKeys, sleep } from "../../utils.js"; 5 5 import { writeFile } from '../../utils/FSUtils.js'; ··· 570 570 artists = [artist]; 571 571 } 572 572 573 - const play: PlayObjectLifecycleless = { 573 + const play: PlayObjectMinimal = { 574 574 data: { 575 575 track, 576 576 album: nonEmptyStringOrDefault(album), ··· 696 696 recording: nonEmptyStringOrDefault<undefined>(mbid) 697 697 }); 698 698 699 - const play: PlayObjectLifecycleless = { 699 + const play: PlayObjectMinimal = { 700 700 data: { 701 701 artists: artistNamesToCredits([...new Set(artistStrings)] as string[]), 702 702 track: title,
+2 -2
src/backend/common/vendor/ListenbrainzApiClient.ts
··· 1 1 import { stringSameness } from '@foxxmd/string-sameness'; 2 2 import dayjs from "dayjs"; 3 3 import request, { Request, Response } from 'superagent'; 4 - import { BrainzMeta, PlayObject, PlayObjectLifecycleless, ScrobbleActionResult, UnixTimestamp, URLData } from "../../../core/Atomic.js"; 4 + import { BrainzMeta, PlayObject, PlayObjectMinimal, ScrobbleActionResult, UnixTimestamp, URLData } from "../../../core/Atomic.js"; 5 5 import { artistNamesToCredits, combinePartsToString, slice } from "../../../core/StringUtils.js"; 6 6 import { 7 7 normalizeListenbrainzUrl, ··· 683 683 } 684 684 685 685 686 - const play: PlayObjectLifecycleless = { 686 + const play: PlayObjectMinimal = { 687 687 data: { 688 688 playDate: dayjs.unix(listened_at), 689 689 track: normalTrackName,
+2 -2
src/backend/common/vendor/RockSkyApiClient.ts
··· 1 1 import dayjs from "dayjs"; 2 2 import request, { Request, Response } from 'superagent'; 3 - import { PlayObject, PlayObjectLifecycleless, ScrobbleActionResult, URLData } from "../../../core/Atomic.js"; 3 + import { PlayObject, PlayObjectMinimal, ScrobbleActionResult, URLData } from "../../../core/Atomic.js"; 4 4 import { artistCreditsToNames, artistNamesToCredits, nonEmptyStringOrDefault } from "../../../core/StringUtils.js"; 5 5 import { UpstreamError } from "../errors/UpstreamError.js"; 6 6 import { AbstractApiOptions, DEFAULT_RETRY_MULTIPLIER, FormatPlayObjectOptions } from "../infrastructure/Atomic.js"; ··· 254 254 web, 255 255 user 256 256 } = opts; 257 - const play: PlayObjectLifecycleless = { 257 + const play: PlayObjectMinimal = { 258 258 data: { 259 259 track: obj.title, 260 260 artists: artistNamesToCredits(nonEmptyStringOrDefault(obj.artist) ? [obj.artist] : []),
+2 -2
src/backend/common/vendor/koito/KoitoApiClient.ts
··· 1 1 import dayjs from "dayjs"; 2 - import { PlayObject, PlayObjectLifecycleless, ScrobbleActionResult, URLData } from "../../../../core/Atomic.js"; 2 + import { PlayObject, PlayObjectMinimal, ScrobbleActionResult, URLData } from "../../../../core/Atomic.js"; 3 3 import { AbstractApiOptions, DEFAULT_RETRY_MULTIPLIER, PaginatedListensTimeRangeOptions, PaginatedTimeRangeListens, PaginatedTimeRangeListensResult } from "../../infrastructure/Atomic.js"; 4 4 import { GetListensOptions, KoitoData, ListenObjectResponse, ListensResponse } from "../../infrastructure/config/client/koito.js"; 5 5 import AbstractApiClient from "../AbstractApiClient.js"; ··· 216 216 } 217 217 218 218 export const listenObjectResponseToPlay = (obj: ListenObjectResponse, options: { newFromSource?: boolean, url?: URL } = {}): PlayObject => { 219 - const play: PlayObjectLifecycleless = { 219 + const play: PlayObjectMinimal = { 220 220 data: { 221 221 track: obj.track.title, 222 222 artists: artistNamesToCredits((obj.track.artists ?? []).map(x => x.name)),
+2 -2
src/backend/common/vendor/maloja/MalojaApiClient.ts
··· 4 4 import AbstractApiClient from "../AbstractApiClient.js"; 5 5 import { getBaseFromUrl, isPortReachableConnect, joinedUrl, normalizeWebAddress } from "../../../utils/NetworkUtils.js"; 6 6 import { MalojaData } from "../../infrastructure/config/client/maloja.js"; 7 - import { PlayObject, PlayObjectLifecycleless, ScrobbleActionResult, URLData } from "../../../../core/Atomic.js"; 7 + import { PlayObject, PlayObjectMinimal, ScrobbleActionResult, URLData } from "../../../../core/Atomic.js"; 8 8 import { AbstractApiOptions, DEFAULT_RETRY_MULTIPLIER, FormatPlayObjectOptions, PaginatedListensTimeRangeOptions, PaginatedTimeRangeListens, PaginatedTimeRangeListensResult } from "../../infrastructure/Atomic.js"; 9 9 import { isNodeNetworkException } from "../../errors/NodeErrors.js"; 10 10 import { isSuperAgentResponseError } from "../../errors/ErrorUtils.js"; ··· 410 410 return [...acc, ...aStrings]; 411 411 }, []); 412 412 const urlParams = new URLSearchParams([['artist', artists[0]], ['title', title]]); 413 - const play: PlayObjectLifecycleless = { 413 + const play: PlayObjectMinimal = { 414 414 data: removeUndefinedKeys({ 415 415 artists: artistNamesToCredits([...new Set(artistStrings)] as string[]), 416 416 track: title,
+2 -2
src/backend/common/vendor/musicbrainz/MusicbrainzApiClient.ts
··· 1 1 import { Response } from 'superagent'; 2 - import { ArtistCredit, PlayObject, PlayObjectLifecycleless, URLData } from "../../../../core/Atomic.js"; 2 + import { ArtistCredit, PlayObject, PlayObjectMinimal, URLData } from "../../../../core/Atomic.js"; 3 3 import { UpstreamError } from "../../errors/UpstreamError.js"; 4 4 import { AbstractApiOptions, FormatPlayObjectOptions, MUSICBRAINZ_URL, MusicbrainzApiConfigData } from "../../infrastructure/Atomic.js"; 5 5 import AbstractApiClient from "../AbstractApiClient.js"; ··· 407 407 } 408 408 } 409 409 410 - const play: PlayObjectLifecycleless = { 410 + const play: PlayObjectMinimal = { 411 411 data: { 412 412 track: data.title, 413 413 artists,
+2 -2
src/backend/common/vendor/teal/TealApiClient.ts
··· 1 1 import dayjs, { Dayjs, ManipulateType } from "dayjs"; 2 - import { PlayObject, PlayObjectLifecycleless, BrainzMeta, SourcePlayerObj, MBID, ScrobbleActionResult, UnixTimestamp } from "../../../../core/Atomic.js"; 2 + import { PlayObject, PlayObjectMinimal, BrainzMeta, SourcePlayerObj, MBID, ScrobbleActionResult, UnixTimestamp } from "../../../../core/Atomic.js"; 3 3 import { getRoot } from "../../../ioc.js"; 4 4 import { removeUndefinedKeys } from "../../../utils.js"; 5 5 import { baseFormatPlayObj } from "../../../utils/PlayTransformUtils.js"; ··· 116 116 117 117 export const recordToPlay = (record: FmTealAlphaFeedPlay.Main, options: RecordOptions = {}): PlayObject => { 118 118 119 - const play: PlayObjectLifecycleless = { 119 + const play: PlayObjectMinimal = { 120 120 data: { 121 121 track: record.trackName, 122 122 artists: record.artists.filter(x => x.artistName !== undefined).map(x => ({ name: x.artistName, mbid: x.artistMbId })),
+1 -1
src/backend/scrobblers/AbstractScrobbleClient.ts
··· 8 8 DeadLetterScrobble, 9 9 NowPlayingUpdateThreshold, 10 10 PlayObject, 11 - PlayObjectLifecycleless, 11 + PlayObjectMinimal, 12 12 QueuedScrobble, ScrobbleActionResult, PlayMatchResult, SourcePlayerObj, TA_DURING, 13 13 TA_FUZZY, 14 14 TrackStringOptions,
+2 -2
src/backend/sources/AzuracastSource.ts
··· 4 4 import { EventEmitter } from "events"; 5 5 import { WS, CloseEvent, ErrorEvent, RetryEvent } from 'iso-websocket' 6 6 import pEvent from 'p-event'; 7 - import { PlayObject, PlayObjectLifecycleless, URLData } from "../../core/Atomic.js"; 7 + import { PlayObject, PlayObjectMinimal, URLData } from "../../core/Atomic.js"; 8 8 import { UpstreamError } from "../common/errors/UpstreamError.js"; 9 9 import { 10 10 FormatPlayObjectOptions, ··· 249 249 250 250 const track: string = title ?? text; 251 251 252 - const play: PlayObjectLifecycleless = { 252 + const play: PlayObjectMinimal = { 253 253 data: { 254 254 artists: artistNamesToCredits(artist !== undefined && artist !== '' ? [artist] : []), 255 255 album: album !== '' ? album : undefined,
+2 -2
src/backend/sources/ChromecastSource.ts
··· 5 5 import dayjs from "dayjs"; 6 6 import { EventEmitter } from "events"; 7 7 import e from "express"; 8 - import { PlayObject, PlayObjectLifecycleless } from "../../core/Atomic.js"; 8 + import { PlayObject, PlayObjectMinimal } from "../../core/Atomic.js"; 9 9 import { artistNamesToCredits, buildTrackString } from "../../core/StringUtils.js"; 10 10 import { NETWORK_ERROR_FAILURE_CODES } from "../common/errors/NodeErrors.js"; 11 11 import { ··· 718 718 trackProgressPosition = currentTime; 719 719 } 720 720 721 - const play: PlayObjectLifecycleless = { 721 + const play: PlayObjectMinimal = { 722 722 data: { 723 723 track, 724 724 album,
+2 -2
src/backend/sources/DeezerInternalSource.ts
··· 1 1 import dayjs from "dayjs"; 2 2 import EventEmitter from "events"; 3 3 import request, { Request, Response, SuperAgent } from 'superagent'; 4 - import { PlayObject, PlayObjectLifecycleless, SOURCE_SOT, TA_CLOSE, TA_DURING, TA_EXACT, TA_FUZZY, TemporalAccuracy } from "../../core/Atomic.js"; 4 + import { PlayObject, PlayObjectMinimal, SOURCE_SOT, TA_CLOSE, TA_DURING, TA_EXACT, TA_FUZZY, TemporalAccuracy } from "../../core/Atomic.js"; 5 5 import { DEFAULT_RETRY_MULTIPLIER, FormatPlayObjectOptions, InternalConfig } from "../common/infrastructure/Atomic.js"; 6 6 import { DeezerInternalSourceConfig, DeezerInternalTrackData, DeezerSourceConfig } from "../common/infrastructure/config/source/deezer.js"; 7 7 import { TRANSFORM_HOOK } from "../common/infrastructure/Transform.js"; ··· 112 112 113 113 static formatPlayObj(obj: DeezerInternalTrackData, options: FormatPlayObjectOptions = {}): PlayObject { 114 114 const {newFromSource = false} = options; 115 - const play: PlayObjectLifecycleless = { 115 + const play: PlayObjectMinimal = { 116 116 data: { 117 117 artists: artistNamesToCredits([obj.ART_NAME]), 118 118 album: obj.ALB_TITLE,
+2 -2
src/backend/sources/DeezerSource.ts
··· 3 3 import passport from "passport"; 4 4 import { Strategy as DeezerStrategy } from 'passport-deezer'; 5 5 import request from 'superagent'; 6 - import { PlayObject, PlayObjectLifecycleless } from "../../core/Atomic.js"; 6 + import { PlayObject, PlayObjectMinimal } from "../../core/Atomic.js"; 7 7 import { DEFAULT_RETRY_MULTIPLIER, FormatPlayObjectOptions, InternalConfig } from "../common/infrastructure/Atomic.js"; 8 8 import { DeezerSourceConfig } from "../common/infrastructure/config/source/deezer.js"; 9 9 import { parseRetryAfterSecsFromObj, sleep, sortByOldestPlayDate, } from "../utils.js"; ··· 72 72 title: albumName, 73 73 } = {}, 74 74 } = obj; 75 - const play: PlayObjectLifecycleless = { 75 + const play: PlayObjectMinimal = { 76 76 data: { 77 77 artists: [artistName], 78 78 album: albumName,
+2 -2
src/backend/sources/IcecastSource.ts
··· 1 1 import { RecentlyPlayedOptions } from "./AbstractSource.js"; 2 2 import { EventEmitter } from "events"; 3 - import { PlayObject, PlayObjectLifecycleless, URLData } from "../../core/Atomic.js"; 3 + import { PlayObject, PlayObjectMinimal, URLData } from "../../core/Atomic.js"; 4 4 import { 5 5 FormatPlayObjectOptions, 6 6 InternalConfig, ··· 209 209 } 210 210 } 211 211 212 - const play: PlayObjectLifecycleless = { 212 + const play: PlayObjectMinimal = { 213 213 data: { 214 214 track, 215 215 artists: artistNamesToCredits(artists)
+2 -2
src/backend/sources/JRiverSource.ts
··· 2 2 import { EventEmitter } from "events"; 3 3 import normalizeUrl from 'normalize-url'; 4 4 import { URL } from "url"; 5 - import { PlayObject, PlayObjectLifecycleless } from "../../core/Atomic.js"; 5 + import { PlayObject, PlayObjectMinimal } from "../../core/Atomic.js"; 6 6 import { FormatPlayObjectOptions, InternalConfig } from "../common/infrastructure/Atomic.js"; 7 7 import { JRiverSourceConfig } from "../common/infrastructure/config/source/jriver.js"; 8 8 import { Info, JRiverApiClient, PLAYER_STATE } from "../common/vendor/JRiverApiClient.js"; ··· 101 101 const album = Album === null || Album === '' ? undefined : Album; 102 102 const length = Number.parseInt(DurationMS.toString()) / 1000; 103 103 104 - const play: PlayObjectLifecycleless = { 104 + const play: PlayObjectMinimal = { 105 105 data: { 106 106 track: Name, 107 107 album: album,
+2 -2
src/backend/sources/JellyfinApiSource.ts
··· 42 42 from "@jellyfin/sdk/lib/index.js"; 43 43 import dayjs from "dayjs"; 44 44 import EventEmitter from "events"; 45 - import { ArtistCredit, BrainzMeta, PlayObject, PlayObjectLifecycleless } from "../../core/Atomic.js"; 45 + import { ArtistCredit, BrainzMeta, PlayObject, PlayObjectMinimal } from "../../core/Atomic.js"; 46 46 import { artistNamesToCredits, artistNameToCredit, buildTrackString, combinePartsToString, truncateStringToLength } from "../../core/StringUtils.js"; 47 47 import { 48 48 FormatPlayObjectOptions, ··· 482 482 playAlbumArtists = normalizedAlbumArtists; 483 483 } 484 484 485 - const play: PlayObjectLifecycleless = { 485 + const play: PlayObjectMinimal = { 486 486 data: { 487 487 artists: playArtists, 488 488 album: Album,
+2 -2
src/backend/sources/MPDSource.ts
··· 2 2 import { EventEmitter } from "events"; 3 3 import path from 'path'; 4 4 import {MPC, type Status, type Song, type PlaylistItem} from 'mpc-js'; 5 - import { BrainzMeta, PlayObject, PlayObjectLifecycleless } from "../../core/Atomic.js"; 5 + import { BrainzMeta, PlayObject, PlayObjectMinimal } from "../../core/Atomic.js"; 6 6 import { 7 7 FormatPlayObjectOptions, 8 8 InternalConfig, ··· 255 255 position = Math.ceil(position); 256 256 } 257 257 258 - const play: PlayObjectLifecycleless = { 258 + const play: PlayObjectMinimal = { 259 259 data: { 260 260 artists: artists !== undefined ? artistNamesToCredits(artists) : [], 261 261 albumArtists: albumArtists !== undefined ? artistNamesToCredits(albumArtists) : [],
+2 -2
src/backend/sources/MPRISSource.ts
··· 2 2 import dayjs from "dayjs"; 3 3 import { DBusInterface, MessageBus, sessionBus, Connection, ConnectOpts } from 'dbus-ts'; 4 4 import EventEmitter from "events"; 5 - import { PlayObject, PlayObjectLifecycleless } from "../../core/Atomic.js"; 5 + import { PlayObject, PlayObjectMinimal } from "../../core/Atomic.js"; 6 6 import { FormatPlayObjectOptions, InternalConfig } from "../common/infrastructure/Atomic.js"; 7 7 import { 8 8 MPRIS_IFACE, ··· 73 73 actualAlbumArtists = albumArtist; 74 74 } 75 75 76 - const play: PlayObjectLifecycleless = { 76 + const play: PlayObjectMinimal = { 77 77 data: { 78 78 track: title, 79 79 album,
+2 -2
src/backend/sources/MopidySource.ts
··· 5 5 import normalizeUrl from 'normalize-url'; 6 6 import pEvent from 'p-event'; 7 7 import { URL } from "url"; 8 - import { PlayObject, PlayObjectLifecycleless } from "../../core/Atomic.js"; 8 + import { PlayObject, PlayObjectMinimal } from "../../core/Atomic.js"; 9 9 import { artistNamesToCredits, buildTrackString } from "../../core/StringUtils.js"; 10 10 import { 11 11 FormatPlayObjectOptions, ··· 160 160 artists = performers; 161 161 } 162 162 163 - const play: PlayObjectLifecycleless = { 163 + const play: PlayObjectMinimal = { 164 164 data: { 165 165 track: name, 166 166 album: albumName,
+2 -2
src/backend/sources/MusicCastSource.ts
··· 1 1 import { MemoryPositionalSource } from "./MemoryPositionalSource.js"; 2 2 import { RecentlyPlayedOptions } from "./AbstractSource.js"; 3 3 import { EventEmitter } from "events"; 4 - import { PlayObject, PlayObjectLifecycleless, URLData } from "../../core/Atomic.js"; 4 + import { PlayObject, PlayObjectMinimal, URLData } from "../../core/Atomic.js"; 5 5 import { 6 6 FormatPlayObjectOptions, 7 7 InternalConfig, ··· 148 148 playback 149 149 } = obj; 150 150 151 - const play: PlayObjectLifecycleless = { 151 + const play: PlayObjectMinimal = { 152 152 data: { 153 153 artists: artistNamesToCredits(artist !== undefined && artist !== '' ? [artist] : []), 154 154 album: album !== '' ? album : undefined,
+2 -2
src/backend/sources/MusikcubeSource.ts
··· 5 5 import normalizeUrl from 'normalize-url'; 6 6 import pEvent from 'p-event'; 7 7 import { URL } from "url"; 8 - import { PlayObject, PlayObjectLifecycleless, URLData } from "../../core/Atomic.js"; 8 + import { PlayObject, PlayObjectMinimal, URLData } from "../../core/Atomic.js"; 9 9 import { UpstreamError } from "../common/errors/UpstreamError.js"; 10 10 import { 11 11 FormatPlayObjectOptions, ··· 190 190 if(album_artist !== undefined && album_artist !== artist) { 191 191 albumArtists.push(album_artist); 192 192 } 193 - const play: PlayObjectLifecycleless = { 193 + const play: PlayObjectMinimal = { 194 194 data: { 195 195 artists: artistNamesToCredits(artists), 196 196 albumArtists: artistNamesToCredits(albumArtists),
+2 -2
src/backend/sources/PlexApiSource.ts
··· 1 1 import EventEmitter from "events"; 2 - import { BrainzMeta, PlayObject, PlayObjectLifecycleless, URLData } from "../../core/Atomic.js"; 2 + import { BrainzMeta, PlayObject, PlayObjectMinimal, URLData } from "../../core/Atomic.js"; 3 3 import { artistNamesToCredits, buildTrackString, combinePartsToString, truncateStringToLength } from "../../core/StringUtils.js"; 4 4 import { 5 5 asPlayerStateDataMaybePlay, ··· 374 374 realArtists.push(artist); 375 375 } 376 376 377 - const play: PlayObjectLifecycleless = { 377 + const play: PlayObjectMinimal = { 378 378 data: { 379 379 artists: artistNamesToCredits(realArtists), 380 380 albumArtists: artistNamesToCredits(albumArtists),
+2 -2
src/backend/sources/SonosSource.ts
··· 1 1 import { EventEmitter } from "events"; 2 - import { PlayObject, PlayObjectLifecycleless } from "../../core/Atomic.js"; 2 + import { PlayObject, PlayObjectMinimal } from "../../core/Atomic.js"; 3 3 import { 4 4 FormatPlayObjectOptions, 5 5 InternalConfig, ··· 374 374 titleStr = Title; 375 375 } 376 376 377 - const play: PlayObjectLifecycleless = { 377 + const play: PlayObjectMinimal = { 378 378 data: { 379 379 track: titleStr, 380 380 album: Album,
+2 -2
src/backend/sources/SpotifySource.ts
··· 2 2 import EventEmitter from "events"; 3 3 import SpotifyWebApi from "spotify-web-api-node"; 4 4 import request from 'superagent'; 5 - import { BrainzMeta, PlayObject, PlayObjectLifecycleless, SCROBBLE_TS_SOC_END, SCROBBLE_TS_SOC_START, ScrobbleTsSOC, SpotifyMeta } from "../../core/Atomic.js"; 5 + import { BrainzMeta, PlayObject, PlayObjectMinimal, SCROBBLE_TS_SOC_END, SCROBBLE_TS_SOC_START, ScrobbleTsSOC, SpotifyMeta } from "../../core/Atomic.js"; 6 6 import { artistNamesToCredits, artistNameToCredit, combinePartsToString, truncateStringToLength } from "../../core/StringUtils.js"; 7 7 import { isNodeNetworkException } from "../common/errors/NodeErrors.js"; 8 8 import { hasUpstreamError, UpstreamError } from "../common/errors/UpstreamError.js"; ··· 212 212 } 213 213 } 214 214 215 - const play: PlayObjectLifecycleless = { 215 + const play: PlayObjectMinimal = { 216 216 data: { 217 217 artists: artists.map(x => artistNameToCredit(x.name)), 218 218 albumArtists: actualAlbumArtists.map(x => artistNameToCredit(x.name)),
+2 -2
src/backend/sources/SubsonicSource.ts
··· 3 3 import isSameOrAfter from "dayjs/plugin/isSameOrAfter.js"; 4 4 import EventEmitter from "events"; 5 5 import request, { Request } from 'superagent'; 6 - import { PlayObject, PlayObjectLifecycleless } from "../../core/Atomic.js"; 6 + import { PlayObject, PlayObjectMinimal } from "../../core/Atomic.js"; 7 7 import { isNodeNetworkException } from "../common/errors/NodeErrors.js"; 8 8 import { UpstreamError } from "../common/errors/UpstreamError.js"; 9 9 import { DEFAULT_RETRY_MULTIPLIER, FormatPlayObjectOptions, InternalConfig, PlayPlatformId } from "../common/infrastructure/Atomic.js"; ··· 77 77 username, 78 78 } = obj; 79 79 80 - const play: PlayObjectLifecycleless = { 80 + const play: PlayObjectMinimal = { 81 81 data: { 82 82 artists: [artistNameToCredit(artist)], 83 83 album,
+2 -2
src/backend/sources/VLCSource.ts
··· 2 2 import { EventEmitter } from "events"; 3 3 import * as VLC from "vlc-client" 4 4 import { VlcMeta, VlcStatus } from "vlc-client/dist/Types.js"; 5 - import { PlayObject, PlayObjectLifecycleless } from "../../core/Atomic.js"; 5 + import { PlayObject, PlayObjectMinimal } from "../../core/Atomic.js"; 6 6 import { 7 7 FormatPlayObjectOptions, 8 8 InternalConfig, ··· 216 216 state 217 217 } = vlcState || {}; 218 218 219 - const play: PlayObjectLifecycleless = { 219 + const play: PlayObjectMinimal = { 220 220 data: { 221 221 artists: artistNamesToCredits(artists), 222 222 albumArtists: artistNamesToCredits(albumArtists),
+2 -2
src/backend/sources/WebScrobblerSource.ts
··· 1 1 import dayjs from "dayjs"; 2 2 import EventEmitter from "events"; 3 - import { PlayObject, PlayObjectLifecycleless, SOURCE_SOT } from "../../core/Atomic.js"; 3 + import { PlayObject, PlayObjectMinimal, SOURCE_SOT } from "../../core/Atomic.js"; 4 4 import { 5 5 FormatPlayObjectOptions, 6 6 InternalConfig, ··· 125 125 const albumArtist = processed.albumArtist ?? parsed.albumArtist; 126 126 const duration = parsed.duration ?? processed.duration; 127 127 128 - const play: PlayObjectLifecycleless = { 128 + const play: PlayObjectMinimal = { 129 129 data: { 130 130 track, 131 131 artists: [artistNameToCredit(artist)],
+2 -2
src/backend/sources/YTMusicSource.ts
··· 1 1 import dayjs, { Dayjs } from "dayjs"; 2 2 import EventEmitter from "events"; 3 - import { PlayObject, PlayObjectLifecycleless } from "../../core/Atomic.js"; 3 + import { PlayObject, PlayObjectMinimal } from "../../core/Atomic.js"; 4 4 import { FormatPlayObjectOptions, InternalConfig } from "../common/infrastructure/Atomic.js"; 5 5 import { YTMusicSourceConfig } from "../common/infrastructure/config/source/ytmusic.js"; 6 6 import { Innertube, UniversalCache, Parser, YTNodes, ApiResponse, IBrowseResponse, Log, SessionOptions } from 'youtubei.js'; ··· 419 419 const durObj = dayjs.duration(dur.seconds, 's') 420 420 duration = durObj.asSeconds(); 421 421 } 422 - const play: PlayObjectLifecycleless = { 422 + const play: PlayObjectMinimal = { 423 423 data: { 424 424 artists: artistNamesToCredits(artists), 425 425 albumArtists: artistNamesToCredits(albumArtists),
+2 -2
src/backend/sources/YandexMusicBridgeSource.ts
··· 2 2 import request from 'superagent'; 3 3 import { MemoryPositionalSource } from "./MemoryPositionalSource.js"; 4 4 import { RecentlyPlayedOptions } from "./AbstractSource.js"; 5 - import { PlayObject, PlayObjectLifecycleless, URLData } from "../../core/Atomic.js"; 5 + import { PlayObject, PlayObjectMinimal, URLData } from "../../core/Atomic.js"; 6 6 import { 7 7 InternalConfig, 8 8 NO_DEVICE, ··· 436 436 ? obj.artists_list 437 437 : (obj.artists ? obj.artists.split(/\s*,\s*/).filter(x => x.trim() !== '') : []); 438 438 439 - const play: PlayObjectLifecycleless = { 439 + const play: PlayObjectMinimal = { 440 440 data: { 441 441 artists: artistNamesToCredits(artists), 442 442 album: obj.album ?? undefined,
+5 -5
src/backend/utils/PlayComparisonUtils.ts
··· 1 1 import { getListDiff, ListDiff } from "@donedeal0/superdiff"; 2 - import { PlayMatchResult, PlayObject, PlayObjectLifecycleless, SOURCE_SOT, TA_CLOSE, TA_DEFAULT_ACCURACY, TA_DURING, TA_EXACT, TA_FUZZY, TemporalAccuracy, TrackStringOptions } from "../../core/Atomic.js"; 2 + import { PlayMatchResult, PlayObject, PlayObjectMinimal, SOURCE_SOT, TA_CLOSE, TA_DEFAULT_ACCURACY, TA_DURING, TA_EXACT, TA_FUZZY, TemporalAccuracy, TrackStringOptions } from "../../core/Atomic.js"; 3 3 import { buildTrackString, capitalize, truncateStringToLength } from "../../core/StringUtils.js"; 4 4 import { comparingMultipleArtists, playObjDataMatch, setIntersection } from "../utils.js"; 5 5 import { comparePlayTemporally, hasAcceptableTemporalAccuracy, temporalAccuracyToString, TemporalPlayComparisonOptions, temporalPlayComparisonSummary } from "./TimeUtils.js"; ··· 15 15 import dayjs from "dayjs"; 16 16 17 17 18 - export const metaInvariantTransform = (play: PlayObject): PlayObjectLifecycleless => { 18 + export const metaInvariantTransform = (play: PlayObject): PlayObjectMinimal => { 19 19 const { 20 20 meta: { 21 21 trackId ··· 45 45 } 46 46 } 47 47 48 - export const playContentInvariantTransform = (play: PlayObject): PlayObjectLifecycleless => { 48 + export const playContentInvariantTransform = (play: PlayObject): PlayObjectMinimal => { 49 49 const { 50 50 data: { 51 51 playDate, ··· 63 63 } 64 64 } 65 65 66 - export const playContentBasicInvariantTransform = (play: PlayObject): PlayObjectLifecycleless => { 66 + export const playContentBasicInvariantTransform = (play: PlayObject): PlayObjectMinimal => { 67 67 const { 68 68 data: { 69 69 playDate, ··· 110 110 return undefined; 111 111 } 112 112 113 - export type PlayTransformer = (play: PlayObject) => PlayObjectLifecycleless; 113 + export type PlayTransformer = (play: PlayObject) => PlayObjectMinimal; 114 114 export type ListTransformers = PlayTransformer[]; 115 115 116 116 export const defaultListTransformers: ListTransformers = [metaInvariantTransform, playDateInvariantTransform];
+2 -2
src/backend/utils/PlayTransformUtils.ts
··· 1 1 import { Logger } from "@foxxmd/logging"; 2 2 import { searchAndReplace as searchAndReplaceFunc, testMaybeRegex as testMaybeRegexFunc } from "@foxxmd/regex-buddy-core"; 3 - import { PlayObject, PlayObjectLifecycleless } from "../../core/Atomic.js"; 3 + import { PlayObject, PlayObjectMinimal } from "../../core/Atomic.js"; 4 4 5 5 import { 6 6 ConditionalSearchAndReplaceRegExp, ··· 173 173 regex?: SuppliedRegex 174 174 } 175 175 176 - export const baseFormatPlayObj = (data: any, play: PlayObjectLifecycleless): PlayObject => { 176 + export const baseFormatPlayObj = (data: any, play: PlayObjectMinimal): PlayObject => { 177 177 const basePlay = { 178 178 data: { 179 179 ...play.data
+2 -2
src/client/components/PlayData.tsx
··· 1 1 import React, { Fragment, useMemo, useState } from 'react'; 2 2 import { EmptyState, DataList, HStack, Tag, Tabs, Wrap, Box, Flex, SegmentGroup, Stack, Text, Tooltip, Separator, IconButton, Container, SimpleGrid, Float, Spacer, Icon, Link, Span, Show } from "@chakra-ui/react" 3 3 import { LuCode, LuText, LuCheck, LuX } from "react-icons/lu" 4 - import { JsonPlayObject, PlayObjectLifecycleless } from '../../core/Atomic.js'; 4 + import { JsonPlayObject, PlayObjectMinimal } from '../../core/Atomic.js'; 5 5 import { shortTodayAwareFormat, timeToHumanTimestamp } from '../../core/TimeUtils.js'; 6 6 import dayjs from 'dayjs'; 7 7 import { ChakraCodeBlock } from './CodeBlock.js'; ··· 26 26 export type DisplayDates = false | 'all' | 'played' | 'seen'; 27 27 28 28 export interface PlayInfoProps { 29 - play?: JsonPlayObject | PlayObjectLifecycleless<string> 29 + play?: JsonPlayObject | PlayObjectMinimal<string> 30 30 final?: JsonPlayObject 31 31 showCodeToggle?: boolean 32 32 showCompare?: boolean
+9 -13
src/core/Atomic.ts
··· 2 2 import { Dayjs } from "dayjs"; 3 3 import { ListenProgress } from "../backend/sources/PlayerState/ListenProgress.js"; 4 4 import { AdditionalTrackInfoResponse } from "../backend/common/vendor/listenbrainz/interfaces.js"; 5 - import { MarkOptional } from "ts-essentials"; 5 + import { MarkOptional, RequiredKeys } from "ts-essentials"; 6 6 import { ErrorObject } from "serialize-error"; 7 7 import { PlayPlatformIdStr } from "../backend/common/infrastructure/Atomic.js"; 8 8 import { FlowControlTerm, TransformHook } from "../backend/common/infrastructure/Transform.js"; ··· 292 292 type: string, input: (object | string) 293 293 } 294 294 295 - export type PlayMetaLifecycleless<D extends DateLike = Dayjs> = MarkOptional<PlayMeta<D>, 'lifecycle'>; 296 - 297 - export interface PlayObjectLifecycleless<D extends DateLike = Dayjs> { 298 - data: PlayData<D>, 299 - meta: PlayMetaLifecycleless<D> 300 - } 301 - 302 295 export type ErrorLike = Error | ErrorObject; 303 296 304 297 /** scrobble action plus the match result before scrobbling */ ··· 308 301 warnings?: string[] 309 302 error?: Error | ErrorObject 310 303 response?: ScrobbleResponse 311 - mergedScrobble?: PlayObjectLifecycleless<D> 304 + mergedScrobble?: AmbPlayObjectMinimal<D> 312 305 } 313 306 314 307 // export interface PlayLifecycle<D extends DateLike = Dayjs> { ··· 350 343 score: number 351 344 breakdowns: string[] 352 345 reason?: string 353 - closestMatchedPlay?: PlayObjectLifecycleless<D> 354 - transformedPlay?: PlayObjectLifecycleless<D> 346 + closestMatchedPlay?: AmbPlayObjectMinimal<D> 347 + transformedPlay?: PlayObjectMinimal 355 348 summary?: string 356 349 createdAt: string 357 350 } ··· 365 358 366 359 export interface PlayOriginal<D extends DateLike = Dayjs> { 367 360 data?: object 368 - play?: PlayObjectLifecycleless<D> 361 + play?: PlayObjectMinimal<D> 369 362 } 370 363 371 364 export interface AmbPlayObject<D extends DateLike = Dayjs> { 372 365 id?: number 373 366 uid?: string 374 367 data: PlayData<D>, 375 - meta: PlayMetaLifecycleless<D> 368 + meta: PlayMeta<D> 376 369 original?: PlayOriginal<D> 377 370 scrobble?: ScrobbleResult<D> 378 371 lifecycle?: LifecycleStep[] 379 372 } 380 373 374 + export type AmbPlayObjectMinimal<D extends DateLike = Dayjs> = Pick<AmbPlayObject<D>, RequiredKeys<AmbPlayObject<D>>>; 375 + 381 376 export const isPlayObject = (obj: object): obj is PlayObject => { 382 377 return obj !== undefined && obj !== null && 'data' in obj && typeof obj.data === 'object' && 'meta' in obj && typeof obj.meta === 'object'; 383 378 } 384 379 385 380 export type PlayObject = AmbPlayObject<Dayjs>; 381 + export type PlayObjectMinimal<D extends DateLike = Dayjs> = AmbPlayObjectMinimal<D>; 386 382 export interface PlayActivity { 387 383 play: JsonPlayObject 388 384 status: string
+2 -2
src/core/PlayUtils.ts
··· 1 1 import { PlayPlatformId } from "../backend/common/infrastructure/Atomic.js"; 2 - import { AmbPlayObject, DateLike, PlayObject, PlayObjectLifecycleless } from "./Atomic.js"; 2 + import { AmbPlayObject, DateLike, PlayObject, PlayObjectMinimal } from "./Atomic.js"; 3 3 import dayjs from "dayjs"; 4 4 5 5 export const sortByNewestDate = (aPlayDate: DateLike, bPlayDate: DateLike) => { ··· 36 36 return `${id[0]}-${id[1]}`; 37 37 }; 38 38 39 - export const statefulInvariantTransform = (play: PlayObject): PlayObjectLifecycleless => { 39 + export const statefulInvariantTransform = (play: PlayObject): PlayObjectMinimal => { 40 40 const { 41 41 meta, 42 42 data