@nuclearplayer/plugin-sdk
Advanced tools
+190
-13
@@ -14,2 +14,4 @@ export declare type Album = { | ||
| export declare type AlbumMetadataCapability = 'albumDetails'; | ||
| export declare type AlbumRef = { | ||
@@ -101,2 +103,25 @@ title: string; | ||
| export declare type FetchFunction = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>; | ||
| export declare class HttpAPI { | ||
| readonly fetch: FetchFunction; | ||
| constructor(host?: HttpHost); | ||
| } | ||
| export declare type HttpHost = { | ||
| fetch: (url: string, init?: HttpRequestInit) => Promise<HttpResponseData>; | ||
| }; | ||
| export declare type HttpRequestInit = { | ||
| method?: string; | ||
| headers?: Record<string, string>; | ||
| body?: string; | ||
| }; | ||
| export declare type HttpResponseData = { | ||
| status: number; | ||
| headers: Record<string, string>; | ||
| body: string; | ||
| }; | ||
| export declare type LoadedPlugin = { | ||
@@ -119,5 +144,26 @@ metadata: PluginMetadata; | ||
| declare class MetadataAPI { | ||
| #private; | ||
| constructor(host?: MetadataHost); | ||
| search(params: SearchParams, providerId?: string): Promise<SearchResults>; | ||
| fetchArtistDetails(artistId: string, providerId?: string): Promise<Artist>; | ||
| fetchArtistAlbums(artistId: string, providerId?: string): Promise<AlbumRef[]>; | ||
| fetchArtistTopTracks(artistId: string, providerId?: string): Promise<TrackRef[]>; | ||
| fetchArtistRelatedArtists(artistId: string, providerId?: string): Promise<ArtistRef[]>; | ||
| fetchAlbumDetails(albumId: string, providerId?: string): Promise<Album>; | ||
| } | ||
| export declare type MetadataHost = { | ||
| search: (params: SearchParams, providerId?: string) => Promise<SearchResults>; | ||
| fetchArtistDetails: (artistId: string, providerId?: string) => Promise<Artist>; | ||
| fetchArtistAlbums: (artistId: string, providerId?: string) => Promise<AlbumRef[]>; | ||
| fetchArtistTopTracks: (artistId: string, providerId?: string) => Promise<TrackRef[]>; | ||
| fetchArtistRelatedArtists: (artistId: string, providerId?: string) => Promise<ArtistRef[]>; | ||
| fetchAlbumDetails: (albumId: string, providerId?: string) => Promise<Album>; | ||
| }; | ||
| export declare type MetadataProvider = ProviderDescriptor<'metadata'> & { | ||
| searchCapabilities?: SearchCapability[]; | ||
| artistMetadataCapabilities?: ArtistMetadataCapability[]; | ||
| albumMetadataCapabilities?: AlbumMetadataCapability[]; | ||
| search?: (params: SearchParams) => Promise<SearchResults>; | ||
@@ -142,5 +188,15 @@ searchArtists?: (params: Omit<SearchParams, 'types'>) => Promise<ArtistRef[]>; | ||
| readonly Providers: Providers; | ||
| readonly Queue: QueueAPI; | ||
| readonly Streaming: StreamingAPI; | ||
| readonly Metadata: MetadataAPI; | ||
| readonly Http: HttpAPI; | ||
| readonly Ytdlp: YtdlpAPI; | ||
| constructor(opts?: { | ||
| settingsHost?: SettingsHost; | ||
| providersHost?: ProvidersHost; | ||
| queueHost?: QueueHost; | ||
| streamingHost?: StreamingHost; | ||
| metadataHost?: MetadataHost; | ||
| httpHost?: HttpHost; | ||
| ytdlpHost?: YtdlpHost; | ||
| }); | ||
@@ -151,5 +207,5 @@ } | ||
| onLoad?(api: NuclearPluginAPI): void | Promise<void>; | ||
| onUnload?(): void | Promise<void>; | ||
| onUnload?(api: NuclearPluginAPI): void | Promise<void>; | ||
| onEnable?(api: NuclearPluginAPI): void | Promise<void>; | ||
| onDisable?(): void | Promise<void>; | ||
| onDisable?(api: NuclearPluginAPI): void | Promise<void>; | ||
| }; | ||
@@ -281,14 +337,72 @@ | ||
| export declare type Queue = { | ||
| items: QueueItem[]; | ||
| currentIndex: number; | ||
| repeatMode: RepeatMode; | ||
| shuffleEnabled: boolean; | ||
| }; | ||
| declare class QueueAPI { | ||
| #private; | ||
| constructor(host?: QueueHost); | ||
| getQueue(): Promise<Queue>; | ||
| getCurrentItem(): Promise<QueueItem | undefined>; | ||
| addToQueue(tracks: Track[]): Promise<void>; | ||
| addNext(tracks: Track[]): Promise<void>; | ||
| addAt(tracks: Track[], index: number): Promise<void>; | ||
| removeByIds(ids: string[]): Promise<void>; | ||
| removeByIndices(indices: number[]): Promise<void>; | ||
| clearQueue(): Promise<void>; | ||
| reorder(fromIndex: number, toIndex: number): Promise<void>; | ||
| updateItemState(id: string, updates: QueueItemStateUpdate): Promise<void>; | ||
| goToNext(): Promise<void>; | ||
| goToPrevious(): Promise<void>; | ||
| goToIndex(index: number): Promise<void>; | ||
| goToId(id: string): Promise<void>; | ||
| setRepeatMode(mode: RepeatMode): Promise<void>; | ||
| setShuffleEnabled(enabled: boolean): Promise<void>; | ||
| subscribe(listener: (queue: Queue) => void): () => void; | ||
| subscribeToCurrentItem(listener: (item: QueueItem | undefined) => void): () => void; | ||
| } | ||
| export declare type QueueHost = { | ||
| getQueue: () => Promise<Queue>; | ||
| getCurrentItem: () => Promise<QueueItem | undefined>; | ||
| addToQueue: (tracks: Track[]) => Promise<void>; | ||
| addNext: (tracks: Track[]) => Promise<void>; | ||
| addAt: (tracks: Track[], index: number) => Promise<void>; | ||
| removeByIds: (ids: string[]) => Promise<void>; | ||
| removeByIndices: (indices: number[]) => Promise<void>; | ||
| clearQueue: () => Promise<void>; | ||
| reorder: (fromIndex: number, toIndex: number) => Promise<void>; | ||
| updateItemState: (id: string, updates: QueueItemStateUpdate) => Promise<void>; | ||
| goToNext: () => Promise<void>; | ||
| goToPrevious: () => Promise<void>; | ||
| goToIndex: (index: number) => Promise<void>; | ||
| goToId: (id: string) => Promise<void>; | ||
| setRepeatMode: (mode: RepeatMode) => Promise<void>; | ||
| setShuffleEnabled: (enabled: boolean) => Promise<void>; | ||
| subscribe: (listener: QueueListener) => () => void; | ||
| subscribeToCurrentItem: (listener: QueueItemListener) => () => void; | ||
| }; | ||
| export declare type QueueItem = { | ||
| id: string; | ||
| title: string; | ||
| artists: ArtistCredit[]; | ||
| album?: string; | ||
| durationMs?: number; | ||
| artwork?: ArtworkSet; | ||
| note?: string; | ||
| addedAtIso?: string; | ||
| source: ProviderRef; | ||
| track: Track; | ||
| status: 'idle' | 'loading' | 'success' | 'error'; | ||
| error?: string; | ||
| addedAtIso: string; | ||
| }; | ||
| export declare type QueueItemListener = (item: QueueItem | undefined) => void; | ||
| export declare type QueueItemStateUpdate = Partial<{ | ||
| status: QueueItem['status']; | ||
| error: QueueItem['error']; | ||
| }>; | ||
| export declare type QueueListener = (queue: Queue) => void; | ||
| export declare type RepeatMode = 'off' | 'all' | 'one'; | ||
| export declare type SearchCapability = SearchCategory | 'unified'; | ||
@@ -318,3 +432,3 @@ | ||
| constructor(host?: SettingsHost); | ||
| register(defs: SettingDefinition[], source: SettingSource): Promise<SettingsRegistrationResult>; | ||
| register(defs: SettingDefinition[]): Promise<SettingsRegistrationResult>; | ||
| get<T extends SettingValue = SettingValue>(id: string): Promise<T | undefined>; | ||
@@ -326,3 +440,3 @@ set<T extends SettingValue = SettingValue>(id: string, value: T): Promise<void>; | ||
| export declare type SettingsHost = { | ||
| register(defs: SettingDefinition[], source: SettingSource): Promise<SettingsRegistrationResult>; | ||
| register(defs: SettingDefinition[]): Promise<SettingsRegistrationResult>; | ||
| get<T extends SettingValue = SettingValue>(id: string): Promise<T | undefined>; | ||
@@ -364,2 +478,39 @@ set<T extends SettingValue = SettingValue>(id: string, value: T): Promise<void>; | ||
| export declare type StreamCandidate = { | ||
| id: string; | ||
| title: string; | ||
| durationMs?: number; | ||
| thumbnail?: string; | ||
| stream?: Stream; | ||
| lastResolvedAtIso?: string; | ||
| failed: boolean; | ||
| source: ProviderRef; | ||
| }; | ||
| declare class StreamingAPI { | ||
| #private; | ||
| constructor(host?: StreamingHost); | ||
| resolveCandidatesForTrack(track: Track): Promise<StreamResolutionResult>; | ||
| resolveStreamForCandidate(candidate: StreamCandidate): Promise<StreamCandidate | undefined>; | ||
| } | ||
| export declare type StreamingHost = { | ||
| resolveCandidatesForTrack: (track: Track) => Promise<StreamResolutionResult>; | ||
| resolveStreamForCandidate: (candidate: StreamCandidate) => Promise<StreamCandidate | undefined>; | ||
| }; | ||
| export declare type StreamingProvider = ProviderDescriptor<'streaming'> & { | ||
| searchForTrack: (artist: string, title: string, album?: string) => Promise<StreamCandidate[]>; | ||
| getStreamUrl: (candidateId: string) => Promise<Stream>; | ||
| supportsLocalFiles?: boolean; | ||
| }; | ||
| export declare type StreamResolutionResult = { | ||
| success: true; | ||
| candidates: StreamCandidate[]; | ||
| } | { | ||
| success: false; | ||
| error: string; | ||
| }; | ||
| export declare type StringFormat = 'text' | 'url' | 'path' | 'token' | 'language'; | ||
@@ -406,3 +557,3 @@ | ||
| localFile?: LocalFileInfo; | ||
| streams?: Stream[]; | ||
| streamCandidates?: StreamCandidate[]; | ||
| }; | ||
@@ -419,2 +570,28 @@ | ||
| export declare class YtdlpAPI { | ||
| private host?; | ||
| constructor(host?: YtdlpHost); | ||
| get available(): boolean; | ||
| search(query: string, maxResults?: number): Promise<YtdlpSearchResult[]>; | ||
| getStream(videoId: string): Promise<YtdlpStreamInfo>; | ||
| } | ||
| export declare type YtdlpHost = { | ||
| search: (query: string, maxResults?: number) => Promise<YtdlpSearchResult[]>; | ||
| getStream: (videoId: string) => Promise<YtdlpStreamInfo>; | ||
| }; | ||
| export declare type YtdlpSearchResult = { | ||
| id: string; | ||
| title: string; | ||
| duration: number | null; | ||
| thumbnail: string | null; | ||
| }; | ||
| export declare type YtdlpStreamInfo = { | ||
| stream_url: string; | ||
| duration: number | null; | ||
| title: string | null; | ||
| }; | ||
| export { } |
+212
-34
@@ -1,3 +0,38 @@ | ||
| import { useState as g, useEffect as f, useMemo as b } from "react"; | ||
| import { useState as l, useEffect as f, useMemo as g } from "react"; | ||
| const b = (s) => { | ||
| if (s instanceof Headers) { | ||
| const t = {}; | ||
| return s.forEach((e, r) => { | ||
| t[r] = e; | ||
| }), t; | ||
| } | ||
| return Array.isArray(s) ? Object.fromEntries(s) : s; | ||
| }; | ||
| function m(s) { | ||
| return async (t, e) => { | ||
| const r = String(t instanceof Request ? t.url : t), a = e?.headers ? b(e.headers) : void 0, o = typeof e?.body == "string" ? e.body : void 0, u = await s.fetch(r, { | ||
| method: e?.method, | ||
| headers: a, | ||
| body: o | ||
| }); | ||
| return new Response(u.body, { | ||
| status: u.status, | ||
| headers: new Headers(u.headers) | ||
| }); | ||
| }; | ||
| } | ||
| const A = { | ||
| fetch: async () => ({ | ||
| status: 501, | ||
| headers: {}, | ||
| body: "HTTP host not configured" | ||
| }) | ||
| }; | ||
| class p { | ||
| fetch; | ||
| constructor(t) { | ||
| this.fetch = m(t ?? A); | ||
| } | ||
| } | ||
| class v { | ||
| #e; | ||
@@ -10,2 +45,34 @@ constructor(t) { | ||
| if (!e) | ||
| throw new Error("Metadata host not available"); | ||
| return t(e); | ||
| } | ||
| search(t, e) { | ||
| return this.#t((r) => r.search(t, e)); | ||
| } | ||
| fetchArtistDetails(t, e) { | ||
| return this.#t((r) => r.fetchArtistDetails(t, e)); | ||
| } | ||
| fetchArtistAlbums(t, e) { | ||
| return this.#t((r) => r.fetchArtistAlbums(t, e)); | ||
| } | ||
| fetchArtistTopTracks(t, e) { | ||
| return this.#t((r) => r.fetchArtistTopTracks(t, e)); | ||
| } | ||
| fetchArtistRelatedArtists(t, e) { | ||
| return this.#t( | ||
| (r) => r.fetchArtistRelatedArtists(t, e) | ||
| ); | ||
| } | ||
| fetchAlbumDetails(t, e) { | ||
| return this.#t((r) => r.fetchAlbumDetails(t, e)); | ||
| } | ||
| } | ||
| class w { | ||
| #e; | ||
| constructor(t) { | ||
| this.#e = t; | ||
| } | ||
| #t(t) { | ||
| const e = this.#e; | ||
| if (!e) | ||
| throw new Error("Providers host not available"); | ||
@@ -27,3 +94,3 @@ return t(e); | ||
| } | ||
| class d { | ||
| class I { | ||
| #e; | ||
@@ -36,7 +103,73 @@ constructor(t) { | ||
| if (!e) | ||
| throw new Error("Queue host not available"); | ||
| return t(e); | ||
| } | ||
| getQueue() { | ||
| return this.#t((t) => t.getQueue()); | ||
| } | ||
| getCurrentItem() { | ||
| return this.#t((t) => t.getCurrentItem()); | ||
| } | ||
| addToQueue(t) { | ||
| return this.#t((e) => e.addToQueue(t)); | ||
| } | ||
| addNext(t) { | ||
| return this.#t((e) => e.addNext(t)); | ||
| } | ||
| addAt(t, e) { | ||
| return this.#t((r) => r.addAt(t, e)); | ||
| } | ||
| removeByIds(t) { | ||
| return this.#t((e) => e.removeByIds(t)); | ||
| } | ||
| removeByIndices(t) { | ||
| return this.#t((e) => e.removeByIndices(t)); | ||
| } | ||
| clearQueue() { | ||
| return this.#t((t) => t.clearQueue()); | ||
| } | ||
| reorder(t, e) { | ||
| return this.#t((r) => r.reorder(t, e)); | ||
| } | ||
| updateItemState(t, e) { | ||
| return this.#t((r) => r.updateItemState(t, e)); | ||
| } | ||
| goToNext() { | ||
| return this.#t((t) => t.goToNext()); | ||
| } | ||
| goToPrevious() { | ||
| return this.#t((t) => t.goToPrevious()); | ||
| } | ||
| goToIndex(t) { | ||
| return this.#t((e) => e.goToIndex(t)); | ||
| } | ||
| goToId(t) { | ||
| return this.#t((e) => e.goToId(t)); | ||
| } | ||
| setRepeatMode(t) { | ||
| return this.#t((e) => e.setRepeatMode(t)); | ||
| } | ||
| setShuffleEnabled(t) { | ||
| return this.#t((e) => e.setShuffleEnabled(t)); | ||
| } | ||
| subscribe(t) { | ||
| return this.#t((e) => e.subscribe(t)); | ||
| } | ||
| subscribeToCurrentItem(t) { | ||
| return this.#t((e) => e.subscribeToCurrentItem(t)); | ||
| } | ||
| } | ||
| class T { | ||
| #e; | ||
| constructor(t) { | ||
| this.#e = t; | ||
| } | ||
| #t(t) { | ||
| const e = this.#e; | ||
| if (!e) | ||
| throw new Error("Settings host not available"); | ||
| return t(e); | ||
| } | ||
| register(t, e) { | ||
| return this.#t((i) => i.register(t, e)); | ||
| register(t) { | ||
| return this.#t((e) => e.register(t)); | ||
| } | ||
@@ -47,16 +180,59 @@ get(t) { | ||
| set(t, e) { | ||
| return this.#t((i) => i.set(t, e)); | ||
| return this.#t((r) => r.set(t, e)); | ||
| } | ||
| subscribe(t, e) { | ||
| return this.#t((i) => i.subscribe(t, e)); | ||
| return this.#t((r) => r.subscribe(t, e)); | ||
| } | ||
| } | ||
| class v { | ||
| class y { | ||
| #e; | ||
| constructor(t) { | ||
| this.#e = t; | ||
| } | ||
| #t(t) { | ||
| const e = this.#e; | ||
| if (!e) | ||
| throw new Error("Streaming host not available"); | ||
| return t(e); | ||
| } | ||
| resolveCandidatesForTrack(t) { | ||
| return this.#t((e) => e.resolveCandidatesForTrack(t)); | ||
| } | ||
| resolveStreamForCandidate(t) { | ||
| return this.#t((e) => e.resolveStreamForCandidate(t)); | ||
| } | ||
| } | ||
| class H { | ||
| host; | ||
| constructor(t) { | ||
| this.host = t; | ||
| } | ||
| get available() { | ||
| return !!this.host; | ||
| } | ||
| async search(t, e) { | ||
| if (!this.host) | ||
| throw new Error("YtdlpAPI: No host configured"); | ||
| return this.host.search(t, e); | ||
| } | ||
| async getStream(t) { | ||
| if (!this.host) | ||
| throw new Error("YtdlpAPI: No host configured"); | ||
| return this.host.getStream(t); | ||
| } | ||
| } | ||
| class S { | ||
| Settings; | ||
| Providers; | ||
| Queue; | ||
| Streaming; | ||
| Metadata; | ||
| Http; | ||
| Ytdlp; | ||
| // All these are optional so we don't have to provide all of them in tests | ||
| constructor(t) { | ||
| this.Settings = new d(t?.settingsHost), this.Providers = new p(t?.providersHost); | ||
| this.Settings = new T(t?.settingsHost), this.Providers = new w(t?.providersHost), this.Queue = new I(t?.queueHost), this.Streaming = new y(t?.streamingHost), this.Metadata = new v(t?.metadataHost), this.Http = new p(t?.httpHost), this.Ytdlp = new H(t?.ytdlpHost); | ||
| } | ||
| } | ||
| class w extends v { | ||
| class P extends S { | ||
| } | ||
@@ -68,33 +244,33 @@ class M extends Error { | ||
| } | ||
| const A = (s, t) => { | ||
| const [e, i] = g(void 0); | ||
| const C = (s, t) => { | ||
| const [e, r] = l(void 0); | ||
| f(() => { | ||
| if (!s) | ||
| return; | ||
| let u = !0, c = !1; | ||
| const r = s.subscribe(t, (n) => { | ||
| u && (c = !0, i(n)); | ||
| let o = !0, u = !1; | ||
| const i = s.subscribe(t, (n) => { | ||
| o && (u = !0, r(n)); | ||
| }); | ||
| return s.get(t).then((n) => { | ||
| u && (c || i(n)); | ||
| o && (u || r(n)); | ||
| }), () => { | ||
| u = !1, r && r(); | ||
| o = !1, i && i(); | ||
| }; | ||
| }, [t, s]); | ||
| const o = b( | ||
| () => (u) => { | ||
| s && s.set(t, u); | ||
| const a = g( | ||
| () => (o) => { | ||
| s && s.set(t, o); | ||
| }, | ||
| [t, s] | ||
| ); | ||
| return [e, o]; | ||
| return [e, a]; | ||
| }; | ||
| function E(s, t, e) { | ||
| function R(s, t, e) { | ||
| if (!s?.items?.length) | ||
| return; | ||
| const i = s.items.filter((r) => !(r.purpose && r.purpose !== t || !r.url)); | ||
| if (!i.length) | ||
| const r = s.items.filter((i) => !(i.purpose && i.purpose !== t || !i.url)); | ||
| if (!r.length) | ||
| return s.items[0]; | ||
| const o = (r) => !r.width || !r.height ? 1 : r.width / r.height, c = ((r) => { | ||
| switch (r) { | ||
| const a = (i) => !i.width || !i.height ? 1 : i.width / i.height, u = ((i) => { | ||
| switch (i) { | ||
| case "avatar": | ||
@@ -111,16 +287,18 @@ case "thumbnail": | ||
| })(t); | ||
| return i.map((r) => { | ||
| const n = Math.min(r.width || 0, r.height || 0), a = Math.abs(o(r) - c), h = Math.abs(n - e), l = n < e ? e / n : 1; | ||
| return r.map((i) => { | ||
| const n = Math.min(i.width || 0, i.height || 0), h = Math.abs(a(i) - u), c = Math.abs(n - e), d = n < e ? e / n : 1; | ||
| return { | ||
| artwork: r, | ||
| score: (l > 1.5 ? -1e3 : 0) + -a * 50 + -h * 0.1 | ||
| artwork: i, | ||
| score: (d > 1.5 ? -1e3 : 0) + -h * 50 + -c * 0.1 | ||
| }; | ||
| }).sort((r, n) => n.score - r.score)[0]?.artwork; | ||
| }).sort((i, n) => n.score - i.score)[0]?.artwork; | ||
| } | ||
| export { | ||
| p as HttpAPI, | ||
| M as MissingCapabilityError, | ||
| v as NuclearAPI, | ||
| w as NuclearPluginAPI, | ||
| E as pickArtwork, | ||
| A as useSetting | ||
| S as NuclearAPI, | ||
| P as NuclearPluginAPI, | ||
| H as YtdlpAPI, | ||
| R as pickArtwork, | ||
| C as useSetting | ||
| }; |
+3
-3
| { | ||
| "name": "@nuclearplayer/plugin-sdk", | ||
| "version": "0.0.14", | ||
| "version": "1.0.0", | ||
| "description": "Plugin SDK for Nuclear music player", | ||
@@ -41,4 +41,4 @@ "type": "module", | ||
| "vitest": "^3.2.4", | ||
| "@nuclearplayer/eslint-config": "0.0.9", | ||
| "@nuclearplayer/tailwind-config": "0.0.9" | ||
| "@nuclearplayer/tailwind-config": "0.0.10", | ||
| "@nuclearplayer/eslint-config": "0.0.10" | ||
| }, | ||
@@ -45,0 +45,0 @@ "peerDependencies": { |
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
64687
19.49%805
69.83%0
-100%6
500%