[READ-ONLY] Mirror of https://github.com/FoxxMD/multi-scrobbler. Scrobble plays from multiple sources to multiple clients docs.multi-scrobbler.app
deezer docker jellyfin koito lastfm listenbrainz maloja mopidy mpris music music-assistant plex scrobble self-hosted spotify subsonic tautulli youtube-music
0

Configure Feed

Select the types of activity you want to include in your feed.

refactor: Remove pony-cause dependency

Use built-in error cause and adapt helper functions from pony-cause so we can remove a dependency

FoxxMD (Apr 10, 2024, 10:56 AM EDT) cede77e3 0572d6a3

+118 -46
-9
package-lock.json
··· 55 55 "passport": "^0.6.0", 56 56 "passport-deezer": "^0.2.0", 57 57 "patch-package": "^8.0.0", 58 - "pony-cause": "^1.1.1", 59 58 "postcss": "^8.4.33", 60 59 "react": "^18.2.0", 61 60 "react-dom": "^18.2.0", ··· 8747 8746 "dev": true, 8748 8747 "engines": { 8749 8748 "node": ">=4" 8750 - } 8751 - }, 8752 - "node_modules/pony-cause": { 8753 - "version": "1.1.1", 8754 - "resolved": "https://registry.npmjs.org/pony-cause/-/pony-cause-1.1.1.tgz", 8755 - "integrity": "sha512-PxkIc/2ZpLiEzQXu5YRDOUgBlfGYBY8156HY5ZcRAwwonMk5W/MrJP2LLkG/hF7GEQzaHo2aS7ho6ZLCOvf+6g==", 8756 - "engines": { 8757 - "node": ">=12.0.0" 8758 8749 } 8759 8750 }, 8760 8751 "node_modules/possible-typed-array-names": {
-1
package.json
··· 89 89 "passport": "^0.6.0", 90 90 "passport-deezer": "^0.2.0", 91 91 "patch-package": "^8.0.0", 92 - "pony-cause": "^1.1.1", 93 92 "postcss": "^8.4.33", 94 93 "react": "^18.2.0", 95 94 "react-dom": "^18.2.0",
+1 -1
src/backend/common/errors/NodeErrors.ts
··· 1 - import { findCauseByFunc } from "../../utils.js"; 1 + import { findCauseByFunc } from "../../utils/ErrorUtils.js"; 2 2 import { isArbitraryObject } from "../infrastructure/Atomic.js"; 3 3 import ErrnoException = NodeJS.ErrnoException; 4 4
+2 -1
src/backend/common/errors/UpstreamError.ts
··· 1 1 import { Response } from 'superagent'; 2 - import { findCauseByFunc } from "../../utils.js"; 2 + 3 + import { findCauseByFunc } from "../../utils/ErrorUtils.js"; 3 4 4 5 export class UpstreamError<T = undefined> extends Error { 5 6
+2 -1
src/backend/common/vendor/maloja/interfaces.ts
··· 1 1 import { ResponseError } from "superagent"; 2 - import { findCauseByFunc } from "../../../utils.js"; 2 + 3 + import { findCauseByFunc } from "../../../utils/ErrorUtils.js"; 3 4 import { isSuperAgentResponseError } from "../../errors/ErrorUtils.js"; 4 5 5 6 export interface MalojaV2ScrobbleData {
+1 -1
src/backend/scrobblers/AbstractScrobbleClient.ts
··· 3 3 import EventEmitter from "events"; 4 4 import { FixedSizeList } from 'fixed-size-list'; 5 5 import { nanoid } from "nanoid"; 6 - import { messageWithCauses } from "pony-cause"; 7 6 import { 8 7 DeadLetterScrobble, 9 8 PlayObject, ··· 40 39 sleep, 41 40 sortByOldestPlayDate, 42 41 } from "../utils.js"; 42 + import { messageWithCauses } from "../utils/ErrorUtils.js"; 43 43 import { compareScrobbleArtists, compareScrobbleTracks, normalizeStr } from "../utils/StringUtils.js"; 44 44 import { 45 45 comparePlayTemporally,
+1 -1
src/backend/sources/AbstractSource.ts
··· 23 23 import { SourceConfig } from "../common/infrastructure/config/source/sources.js"; 24 24 import TupleMap from "../common/TupleMap.js"; 25 25 import { 26 - findCauseByFunc, 27 26 formatNumber, 28 27 genGroupId, 29 28 playObjDataMatch, ··· 32 31 sortByNewestPlayDate, 33 32 sortByOldestPlayDate, 34 33 } from "../utils.js"; 34 + import { findCauseByFunc } from "../utils/ErrorUtils.js"; 35 35 import { comparePlayTemporally, temporalAccuracyIsAtLeast } from "../utils/TimeUtils.js"; 36 36 37 37 export interface RecentlyPlayedOptions {
+1 -1
src/backend/sources/ChromecastSource.ts
··· 5 5 import dayjs from "dayjs"; 6 6 import { EventEmitter } from "events"; 7 7 import e from "express"; 8 - import { findCauseByReference } from "pony-cause"; 9 8 import { PlayObject } from "../../core/Atomic.js"; 10 9 import { buildTrackString } from "../../core/StringUtils.js"; 11 10 import { NETWORK_ERROR_FAILURE_CODES } from "../common/errors/NodeErrors.js"; ··· 32 31 PlatformType 33 32 } from "../common/vendor/chromecast/interfaces.js"; 34 33 import { difference, genGroupIdStr, parseBool } from "../utils.js"; 34 + import { findCauseByReference } from "../utils/ErrorUtils.js"; 35 35 import { discoveryAvahi, discoveryNative } from "../utils/MDNSUtils.js"; 36 36 import { RecentlyPlayedOptions } from "./AbstractSource.js"; 37 37 import MemorySource from "./MemorySource.js";
+1 -1
src/backend/sources/SpotifySource.ts
··· 19 19 import { SpotifySourceConfig } from "../common/infrastructure/config/source/spotify.js"; 20 20 import { 21 21 combinePartsToString, 22 - findCauseByFunc, 23 22 parseRetryAfterSecsFromObj, 24 23 readJson, 25 24 sleep, 26 25 sortByOldestPlayDate, 27 26 writeFile, 28 27 } from "../utils.js"; 28 + import { findCauseByFunc } from "../utils/ErrorUtils.js"; 29 29 import { RecentlyPlayedOptions } from "./AbstractSource.js"; 30 30 import MemorySource from "./MemorySource.js"; 31 31 import AlbumObjectSimplified = SpotifyApi.AlbumObjectSimplified;
+2 -1
src/backend/sources/SubsonicSource.ts
··· 9 9 import { DEFAULT_RETRY_MULTIPLIER, FormatPlayObjectOptions, InternalConfig } from "../common/infrastructure/Atomic.js"; 10 10 import { SubSonicSourceConfig } from "../common/infrastructure/config/source/subsonic.js"; 11 11 import { getSubsonicResponse, SubsonicResponse, SubsonicResponseCommon } from "../common/vendor/subsonic/interfaces.js"; 12 - import { findCauseByFunc, parseRetryAfterSecsFromObj, removeDuplicates, sleep } from "../utils.js"; 12 + import { parseRetryAfterSecsFromObj, removeDuplicates, sleep } from "../utils.js"; 13 + import { findCauseByFunc } from "../utils/ErrorUtils.js"; 13 14 import { RecentlyPlayedOptions } from "./AbstractSource.js"; 14 15 import MemorySource from "./MemorySource.js"; 15 16
-28
src/backend/utils.ts
··· 11 11 import { accessSync, constants, promises } from "fs"; 12 12 import JSON5 from 'json5'; 13 13 import pathUtil from "path"; 14 - import { getErrorCause } from "pony-cause"; 15 14 import { TimeoutError, WebapiError } from "spotify-web-api-node/src/response-error.js"; 16 15 import { PlayObject } from "../core/Atomic.js"; 17 16 import { ··· 766 765 return eArtists.length > 1 || cArtists.length > 1; 767 766 } 768 767 769 - /** 770 - * Adapted from https://github.com/voxpelli/pony-cause/blob/main/lib/helpers.js to find cause by truthy function 771 - * */ 772 - export const findCauseByFunc = (err: any, func: (e: Error) => boolean) => { 773 - if (!err || !func) return; 774 - if (!(err instanceof Error)) return; 775 - if (typeof func !== 'function') { 776 - return; 777 - } 778 - 779 - /** 780 - * Ensures we don't go circular 781 - */ 782 - const seen = new Set<Error>(); 783 - 784 - let currentErr: Error | undefined = err; 785 - 786 - while (currentErr && !seen.has(currentErr)) { 787 - seen.add(currentErr); 788 - 789 - if (func(currentErr)) { 790 - return currentErr; 791 - } 792 - 793 - currentErr = getErrorCause(currentErr); 794 - } 795 - };
+107
src/backend/utils/ErrorUtils.ts
··· 1 + /** 2 + * Adapted from https://github.com/voxpelli/pony-cause/blob/main/lib/helpers.js to find cause by truthy function 3 + * */ 4 + export const findCauseByFunc = (err: any, func: (e: Error) => boolean) => { 5 + if (!err || !func) return; 6 + if (!(err instanceof Error)) return; 7 + if (typeof func !== 'function') { 8 + return; 9 + } 10 + 11 + /** 12 + * Ensures we don't go circular 13 + */ 14 + const seen = new Set<Error>(); 15 + 16 + let currentErr: Error | undefined = err; 17 + 18 + while (currentErr && !seen.has(currentErr)) { 19 + seen.add(currentErr); 20 + 21 + if (func(currentErr)) { 22 + return currentErr; 23 + } 24 + 25 + currentErr = getErrorCause(currentErr); 26 + } 27 + }; 28 + export const findCauseByMessage = (err: any, msg: string) => { 29 + return findCauseByFunc(err, (e => e.message.toLocaleLowerCase().includes(msg.toLocaleLowerCase()))); 30 + } 31 + /** 32 + * Adapted from https://github.com/voxpelli/pony-cause 33 + * */ 34 + export const getErrorCause = (err: Error | { 35 + cause?: unknown | (() => Error | { 36 + cause?: unknown | (() => Error | any); 37 + }); 38 + }): Error | undefined => { 39 + if (!err) return; 40 + 41 + const cause = err.cause; 42 + 43 + // VError / NError style causes 44 + if (typeof cause === 'function') { 45 + const causeResult = cause(); 46 + 47 + return causeResult instanceof Error 48 + ? causeResult 49 + : undefined; 50 + } else { 51 + return cause instanceof Error 52 + ? cause 53 + : undefined; 54 + } 55 + }; 56 + /** 57 + * Adapted from https://github.com/voxpelli/pony-cause 58 + * */ 59 + export const findCauseByReference = <T extends Error>(err: unknown, reference: new (...args: any[]) => T): T | undefined => { 60 + if (!err || !reference) return; 61 + if (!(err instanceof Error)) return; 62 + if (!(reference.prototype instanceof Error) && 63 + // @ts-expect-error we are purposely checking if ref is generic error class 64 + reference !== Error) return; 65 + 66 + const seen = new Set(); 67 + 68 + let currentErr = err; 69 + 70 + while (currentErr && !seen.has(currentErr)) { 71 + seen.add(currentErr); 72 + 73 + if (currentErr instanceof reference) { 74 + return currentErr; 75 + } 76 + 77 + currentErr = getErrorCause(currentErr); 78 + } 79 + }; 80 + /** 81 + * Adapted from https://github.com/voxpelli/pony-cause 82 + * */ 83 + const _messageWithCauses = (err: Error, seen = new Set<Error>()) => { 84 + if (!(err instanceof Error)) return ''; 85 + 86 + const message = err.message; 87 + 88 + // Ensure we don't go circular or crazily deep 89 + if (seen.has(err)) { 90 + return message + ': ...'; 91 + } 92 + 93 + const cause = getErrorCause(err); 94 + 95 + if (cause) { 96 + seen.add(err); 97 + 98 + return (message + ': ' + 99 + _messageWithCauses(cause, seen)); 100 + } else { 101 + return message; 102 + } 103 + }; 104 + /** 105 + * Adapted from https://github.com/voxpelli/pony-cause 106 + * */ 107 + export const messageWithCauses = (err: Error) => _messageWithCauses(err);