diff --git a/config/plex.json.example b/config/plex.json.example index 679c86cb..278d3326 100644 --- a/config/plex.json.example +++ b/config/plex.json.example @@ -16,7 +16,7 @@ "options": { "logPayload": true, "logFilterFailure": "debug", - "ignoreInvalidCert": "false" + "ignoreInvalidCert": false } } ] diff --git a/config/plex.webhook.json.example b/config/plex.webhook.json.example deleted file mode 100644 index 35e92071..00000000 --- a/config/plex.webhook.json.example +++ /dev/null @@ -1,17 +0,0 @@ -[ - // DEPRECATED, use API Source instead - // rename files to plex.json to use - { - "name": "MyPlex", - "enable": true, - "clients": [], - "data": { - "user": ["username@gmail.com","anotherUser@gmail.com"], - "libraries": ["music","my podcasts"], - "servers": ["myServer","anotherServer"], - "options": { - "logFilterFailure": "warn" - } - } - } -] diff --git a/config/tautulli.json.example b/config/tautulli.json.example deleted file mode 100644 index ebb8b233..00000000 --- a/config/tautulli.json.example +++ /dev/null @@ -1,15 +0,0 @@ -[ - { - "name": "MyTautuilli", - "enable": true, - "clients": [], - "data": { - "user": ["username@gmail.com","anotherUser@gmail.com"], - "libraries": ["music","my podcasts"], - "servers": ["myServer","anotherServer"], - "options": { - "logFilterFailure": "warn" - } - } - } -] diff --git a/src/backend/common/infrastructure/Atomic.ts b/src/backend/common/infrastructure/Atomic.ts index e9fbe0fc..281d8cae 100644 --- a/src/backend/common/infrastructure/Atomic.ts +++ b/src/backend/common/infrastructure/Atomic.ts @@ -10,7 +10,6 @@ import { MusicBrainzApi } from 'musicbrainz-api'; export type SourceType = 'spotify' | 'plex' - | 'tautulli' | 'subsonic' | 'jellyfin' | 'lastfm' @@ -39,7 +38,6 @@ export type SourceType = export const sourceTypes: SourceType[] = [ 'spotify', 'plex', - 'tautulli', 'subsonic', 'jellyfin', 'lastfm', diff --git a/src/backend/common/infrastructure/config/source/plex.ts b/src/backend/common/infrastructure/config/source/plex.ts index dda2d20e..57b91106 100644 --- a/src/backend/common/infrastructure/config/source/plex.ts +++ b/src/backend/common/infrastructure/config/source/plex.ts @@ -1,41 +1,6 @@ import { PollingOptions } from "../common.js"; import { CommonSourceConfig, CommonSourceData, CommonSourceOptions } from "./index.js"; -export interface PlexSourceData extends CommonSourceData { - /** - * optional list of users to scrobble tracks from - * - * If none are provided tracks from all users will be scrobbled - * - * @examples [["MyUser1", "MyUser2"]] - * */ - user?: string | string[] - /** - * optional list of libraries to scrobble tracks from - * - * If none are provided tracks from all libraries will be scrobbled - * - * @examples [["Audio","Music"]] - * */ - libraries?: string | string[] - /** - * optional list of servers to scrobble tracks from - * - * If none are provided tracks from all servers will be scrobbled - * - * @examples [["MyServerName"]] - * */ - servers?: string | string[] -} - -export interface PlexSourceConfig extends CommonSourceConfig { - data: PlexSourceData -} - -export interface PlexSourceAIOConfig extends PlexSourceConfig { - type: 'plex' -} - export interface PlexApiData extends CommonSourceData, PollingOptions { token?: string /** @@ -88,11 +53,11 @@ export interface PlexApiOptions extends CommonSourceOptions { export interface PlexApiSourceConfig extends CommonSourceConfig { data: PlexApiData - options: PlexApiOptions + options?: PlexApiOptions } export interface PlexApiSourceAIOConfig extends PlexApiSourceConfig { type: 'plex' } -export type PlexCompatConfig = PlexApiSourceConfig | PlexSourceConfig; \ No newline at end of file +export type PlexCompatConfig = PlexApiSourceConfig; \ No newline at end of file diff --git a/src/backend/common/infrastructure/config/source/sources.ts b/src/backend/common/infrastructure/config/source/sources.ts index 61bc6736..a8855207 100644 --- a/src/backend/common/infrastructure/config/source/sources.ts +++ b/src/backend/common/infrastructure/config/source/sources.ts @@ -13,10 +13,9 @@ import { MPDSourceAIOConfig, MPDSourceConfig } from "./mpd.js"; import { MPRISSourceAIOConfig, MPRISSourceConfig } from "./mpris.js"; import { MusikcubeSourceAIOConfig, MusikcubeSourceConfig } from "./musikcube.js"; import { MusicCastSourceConfig, MusicCastSourceAIOConfig } from "./musiccast.js"; -import { PlexSourceAIOConfig, PlexSourceConfig, PlexApiSourceConfig, PlexApiSourceAIOConfig } from "./plex.js"; +import { PlexApiSourceConfig, PlexApiSourceAIOConfig } from "./plex.js"; import { SpotifySourceAIOConfig, SpotifySourceConfig } from "./spotify.js"; import { SubsonicSourceAIOConfig, SubSonicSourceConfig } from "./subsonic.js"; -import { TautulliSourceAIOConfig, TautulliSourceConfig } from "./tautulli.js"; import { VLCSourceAIOConfig, VLCSourceConfig } from "./vlc.js"; import { WebScrobblerSourceAIOConfig, WebScrobblerSourceConfig } from "./webscrobbler.js"; import { YTMusicSourceAIOConfig, YTMusicSourceConfig } from "./ytmusic.js"; @@ -29,9 +28,7 @@ import { RockskySourceAIOConfig, RockskySourceConfig } from "./rocksky.js"; export type SourceConfig = SpotifySourceConfig - | PlexSourceConfig | PlexApiSourceConfig - | TautulliSourceConfig | DeezerSourceConfig | DeezerInternalSourceConfig | ListenbrainzEndpointSourceConfig @@ -60,9 +57,7 @@ export type SourceConfig = export type SourceAIOConfig = SpotifySourceAIOConfig - | PlexSourceAIOConfig | PlexApiSourceAIOConfig - | TautulliSourceAIOConfig | DeezerSourceAIOConfig | DeezerInternalAIOConfig | ListenbrainzEndpointSourceAIOConfig diff --git a/src/backend/common/infrastructure/config/source/tautulli.ts b/src/backend/common/infrastructure/config/source/tautulli.ts deleted file mode 100644 index f5afb538..00000000 --- a/src/backend/common/infrastructure/config/source/tautulli.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { PlexSourceConfig } from "./plex.js"; - -export interface TautulliSourceConfig extends PlexSourceConfig { -} - -export interface TautulliSourceAIOConfig extends TautulliSourceConfig { - type: 'tautulli' -} diff --git a/src/backend/server/api.ts b/src/backend/server/api.ts index e8defad7..6133ea42 100644 --- a/src/backend/server/api.ts +++ b/src/backend/server/api.ts @@ -27,8 +27,6 @@ import { setupDeezerRoutes } from "./deezerRoutes.js"; import {setupLZEndpointRoutes} from "./endpointListenbrainzRoutes.js"; import {setupLastfmEndpointRoutes} from "./endpointLastfmRoutes.js"; import { makeClientCheckMiddle, makeSourceCheckMiddle } from "./middleware.js"; -import { setupPlexRoutes } from "./plexRoutes.js"; -import { setupTautulliRoutes } from "./tautulliRoutes.js"; import { setupWebscrobblerRoutes } from "./webscrobblerRoutes.js"; import ScrobbleSources from "../sources/ScrobbleSources.js"; import ScrobbleClients from "../scrobblers/ScrobbleClients.js"; @@ -147,8 +145,6 @@ export const setupApi = (app: ExpressWithAsync, logger: Logger, appLoggerStream: }); }); - setupTautulliRoutes(app, logger, scrobbleSources); - setupPlexRoutes(app, logger, scrobbleSources); setupDeezerRoutes(app, logger, scrobbleSources); setupWebscrobblerRoutes(app, logger, scrobbleSources); setupLZEndpointRoutes(app, logger, scrobbleSources); diff --git a/src/backend/server/plexRoutes.ts b/src/backend/server/plexRoutes.ts deleted file mode 100644 index 196a6ee6..00000000 --- a/src/backend/server/plexRoutes.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { ExpressWithAsync } from "@awaitjs/express"; -import { childLogger, Logger } from "@foxxmd/logging"; -import { ExpressHandler } from "../common/infrastructure/Atomic.js"; -import { PlexNotifier } from "../sources/ingressNotifiers/PlexNotifier.js"; -import PlexSource, { plexRequestMiddle } from "../sources/PlexSource.js"; -import ScrobbleSources from "../sources/ScrobbleSources.js"; - -export const setupPlexRoutes = (app: ExpressWithAsync, logger: Logger, scrobbleSources: ScrobbleSources) => { - - const plexMiddle = plexRequestMiddle(logger); - const plexLog = childLogger(logger, 'Plex Request'); - const plexIngress = new PlexNotifier(logger); - const plexIngressMiddle: ExpressHandler = async (req, res, next) => { - // track request before parsing body to ensure we at least log that something is happening - // (in the event body parsing does not work or request is not POST/PATCH) - plexIngress.trackIngress(req, true); - next(); - }; - const plexIngressRoute: ExpressHandler = async (req, res) => { - plexIngress.trackIngress(req, false); - - const {payload} = req as any; - if (payload !== undefined) { - const playObj = PlexSource.formatPlayObj(payload, {newFromSource: true}); - - const pSources = scrobbleSources.getByType('plex').filter(x => x instanceof PlexSource) as PlexSource[]; - if (pSources.length === 0) { - plexLog.warn('Received valid Plex webhook payload but no Plex Webhook sources are configured'); - } - - for (const source of pSources) { - await source.handle(playObj); - } - } - - res.send('OK'); - }; - app.postAsync('/plex', async (req, res) => { - res.redirect(307, '/api/plex/ingress'); - }); - app.postAsync('/api/plex/ingress', plexIngressMiddle, plexMiddle, plexIngressRoute); -} - diff --git a/src/backend/server/tautulliRoutes.ts b/src/backend/server/tautulliRoutes.ts deleted file mode 100644 index 66f4dd69..00000000 --- a/src/backend/server/tautulliRoutes.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { ExpressWithAsync } from "@awaitjs/express"; -import { Logger } from "@foxxmd/logging"; -import { ExpressHandler } from "../common/infrastructure/Atomic.js"; -import { TautulliNotifier } from "../sources/ingressNotifiers/TautulliNotifier.js"; -import ScrobbleSources from "../sources/ScrobbleSources.js"; -import TautulliSource from "../sources/TautulliSource.js"; -import { isDebugMode, parseBool } from "../utils.js"; - -export const setupTautulliRoutes = (app: ExpressWithAsync, logger: Logger, scrobbleSources: ScrobbleSources) => { - - const tauIngress = new TautulliNotifier(logger); - const tautulliIngressRoute: ExpressHandler = async function(this: any, req, res) { - tauIngress.trackIngress(req, false); - - const payload = TautulliSource.formatPlayObj(req, {newFromSource: true}); - // try to get config name from payload - if (req.body.scrobblerConfig !== undefined) { - const source = scrobbleSources.getByName(req.body.scrobblerConfig); - if (source !== undefined) { - if (source.type !== 'tautulli') { - logger.warn(`Tautulli event specified a config name but the configured source was not a Tautulli type: ${req.body.scrobblerConfig}`); - return res.send('OK'); - } else { - if((source.config.options?.logPayload ?? isDebugMode()) === true) { - source.logger.debug(`Received Payload`, req.body); - } - // @ts-expect-error TS(2339): Property 'handle' does not exist on type 'never'. - await source.handle(payload); - return res.send('OK'); - } - } else { - logger.warn(`Tautulli event specified a config name but no configured source found: ${req.body.scrobblerConfig}`); - return res.send('OK'); - } - } - // if none specified we'll iterate through all tautulli sources and hopefully the user has configured them with filters - const tSources = scrobbleSources.getByType('tautulli'); - for (const source of tSources) { - // @ts-expect-error TS(2339): Property 'handle' does not exist on type 'never'. - await source.handle(payload); - } - - res.send('OK'); - }; - - app.postAsync('/tautulli', async (req, res) => { - res.redirect(307, '/api/tautulli/ingress'); - }); - app.postAsync('/api/tautulli/ingress', tautulliIngressRoute); -} - diff --git a/src/backend/sources/PlexSource.ts b/src/backend/sources/PlexSource.ts deleted file mode 100644 index 1d754d1f..00000000 --- a/src/backend/sources/PlexSource.ts +++ /dev/null @@ -1,329 +0,0 @@ -import { childLogger, Logger } from "@foxxmd/logging"; -import concatStream from 'concat-stream'; -import dayjs from "dayjs"; -import EventEmitter from "events"; -import formidable, { Files, File } from 'formidable'; -import { file } from "jscodeshift"; -import { PlayObject } from "../../core/Atomic.js"; -import { combinePartsToString, truncateStringToLength } from "../../core/StringUtils.js"; -import { FormatPlayObjectOptions, InternalConfig, SourceType } from "../common/infrastructure/Atomic.js"; -import { PlexSourceConfig } from "../common/infrastructure/config/source/plex.js"; -import { getFileIdentifier, getValidMultipartJsonFile } from "../utils/RequestUtils.js"; -import AbstractSource from "./AbstractSource.js"; - -const shortDeviceId = truncateStringToLength(10, ''); - -export default class PlexSource extends AbstractSource { - users: string[]; - libraries: string[]; - servers: string[]; - - multiPlatform: boolean = true; - - declare config: PlexSourceConfig; - - constructor(name: any, config: PlexSourceConfig, internal: InternalConfig, type: SourceType = 'plex',emitter: EventEmitter) { - super(type, name, config, internal, emitter); - const { - data: { - user = [], - libraries = [], - servers = [], - } = {}, - options: { - logFilterFailure = 'warn' - } = {} - } = this.config - - if(logFilterFailure !== false && !['debug', 'warn'].includes(logFilterFailure)) { - this.logger.warn(`logFilterFailure value of '${logFilterFailure.toString()}' is NOT VALID. Logging will not occur if filters fail. You should fix this.`); - } - - if (!Array.isArray(user)) { - if(user.trim() === '') { - this.users = []; - } else { - this.users = user.split(',').map(x => x.trim()); - } - } else { - this.users = user; - } - this.users = this.users.map((x: any) => x.toLocaleLowerCase()) - - if (!Array.isArray(libraries)) { - this.libraries = [libraries]; - } else { - this.libraries = libraries; - } - this.libraries = this.libraries.map((x: any) => x.toLocaleLowerCase()) - - if (!Array.isArray(servers)) { - this.servers = [servers]; - } else { - this.servers = servers; - } - this.servers = this.servers.map((x: any) => x.toLocaleLowerCase()) - - if (this.users.length === 0 && this.libraries.length === 0 && this.servers.length === 0) { - this.logger.warn('Initializing, but with no filters! All tracks from all users on all servers and libraries will be scrobbled.'); - } else { - this.logger.info(`Initializing with the following filters => Users: ${this.users.length === 0 ? 'N/A' : this.users.join(', ')} | Libraries: ${this.libraries.length === 0 ? 'N/A' : this.libraries.join(', ')} | Servers: ${this.servers.length === 0 ? 'N/A' : this.servers.join(', ')}`); - } - - this.logger.warn('Plex WEBHOOK source is DEPRECATED and will be removed in an upcoming release! Please switch to Plex API Source as soon as possible.'); - } - - static formatPlayObj(obj: any, options: FormatPlayObjectOptions = {}): PlayObject { - const {newFromSource = false} = options; - const { - event, - Account: { - // @ts-expect-error TS(2525): Initializer provides no value for this binding ele... Remove this comment to see the full error message - title: user, - } = {}, - Metadata: { - // @ts-expect-error TS(2525): Initializer provides no value for this binding ele... Remove this comment to see the full error message - type, - // @ts-expect-error TS(2525): Initializer provides no value for this binding ele... Remove this comment to see the full error message - title: track, - // @ts-expect-error TS(2525): Initializer provides no value for this binding ele... Remove this comment to see the full error message - parentTitle: album, - // @ts-expect-error TS(2525): Initializer provides no value for this binding ele... Remove this comment to see the full error message - grandparentTitle: artist, // OR album artist - // @ts-expect-error TS(2525): Initializer provides no value for this binding ele... Remove this comment to see the full error message - librarySectionTitle: library, - // plex returns the track artist as originalTitle (when there is an album artist) - // otherwise this is undefined - originalTitle: trackArtist = undefined - } = {}, - Server: { - // @ts-expect-error TS(2525): Initializer provides no value for this binding ele... Remove this comment to see the full error message - title: server - } = {}, - Player: { - title, - uuid, - } - } = obj; - - const artists: string[] = []; - const albumArtists: string[] = []; - if(trackArtist !== undefined) { - artists.push(trackArtist); - albumArtists.push(artist); - } else { - artists.push(artist); - } - return { - data: { - artists, - albumArtists, - album, - track, - playDate: dayjs(), - }, - meta: { - event, - mediaType: type, - user, - library, - server, - source: 'Plex', - newFromSource, - deviceId: combinePartsToString([shortDeviceId(uuid), title]) - } - } - } - - protected logFilterFailure = (str: string, meta?: any) => { - const { - options: { - logFilterFailure = 'warn' - } = {} - } = this.config; - - if(logFilterFailure === false || !['warn','debug'].includes(logFilterFailure)) { - return false; - } - - this.logger[logFilterFailure](str, meta); - } - - isValidEvent = (playObj: PlayObject) => { - const { - meta: { - mediaType, event, user, library, server - }, - data: { - artists, - track, - } = {} - } = playObj; - - const hint = this.type === 'tautulli' ? ' (Check notification agent json data configuration)' : ''; - - if (event !== undefined && event !== 'media.scrobble') { - this.logger.debug(`Will not scrobble event because it is not media.scrobble (${event})`, { - artists, - track - }) - return false; - } - - if (mediaType !== 'track') { - this.logger.debug(`Will not scrobble event because media type was not a track (${mediaType})`, { - artists, - track - }); - return false; - } - - if (this.users.length !== 0) { - if (user === undefined) { - this.logFilterFailure(`Config defined users but payload contained no user info${hint}`); - } else if (!this.users.includes(user.toLocaleLowerCase())) { - this.logFilterFailure(`Will not scrobble event because author was not an allowed user. Expected: ${this.users.map(x => `'${x}'`).join(' or ')} | Found: '${user.toLocaleLowerCase()}'`, { - artists, - track - }) - return false; - } - } - - if (this.libraries.length !== 0) { - if (library === undefined) { - this.logFilterFailure(`Config defined libraries but payload contained no library info${hint}`); - } else if (!this.libraries.includes(library.toLocaleLowerCase())) { - this.logFilterFailure(`Will not scrobble event because library was not an allowed library. Expected: ${this.libraries.map(x => `'${x}'`).join(' or ')} | Found: '${library.toLocaleLowerCase()}'`, { - artists, - track - }) - return false; - } - } - - if (this.servers.length !== 0) { - if (server === undefined) { - this.logFilterFailure(`Config defined server but payload contained no server info${hint}`); - } else if (!this.servers.includes(server.toLocaleLowerCase())) { - this.logFilterFailure(`Will not scrobble event because server was not an allowed server. Expected: ${this.servers.map(x => `'${x}'`).join(' or ')} | Found: '${server.toLocaleLowerCase()}'`, { - artists, - track - }) - return false; - } - } - - return true; - } - - handle = async (playObj: any) => { - if (!this.isValidEvent(playObj)) { - return; - } - - try { - const discovered = await this.discover([playObj]); - await this.scrobble(discovered); - } catch (e) { - this.logger.error('Encountered error while scrobbling') - this.logger.error(e) - } - } -} - -export const plexRequestMiddle = (logger: Logger) => { - - const plexLog = childLogger(logger, 'Plex Request'); - - return async (req: any, res: any, next: any) => { - - const form = formidable({ - allowEmptyFiles: true, - multiples: true, - fileWriteStreamHandler: (file: any) => concatStream((data: any) => { - file.buffer = data; - }) - }); - form.on('progress', (received: any, expected: any) => { - plexLog.debug(`Received ${received} bytes of expected ${expected}`); - }); - form.on('error', (err: any) => { - plexLog.error(err); - }) - form.on('aborted', () => { - plexLog.warn('Request aborted') - }) - form.on('end', () => { - plexLog.debug('Received end of form data from Plex'); - }); - form.on('fileBegin', (formname: any, file: any) => { - plexLog.debug(`File Begin: ${formname}`); - }); - form.on('file', (formname: any) => { - plexLog.debug(`File Received: ${formname}`); - }); - - - plexLog.debug('Receiving request from Plex...'); - - return new Promise((resolve, reject) => { - form.parse(req, (err: any, fields: any, files: Files | File) => { - if (err) { - plexLog.error('Error occurred while parsing formdata'); - plexLog.error(err); - next(err); - reject(err); - return; - } - - let validFile, - fileResults; - try { - const [vf, fr] = getValidMultipartJsonFile(files); - validFile = vf; - fileResults = fr; - } catch (e) { - const parseError = new Error('Could not parse plex webhook formdata to valid files', {cause: e}); - plexLog.error(parseError) - next(parseError); - reject(parseError); - return; - } - - if (validFile === undefined) { - const validError = new Error(`No files parsed from formdata had a mimetype that included 'json' => ${fileResults.join('\n')}`); - plexLog.error(validError); - next(validError); - reject(validError); - return; - } else { - plexLog.debug(`formdata file results => ${fileResults.join('\n')}`); - } - - if(!('buffer' in validFile)) { - const buffErr = new Error(`${getFileIdentifier(validFile as unknown as File)} file should have had buffer but it did not!`); - plexLog.error(buffErr); - next(buffErr); - reject(buffErr); - return; - } - - const payloadRaw = validFile.buffer.toString(); - let payload = null; - try { - payload = JSON.parse(payloadRaw); - req.payload = payload; - next(); - resolve(undefined); - } catch (e) { - const jsonParseError = new Error(`Error occurred while trying to parse Plex formdata file ${getFileIdentifier(validFile as unknown as File)} to json. Raw text:\n${payloadRaw}`, {cause: e}); - plexLog.error(jsonParseError); - next(jsonParseError); - reject(jsonParseError); - return; - } - }); - }); - }; -} diff --git a/src/backend/sources/ScrobbleSources.ts b/src/backend/sources/ScrobbleSources.ts index 80fc5c5a..5576ff90 100644 --- a/src/backend/sources/ScrobbleSources.ts +++ b/src/backend/sources/ScrobbleSources.ts @@ -22,12 +22,11 @@ import { IcecastData, IcecastSourceConfig, IcecastSourceOptions } from "../commo import { MPDData, MPDSourceConfig } from "../common/infrastructure/config/source/mpd.js"; import { MPRISData, MPRISSourceConfig } from "../common/infrastructure/config/source/mpris.js"; import { MusikcubeData, MusikcubeSourceConfig } from "../common/infrastructure/config/source/musikcube.js"; -import { PlexApiSourceConfig, PlexCompatConfig, PlexSourceConfig } from "../common/infrastructure/config/source/plex.js"; +import { PlexApiSourceConfig } from "../common/infrastructure/config/source/plex.js"; import { MalojaSourceConfig } from "../common/infrastructure/config/source/maloja.js"; import { SourceAIOConfig, SourceConfig } from "../common/infrastructure/config/source/sources.js"; import { SpotifySourceConfig, SpotifySourceData } from "../common/infrastructure/config/source/spotify.js"; import { SubsonicData, SubSonicSourceConfig } from "../common/infrastructure/config/source/subsonic.js"; -import { TautulliSourceConfig } from "../common/infrastructure/config/source/tautulli.js"; import { VLCData, VLCSourceConfig } from "../common/infrastructure/config/source/vlc.js"; import { WebScrobblerSourceConfig } from "../common/infrastructure/config/source/webscrobbler.js"; import { YTMusicData, YTMusicSourceConfig } from "../common/infrastructure/config/source/ytmusic.js"; @@ -51,10 +50,8 @@ import { MPDSource } from "./MPDSource.js"; import { MPRISSource } from "./MPRISSource.js"; import { MusikcubeSource } from "./MusikcubeSource.js"; import { MusicCastSource } from "./MusicCastSource.js"; -import PlexSource from "./PlexSource.js"; import SpotifySource from "./SpotifySource.js"; import { SubsonicSource } from "./SubsonicSource.js"; -import TautulliSource from "./TautulliSource.js"; import { VLCSource } from "./VLCSource.js"; import { WebScrobblerSource } from "./WebScrobblerSource.js"; import YTMusicSource from "./YTMusicSource.js"; @@ -144,9 +141,6 @@ export default class ScrobbleSources { case 'plex': this.schemaDefinitions[type] = getTypeSchemaFromConfigGenerator("PlexCompatConfig"); break; - case 'tautulli': - this.schemaDefinitions[type] = getTypeSchemaFromConfigGenerator("TautulliSourceConfig"); - break; case 'deezer': this.schemaDefinitions[type] = getTypeSchemaFromConfigGenerator("DeezerCompatConfig"); break; @@ -344,25 +338,6 @@ export default class ScrobbleSources { }) } break; - case 'tautulli': - const t = { - // support this for now - user: process.env.TAUTULLI_USER - }; - if(t.user === undefined) { - t.user = process.env.PLEX_USER; - } - if (!Object.values(t).every(x => x === undefined)) { - configs.push({ - type: 'tautulli', - name: 'unnamed', - source: 'ENV', - mode: 'single', - configureAs: defaultConfigureAs, - data: t, - }) - } - break; case 'plex': const p = { user: process.env.PLEX_USER, @@ -838,15 +813,7 @@ export default class ScrobbleSources { newSource = new SpotifySource(name, compositeConfig as SpotifySourceConfig, this.internalConfig, this.emitter); break; case 'plex': - const plexConfig = compositeConfig as PlexCompatConfig; - if(plexConfig.data.token !== undefined) { - newSource = await new PlexApiSource(name, compositeConfig as PlexApiSourceConfig, this.internalConfig, this.emitter); - } else { - newSource = await new PlexSource(name, compositeConfig as PlexSourceConfig, this.internalConfig, 'plex', this.emitter); - } - break; - case 'tautulli': - newSource = await new TautulliSource(name, compositeConfig as TautulliSourceConfig, this.internalConfig, this.emitter); + newSource = await new PlexApiSource(name, compositeConfig as PlexApiSourceConfig, this.internalConfig, this.emitter); break; case 'subsonic': newSource = new SubsonicSource(name, compositeConfig as SubSonicSourceConfig, this.internalConfig, this.emitter); diff --git a/src/backend/sources/TautulliSource.ts b/src/backend/sources/TautulliSource.ts deleted file mode 100644 index e38916ff..00000000 --- a/src/backend/sources/TautulliSource.ts +++ /dev/null @@ -1,74 +0,0 @@ -import dayjs from "dayjs"; -import EventEmitter from "events"; -import { Request } from "express"; -import { PlayObject } from "../../core/Atomic.js"; -import { combinePartsToString, truncateStringToLength } from "../../core/StringUtils.js"; -import { FormatPlayObjectOptions, InternalConfig } from "../common/infrastructure/Atomic.js"; -import { TautulliSourceConfig } from "../common/infrastructure/config/source/tautulli.js"; -import PlexSource from "./PlexSource.js"; - -const shortDeviceId = truncateStringToLength(10, ''); - -export default class TautulliSource extends PlexSource { - - declare config: TautulliSourceConfig; - constructor(name: any, config: TautulliSourceConfig, internal: InternalConfig, emitter: EventEmitter) { - super(name, config, internal, 'tautulli', emitter); - this.logger.warn('Tatutulli source is DEPRECATED and will be removed in an upcoming release! Please switch to Plex API Source as soon as possible.'); - } - static formatPlayObj(obj: Request, options: FormatPlayObjectOptions = {}): PlayObject { - const {newFromSource = false} = options; - const { - body :{ - artist_name, - track_name, - track_artist, - album_name, - media_type, - title, - library_name, - server, - version, - duration, - username, - library, - machine_id = '', - session_key, - action, - platform, - device, - player, - } = {} - } = obj; - const artists: string[] = []; - const albumArtists: string[] = []; - if (track_artist !== undefined && track_artist !== artist_name) { - artists.push(track_artist); - albumArtists.push(artist_name); - } else { - artists.push(artist_name); - } - return { - data: { - artists, - album: album_name, - albumArtists, - track: track_name, - duration, - playDate: dayjs(), - }, - meta: { - title, - event: action === 'watched' ? 'media.scrobble' : action, - library: library_name ?? library, - server, - sourceVersion: version, - mediaType: media_type, - user: username, - source: 'Tautulli', - newFromSource, - deviceId: combinePartsToString([shortDeviceId(machine_id), session_key, player]) - } - } - } -} diff --git a/src/backend/sources/ingressNotifiers/PlexNotifier.ts b/src/backend/sources/ingressNotifiers/PlexNotifier.ts deleted file mode 100644 index c12e1814..00000000 --- a/src/backend/sources/ingressNotifiers/PlexNotifier.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { Logger } from "@foxxmd/logging"; -import { Request } from "express"; -import PlexSource from "../PlexSource.js"; -import { IngressNotifier } from "./IngressNotifier.js"; - -export class PlexNotifier extends IngressNotifier { - - constructor(logger: Logger) { - super('Plex', logger); - } - - seenServers: string[] = []; - notifyBySource(req: Request, isRaw: boolean): [boolean, (string | undefined)] { - if(!isRaw) { - const { payload } = req as any; - if(payload === undefined) { - - return [false, 'Received a request without any data']; - - } - - const playObj = PlexSource.formatPlayObj(payload, {newFromSource: true}); - - if(playObj.meta.server === undefined) { - return [false, `Payload from Plex did not contain server info! Check Plex logs for any errors.`]; - } - - if(!this.seenServers.includes(playObj.meta.server)) { - this.seenServers.push(playObj.meta.server); - return [true, `Received valid data from server ${playObj.meta.server} for the first time.`]; - } - } - return [true, undefined]; - } - - notifyByRequest(req: Request, isRaw: boolean): string | undefined { - if(req.method !== 'POST') { - return `Expected POST request (webhook payload) but received ${req.method}`; - } - return; - } - -} diff --git a/src/backend/sources/ingressNotifiers/TautulliNotifier.ts b/src/backend/sources/ingressNotifiers/TautulliNotifier.ts deleted file mode 100644 index ccc5dac9..00000000 --- a/src/backend/sources/ingressNotifiers/TautulliNotifier.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { Logger } from "@foxxmd/logging"; -import { Request } from "express"; -import TautulliSource from "../TautulliSource.js"; -import { IngressNotifier } from "./IngressNotifier.js"; - -export class TautulliNotifier extends IngressNotifier { - - constructor(logger: Logger) { - super('Tautulli', logger); - } - - seenServers: string[] = []; - notifyBySource(req: Request, isRaw: boolean): [boolean, (string | undefined)] { - if(!isRaw) { - const playObj = TautulliSource.formatPlayObj(req, {newFromSource: true}); - - if(!this.seenServers.includes(playObj.meta.server)) { - this.seenServers.push(playObj.meta.server); - const msg = [`Received data from server ${playObj.meta.server} for the first time.`]; - if(req.body === undefined) { - msg.push('WARNING: Payload was empty.'); - } - if(playObj.meta.library === undefined) { - msg.push('WARNING: library was not defined in payload. If you want to filter plays by library this must be present in webhook payload.'); - } - return [true, msg.join(' ')]; - } - } - return [true, undefined]; - } - - notifyByRequest(req: Request, isRaw: boolean): string | undefined { - if(req.method !== 'POST') { - return `Expected POST request (webhook payload) but received ${req.method}`; - } - return; - } - -}