feat(client): Implement createdAt dates for scrobble actions

This commit is contained in:
FoxxMD 2026-07-07 16:37:45 +00:00
parent 3d6e86f258
commit e6f4a1dbd2
3 changed files with 29 additions and 16 deletions

View file

@ -19,7 +19,8 @@ import {
CLIENT_DEAD_QUEUE,
PlayOriginal,
PlayLifecycle,
SourcePlayerJson
SourcePlayerJson,
QUEUE_STATUS_COMPLETED
} from "../../core/Atomic.js";
import { artistNamesToCredits, buildTrackString, capitalize, truncateStringToLength } from "../../core/StringUtils.js";
import AbstractComponent from "../common/AbstractComponent.js";
@ -932,6 +933,7 @@ export default abstract class AbstractScrobbleClient extends AbstractComponent i
...scrobble,
payload: result.payload,
warnings: result.warnings,
createdAt: dayjs(),
response: result.response,
mergedScrobble: result.mergedScrobble !== undefined ? statefulInvariantTransform(result.mergedScrobble) : undefined
}
@ -1145,7 +1147,8 @@ export default abstract class AbstractScrobbleClient extends AbstractComponent i
const { summary, ...matchResult } = await this.existingScrobble(currQueuedPlay.play, historicalPlays);
currQueuedPlay.play.scrobble = {
...(currQueuedPlay.play.scrobble ?? {}),
match: matchResult
match: matchResult,
createdAt: dayjs()
}
signal.throwIfAborted();
if (!matchResult.match) {
@ -1158,6 +1161,7 @@ export default abstract class AbstractScrobbleClient extends AbstractComponent i
//handledShiftedPlay = true;
} catch (e) {
currQueuedPlay.play.scrobble = {
createdAt: dayjs()
};
const submitError = findCauseByReference(e, ScrobbleSubmitError);
@ -1210,7 +1214,7 @@ export default abstract class AbstractScrobbleClient extends AbstractComponent i
queueState.error = queueError;
await this.playRepo.updateById(currQueuedPlay.id, {state: 'failed', error: queueError, play: currQueuedPlay.play});
currQueuedPlay.state = 'failed';
currQueuedPlay.error = queueError;
//currQueuedPlay.error = queueError;
} else {
await this.queueRepo.updateById(queueState.id, {queueStatus: 'completed'});
await this.playRepo.updateById(currQueuedPlay.id, {state: successState ?? 'scrobbled', play: currQueuedPlay.play});
@ -1314,15 +1318,14 @@ export default abstract class AbstractScrobbleClient extends AbstractComponent i
// return [false];
// }
let deadQueueState: QueueStateSelect;
let deadScrobble: PlaySelectWithQueueStates = await this.playRepo.findByUid(uid, {hydrate: ['asPlay']});
const deadScrobble: PlaySelectWithQueueStates = await this.playRepo.findByUid(uid, {hydrate: ['asPlay']});
if(deadScrobble === undefined) {
throw new Error(`Play ${uid} does not exist for ${this.name}`);
}
if(deadScrobble.state === 'scrobbled') {
throw new Error(`Play ${uid} is already scrobbled.`);
}
deadQueueState = deadScrobble.queueStates.find(x => x.queueName === CLIENT_DEAD_QUEUE);
const deadQueueState: QueueStateSelect = deadScrobble.queueStates.find(x => x.queueName === CLIENT_DEAD_QUEUE);
if(deadQueueState === undefined) {
throw new Error(`Play ${uid} is not currently queued in dead letter.`);
}
@ -1352,7 +1355,7 @@ export default abstract class AbstractScrobbleClient extends AbstractComponent i
}
this.queueRepo.updateById(deadQueueState.id, {retries: deadQueueState.retries + 1, error: e, updatedAt: dayjs(), queueStatus: 'failed'});
this.playRepo.updateById(deadScrobble.id, {error: e});
//this.playRepo.updateById(deadScrobble.id, {error: e});
// deadScrobble.retries++;
// deadScrobble.error = messageWithCauses(e);
// deadScrobble.lastRetry = dayjs();
@ -1365,18 +1368,23 @@ export default abstract class AbstractScrobbleClient extends AbstractComponent i
const {summary, ...matchResult} = await this.existingScrobble(deadScrobble.play, historicalPlays);
deadScrobble.play.scrobble = {
...(deadScrobble.play.scrobble ?? {}),
match: matchResult
match: matchResult,
createdAt: dayjs()
}
if(!matchResult.match) {
const transformedScrobble = await this.transformPlay(deadScrobble.play, TRANSFORM_HOOK.postCompare);
signal?.throwIfAborted();
try {
const scrobbledPlay = await this.scrobble(transformedScrobble);
deadScrobble.play = scrobbledPlay;
await this.addScrobbledTrack(scrobbledPlay);
this.playRepo.updateById(deadScrobble.id, {play: deadScrobble.play});
this.queueRepo.updateById(deadQueueState.id, {error: null, updatedAt: dayjs(), queueStatus: QUEUE_STATUS_COMPLETED});
this.removeDeadLetterScrobble(deadScrobble, 'scrobbled', true);
} catch (e) {
deadScrobble.play.scrobble = {
...(deadScrobble.play.scrobble ?? {})
...(deadScrobble.play.scrobble ?? {}),
createdAt: dayjs()
}
const submitError = findCauseByReference(e, ScrobbleSubmitError);
if(submitError !== undefined) {
@ -1389,7 +1397,7 @@ export default abstract class AbstractScrobbleClient extends AbstractComponent i
}
this.queueRepo.updateById(deadQueueState.id, {retries: deadQueueState.retries + 1, error: e, updatedAt: dayjs(), queueStatus: 'failed'});
this.playRepo.updateById(deadScrobble.id, {error: e});
this.playRepo.updateById(deadScrobble.id, {play: deadScrobble.play});
// deadScrobble.retries++;
// deadScrobble.error = messageWithCauses(e);
// deadScrobble.lastRetry = dayjs();
@ -1399,6 +1407,7 @@ export default abstract class AbstractScrobbleClient extends AbstractComponent i
return [false, deadScrobble];
}
} else {
this.playRepo.updateById(deadScrobble.id, {play: deadScrobble.play});
this.deadLogger.verbose(`Looks like ${buildTrackString(deadScrobble.play)} was already scrobbled!\n${summary}`);
this.removeDeadLetterScrobble(deadScrobble, 'duped', true);
}

View file

@ -313,6 +313,7 @@ export interface ScrobbleResult<D extends DateLike = Dayjs> {
error?: Error | ErrorObject
response?: ScrobbleResponse
mergedScrobble?: AmbPlayObjectMinimal<D>
createdAt?: D
}
export interface PlayLifecycle<D extends DateLike = Dayjs> {

View file

@ -1,7 +1,6 @@
import { Traverse, TraverseContext } from 'neotraverse/modern';
import { Traverse } from 'neotraverse/modern';
import { faker } from '@faker-js/faker';
import { AmbPlayObject, DateLike, LifecycleInput, LifecycleStep, ObjectPlayData, PLAY_STATES, PlayMeta, PlayObject, PlayOriginal, PlayState, ScrobbleResult } from '../../Atomic.js';
import { MarkOptional } from 'ts-essentials';
import { generateBrainz, generateMbid, generatePlay, GeneratePlayOpts, generatePlays } from '../../PlayTestUtils.js';
import { statefulInvariantTransform } from '../../PlayUtils.js';
import clone from 'clone';
@ -52,7 +51,7 @@ export const generatePlayWithLifecycle = (opts: GeneratePlayWithLifecycleOptions
//lplay.meta.lifecycle.original = lifecyclelessInvariantTransform(original);
//lplay.meta.lifecycle.input = generateRandomObj();
let steps: LifecycleStep[] = [];
const steps: LifecycleStep[] = [];
let transformedPlay = clone(original);
if(preCompare !== undefined) {
@ -115,7 +114,9 @@ export const playWithLifecycleScrobble = async (play: PlayObject, opts: Scrobble
error = false,
} = opts;
const scrobbleRes: ScrobbleResult = {};
const scrobbleRes: ScrobbleResult = {
createdAt: dayjs()
};
const existingPlays = generatePlays(2);
@ -278,8 +279,10 @@ const generateRandomVal = (depth: number = 0, opt: RandomObjOptions = {}, typeId
case 7:
return generateRandomObj(depth + 1, opt);
case 8:
const typeId = faker.number.int({ min: 4, max: depth > (opt.maxDepth ?? 3) ? 6 : 7 });
return faker.helpers.multiple(() => generateRandomVal(depth + 1, opt, typeId), { count: { min: 1, max: opt.maxObjSize ?? 7 } })
{
const typeId = faker.number.int({ min: 4, max: depth > (opt.maxDepth ?? 3) ? 6 : 7 });
return faker.helpers.multiple(() => generateRandomVal(depth + 1, opt, typeId), { count: { min: 1, max: opt.maxObjSize ?? 7 } })
}
}
}