import type {Logger} from "@foxxmd/logging"; import type EventEmitter from "events"; import type {PlayObject, SourcePlayerObj} from "../../core/Atomic.ts"; import { buildTrackString, capitalize } from "../../core/StringUtils.ts"; import { isNodeNetworkException } from "../common/errors/NodeErrors.ts"; import type {FormatPlayObjectOptions, TimeRangeListensFetcher} from "../common/infrastructure/Atomic.ts"; import { playToListenPayload } from '../common/vendor/listenbrainz/lzUtils.ts'; import AbstractScrobbleClient, { nowPlayingUpdateByPlayDuration } from "./AbstractScrobbleClient.ts"; import { isDebugMode } from "../utils.ts"; import type {KoitoClientConfig} from "../common/infrastructure/config/client/koito.ts"; import { KoitoApiClient, listenObjectResponseToPlay } from "../common/vendor/koito/KoitoApiClient.ts"; import { createGetScrobblesForTimeRangeFunc } from "../utils/ListenFetchUtils.ts"; export default class KoitoScrobbler extends AbstractScrobbleClient { api: KoitoApiClient; requiresAuth = true; requiresAuthInteraction = false; getScrobblesForTimeRange: TimeRangeListensFetcher declare config: KoitoClientConfig; constructor(name: any, config: KoitoClientConfig, options = {}, emitter: EventEmitter, logger: Logger) { super('koito', name, config, emitter, logger); this.api = new KoitoApiClient(name, config.data, {logger: this.logger}); // https://listenbrainz.readthedocs.io/en/latest/users/api/core.html#get--1-user-(user_name)-listens // 1000 is way too high. maxing at 100 this.MAX_INITIAL_SCROBBLES_FETCH = 100; this.supportsNowPlaying = true; this.nowPlayingMaxThreshold = nowPlayingUpdateByPlayDuration; this.getScrobblesForTimeRange = createGetScrobblesForTimeRangeFunc(this.api, this.api.logger); } formatPlayObj = (obj: any, options: FormatPlayObjectOptions = {}) => listenObjectResponseToPlay(obj, options); public playToClientPayload(playObject: PlayObject): object { return playToListenPayload(playObject); } protected async doBuildInitData(): Promise { const { data: { token, } = {} } = this.config; if (token === undefined) { throw new Error('Must provide a User Token'); } return true; } protected async doCheckConnection(): Promise { await this.api.testConnection(); return true; } doAuthentication = async () => { try { return await this.api.testAuth(); } catch (e) { if(isNodeNetworkException(e)) { this.logger.error('Could not communicate with Koito API'); } throw e; } } doScrobble = async (playObj: PlayObject) => { const { meta: { source, newFromSource = false, } = {} } = playObj; try { const result = await this.api.submitListen(playObj, { log: isDebugMode()}); if (newFromSource) { this.logger.info(`Scrobbled (New) => (${source}) ${buildTrackString(playObj)}`); } else { this.logger.info(`Scrobbled (Backlog) => (${source}) ${buildTrackString(playObj)}`); } return result; } catch (e) { await this.notify({title: `Client - ${capitalize(this.type)} - ${this.name} - Scrobble Error`, message: `Failed to scrobble => ${buildTrackString(playObj)} | Error: ${e.message}`, priority: 'error'}); throw e; } } doPlayingNow = async (data: SourcePlayerObj) => { try { await this.api.submitListen(data.play, { listenType: 'playing_now'}); } catch (e) { throw e; } } }