refactor: Re-organize server/client structure so everything exists on same port
Used https://spin.atomicobject.com/2020/08/17/cra-express-share-code as a base for refactoring
@@ -17,3 +17,4 @@ flatpak/generated-sources.json
|
||||
flatpak/.flatpak-builder
|
||||
docsite/build
|
||||
docsite/node_modules
|
||||
build
|
||||
|
||||
@@ -127,3 +127,5 @@ src/**/**.js.map
|
||||
*.bak
|
||||
.flatpak-builder
|
||||
flatpak/generated-sources.json
|
||||
|
||||
build
|
||||
|
||||
@@ -4,11 +4,6 @@
|
||||
"description": "scrobble plays from multiple sources to multiple clients",
|
||||
"main": "src/index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"build": "tsc",
|
||||
"start": "concurrently -c \"auto\" --names \"server,client\" \"npm:server\" \"npm:client\"",
|
||||
"server": "node src/index.js",
|
||||
"client": "node start-client.js",
|
||||
"schema": "npm run -s schema-aio & npm run -s schema-source & npm run -s schema-client & npm run -s schema-aiosource & npm run -s schema-aioclient",
|
||||
"schema-client": "typescript-json-schema tsconfig.json ClientConfig --out src/common/schema/client.json --required --titles --tsNodeRegister --refs --validationKeywords deprecationMessage",
|
||||
"schema-source": "typescript-json-schema tsconfig.json SourceConfig --out src/common/schema/source.json --required --titles --tsNodeRegister --refs --validationKeywords deprecationMessage",
|
||||
@@ -17,7 +12,17 @@
|
||||
"schema-aioclient": "typescript-json-schema tsconfig.json AIOClientConfig --out src/common/schema/aio-client.json --titles --required --tsNodeRegister --refs --validationKeywords deprecationMessage",
|
||||
"typedoc": "typedoc",
|
||||
"postinstall": "patch-package",
|
||||
"circular": "madge --circular --extensions ts src/index.ts"
|
||||
"circular": "madge --circular --extensions ts src/index.ts",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject",
|
||||
"dev": "concurrently -p name -c \"yellow,magenta,blue\" -n \"webpack-server,nodemon-server,CRA\" \"yarn dev:server:webpack\" \"yarn dev:server:nodemon\" \"yarn dev:client\"",
|
||||
"dev:client": "wait-on -l tcp:9079 && react-scripts start",
|
||||
"dev:server": "concurrently -p name -c \"yellow,magenta\" -n \"webpack-server,nodemon-server\" \"yarn dev:server:webpack\" \"yarn dev:server:nodemon\"",
|
||||
"dev:server:webpack": "webpack --config webpack.config.server.js --watch",
|
||||
"dev:server:nodemon": "rm -f build/server.js && wait-on -l build/server.js && nodemon build/server.js",
|
||||
"build": "yarn build:client && yarn build:server",
|
||||
"build:client": "react-scripts build",
|
||||
"build:server": "webpack --config webpack.config.server.js"
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
@@ -38,15 +43,15 @@
|
||||
"bugs": {
|
||||
"url": "https://github.com/FoxxMD/multi-scrobbler/issues"
|
||||
},
|
||||
"homepage": "https://github.com/FoxxMD/multi-scrobbler#readme",
|
||||
"private": true,
|
||||
"workspaces": [
|
||||
"web"
|
||||
],
|
||||
"homepage": ".",
|
||||
"proxy": "http://localhost:9079",
|
||||
"dependencies": {
|
||||
"@awaitjs/express": "^0.6.3",
|
||||
"@foxxmd/winston": "3.3.31",
|
||||
"@kenyip/backoff-strategies": "^1.0.4",
|
||||
"@react-nano/use-event-source": "^0.13.0",
|
||||
"@tanstack/react-query": "^4.32.1",
|
||||
"ajv": "^7.2.4",
|
||||
"better-sse": "^0.8.0",
|
||||
"body-parser": "^1.19.0",
|
||||
@@ -66,19 +71,32 @@
|
||||
"iti": "^0.6.0",
|
||||
"json5": "^2.2.3",
|
||||
"kodi-api": "^0.2.1",
|
||||
"ky": "^0.33.3",
|
||||
"lastfm-node-client": "^2.2.0",
|
||||
"mopidy": "^1.3.0",
|
||||
"nodemon": "^3.0.1",
|
||||
"normalize-url": "^6.1.0",
|
||||
"ntfy": "1.0.5",
|
||||
"p-event": "^4.2.0",
|
||||
"passport": "^0.6.0",
|
||||
"passport-deezer": "^0.2.0",
|
||||
"pony-cause": "^1.1.1",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-router-dom": "^6.15.0",
|
||||
"react-scripts": "5.0.1",
|
||||
"safe-stable-stringify": "^1.1.1",
|
||||
"socket.io": "^4.6.1",
|
||||
"spotify-web-api-node": "^5.0.2",
|
||||
"superagent": "^8.0.9",
|
||||
"tailwindcss": "^3.3.3",
|
||||
"triple-beam": "^1.3.0",
|
||||
"ts-loader": "^9.4.4",
|
||||
"typescript": "^4.9.5",
|
||||
"wait-on": "^7.0.1",
|
||||
"web-vitals": "^2.1.4",
|
||||
"webpack-cli": "^5.1.4",
|
||||
"webpack-node-externals": "^3.0.0",
|
||||
"winston-daily-rotate-file": "^4.5.0",
|
||||
"winston-duplex": "0.1.3",
|
||||
"winston-null": "^2.0.0",
|
||||
@@ -87,14 +105,22 @@
|
||||
"youtube-music-ts-api": "github:FoxxMD/youtube-music-ts-api#libraryHistoryImprovements-dist"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2",
|
||||
"@testing-library/jest-dom": "^5.17.0",
|
||||
"@testing-library/react": "^13.4.0",
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
"@tsconfig/node18": "^1.0.1",
|
||||
"@types/common-tags": "^1.8.1",
|
||||
"@types/concat-stream": "^2.0.0",
|
||||
"@types/express": "^4.17.13",
|
||||
"@types/express-session": "^1.17.4",
|
||||
"@types/formidable": "^2.0.5",
|
||||
"@types/jest": "^27.5.2",
|
||||
"@types/node": "^18.0.0",
|
||||
"@types/passport": "^1.0.12",
|
||||
"@types/react": "^18.2.18",
|
||||
"@types/react-dom": "^18.2.7",
|
||||
"@types/react-window": "^1.8.5",
|
||||
"@types/spotify-web-api-node": "^5.0.7",
|
||||
"@types/superagent": "^4.1.16",
|
||||
"@types/triple-beam": "^1.3.2",
|
||||
@@ -107,6 +133,18 @@
|
||||
"typescript": "^4.9.5",
|
||||
"typescript-json-schema": "~0.55"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"overrides": {
|
||||
"spotify-web-api-node": {
|
||||
"superagent": "$superagent"
|
||||
|
||||
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 9.4 KiB |
@@ -10,17 +10,17 @@ import './App.css';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||
const queryClient = new QueryClient()
|
||||
import Dashboard from "./dashboard/dashboard";
|
||||
import RecentPage from "./recent/RecentPage";
|
||||
//import RecentPage from "./recent/RecentPage";
|
||||
|
||||
const router = createBrowserRouter([
|
||||
{
|
||||
path: "/",
|
||||
element: <Dashboard />,
|
||||
},
|
||||
{
|
||||
/* {
|
||||
path: "/recent",
|
||||
element: <RecentPage />,
|
||||
},
|
||||
},*/
|
||||
]);
|
||||
|
||||
function App() {
|
||||
@@ -1,13 +1,10 @@
|
||||
import React, {Fragment, PropsWithChildren, ReactElement} from 'react';
|
||||
import SkeletonTitle from "./skeleton/SkeletonTitle.js";
|
||||
import SkeletonParagraph from "./skeleton/SkeletonParagraph.js";
|
||||
import StatusCardSkeleton, {StatusCardSkeletonData} from "./statusCard/StatusCardSkeleton.js";
|
||||
import {PlayData, PlayObject, TrackStringOptions} from "../../../src/common/infrastructure/Atomic.js";
|
||||
|
||||
import {buildTrackString, buildTrackStringReactOptions} from "../../../src/utils/StringUtils.js";
|
||||
import {JsonPlayObject, TrackStringOptions} from "../../core/Atomic.js";
|
||||
import {buildTrackString, buildTrackStringReactOptions} from "../../core/StringUtils.js";
|
||||
|
||||
interface PlayObjectProps {
|
||||
data: PlayObject
|
||||
data: JsonPlayObject
|
||||
artistLength?: number
|
||||
trackLength?: number
|
||||
buildOptions?: TrackStringOptions<ReactElement> & {includeWeb?: boolean}
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, {Fragment} from 'react';
|
||||
import {ClientStatusData} from "../../../../src/common/infrastructure/Atomic";
|
||||
import StatusCardSkeleton, {StatusCardSkeletonData} from "./StatusCardSkeleton";
|
||||
import SkeletonParagraph from "../skeleton/SkeletonParagraph";
|
||||
import {ClientStatusData} from "../../../core/Atomic";
|
||||
|
||||
export interface ClientStatusCardData extends StatusCardSkeletonData {
|
||||
loading?: boolean
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, {Fragment} from 'react';
|
||||
import {ClientStatusData, SourceStatusData} from "../../../../src/common/infrastructure/Atomic";
|
||||
import StatusCardSkeleton, {StatusCardSkeletonData} from "./StatusCardSkeleton";
|
||||
import SkeletonParagraph from "../skeleton/SkeletonParagraph";
|
||||
import {SourceStatusData} from "../../../core/Atomic";
|
||||
|
||||
export interface SourceStatusCardData extends StatusCardSkeletonData {
|
||||
loading?: boolean
|
||||
@@ -1,5 +1,4 @@
|
||||
import React, {PropsWithChildren} from 'react';
|
||||
import {ClientStatusData, SourceStatusData} from "../../../../src/common/infrastructure/Atomic";
|
||||
import SkeletonTitle from "../skeleton/SkeletonTitle";
|
||||
import SkeletonParagraph from "../skeleton/SkeletonParagraph";
|
||||
|
||||
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
@@ -3,13 +3,13 @@ import {
|
||||
useQuery,
|
||||
} from '@tanstack/react-query'
|
||||
import ky from "ky";
|
||||
import {PlayObject} from "../../../src/common/infrastructure/Atomic";
|
||||
import PlayDisplay from "../components/PlayDisplay";
|
||||
import {JsonPlayObject} from "../../core/Atomic.js";
|
||||
|
||||
const recent = () => {
|
||||
const {isLoading, isSuccess, isError, data, error} = useQuery({
|
||||
queryKey: ['recent?name=default&type=spotify'], queryFn: async () => {
|
||||
return await ky.get('/api/recent?name=default&type=spotify').json() as PlayObject[]
|
||||
return await ky.get('/api/recent?name=default&type=spotify').json() as JsonPlayObject[]
|
||||
}
|
||||
});
|
||||
|
||||
@@ -4,9 +4,9 @@ import {
|
||||
} from '@tanstack/react-query'
|
||||
import ky from "ky";
|
||||
import StatusCardSkeleton from "../components/statusCard/StatusCardSkeleton";
|
||||
import {ClientStatusData, SourceStatusData} from "../../../src/common/infrastructure/Atomic";
|
||||
import SourceStatusCard from "../components/statusCard/SourceStatusCard";
|
||||
import ClientStatusCard from "../components/statusCard/ClientStatusCard";
|
||||
import {ClientStatusData, SourceStatusData} from "../../core/Atomic";
|
||||
|
||||
const StatusSection = () => {
|
||||
const {isLoading, isSuccess, isError, data, error} = useQuery({
|
||||
@@ -0,0 +1,125 @@
|
||||
import {Dayjs} from "dayjs";
|
||||
|
||||
export interface SourceStatusData {
|
||||
status: string;
|
||||
type: "spotify" | "plex" | "tautulli" | "subsonic" | "jellyfin" | "lastfm" | "deezer" | "ytmusic" | "mpris" | "mopidy" | "listenbrainz" | "jriver" | "kodi";
|
||||
display: string;
|
||||
tracksDiscovered: number;
|
||||
name: string;
|
||||
canPoll: boolean;
|
||||
hasAuth: boolean;
|
||||
hasAuthInteraction: boolean;
|
||||
authed: boolean;
|
||||
}
|
||||
|
||||
export interface ClientStatusData {
|
||||
status: string;
|
||||
type: "maloja" | "lastfm" | "listenbrainz";
|
||||
display: string;
|
||||
tracksDiscovered: number;
|
||||
name: string;
|
||||
hasAuth: boolean;
|
||||
}
|
||||
|
||||
export interface TrackStringOptions<T = string> {
|
||||
include?: ('time' | 'artist' | 'track' | 'timeFromNow' | 'trackId')[]
|
||||
transformers?: {
|
||||
artists?: (a: string[]) => T | string
|
||||
track?: (t: string, hasExistingParts?: boolean) => T | string
|
||||
time?: (t: Dayjs) => T | string
|
||||
timeFromNow?: (t: Dayjs) => T | string
|
||||
reducer?: (arr: (T | string)[]) => T //(acc: T, curr: T | string) => T
|
||||
}
|
||||
}
|
||||
|
||||
export interface PlayProgress {
|
||||
timestamp: Dayjs
|
||||
position?: number
|
||||
positionPercent?: number
|
||||
}
|
||||
|
||||
export type ListenRange = [PlayProgress, PlayProgress]
|
||||
|
||||
export interface TrackData {
|
||||
artists?: string[]
|
||||
album?: string
|
||||
track?: string
|
||||
/**
|
||||
* The length of the track, in seconds
|
||||
* */
|
||||
duration?: number
|
||||
|
||||
meta?: {
|
||||
brainz?: {
|
||||
artist?: string
|
||||
albumArtist?: string
|
||||
album?: string
|
||||
track?: string
|
||||
releaseGroup?: string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface PlayData extends TrackData {
|
||||
/**
|
||||
* The date the track was played at
|
||||
* */
|
||||
playDate?: Dayjs | string
|
||||
/** Number of seconds the track was listened to */
|
||||
listenedFor?: number
|
||||
listenRanges?: ListenRange[]
|
||||
}
|
||||
|
||||
export interface PlayMeta {
|
||||
source?: string
|
||||
|
||||
/**
|
||||
* Specifies from what facet/data from the source this play was parsed from IE history, now playing, etc...
|
||||
* */
|
||||
parsedFrom?: string
|
||||
/**
|
||||
* Unique ID for this track, given by the Source
|
||||
* */
|
||||
trackId?: string
|
||||
|
||||
/**
|
||||
* Atomic ID for this instance of played tracked IE a unique ID for "this track played at this time"
|
||||
* */
|
||||
playId?: string
|
||||
newFromSource?: boolean
|
||||
url?: {
|
||||
web: string
|
||||
[key: string]: string
|
||||
}
|
||||
user?: string
|
||||
mediaType?: string
|
||||
server?: string
|
||||
library?: string
|
||||
/**
|
||||
* The position the "player" is at in the track at the time the play was reported, in seconds
|
||||
* */
|
||||
trackProgressPosition?: number
|
||||
/**
|
||||
* A unique identifier for the device playing this track
|
||||
* */
|
||||
deviceId?: string
|
||||
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
export interface AmbPlayObject {
|
||||
data: PlayData,
|
||||
meta: PlayMeta
|
||||
}
|
||||
|
||||
export interface PlayObject extends AmbPlayObject {
|
||||
data: ObjectPlayData,
|
||||
}
|
||||
|
||||
export interface JsonPlayObject extends AmbPlayObject {
|
||||
playDate?: string
|
||||
}
|
||||
|
||||
export interface ObjectPlayData extends PlayData {
|
||||
playDate?: Dayjs
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
import dayjs, {Dayjs} from "dayjs";
|
||||
import {PlayObject, TrackStringOptions} from "../common/infrastructure/Atomic.js";
|
||||
import React, {ReactElement} from "react";
|
||||
import utc from "dayjs/plugin/utc.js";
|
||||
import isBetween from "dayjs/plugin/isBetween.js";
|
||||
import relativeTime from "dayjs/plugin/relativeTime.js";
|
||||
import duration from "dayjs/plugin/duration.js";
|
||||
import timezone from "dayjs/plugin/timezone.js";
|
||||
import {AmbPlayObject, TrackStringOptions} from "./Atomic.js";
|
||||
|
||||
dayjs.extend(utc)
|
||||
dayjs.extend(isBetween);
|
||||
@@ -36,7 +36,7 @@ export const defaultBuildTrackStringTransformers = {
|
||||
time: defaultTimeFunc,
|
||||
timeFromNow: defaultTimeFromNowFunc,
|
||||
}
|
||||
export const buildTrackString = <T = string>(playObj: PlayObject, options: TrackStringOptions<T> = {}): T => {
|
||||
export const buildTrackString = <T = string>(playObj: AmbPlayObject, options: TrackStringOptions<T> = {}): T => {
|
||||
const {
|
||||
include = ['time', 'artist', 'track'],
|
||||
transformers: {
|
||||
@@ -59,6 +59,8 @@ export const buildTrackString = <T = string>(playObj: PlayObject, options: Track
|
||||
} = {},
|
||||
} = playObj;
|
||||
|
||||
const pd = typeof playDate === 'string' ? dayjs(playDate) : playDate;
|
||||
|
||||
const strParts: (T | string)[] = [];
|
||||
if (include.includes('trackId') && trackId !== undefined) {
|
||||
strParts.push(`(${trackId})`);
|
||||
@@ -70,10 +72,10 @@ export const buildTrackString = <T = string>(playObj: PlayObject, options: Track
|
||||
strParts.push(trackFunc(track, strParts.length > 0));
|
||||
}
|
||||
if (include.includes('time')) {
|
||||
strParts.push(timeFunc(playDate));
|
||||
strParts.push(timeFunc(pd));
|
||||
}
|
||||
if (include.includes('timeFromNow')) {
|
||||
const tfn = timeFromNow(playDate);
|
||||
const tfn = timeFromNow(pd);
|
||||
if (tfn !== undefined) {
|
||||
strParts.push(tfn)
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import './index.css';
|
||||
import App from './App';
|
||||
import reportWebVitals from './reportWebVitals';
|
||||
import './client/index.css';
|
||||
import App from './client/App';
|
||||
import reportWebVitals from './client/reportWebVitals';
|
||||
|
||||
const root = ReactDOM.createRoot(
|
||||
document.getElementById('root') as HTMLElement
|
||||
@@ -3,8 +3,8 @@ import {getRoot} from "../ioc.js";
|
||||
import {capitalize} from "../utils.js";
|
||||
import {makeClientCheckMiddle, makeSourceCheckMiddle} from "./middleware.js";
|
||||
import AbstractSource from "../sources/AbstractSource.js";
|
||||
import {ClientStatusData, PlayObject, SourceStatusData, TrackStringOptions} from "../common/infrastructure/Atomic.js";
|
||||
import {buildTrackString, longestString, truncateStringToLength} from "../utils/StringUtils.js";
|
||||
import {ClientStatusData, PlayObject, SourceStatusData, TrackStringOptions} from "../../core/Atomic.js";
|
||||
import {buildTrackString, longestString, truncateStringToLength} from "../../core/StringUtils.js";
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import {capitalize, mergeArr} from "../utils.js";
|
||||
import {Logger} from '@foxxmd/winston';
|
||||
import {FormatPlayObjectOptions, PlayObject} from "../common/infrastructure/Atomic.js";
|
||||
import {FormatPlayObjectOptions} from "../common/infrastructure/Atomic.js";
|
||||
import winston from '@foxxmd/winston';
|
||||
import {PlayObject} from "../../core/Atomic.js";
|
||||
|
||||
export default abstract class AbstractApiClient {
|
||||
name: string;
|
||||
@@ -5,8 +5,9 @@ import { KodiClient } from 'kodi-api'
|
||||
import normalizeUrl from "normalize-url";
|
||||
import {URL} from "url";
|
||||
import {RecentlyPlayedOptions} from "../sources/AbstractSource.js";
|
||||
import {FormatPlayObjectOptions, PlayObject} from "../common/infrastructure/Atomic.js";
|
||||
import {FormatPlayObjectOptions} from "../common/infrastructure/Atomic.js";
|
||||
import dayjs from "dayjs";
|
||||
import {PlayObject} from "../../core/Atomic.js";
|
||||
|
||||
interface KodiDuration {
|
||||
hours: number
|
||||
@@ -2,8 +2,9 @@ import LastFm, {AuthGetSessionResponse, TrackObject, UserGetInfoResponse} from "
|
||||
import AbstractApiClient from "./AbstractApiClient.js";
|
||||
import dayjs from "dayjs";
|
||||
import {readJson, sleep, writeFile} from "../utils.js";
|
||||
import {FormatPlayObjectOptions, PlayObject} from "../common/infrastructure/Atomic.js";
|
||||
import {FormatPlayObjectOptions} from "../common/infrastructure/Atomic.js";
|
||||
import {LastfmData} from "../common/infrastructure/config/client/lastfm.js";
|
||||
import {PlayObject} from "../../core/Atomic.js";
|
||||
|
||||
const badErrors = [
|
||||
'api key suspended',
|
||||
@@ -1,7 +1,7 @@
|
||||
import AbstractApiClient from "./AbstractApiClient.js";
|
||||
import request, {Request} from 'superagent';
|
||||
import {ListenBrainzClientData} from "../common/infrastructure/config/client/listenbrainz.js";
|
||||
import {FormatPlayObjectOptions, PlayObject} from "../common/infrastructure/Atomic.js";
|
||||
import {FormatPlayObjectOptions} from "../common/infrastructure/Atomic.js";
|
||||
import dayjs from "dayjs";
|
||||
import {
|
||||
containsDelimiters, findDelimiters,
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
unique,
|
||||
uniqueNormalizedStrArr
|
||||
} from "../utils.js";
|
||||
import {PlayObject} from "../../core/Atomic.js";
|
||||
|
||||
|
||||
export interface ArtistMBIDMapping {
|
||||
@@ -10,14 +10,15 @@ import {
|
||||
INITIALIZING,
|
||||
InitState,
|
||||
NOT_INITIALIZED,
|
||||
PlayObject, ScrobbledPlayObject, TrackStringOptions
|
||||
ScrobbledPlayObject
|
||||
} from "../common/infrastructure/Atomic.js";
|
||||
import winston, {Logger} from '@foxxmd/winston';
|
||||
import {CommonClientConfig} from "../common/infrastructure/config/client/index.js";
|
||||
import {ClientConfig} from "../common/infrastructure/config/client/clients.js";
|
||||
import {Notifiers} from "../notifier/Notifiers.js";
|
||||
import {FixedSizeList} from 'fixed-size-list';
|
||||
import {buildTrackString, truncateStringToLength} from "../utils/StringUtils.js";
|
||||
import {PlayObject, TrackStringOptions} from "../../core/Atomic.js";
|
||||
import {buildTrackString, truncateStringToLength} from "../../core/StringUtils.js";
|
||||
|
||||
export default abstract class AbstractScrobbleClient {
|
||||
|
||||
@@ -12,14 +12,14 @@ import LastfmApiClient from "../apis/LastfmApiClient.js";
|
||||
import {
|
||||
FormatPlayObjectOptions,
|
||||
INITIALIZING,
|
||||
PlayObject, ScrobbledPlayObject,
|
||||
TrackStringOptions
|
||||
ScrobbledPlayObject
|
||||
} from "../common/infrastructure/Atomic.js";
|
||||
import {LastfmClientConfig} from "../common/infrastructure/config/client/lastfm.js";
|
||||
import {TrackScrobbleResponse, UserGetRecentTracksResponse} from "lastfm-node-client";
|
||||
import {Notifiers} from "../notifier/Notifiers.js";
|
||||
import {Logger} from '@foxxmd/winston';
|
||||
import {buildTrackString, truncateStringToLength} from "../utils/StringUtils.js";
|
||||
import {PlayObject, TrackStringOptions} from "../../core/Atomic.js";
|
||||
import {buildTrackString} from "../../core/StringUtils.js";
|
||||
|
||||
export default class LastfmScrobbler extends AbstractScrobbleClient {
|
||||
|
||||
@@ -11,15 +11,14 @@ import {
|
||||
import LastfmApiClient from "../apis/LastfmApiClient.js";
|
||||
import {
|
||||
FormatPlayObjectOptions,
|
||||
INITIALIZING,
|
||||
PlayObject,
|
||||
TrackStringOptions
|
||||
INITIALIZING
|
||||
} from "../common/infrastructure/Atomic.js";
|
||||
import {Notifiers} from "../notifier/Notifiers.js";
|
||||
import {Logger} from '@foxxmd/winston';
|
||||
import {ListenBrainzClientConfig} from "../common/infrastructure/config/client/listenbrainz.js";
|
||||
import {ListenbrainzApiClient} from "../apis/ListenbrainzApiClient.js";
|
||||
import {buildTrackString, truncateStringToLength} from "../utils/StringUtils.js";
|
||||
import {PlayObject, TrackStringOptions} from "../../core/Atomic.js";
|
||||
import {buildTrackString} from "../../core/StringUtils.js";
|
||||
|
||||
export default class ListenbrainzScrobbler extends AbstractScrobbleClient {
|
||||
|
||||
@@ -11,9 +11,7 @@ import {
|
||||
} from "../utils.js";
|
||||
import {
|
||||
FormatPlayObjectOptions,
|
||||
INITIALIZING,
|
||||
PlayObject,
|
||||
TrackStringOptions
|
||||
INITIALIZING
|
||||
} from "../common/infrastructure/Atomic.js";
|
||||
import {MalojaClientConfig} from "../common/infrastructure/config/client/maloja.js";
|
||||
import {Notifiers} from "../notifier/Notifiers.js";
|
||||
@@ -24,7 +22,8 @@ import {
|
||||
MalojaScrobbleV2RequestData,
|
||||
MalojaScrobbleV3RequestData, MalojaScrobbleV3ResponseData, MalojaV2ScrobbleData, MalojaV3ScrobbleData
|
||||
} from "../apis/maloja/interfaces";
|
||||
import {buildTrackString, truncateStringToLength} from "../utils/StringUtils.js";
|
||||
import {PlayObject, TrackStringOptions} from "../../core/Atomic.js";
|
||||
import {buildTrackString} from "../../core/StringUtils.js";
|
||||
|
||||
const feat = ["ft.", "ft", "feat.", "feat", "featuring", "Ft.", "Ft", "Feat.", "Feat", "Featuring"];
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
} from "../utils.js";
|
||||
import MalojaScrobbler from "./MalojaScrobbler.js";
|
||||
import LastfmScrobbler from "./LastfmScrobbler.js";
|
||||
import {clientTypes, ConfigMeta, PlayObject} from "../common/infrastructure/Atomic.js";
|
||||
import {clientTypes, ConfigMeta} from "../common/infrastructure/Atomic.js";
|
||||
import {AIOConfig} from "../common/infrastructure/config/aioConfig.js";
|
||||
import * as aioSchema from '../common/schema/aio-client.json';
|
||||
import * as clientSchema from '../common/schema/client.json';
|
||||
@@ -22,7 +22,8 @@ import winston from '@foxxmd/winston';
|
||||
import ListenbrainzScrobbler from "./ListenbrainzScrobbler.js";
|
||||
import {ListenBrainzClientConfig} from "../common/infrastructure/config/client/listenbrainz.js";
|
||||
import {ErrorWithCause} from "pony-cause";
|
||||
import {buildTrackString} from "../utils/StringUtils.js";
|
||||
import {PlayObject} from "../../core/Atomic.js";
|
||||
import {buildTrackString} from "../../core/StringUtils.js";
|
||||
|
||||
type groupedNamedConfigs = {[key: string]: ParsedConfig[]};
|
||||
|
||||
@@ -4,5 +4,5 @@ import * as path from 'path';
|
||||
//const __filename = fileURLToPath(import.meta.url);
|
||||
//const __dirname = path.dirname(__filename);
|
||||
|
||||
export const projectDir = path.resolve(__dirname, '../../');
|
||||
export const projectDir = process.cwd(); //path.resolve(__dirname, '../../../');
|
||||
export const configDir: string = path.resolve(projectDir, './config');
|
||||
@@ -3,9 +3,9 @@ import {FixedSizeList} from 'fixed-size-list';
|
||||
import {MESSAGE} from 'triple-beam';
|
||||
import {Logger} from '@foxxmd/winston';
|
||||
import TupleMap from "../TupleMap.js";
|
||||
import is from "@sindresorhus/is";
|
||||
import {Request, Response} from "express";
|
||||
import {NextFunction, ParamsDictionary, Query} from "express-serve-static-core";
|
||||
import {PlayMeta, PlayObject} from "../../../core/Atomic.js";
|
||||
|
||||
export type SourceType = 'spotify' | 'plex' | 'tautulli' | 'subsonic' | 'jellyfin' | 'lastfm' | 'deezer' | 'ytmusic' | 'mpris' | 'mopidy' | 'listenbrainz' | 'jriver' | 'kodi';
|
||||
export const sourceTypes: SourceType[] = ['spotify', 'plex', 'tautulli', 'subsonic', 'jellyfin', 'lastfm', 'deezer', 'ytmusic', 'mpris', 'mopidy', 'listenbrainz', 'jriver', 'kodi'];
|
||||
@@ -69,85 +69,6 @@ export const asPlayerStateData = (obj: object): obj is PlayerStateData => {
|
||||
return 'platformId' in obj && 'play' in obj;
|
||||
}
|
||||
|
||||
export interface PlayProgress {
|
||||
timestamp: Dayjs
|
||||
position?: number
|
||||
positionPercent?: number
|
||||
}
|
||||
export type ListenRange = [PlayProgress, PlayProgress]
|
||||
|
||||
export interface TrackData {
|
||||
artists?: string[]
|
||||
album?: string
|
||||
track?: string
|
||||
/**
|
||||
* The length of the track, in seconds
|
||||
* */
|
||||
duration?: number
|
||||
|
||||
meta?: {
|
||||
brainz?: {
|
||||
artist?: string
|
||||
albumArtist?: string
|
||||
album?: string
|
||||
track?: string
|
||||
releaseGroup?: string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface PlayData extends TrackData {
|
||||
/**
|
||||
* The date the track was played at
|
||||
* */
|
||||
playDate?: Dayjs
|
||||
/** Number of seconds the track was listened to */
|
||||
listenedFor?: number
|
||||
listenRanges?: ListenRange[]
|
||||
}
|
||||
|
||||
export interface PlayMeta {
|
||||
source?: string
|
||||
|
||||
/**
|
||||
* Specifies from what facet/data from the source this play was parsed from IE history, now playing, etc...
|
||||
* */
|
||||
parsedFrom?: string
|
||||
/**
|
||||
* Unique ID for this track, given by the Source
|
||||
* */
|
||||
trackId?: string
|
||||
|
||||
/**
|
||||
* Atomic ID for this instance of played tracked IE a unique ID for "this track played at this time"
|
||||
* */
|
||||
playId?: string
|
||||
newFromSource?: boolean
|
||||
url?: {
|
||||
web: string
|
||||
[key: string]: string
|
||||
}
|
||||
user?: string
|
||||
mediaType?: string
|
||||
server?: string
|
||||
library?: string
|
||||
/**
|
||||
* The position the "player" is at in the track at the time the play was reported, in seconds
|
||||
* */
|
||||
trackProgressPosition?: number
|
||||
/**
|
||||
* A unique identifier for the device playing this track
|
||||
* */
|
||||
deviceId?: string
|
||||
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
export interface PlayObject {
|
||||
data: PlayData,
|
||||
meta: PlayMeta
|
||||
}
|
||||
|
||||
export interface FormatPlayObjectOptions {
|
||||
newFromSource?: boolean
|
||||
parsedFrom?: string
|
||||
@@ -173,17 +94,6 @@ export const NO_USER = 'SingleUser';
|
||||
|
||||
export const SINGLE_USER_PLATFORM_ID: PlayPlatformId = [NO_DEVICE, NO_USER];
|
||||
|
||||
export interface TrackStringOptions<T = string> {
|
||||
include?: ('time' | 'artist' | 'track' | 'timeFromNow' | 'trackId')[]
|
||||
transformers?: {
|
||||
artists?: (a: string[]) => T | string
|
||||
track?: (t: string, hasExistingParts?: boolean) => T | string
|
||||
time?: (t: Dayjs) => T | string
|
||||
timeFromNow?: (t: Dayjs) => T | string
|
||||
reducer?: (arr: (T|string)[]) => T //(acc: T, curr: T | string) => T
|
||||
}
|
||||
}
|
||||
|
||||
export interface ScrobbledPlayObject {
|
||||
play: PlayObject
|
||||
scrobble: PlayObject
|
||||
@@ -282,27 +192,6 @@ export type ExpressRequest = Request<ParamsDictionary, any, any, Query, Record<s
|
||||
export type ExpressResponse = Response<any, Record<string, any>>;
|
||||
export type ExpressHandler = (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => Promise<void | Response<any, Record<string, any>>>
|
||||
|
||||
export interface SourceStatusData {
|
||||
status: string;
|
||||
type: "spotify" | "plex" | "tautulli" | "subsonic" | "jellyfin" | "lastfm" | "deezer" | "ytmusic" | "mpris" | "mopidy" | "listenbrainz" | "jriver" | "kodi";
|
||||
display: string;
|
||||
tracksDiscovered: number;
|
||||
name: string;
|
||||
canPoll: boolean;
|
||||
hasAuth: boolean;
|
||||
hasAuthInteraction: boolean;
|
||||
authed: boolean;
|
||||
}
|
||||
|
||||
export interface ClientStatusData {
|
||||
status: string;
|
||||
type: "maloja" | "lastfm" | "listenbrainz";
|
||||
display: string;
|
||||
tracksDiscovered: number;
|
||||
name: string;
|
||||
hasAuth: boolean;
|
||||
}
|
||||
|
||||
export interface numberFormatOptions {
|
||||
toFixed: number,
|
||||
defaultVal?: any,
|
||||
@@ -11,8 +11,6 @@ import DailyRotateFile from 'winston-daily-rotate-file';
|
||||
import dayjs from "dayjs";
|
||||
import stringify from 'safe-stable-stringify';
|
||||
import {SPLAT, LEVEL, MESSAGE} from 'triple-beam';
|
||||
import {Symbol} from "typescript-json-schema";
|
||||
import {truncateStringToLength} from "../utils/StringUtils.js";
|
||||
|
||||
const {combine, printf, timestamp, label, splat, errors} = format;
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
remoteHostIdentifiers,
|
||||
sleep
|
||||
} from "./utils.js";
|
||||
import {makeClientCheckMiddle, makeSourceCheckMiddle} from "./server/middleware.js";
|
||||
import {makeClientCheckMiddle, makeSourceCheckMiddle} from "./api/middleware.js";
|
||||
import TautulliSource from "./sources/TautulliSource.js";
|
||||
import PlexSource, {plexRequestMiddle} from "./sources/PlexSource.js";
|
||||
import JellyfinSource from "./sources/JellyfinSource.js";
|
||||
@@ -33,9 +33,7 @@ import AbstractSource from "./sources/AbstractSource.js";
|
||||
import {
|
||||
ExpressHandler,
|
||||
LogInfo,
|
||||
LogLevel,
|
||||
PlayObject,
|
||||
TrackStringOptions
|
||||
LogLevel
|
||||
} from "./common/infrastructure/Atomic.js";
|
||||
import SpotifySource from "./sources/SpotifySource.js";
|
||||
import {JellyfinNotifier} from "./sources/ingressNotifiers/JellyfinNotifier.js";
|
||||
@@ -45,9 +43,12 @@ import {AIOConfig} from "./common/infrastructure/config/aioConfig.js";
|
||||
import {getRoot} from "./ioc.js";
|
||||
import {formatLogToHtml, getLogger, isLogLineMinLevel} from "./common/logging.js";
|
||||
import {MESSAGE} from "triple-beam";
|
||||
import {setupApi} from "./server/api.js";
|
||||
import {setupApi} from "./api/api.js";
|
||||
import {Transform} from "stream";
|
||||
import {buildTrackString, longestString, truncateStringToLength} from "./utils/StringUtils.js";
|
||||
import {PlayObject, TrackStringOptions} from "../core/Atomic.js";
|
||||
import {buildTrackString, longestString, truncateStringToLength} from "../core/StringUtils.js";
|
||||
|
||||
const buildDir = path.join(process.cwd() + "/build");
|
||||
|
||||
|
||||
dayjs.extend(utc)
|
||||
@@ -67,6 +68,13 @@ let io: Server | undefined = undefined;
|
||||
|
||||
app.use(router);
|
||||
app.use(bodyParser.json());
|
||||
app.use(
|
||||
bodyParser.urlencoded({
|
||||
extended: true,
|
||||
})
|
||||
);
|
||||
|
||||
app.use(express.static(buildDir));
|
||||
|
||||
app.use(session({secret: 'keyboard cat', resave: false, saveUninitialized: false}));
|
||||
app.use(passport.initialize());
|
||||
@@ -85,6 +93,7 @@ initLogger.stream().on('log', (log: LogInfo) => {
|
||||
|
||||
let logger: Logger;
|
||||
|
||||
const f = projectDir;
|
||||
const configDir = process.env.CONFIG_DIR || path.resolve(projectDir, `./config`);
|
||||
|
||||
|
||||
@@ -649,6 +658,10 @@ const configDir = process.env.CONFIG_DIR || path.resolve(projectDir, `./config`)
|
||||
logger.info(`Some sources are not ready, open ${localUrl} to continue`);
|
||||
}
|
||||
|
||||
app.get("/*", function (req, res) {
|
||||
res.sendFile(path.join(buildDir, "index.html"));
|
||||
});
|
||||
|
||||
app.set('views', path.resolve(projectDir, 'src/views'));
|
||||
app.set('view engine', 'ejs');
|
||||
logger.info(`Server started at ${localUrl}`);
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
GroupedFixedPlays,
|
||||
GroupedPlays,
|
||||
InternalConfig, NO_DEVICE, NO_USER,
|
||||
PlayObject, PlayPlatformId, PlayUserId, ProgressAwarePlayObject, SINGLE_USER_PLATFORM_ID,
|
||||
PlayPlatformId, PlayUserId, ProgressAwarePlayObject, SINGLE_USER_PLATFORM_ID,
|
||||
SourceType
|
||||
} from "../common/infrastructure/Atomic.js";
|
||||
import {Logger} from '@foxxmd/winston';
|
||||
@@ -19,7 +19,8 @@ import {SourceConfig} from "../common/infrastructure/config/source/sources.js";
|
||||
import {EventEmitter} from "events";
|
||||
import {FixedSizeList} from "fixed-size-list";
|
||||
import TupleMap from "../common/TupleMap.js";
|
||||
import {buildTrackString} from "../utils/StringUtils.js";
|
||||
import {PlayObject} from "../../core/Atomic.js";
|
||||
import {buildTrackString} from "../../core/StringUtils.js";
|
||||
|
||||
export interface RecentlyPlayedOptions {
|
||||
limit?: number
|
||||
@@ -4,8 +4,9 @@ import {Strategy as DeezerStrategy} from 'passport-deezer';
|
||||
import AbstractSource, {RecentlyPlayedOptions} from "./AbstractSource.js";
|
||||
import dayjs from "dayjs";
|
||||
import {DeezerSourceConfig} from "../common/infrastructure/config/source/deezer.js";
|
||||
import {FormatPlayObjectOptions, InternalConfig, PlayObject} from "../common/infrastructure/Atomic.js";
|
||||
import {FormatPlayObjectOptions, InternalConfig} from "../common/infrastructure/Atomic.js";
|
||||
import EventEmitter from "events";
|
||||
import {PlayObject} from "../../core/Atomic.js";
|
||||
|
||||
export default class DeezerSource extends AbstractSource {
|
||||
workingCredsPath;
|
||||
@@ -1,5 +1,5 @@
|
||||
import MemorySource from "./MemorySource.js";
|
||||
import {FormatPlayObjectOptions, InternalConfig, PlayObject} from "../common/infrastructure/Atomic.js";
|
||||
import {FormatPlayObjectOptions, InternalConfig} from "../common/infrastructure/Atomic.js";
|
||||
import dayjs from "dayjs";
|
||||
import {URL} from "url";
|
||||
import normalizeUrl from 'normalize-url';
|
||||
@@ -7,6 +7,7 @@ import {EventEmitter} from "events";
|
||||
import {RecentlyPlayedOptions} from "./AbstractSource.js";
|
||||
import {JRiverSourceConfig} from "../common/infrastructure/config/source/jriver.js";
|
||||
import {Info, JRiverApiClient, PLAYER_STATE} from "../apis/JRiverApiClient.js";
|
||||
import {PlayObject} from "../../core/Atomic.js";
|
||||
|
||||
export class JRiverSource extends MemorySource {
|
||||
declare config: JRiverSourceConfig;
|
||||
@@ -8,12 +8,13 @@ import {
|
||||
comparePlayTemporally, temporalPlayComparisonSummary, doubleReturnNewline
|
||||
} from "../utils.js";
|
||||
import {JellySourceConfig} from "../common/infrastructure/config/source/jellyfin.js";
|
||||
import {FormatPlayObjectOptions, InternalConfig, PlayObject, PlayPlatformId} from "../common/infrastructure/Atomic.js";
|
||||
import {FormatPlayObjectOptions, InternalConfig, PlayPlatformId} from "../common/infrastructure/Atomic.js";
|
||||
import EventEmitter from "events";
|
||||
import {PlayerStateOptions} from "./PlayerState/AbstractPlayerState.js";
|
||||
import {Logger} from "@foxxmd/winston";
|
||||
import {JellyfinPlayerState} from "./PlayerState/JellyfinPlayerState.js";
|
||||
import {buildTrackString, truncateStringToLength} from "../utils/StringUtils.js";
|
||||
import {PlayObject} from "../../core/Atomic.js";
|
||||
import {buildTrackString, truncateStringToLength} from "../../core/StringUtils.js";
|
||||
|
||||
const shortDeviceId = truncateStringToLength(10, '');
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import MemorySource from "./MemorySource.js";
|
||||
import {FormatPlayObjectOptions, InternalConfig, PlayObject} from "../common/infrastructure/Atomic.js";
|
||||
import {FormatPlayObjectOptions, InternalConfig} from "../common/infrastructure/Atomic.js";
|
||||
import {EventEmitter} from "events";
|
||||
import {RecentlyPlayedOptions} from "./AbstractSource.js";
|
||||
import {KodiSourceConfig} from "../common/infrastructure/config/source/kodi.js";
|
||||
import {KodiApiClient} from "../apis/KodiApiClient.js";
|
||||
import {PlayObject} from "../../core/Atomic.js";
|
||||
|
||||
export class KodiSource extends MemorySource {
|
||||
declare config: KodiSourceConfig;
|
||||
@@ -2,9 +2,10 @@ import AbstractSource, {RecentlyPlayedOptions} from "./AbstractSource.js";
|
||||
import LastfmApiClient from "../apis/LastfmApiClient.js";
|
||||
import {sortByOldestPlayDate} from "../utils.js";
|
||||
import {LastfmClientConfig} from "../common/infrastructure/config/client/lastfm.js";
|
||||
import {FormatPlayObjectOptions, InternalConfig, PlayObject} from "../common/infrastructure/Atomic.js";
|
||||
import {FormatPlayObjectOptions, InternalConfig} from "../common/infrastructure/Atomic.js";
|
||||
import {UserGetRecentTracksResponse} from "lastfm-node-client";
|
||||
import EventEmitter from "events";
|
||||
import {PlayObject} from "../../core/Atomic.js";
|
||||
|
||||
export default class LastfmSource extends AbstractSource {
|
||||
|
||||
@@ -7,12 +7,13 @@ import {
|
||||
PlaybackStatus, PlayerInfo,
|
||||
PROPERTIES_IFACE
|
||||
} from "../common/infrastructure/config/source/mpris.js";
|
||||
import {FormatPlayObjectOptions, InternalConfig, PlayObject} from "../common/infrastructure/Atomic.js";
|
||||
import {FormatPlayObjectOptions, InternalConfig} from "../common/infrastructure/Atomic.js";
|
||||
import MemorySource from "./MemorySource.js";
|
||||
import {RecentlyPlayedOptions} from "./AbstractSource.js";
|
||||
import {removeDuplicates} from "../utils.js";
|
||||
import EventEmitter from "events";
|
||||
import {ErrorWithCause} from "pony-cause";
|
||||
import {PlayObject} from "../../core/Atomic.js";
|
||||
|
||||
|
||||
export class MPRISSource extends MemorySource {
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
asPlayerStateData,
|
||||
DeviceId,
|
||||
GroupedPlays, PlayerStateData,
|
||||
PlayObject, PlayPlatformId, PlayUserId,
|
||||
PlayPlatformId, PlayUserId,
|
||||
ProgressAwarePlayObject,
|
||||
ScrobbleThresholdResult
|
||||
} from "../common/infrastructure/Atomic.js";
|
||||
@@ -24,7 +24,8 @@ import TupleMap from "../common/TupleMap.js";
|
||||
import {AbstractPlayerState, PlayerStateOptions} from "./PlayerState/AbstractPlayerState.js";
|
||||
import {GenericPlayerState} from "./PlayerState/GenericPlayerState.js";
|
||||
import {Logger} from "@foxxmd/winston";
|
||||
import {buildTrackString} from "../utils/StringUtils.js";
|
||||
import {PlayObject} from "../../core/Atomic.js";
|
||||
import {buildTrackString} from "../../core/StringUtils.js";
|
||||
|
||||
export default class MemorySource extends AbstractSource {
|
||||
/*
|
||||
@@ -4,7 +4,6 @@ import {
|
||||
FormatPlayObjectOptions,
|
||||
InternalConfig,
|
||||
PlayerStateData,
|
||||
PlayObject,
|
||||
SINGLE_USER_PLATFORM_ID
|
||||
} from "../common/infrastructure/Atomic.js";
|
||||
import dayjs from "dayjs";
|
||||
@@ -15,10 +14,8 @@ import {EventEmitter} from "events";
|
||||
import pEvent from 'p-event';
|
||||
import winston from '@foxxmd/winston';
|
||||
import {RecentlyPlayedOptions} from "./AbstractSource.js";
|
||||
import request from "superagent";
|
||||
import {removeDuplicates} from "../utils.js";
|
||||
import {SubsonicSource} from "./SubsonicSource.js";
|
||||
import {buildTrackString} from "../utils/StringUtils.js";
|
||||
import {PlayObject} from "../../core/Atomic.js";
|
||||
import {buildTrackString} from "../../core/StringUtils.js";
|
||||
|
||||
export class MopidySource extends MemorySource {
|
||||
declare config: MopidySourceConfig;
|
||||
@@ -1,8 +1,6 @@
|
||||
import {
|
||||
CALCULATED_PLAYER_STATUSES,
|
||||
CalculatedPlayerStatus,
|
||||
ListenRange, PlayData,
|
||||
PlayObject,
|
||||
PlayPlatformId,
|
||||
REPORTED_PLAYER_STATUSES,
|
||||
ReportedPlayerStatus
|
||||
@@ -11,7 +9,8 @@ import dayjs, {Dayjs} from "dayjs";
|
||||
import {formatNumber, genGroupIdStr, playObjDataMatch, progressBar} from "../../utils.js";
|
||||
import {Logger} from "@foxxmd/winston";
|
||||
import {ListenProgress} from "./ListenProgress.js";
|
||||
import {buildTrackString} from "../../utils/StringUtils.js";
|
||||
import {ListenRange, PlayData, PlayObject} from "../../../core/Atomic.js";
|
||||
import {buildTrackString} from "../../../core/StringUtils.js";
|
||||
|
||||
export interface PlayerStateIntervals {
|
||||
staleInterval?: number
|
||||
@@ -1,7 +1,8 @@
|
||||
import {GenericPlayerState} from "./GenericPlayerState.js";
|
||||
import {Logger} from "@foxxmd/winston";
|
||||
import {PlayObject, PlayPlatformId, ReportedPlayerStatus} from "../../common/infrastructure/Atomic.js";
|
||||
import {PlayPlatformId, ReportedPlayerStatus} from "../../common/infrastructure/Atomic.js";
|
||||
import {PlayerStateOptions} from "./AbstractPlayerState.js";
|
||||
import {PlayObject} from "../../../core/Atomic.js";
|
||||
|
||||
export class JellyfinPlayerState extends GenericPlayerState {
|
||||
constructor(logger: Logger, platformId: PlayPlatformId, opts?: PlayerStateOptions) {
|
||||
@@ -1,5 +1,6 @@
|
||||
import dayjs, {Dayjs} from "dayjs";
|
||||
import {PlayProgress} from "../../common/infrastructure/Atomic.js";
|
||||
|
||||
import {PlayProgress} from "../../../core/Atomic.js";
|
||||
|
||||
export class ListenProgress implements PlayProgress {
|
||||
|
||||
@@ -7,10 +7,11 @@ import AbstractSource from "./AbstractSource.js";
|
||||
import formidable from 'formidable';
|
||||
import concatStream from 'concat-stream';
|
||||
import {PlexSourceConfig} from "../common/infrastructure/config/source/plex.js";
|
||||
import {FormatPlayObjectOptions, InternalConfig, PlayObject, SourceType} from "../common/infrastructure/Atomic.js";
|
||||
import {FormatPlayObjectOptions, InternalConfig, SourceType} from "../common/infrastructure/Atomic.js";
|
||||
import EventEmitter from "events";
|
||||
import winston from '@foxxmd/winston';
|
||||
import {buildTrackString, truncateStringToLength} from "../utils/StringUtils.js";
|
||||
import {PlayObject} from "../../core/Atomic.js";
|
||||
import {truncateStringToLength} from "../../core/StringUtils.js";
|
||||
|
||||
const shortDeviceId = truncateStringToLength(10, '');
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
InternalConfig,
|
||||
NO_USER,
|
||||
PlayerStateData,
|
||||
PlayObject, ReportedPlayerStatus, SourceData
|
||||
ReportedPlayerStatus, SourceData
|
||||
} from "../common/infrastructure/Atomic.js";
|
||||
import PlayHistoryObject = SpotifyApi.PlayHistoryObject;
|
||||
import EventEmitter from "events";
|
||||
@@ -23,7 +23,8 @@ import AlbumObjectSimplified = SpotifyApi.AlbumObjectSimplified;
|
||||
import UserDevice = SpotifyApi.UserDevice;
|
||||
import MemorySource from "./MemorySource.js";
|
||||
import {ErrorWithCause} from "pony-cause";
|
||||
import {buildTrackString, truncateStringToLength} from "../utils/StringUtils.js";
|
||||
import {PlayObject} from "../../core/Atomic.js";
|
||||
import {buildTrackString, truncateStringToLength} from "../../core/StringUtils.js";
|
||||
|
||||
const scopes = ['user-read-recently-played', 'user-read-currently-playing', 'user-read-playback-state', 'user-read-playback-position'];
|
||||
const state = 'random';
|
||||
@@ -5,10 +5,10 @@ import isSameOrAfter from "dayjs/plugin/isSameOrAfter.js";
|
||||
import {parseRetryAfterSecsFromObj, removeDuplicates, sleep} from "../utils.js";
|
||||
import MemorySource from "./MemorySource.js";
|
||||
import {SubSonicSourceConfig} from "../common/infrastructure/config/source/subsonic.js";
|
||||
import {FormatPlayObjectOptions, InternalConfig, PlayObject} from "../common/infrastructure/Atomic.js";
|
||||
import {FormatPlayObjectOptions, InternalConfig} from "../common/infrastructure/Atomic.js";
|
||||
import {RecentlyPlayedOptions} from "./AbstractSource.js";
|
||||
import EventEmitter from "events";
|
||||
import {buildTrackString} from "../utils/StringUtils.js";
|
||||
import {PlayObject} from "../../core/Atomic.js";
|
||||
|
||||
dayjs.extend(isSameOrAfter);
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import dayjs from "dayjs";
|
||||
import PlexSource from "./PlexSource.js";
|
||||
import {TautulliSourceConfig} from "../common/infrastructure/config/source/tautulli.js";
|
||||
import {FormatPlayObjectOptions, InternalConfig, PlayObject} from "../common/infrastructure/Atomic.js";
|
||||
import {FormatPlayObjectOptions, InternalConfig} from "../common/infrastructure/Atomic.js";
|
||||
import {combinePartsToString} from "../utils.js";
|
||||
import EventEmitter from "events";
|
||||
import {Request} from "express";
|
||||
import {truncateStringToLength} from "../utils/StringUtils.js";
|
||||
import {PlayObject} from "../../core/Atomic.js";
|
||||
import {truncateStringToLength} from "../../core/StringUtils.js";
|
||||
|
||||
const shortDeviceId = truncateStringToLength(10, '');
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import YouTubeMusic from "youtube-music-ts-api";
|
||||
|
||||
import AbstractSource, {RecentlyPlayedOptions} from "./AbstractSource.js";
|
||||
import {FormatPlayObjectOptions, InternalConfig, PlayObject} from "../common/infrastructure/Atomic.js";
|
||||
import {FormatPlayObjectOptions, InternalConfig} from "../common/infrastructure/Atomic.js";
|
||||
import {IYouTubeMusicAuthenticated} from "youtube-music-ts-api/interfaces-primary";
|
||||
import dayjs from "dayjs";
|
||||
import {parseDurationFromTimestamp, playObjDataMatch} from "../utils.js";
|
||||
import {IPlaylistDetail, ITrackDetail} from "youtube-music-ts-api/interfaces-supplementary";
|
||||
import {YTMusicSourceConfig} from "../common/infrastructure/config/source/ytmusic.js";
|
||||
import EventEmitter from "events";
|
||||
import {PlayObject} from "../../core/Atomic.js";
|
||||
|
||||
export default class YTMusicSource extends AbstractSource {
|
||||
apiInstance?: IYouTubeMusicAuthenticated
|
||||