import type EventEmitter from "events"; import { COMPONENT_AUTH_TYPE, type ComponentAuthType, PARSED_FROM, type PlayObject, SOURCE_SOT } from "../../core/Atomic.ts"; import type {FormatPlayObjectOptions, InternalConfig} from "../common/infrastructure/Atomic.ts"; import type {RecentlyPlayedOptions} from "./AbstractSource.ts"; import MemorySource from "./MemorySource.ts"; import { TealApiUnauthenticatedClient } from "../common/vendor/teal/TealApiClient.ts"; import { recordToPlay } from "../common/vendor/teal/TealApiClient.ts"; import type {TealSourceConfig} from "../common/infrastructure/config/source/tealfm.ts"; import { ATProtoAppApiClient } from "../common/vendor/atproto/ATProtoAppApiClient.ts"; import { parseArrayFromMaybeString } from "../utils/StringUtils.ts"; export default class TealfmSource extends MemorySource { client: TealApiUnauthenticatedClient; override authType: ComponentAuthType = COMPONENT_AUTH_TYPE.unattended; requiresAuth = true; requiresAuthInteraction = false; serviceAllow: string[] serviceDeny: string[] declare config: TealSourceConfig; constructor(name: any, config: TealSourceConfig, internal: InternalConfig, emitter: EventEmitter) { const { data: { interval = 15, maxInterval = 60, ...restData } = {} } = config; super('tealfm', name, {...config, data: {interval, maxInterval, ...restData}}, internal, emitter); this.canPoll = true; this.canBacklog = true; this.client = new TealApiUnauthenticatedClient(name, config.data, {...internal, logger: internal.logger}); this.playerSourceOfTruth = SOURCE_SOT.HISTORY; this.supportsUpstreamRecentlyPlayed = true this.SCROBBLE_BACKLOG_COUNT = 20; } static formatPlayObj(obj: any, options: FormatPlayObjectOptions = {}){ return recordToPlay(obj); } protected async doBuildInitData(): Promise { const { data: { identifier, serviceAllow = [], serviceDeny = [] } = {} } = this.config; if (identifier === undefined) { throw new Error('Must provide an identifier'); } await this.client.client.initClient(); this.serviceAllow = parseArrayFromMaybeString(serviceAllow, {lower: true}); if(this.serviceAllow.length > 0) { this.logger.debug(`Discover Plays from these services only: ${this.serviceAllow.join(',')}`); } else { this.serviceDeny = parseArrayFromMaybeString(serviceDeny, {lower: true}); if(this.serviceDeny.length > 0) { this.logger.debug(`Do not discover Plays from these services: ${this.serviceDeny.join(',')}`); } } return true; } protected async doCheckConnection(): Promise { if (this.client instanceof ATProtoAppApiClient) { try { return await this.client.checkPds(this.config.data); } catch (e) { throw e; } } else { return true; } } getRecentlyPlayed = async(options: RecentlyPlayedOptions = {}) => { const {limit = 20} = options; let plays: PlayObject[]; this.setStatus('Checking for new Plays'); try { const {data} = await this.client.getPagelessTimeRangeListens({limit}) plays = data; } catch (e) { throw new Error('Error occurred while trying to fetch records', {cause: e}); } await this.processRecentPlays([]); if(this.serviceAllow.length > 0) { plays = plays.filter(x => (x.meta.musicService !== undefined && this.serviceAllow.some(y => x.meta.musicService.toLocaleLowerCase().includes(y))) || (x.meta.musicService === undefined && this.serviceAllow.includes('unknown')) ); } else if(this.serviceDeny.length > 0) { plays = plays.filter(x => (x.meta.musicService !== undefined && !this.serviceDeny.some(y => x.meta.musicService.toLocaleLowerCase().includes(y))) || (x.meta.musicService === undefined && !this.serviceAllow.includes('unknown')) ); } return plays.map((x) => ({...x, meta: {...x.meta, parsedFrom: PARSED_FROM.history}})); } getUpstreamRecentlyPlayed = async (options: RecentlyPlayedOptions = {}): Promise => { try { return await this.getRecentlyPlayed(options); } catch (e) { throw e; } } protected getBackloggedPlays = async (options: RecentlyPlayedOptions = {}) => await this.getRecentlyPlayed({formatted: true, ...options}) }