diff --git a/src/backend/common/infrastructure/Atomic.ts b/src/backend/common/infrastructure/Atomic.ts index 93f4b726..b747ef87 100644 --- a/src/backend/common/infrastructure/Atomic.ts +++ b/src/backend/common/infrastructure/Atomic.ts @@ -445,36 +445,39 @@ export interface PaginatedTimeRangeOptions { export type PaginatedTimeRangeCommonOptions = Partial & PaginatedLimit; -export interface PaginatedListensOptions extends PaginatedLimit { - page: number +export type CursorType = number | string; + +export interface PaginatedListensOptions extends PaginatedLimit { + cursor: T } export interface PagelessListensTimeRangeOptions extends PaginatedTimeRangeCommonOptions { } -export interface PaginatedListensTimeRangeOptions extends Partial, PaginatedListensOptions { +export interface PaginatedListensTimeRangeOptions extends Partial, PaginatedListensOptions { } -export interface PaginatedResults { +export interface PaginatedResults { total?: number more?: boolean + cursorNext?: T order?: 'desc' | 'asc' } export interface PaginatedListens { - getPaginatedListens(params: PaginatedListensOptions): Promise<{data: PlayObject[], meta: PaginatedListensOptions & PaginatedResults}> + getPaginatedListens(params: PaginatedListensOptions): Promise<{data: PlayObject[], meta: PaginatedListensOptions & PaginatedResults}> } export const hasPaginagedListens = (obj: Object): obj is PaginatedListens => { return 'getPaginatedListens' in obj; } -export interface PaginatedTimeRangeListensResult { +export interface PaginatedTimeRangeListensResult { data: PlayObject[]; - meta: PaginatedListensTimeRangeOptions & PaginatedResults; + meta: PaginatedListensTimeRangeOptions & PaginatedResults; } -export interface PaginatedTimeRangeListens { - getPaginatedTimeRangeListens(params: PaginatedListensTimeRangeOptions): Promise +export interface PaginatedTimeRangeListens { + getPaginatedTimeRangeListens(params: PaginatedListensTimeRangeOptions): Promise> getPaginatedUnitOfTime(): ManipulateType; } @@ -498,4 +501,4 @@ export const hasPagelessTimeRangeListens = (obj: Object): obj is PagelessTimeRan export type PaginatedTimeRangeSource = PaginatedTimeRangeListens | PagelessTimeRangeListens; export type PaginatedSource = PaginatedListens | PaginatedTimeRangeSource; -export type TimeRangeListensFetcher = (opts: PaginatedTimeRangeCommonOptions | PaginatedListensTimeRangeOptions) => Promise \ No newline at end of file +export type TimeRangeListensFetcher = (opts: PaginatedTimeRangeCommonOptions | PaginatedListensTimeRangeOptions) => Promise \ No newline at end of file diff --git a/src/backend/common/vendor/LastfmApiClient.ts b/src/backend/common/vendor/LastfmApiClient.ts index 1d4963ec..db7800f3 100644 --- a/src/backend/common/vendor/LastfmApiClient.ts +++ b/src/backend/common/vendor/LastfmApiClient.ts @@ -37,7 +37,7 @@ export const LIBREFM_PATH = '/2.0/'; export const LASTFM_HOST = 'ws.audioscrobbler.com'; export const LASTFM_PATH = '/2.0'; -export default class LastfmApiClient extends AbstractApiClient implements PaginatedTimeRangeListens { +export default class LastfmApiClient extends AbstractApiClient implements PaginatedTimeRangeListens { user?: string; declare config: LastfmData; @@ -253,7 +253,7 @@ export default class LastfmApiClient extends AbstractApiClient implements Pagina return 'second'; } - getPaginatedTimeRangeListens = async (fetchOptions: PaginatedListensTimeRangeOptions, options: {includeNowPlaying?: boolean} = {}): Promise => { + getPaginatedTimeRangeListens = async (fetchOptions: PaginatedListensTimeRangeOptions, options: {includeNowPlaying?: boolean} = {}): Promise> => { const resp = await this.getRecentTracksWithPagination(fetchOptions); @@ -301,7 +301,7 @@ export default class LastfmApiClient extends AbstractApiClient implements Pagina } }, []); - return {data: plays, meta: {...fetchOptions, total: parseInt(total, 10), more: fetchOptions.page < parseInt(totalPages, 10)}}; + return {data: plays, meta: {...fetchOptions, total: parseInt(total, 10), more: fetchOptions.cursor < parseInt(totalPages, 10)}}; } diff --git a/src/backend/common/vendor/koito/KoitoApiClient.ts b/src/backend/common/vendor/koito/KoitoApiClient.ts index efc43309..20972ce6 100644 --- a/src/backend/common/vendor/koito/KoitoApiClient.ts +++ b/src/backend/common/vendor/koito/KoitoApiClient.ts @@ -20,7 +20,7 @@ interface SubmitOptions { const KOITO_LZ_PATH: RegExp = new RegExp(/^\/apis\/listenbrainz(\/?1?\/?)?$/); -export class KoitoApiClient extends AbstractApiClient implements PaginatedTimeRangeListens { +export class KoitoApiClient extends AbstractApiClient implements PaginatedTimeRangeListens { declare config: KoitoData; url: URLData; @@ -147,7 +147,7 @@ export class KoitoApiClient extends AbstractApiClient implements PaginatedTimeRa } } - getPaginatedTimeRangeListens = async (params: PaginatedListensTimeRangeOptions): Promise => { + getPaginatedTimeRangeListens = async (params: PaginatedListensTimeRangeOptions): Promise> => { let resp: ListensResponse; try { @@ -161,7 +161,7 @@ export class KoitoApiClient extends AbstractApiClient implements PaginatedTimeRa meta: { ...params, total: resp.total_record_count, - page: resp.current_page, + cursor: resp.current_page, more: resp.has_next_page } } diff --git a/src/backend/common/vendor/maloja/MalojaApiClient.ts b/src/backend/common/vendor/maloja/MalojaApiClient.ts index a782db34..3584cf44 100644 --- a/src/backend/common/vendor/maloja/MalojaApiClient.ts +++ b/src/backend/common/vendor/maloja/MalojaApiClient.ts @@ -18,7 +18,7 @@ import { ScrobbleSubmitError } from '../../errors/MSErrors.js'; -export class MalojaApiClient extends AbstractApiClient implements PaginatedTimeRangeListens { +export class MalojaApiClient extends AbstractApiClient implements PaginatedTimeRangeListens { declare config: MalojaData; url: URLData; @@ -189,11 +189,11 @@ export class MalojaApiClient extends AbstractApiClient implements PaginatedTimeR // return list.map(formatPlayObj); // } - getPaginatedTimeRangeListens = async (params: PaginatedListensTimeRangeOptions): Promise => { + getPaginatedTimeRangeListens = async (params: PaginatedListensTimeRangeOptions): Promise> => { const opts: RecentlyPlayedRequestOptions = { perpage: params.limit, - page: params.page, + page: params.cursor, from: params.from !== undefined ? dayjs.unix(params.from).format('YYYY/MM/DD') : undefined, until: params.to !== undefined ? dayjs.unix(params.from).format('YYYY/MM/DD') : undefined }; diff --git a/src/backend/scrobblers/KoitoScrobbler.ts b/src/backend/scrobblers/KoitoScrobbler.ts index 639bf3d3..d8ba226c 100644 --- a/src/backend/scrobblers/KoitoScrobbler.ts +++ b/src/backend/scrobblers/KoitoScrobbler.ts @@ -71,9 +71,9 @@ export default class KoitoScrobbler extends AbstractScrobbleClient { getScrobblesForRefresh = async (limit: number) => { if(this.queuedScrobbles.length === 0) { - return await this.getScrobblesForTimeRange({limit, page: 0}); + return await this.getScrobblesForTimeRange({limit, cursor: 0}); } else { - return await this.getScrobblesForTimeRange({limit, page: 0, from: this.queuedScrobbles[0].play.data.playDate.unix(), to: dayjs().unix()}); + return await this.getScrobblesForTimeRange({limit, cursor: 0, from: this.queuedScrobbles[0].play.data.playDate.unix(), to: dayjs().unix()}); } } diff --git a/src/backend/scrobblers/LastfmScrobbler.ts b/src/backend/scrobblers/LastfmScrobbler.ts index 7d73f0ff..8712f70d 100644 --- a/src/backend/scrobblers/LastfmScrobbler.ts +++ b/src/backend/scrobblers/LastfmScrobbler.ts @@ -63,9 +63,9 @@ export default class LastfmScrobbler extends AbstractScrobbleClient { getScrobblesForRefresh = async (limit: number) => { let plays: PlayObject[] = []; if(this.queuedScrobbles.length === 0) { - plays = await this.getScrobblesForTimeRange({limit, page: 1}); + plays = await this.getScrobblesForTimeRange({limit, cursor: 1}); } else { - plays = await this.getScrobblesForTimeRange({limit, page: 1, from: this.queuedScrobbles[0].play.data.playDate.unix(), to: dayjs().unix()}); + plays = await this.getScrobblesForTimeRange({limit, cursor: 1, from: this.queuedScrobbles[0].play.data.playDate.unix(), to: dayjs().unix()}); } return plays.filter(x => !x.meta.nowPlaying); } diff --git a/src/backend/scrobblers/MalojaScrobbler.ts b/src/backend/scrobblers/MalojaScrobbler.ts index 532d86e9..d3a76432 100644 --- a/src/backend/scrobblers/MalojaScrobbler.ts +++ b/src/backend/scrobblers/MalojaScrobbler.ts @@ -82,9 +82,9 @@ export default class MalojaScrobbler extends AbstractScrobbleClient { getScrobblesForRefresh = async (limit: number) => { if(this.queuedScrobbles.length === 0) { - return await this.getScrobblesForTimeRange({limit, page: 0}); + return await this.getScrobblesForTimeRange({limit, cursor: 0}); } else { - return await this.getScrobblesForTimeRange({limit, page: 0, from: this.queuedScrobbles[0].play.data.playDate.unix(), to: dayjs().unix()}); + return await this.getScrobblesForTimeRange({limit, cursor: 0, from: this.queuedScrobbles[0].play.data.playDate.unix(), to: dayjs().unix()}); } } diff --git a/src/backend/sources/KoitoSource.ts b/src/backend/sources/KoitoSource.ts index 4bab5b17..71830d1f 100644 --- a/src/backend/sources/KoitoSource.ts +++ b/src/backend/sources/KoitoSource.ts @@ -61,13 +61,13 @@ export default class KoitoSource extends MemorySource { getRecentlyPlayed = async(options: RecentlyPlayedOptions = {}) => { const {limit = 20} = options; await this.processRecentPlays([]); - const resp = await this.getScrobblesForTimeRange({limit, page: 0 }); + const resp = await this.getScrobblesForTimeRange({limit, cursor: 0 }); return resp; } getUpstreamRecentlyPlayed = async (options: RecentlyPlayedOptions = {}): Promise => { try { - const resp = await this.getScrobblesForTimeRange({limit: 20, page: 0 }); + const resp = await this.getScrobblesForTimeRange({limit: 20, cursor: 0 }); return resp; } catch (e) { throw e; diff --git a/src/backend/sources/LastfmSource.ts b/src/backend/sources/LastfmSource.ts index ac8e7888..8503a075 100644 --- a/src/backend/sources/LastfmSource.ts +++ b/src/backend/sources/LastfmSource.ts @@ -74,7 +74,7 @@ export default class LastfmSource extends MemorySource { getLastfmRecentTrack = async(options: RecentlyPlayedOptions = {}): Promise<[PlayObject[], PlayObject[]]> => { const {limit = 20} = options; try { - const {data: plays} = await this.api.getPaginatedTimeRangeListens({limit, page: 1}); + const {data: plays} = await this.api.getPaginatedTimeRangeListens({limit, cursor: 1}); plays.sort(sortByOldestPlayDate); // if the track is "now playing" it doesn't get a timestamp so we can't determine when it started playing // and don't want to accidentally count the same track at different timestamps by artificially assigning it 'now' as a timestamp diff --git a/src/backend/sources/MalojaSource.ts b/src/backend/sources/MalojaSource.ts index c592a3af..c64f8871 100644 --- a/src/backend/sources/MalojaSource.ts +++ b/src/backend/sources/MalojaSource.ts @@ -56,13 +56,13 @@ export default class MalojaSource extends MemorySource { getRecentlyPlayed = async (options: RecentlyPlayedOptions = {}) => { const { limit = 20 } = options; await this.processRecentPlays([]); - const resp = await this.api.getPaginatedTimeRangeListens({limit, page: 0}); + const resp = await this.api.getPaginatedTimeRangeListens({limit, cursor: 0}); return resp.data; } getUpstreamRecentlyPlayed = async (options: RecentlyPlayedOptions = {}): Promise => { try { - const resp = await this.api.getPaginatedTimeRangeListens({limit: 20, page: 0}); + const resp = await this.api.getPaginatedTimeRangeListens({limit: 20, cursor: 0}); return resp.data; } catch (e) { throw e; diff --git a/src/backend/utils/ListenFetchUtils.ts b/src/backend/utils/ListenFetchUtils.ts index 1c8a4dd2..e83396bb 100644 --- a/src/backend/utils/ListenFetchUtils.ts +++ b/src/backend/utils/ListenFetchUtils.ts @@ -1,4 +1,4 @@ -import { childLogger } from "@foxxmd/logging"; +import { childLogger, Logger } from "@foxxmd/logging"; import dayjs from "dayjs"; import { PlayObject } from "../../core/Atomic.js"; import { hasPagelessTimeRangeListens, hasPaginatedTimeRangeListens, PagelessListensTimeRangeOptions, PagelessTimeRangeListens, PaginatedListensTimeRangeOptions, PaginatedTimeRangeCommonOptions, PaginatedTimeRangeListens, PaginatedTimeRangeSource, TimeRangeListensFetcher } from "../common/infrastructure/Atomic.js"; @@ -6,6 +6,11 @@ import { MaybeLogger } from "../common/logging.js"; import { sortByNewestPlayDate, sortByOldestPlayDate } from "../utils.js"; import { todayAwareFormat } from "./TimeUtils.js"; +export interface TimeRangeFetchOptions { + logger?: MaybeLogger | Logger + +} + export const createGetScrobblesForTimeRangeFunc = (fetcher: T, pLogger: MaybeLogger = new MaybeLogger()): TimeRangeListensFetcher => { let requestCount: number; const logger = pLogger instanceof MaybeLogger ? pLogger : childLogger(pLogger, ['Pagination']); @@ -89,10 +94,10 @@ export const createGetScrobblesForTimeRangeFunc = => { + return async (opts: PaginatedListensTimeRangeOptions): Promise => { requestCount = 0; let more = true; - let currOpts: PaginatedListensTimeRangeOptions = { page: 1, ...opts }; + let currOpts: PaginatedListensTimeRangeOptions = opts; let initial = true; let timeRangeHint: string; if(currOpts.to !== undefined && currOpts.from !== undefined) { @@ -105,7 +110,7 @@ export const createGetScrobblesForTimeRangeFunc =