@discord-player/plugin-host 0.0.0-stage → 8.0.0-dev.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +222 -2
  3. package/dist/compat/base-extractor.d.ts +88 -0
  4. package/dist/compat/base-extractor.js +118 -0
  5. package/dist/compat/base-extractor.js.map +1 -0
  6. package/dist/compat/extractor-execution-context.d.ts +69 -0
  7. package/dist/compat/extractor-execution-context.js +121 -0
  8. package/dist/compat/extractor-execution-context.js.map +1 -0
  9. package/dist/compat/extractor-plugin.d.ts +1 -0
  10. package/dist/compat/extractor-plugin.js +117 -0
  11. package/dist/compat/extractor-plugin.js.map +1 -0
  12. package/dist/compat/extractor-types.d.ts +49 -0
  13. package/dist/compat/extractor-types.js +2 -0
  14. package/dist/compat/extractor-types.js.map +1 -0
  15. package/dist/compat/extractors.d.ts +4 -0
  16. package/dist/compat/extractors.js +6 -0
  17. package/dist/compat/extractors.js.map +1 -0
  18. package/dist/compat/query-types.d.ts +15 -0
  19. package/dist/compat/query-types.js +27 -0
  20. package/dist/compat/query-types.js.map +1 -0
  21. package/dist/compat/utils.d.ts +28 -0
  22. package/dist/compat/utils.js +30 -0
  23. package/dist/compat/utils.js.map +1 -0
  24. package/dist/errors.d.ts +56 -0
  25. package/dist/errors.js +63 -0
  26. package/dist/errors.js.map +1 -0
  27. package/dist/host.d.ts +55 -0
  28. package/dist/host.js +230 -0
  29. package/dist/host.js.map +1 -0
  30. package/dist/index.d.ts +16 -0
  31. package/dist/index.js +17 -0
  32. package/dist/index.js.map +1 -0
  33. package/dist/legacy-player.d.ts +47 -0
  34. package/dist/legacy-player.js +2 -0
  35. package/dist/legacy-player.js.map +1 -0
  36. package/dist/plugins/execution-options.d.ts +25 -0
  37. package/dist/plugins/execution-options.js +2 -0
  38. package/dist/plugins/execution-options.js.map +1 -0
  39. package/dist/plugins/operation-dispatch.d.ts +1 -0
  40. package/dist/plugins/operation-dispatch.js +100 -0
  41. package/dist/plugins/operation-dispatch.js.map +1 -0
  42. package/dist/plugins/plugin-error.d.ts +17 -0
  43. package/dist/plugins/plugin-error.js +23 -0
  44. package/dist/plugins/plugin-error.js.map +1 -0
  45. package/dist/plugins/registry-internals.d.ts +58 -0
  46. package/dist/plugins/registry-internals.js +2 -0
  47. package/dist/plugins/registry-internals.js.map +1 -0
  48. package/dist/plugins/registry.d.ts +64 -0
  49. package/dist/plugins/registry.js +229 -0
  50. package/dist/plugins/registry.js.map +1 -0
  51. package/dist/plugins/stream-resolver.d.ts +1 -0
  52. package/dist/plugins/stream-resolver.js +81 -0
  53. package/dist/plugins/stream-resolver.js.map +1 -0
  54. package/dist/plugins/types.d.ts +168 -0
  55. package/dist/plugins/types.js +11 -0
  56. package/dist/plugins/types.js.map +1 -0
  57. package/dist/protocol.d.ts +23 -0
  58. package/dist/protocol.js +27 -0
  59. package/dist/protocol.js.map +1 -0
  60. package/dist/runtime.d.ts +56 -0
  61. package/dist/runtime.js +118 -0
  62. package/dist/runtime.js.map +1 -0
  63. package/dist/structures/playlist.d.ts +139 -0
  64. package/dist/structures/playlist.js +117 -0
  65. package/dist/structures/playlist.js.map +1 -0
  66. package/dist/structures/search-result.d.ts +30 -0
  67. package/dist/structures/search-result.js +35 -0
  68. package/dist/structures/search-result.js.map +1 -0
  69. package/dist/structures/track-types.d.ts +122 -0
  70. package/dist/structures/track-types.js +2 -0
  71. package/dist/structures/track-types.js.map +1 -0
  72. package/dist/structures/track.d.ts +136 -0
  73. package/dist/structures/track.js +268 -0
  74. package/dist/structures/track.js.map +1 -0
  75. package/dist/types.d.ts +25 -0
  76. package/dist/types.js +2 -0
  77. package/dist/types.js.map +1 -0
  78. package/dist/utils/legacy-serialization-schemas.d.ts +164 -0
  79. package/dist/utils/legacy-serialization-schemas.js +108 -0
  80. package/dist/utils/legacy-serialization-schemas.js.map +1 -0
  81. package/dist/utils/parse-serialized-entity.d.ts +1 -0
  82. package/dist/utils/parse-serialized-entity.js +43 -0
  83. package/dist/utils/parse-serialized-entity.js.map +1 -0
  84. package/dist/utils/safe-event-emitter.d.ts +10 -0
  85. package/dist/utils/safe-event-emitter.js +10 -0
  86. package/dist/utils/safe-event-emitter.js.map +1 -0
  87. package/dist/utils/serde.d.ts +54 -0
  88. package/dist/utils/serde.js +83 -0
  89. package/dist/utils/serde.js.map +1 -0
  90. package/dist/utils/serialization-schemas.d.ts +90 -0
  91. package/dist/utils/serialization-schemas.js +45 -0
  92. package/dist/utils/serialization-schemas.js.map +1 -0
  93. package/dist/utils/serialization.d.ts +36 -0
  94. package/dist/utils/serialization.js +84 -0
  95. package/dist/utils/serialization.js.map +1 -0
  96. package/package.json +58 -3
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2020 Androz2091
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,223 @@
1
- # Temporary Holding Version
1
+ # @discord-player/plugin-host
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Standalone source execution for Discord Player v8, built on the published
4
+ `@remedia/plugin-host`. Requires Node.js 22.18 or newer.
5
+
6
+ ```text
7
+ discord-player → @discord-player/plugin-host → @remedia/plugin-host
8
+ ```
9
+
10
+ `discord-player` installs this package as a regular dependency and re-exports its
11
+ canonical `BaseExtractor`, `Track`, `Playlist`, `SearchResult`, plugin declarations,
12
+ registry, and serialization classes. Constructor identity and `instanceof` work
13
+ across package imports. No Player, queue manager, Discord client, native audio
14
+ binding, or Remedia audio engine is initialized by importing or creating a host.
15
+ The `@remedia/client` dependency supplies source descriptor types only.
16
+
17
+ The official `@discord-player/extractor` collection is an optional, separate package.
18
+ It is neither bundled nor automatically registered.
19
+
20
+ ## Registration and direct execution
21
+
22
+ ```ts
23
+ import { createSourcePluginHost, SOURCE_PLUGIN_ID } from '@discord-player/plugin-host';
24
+ import { MyExtractor } from './my-extractor.js';
25
+ import { mySourcePlugin } from './my-source-plugin.js';
26
+
27
+ const host = createSourcePluginHost({ name: 'My Source Host' });
28
+ await host.extractors.register(MyExtractor);
29
+ await host.plugins.register(mySourcePlugin());
30
+
31
+ const response = await host.handle({
32
+ pluginId: SOURCE_PLUGIN_ID,
33
+ operation: 'source.search',
34
+ input: { query: 'example' },
35
+ });
36
+ if (response.handled) console.log(response.result);
37
+ // Or use typed source APIs directly:
38
+ const result = await host.runtime.search('example');
39
+ const track = result.first();
40
+ if (track) {
41
+ const source = await host.plugins.resolveStream(track);
42
+ // Caller owns consumption and destruction of returned Readables.
43
+ console.log(source);
44
+ }
45
+ await host.close();
46
+ ```
47
+
48
+ Direct execution preserves model and stream references. It needs no public URL,
49
+ listener, or serialization. `host.remedia` exposes the actual generic Remedia host
50
+ for discovery and lifecycle inspection; use `host.handle` and `host.fetch` as the
51
+ application entrypoints, so the boundary conversion remains active.
52
+
53
+ Both Player and standalone execution use `SourcePluginRuntime`. There is one
54
+ registry with the existing `first`, `collect`, `pipe`, and `broadcast` policies,
55
+ provider priorities, protocol matching, bridging, registration cancellation,
56
+ post-stream transforms, and cleanup ownership. Player retains all queue, voice,
57
+ Gateway, crossfade, and playback orchestration.
58
+
59
+ ## Hono
60
+
61
+ ```ts
62
+ import { Hono } from 'hono';
63
+ import { serve } from '@hono/node-server';
64
+ import { createSourcePluginHost } from '@discord-player/plugin-host';
65
+ import { MyExtractor } from './my-extractor.js';
66
+
67
+ const host = createSourcePluginHost({ name: 'Sources', publicUrl: 'http://localhost:3000' });
68
+ await host.extractors.register(MyExtractor);
69
+ const app = new Hono();
70
+ app.all('*', (c) => host.fetch(c.req.raw));
71
+ const server = serve({ fetch: app.fetch, port: 3000 });
72
+ process.once('SIGTERM', () => {
73
+ server.close();
74
+ void host.close();
75
+ });
76
+ ```
77
+
78
+ ## Express
79
+
80
+ Mount Remedia's existing middleware at the origin root before body parsers.
81
+
82
+ ```ts
83
+ import express from 'express';
84
+ import { createNodeMiddleware } from '@remedia/plugin-host/node';
85
+ import { createSourcePluginHost } from '@discord-player/plugin-host';
86
+ import { MyExtractor } from './my-extractor.js';
87
+
88
+ const host = createSourcePluginHost({ name: 'Sources', publicUrl: 'http://localhost:3000' });
89
+ await host.extractors.register(MyExtractor);
90
+ const app = express();
91
+ app.use(createNodeMiddleware(host));
92
+ app.use(express.json());
93
+ const server = app.listen(3000);
94
+ process.once('SIGTERM', () => {
95
+ server.close();
96
+ void host.close();
97
+ });
98
+ ```
99
+
100
+ No listener starts automatically. Remedia owns HTTP routing, authorization,
101
+ request limits, media tokens, backpressure, stream cancellation, and disposal.
102
+ Configure `token`, `mediaTtlMs`, and `maxMediaTokens` through the host options.
103
+ `publicUrl` is a trusted HTTP(S) origin and is needed only for HTTP media results.
104
+
105
+ ## HTTP source contract
106
+
107
+ One aggregate registration, `discord-player.source`, advertises:
108
+
109
+ | Operation | Input | Result |
110
+ | ---------------- | ------------------------------- | ------------------------------------------ |
111
+ | `source.search` | `{ query, options? }` | Metadata through normal provider matching |
112
+ | `source.resolve` | `{ query, pluginId, options? }` | Metadata from an explicit provider |
113
+ | `source.related` | `{ track }` | Related metadata from the track's provider |
114
+ | `source.stream` | `{ track }` | Deferred Remedia media descriptor |
115
+
116
+ `source.stream` includes normal bridge fallback and post-stream transformations;
117
+ internal operation handlers are not separate Remedia registrations. HTTP options
118
+ accept portable plugin/source/searchEngine hints, limit, requester ID, and blocked
119
+ provider IDs. Local HTTP request options, functions and Discord objects do not
120
+ cross the boundary. The transport validates input before source execution.
121
+
122
+ ```ts
123
+ import { SOURCE_PLUGIN_ID, deserializeSourceResult, type PortableSourceResult } from '@discord-player/plugin-host';
124
+
125
+ const response = await fetch('http://localhost:3000/remedia/v1/execute', {
126
+ method: 'POST',
127
+ headers: { 'content-type': 'application/json' },
128
+ body: JSON.stringify({ pluginId: SOURCE_PLUGIN_ID, operation: 'source.search', input: { query: 'example' } }),
129
+ });
130
+ if (!response.ok) throw new Error(`Source search failed: ${response.status}`);
131
+ const search = (await response.json()) as { handled: boolean; result: PortableSourceResult };
132
+ if (!search.handled) throw new Error('Unhandled search');
133
+ const localModels = deserializeSourceResult(search.result);
134
+ console.log(localModels.first());
135
+
136
+ // Send the original portable representation back, including its optional state hint.
137
+ const mediaResponse = await fetch('http://localhost:3000/remedia/v1/execute', {
138
+ method: 'POST',
139
+ headers: { 'content-type': 'application/json' },
140
+ body: JSON.stringify({
141
+ pluginId: SOURCE_PLUGIN_ID,
142
+ operation: 'source.stream',
143
+ input: { track: search.result.tracks[0] },
144
+ }),
145
+ });
146
+ if (!mediaResponse.ok) throw new Error(`Source stream failed: ${mediaResponse.status}`);
147
+ const media = (await mediaResponse.json()) as { handled: boolean; result: { url: string; expiresAtMs: number } };
148
+ if (!media.handled) throw new Error('Unhandled media');
149
+ // Redeem media.result.url before expiresAtMs; this is a short-lived, single-use grant.
150
+ const bytes = await fetch(media.result.url);
151
+ console.log(bytes.body);
152
+ ```
153
+
154
+ Media resolution starts when the grant is redeemed, using that request's
155
+ cancellation signal rather than the completed JSON request's signal. Readables,
156
+ files, buffers, and upstream HTTP(S) URLs become encoded byte streams before
157
+ post-stream transforms. URLs are proxied inside the host so provider resolution
158
+ and transforms remain there. The adapter never buffers complete media files.
159
+ Direct calls retain native descriptors; HTTP never returns nested Readables.
160
+
161
+ ## Portable metadata and process-local state
162
+
163
+ HTTP results contain version 1 Track and Playlist data. A `PortableSourceTrack`
164
+ adds an optional opaque `sourceState` field. `deserializeSourceResult()` removes
165
+ that transport extension before strict versioned deserialization and links the
166
+ shared Track instances to their Playlist. For raw `deserialize()`, remove the
167
+ `sourceState` extension first. Provider identity remains in `sourceReference`.
168
+
169
+ The host retains original Track objects (including `raw.engine`, extractor
170
+ identity and metadata callbacks) in a bounded cache: 10000 tracks and one hour by
171
+ default, configurable with `maxTrackStates` and `trackStateTtlMs`. Expiration is
172
+ checked on use; eviction releases references. Cache entries are invalidated by
173
+ provider removal and host shutdown. No media streams are retained by this cache.
174
+
175
+ After eviction, expiration, or restart, modern plugins receive reconstructed
176
+ Track metadata and their portable sourceReference. Legacy extractors re-run
177
+ `handle()` through their original provider using the public Track URL, select the
178
+ matching URL, and then receive the newly constructed Track in `stream()`. If the
179
+ provider is unavailable or cannot reconstruct the track, execution fails with an
180
+ explicit diagnostic. This means queued tracks can survive cache loss when their
181
+ provider supports URL reconstruction; ephemeral-only sources must be searched
182
+ again. A serialized Track is never a promise of permanent source availability.
183
+
184
+ Raw engines, credentials, functions, live streams, circular objects and legacy
185
+ metadata callbacks are never serialized. Modern metadata and sourceReference
186
+ must follow the existing JSON-safe contract; plugins must put only public source
187
+ identifiers there, never secrets. Invalid metadata fails serialization rather
188
+ than being silently removed. Credentials belong to host-local plugin options.
189
+
190
+ ## Legacy compatibility
191
+
192
+ The audited v7 extractors use `this.context.player` for Track/Playlist construction,
193
+ source lookup/bridging, diagnostics and `probeTimeout`. `LegacyPlayerContext` is a
194
+ structural source API, backed by the runtime when standalone and by the actual
195
+ Player when embedded. It supports plugins, extractors, search, debug/error events,
196
+ source options, and legacy constructors. Optional `getRemedia(false)` returns
197
+ null standalone; required audio access and queue/voice operations fail explicitly.
198
+ It never instantiates a dummy Player, queue or voice connection.
199
+
200
+ Legacy raw PCM/Opus override streams remain unsupported: return encoded media.
201
+ Queue history, Discord clients and other process-local playback objects are not
202
+ part of HTTP requests. An extractor that needs arbitrary Player/voice APIs or
203
+ cannot reconstruct private state after expiration requires migration; the source
204
+ compatibility surface does not emulate the whole Player API.
205
+
206
+ ## Remedia server integration boundary
207
+
208
+ The inspected Remedia Rust sources do not implement plugin-host discovery or
209
+ source operation routing. Creating a remote Remedia client does not register this
210
+ host with an audio server. A bot can call this HTTP protocol explicitly and redeem
211
+ the media descriptor, or pass a reachable descriptor URL to supported playback
212
+ APIs before the grant expires. Automatic Rust-side routing still requires host
213
+ configuration/discovery, authenticated operation execution, sourceReference
214
+ round-tripping, cancellation, and timely media-grant redemption. No new Rust API,
215
+ custom IPC transport or automatic remote registry is claimed by this package.
216
+
217
+ ## Validation
218
+
219
+ `pnpm build`, `pnpm typecheck`, `pnpm test`, `pnpm lint`, and `pnpm format:check`
220
+ cover the workspace. Source tests use synthetic encoded bytes and local fixtures,
221
+ without public media-provider access. CI builds the host before discord-player,
222
+ includes its dist artifacts, and publishes it before dependent packages using the
223
+ shared release inventory.
@@ -0,0 +1,88 @@
1
+ import type { Readable } from 'node:stream';
2
+ import type { SourceRuntimeEvents } from '../legacy-player.js';
3
+ import type { Track } from '../structures/track.js';
4
+ import type { Playlist } from '../structures/playlist.js';
5
+ import type { ExtractorExecutionContext } from './extractor-execution-context.js';
6
+ import type { SearchQueryType } from './query-types.js';
7
+ import type { ExtractorInfo, ExtractorSearchContext, ExtractorStreamable, NextFunction } from './extractor-types.js';
8
+ /** Minimal v7 lifecycle and source API, adapted to the source operation registry. */
9
+ export declare class BaseExtractor<Options extends object = object> {
10
+ /** Owning legacy registration context. */
11
+ context: ExtractorExecutionContext;
12
+ /** Original local extractor configuration. */
13
+ options: Options;
14
+ /** Default identifier; subclasses should override. */
15
+ static identifier: string;
16
+ /** Higher priority wins before match confidence. */
17
+ priority: number;
18
+ /** Supported explicit query protocols. */
19
+ protocols: string[];
20
+ /** Create a compatibility instance; use player.extractors.register to own it.
21
+ * @param context Owning compatibility context.
22
+ * @param options Original extractor options.
23
+ */
24
+ constructor(
25
+ /** Owning legacy registration context. */
26
+ context: ExtractorExecutionContext,
27
+ /** Original local extractor configuration. */
28
+ options?: Options);
29
+ /** Subclass static identifier. */
30
+ get identifier(): string;
31
+ /** Obsolete demux support is unavailable. */
32
+ get supportsDemux(): boolean;
33
+ /** Acquire extractor resources. */
34
+ activate(): Promise<void>;
35
+ /** Release extractor resources. */
36
+ deactivate(): Promise<void>;
37
+ /** Recreate the owned registration with new options.
38
+ * @param options Replacement options.
39
+ */
40
+ reconfigure(options: Options): Promise<void>;
41
+ /** Test whether this extractor accepts a query.
42
+ * @param query Search text.
43
+ * @param type Query hint.
44
+ */
45
+ validate(query: string, type?: SearchQueryType | null): Promise<boolean>;
46
+ /** Resolve metadata.
47
+ * @param query Search text.
48
+ * @param context Search hints.
49
+ */
50
+ handle(query: string, context: ExtractorSearchContext): Promise<ExtractorInfo>;
51
+ /** Resolve media, or decline to enable bridging.
52
+ * @param track Track to stream.
53
+ */
54
+ stream(track: Track): Promise<ExtractorStreamable | undefined>;
55
+ /** Resolve related metadata.
56
+ * @param track Seed track.
57
+ * @param history Optional queue history; local execution only.
58
+ */
59
+ getRelatedTracks(track: Track, history?: unknown): Promise<ExtractorInfo>;
60
+ /** Explicit fallback for another source.
61
+ * @param track Track needing a stream.
62
+ * @param sourceExtractor Original extractor, if legacy.
63
+ */
64
+ bridge(track: Track, sourceExtractor: BaseExtractor<any> | null): Promise<ExtractorStreamable | null>;
65
+ /** Transform an encoded local Readable. Raw codec overrides are unsupported.
66
+ * @param stream Encoded input.
67
+ * @param next Completion callback.
68
+ */
69
+ handlePostStream(stream: Readable, next: NextFunction): void;
70
+ /** Build an anonymous bridge query.
71
+ * @param track Track metadata.
72
+ */
73
+ createBridgeQuery(track: Track): string;
74
+ /** Construct a metadata response.
75
+ * @param playlist Optional playlist.
76
+ * @param tracks Ordered tracks, defaulting to playlist entries.
77
+ */
78
+ createResponse(playlist?: Playlist | null, tracks?: Track[]): ExtractorInfo;
79
+ /** Publish a diagnostic.
80
+ * @param message Diagnostic text.
81
+ */
82
+ debug(message: string): void;
83
+ /** Forward supported Player events.
84
+ * @param event Player event.
85
+ * @param args Event arguments.
86
+ */
87
+ emit<K extends keyof SourceRuntimeEvents>(event: K, ...args: SourceRuntimeEvents[K]): boolean;
88
+ }
@@ -0,0 +1,118 @@
1
+ /** Minimal v7 lifecycle and source API, adapted to the source operation registry. */
2
+ export class BaseExtractor {
3
+ context;
4
+ options;
5
+ /** Default identifier; subclasses should override. */
6
+ static identifier = 'org.example.extractor';
7
+ /** Higher priority wins before match confidence. */
8
+ priority = 1;
9
+ /** Supported explicit query protocols. */
10
+ protocols = [];
11
+ /** Create a compatibility instance; use player.extractors.register to own it.
12
+ * @param context Owning compatibility context.
13
+ * @param options Original extractor options.
14
+ */
15
+ constructor(
16
+ /** Owning legacy registration context. */
17
+ context,
18
+ /** Original local extractor configuration. */
19
+ options = {}) {
20
+ this.context = context;
21
+ this.options = options;
22
+ }
23
+ /** Subclass static identifier. */
24
+ get identifier() {
25
+ return this.constructor.identifier;
26
+ }
27
+ /** Obsolete demux support is unavailable. */
28
+ get supportsDemux() {
29
+ return false;
30
+ }
31
+ /** Acquire extractor resources. */
32
+ async activate() { }
33
+ /** Release extractor resources. */
34
+ async deactivate() { }
35
+ /** Recreate the owned registration with new options.
36
+ * @param options Replacement options.
37
+ */
38
+ async reconfigure(options) {
39
+ await this.context.reconfigure(this.identifier, options);
40
+ }
41
+ /** Test whether this extractor accepts a query.
42
+ * @param query Search text.
43
+ * @param type Query hint.
44
+ */
45
+ async validate(query, type) {
46
+ void query;
47
+ void type;
48
+ return false;
49
+ }
50
+ /** Resolve metadata.
51
+ * @param query Search text.
52
+ * @param context Search hints.
53
+ */
54
+ async handle(query, context) {
55
+ void query;
56
+ void context;
57
+ return this.createResponse();
58
+ }
59
+ /** Resolve media, or decline to enable bridging.
60
+ * @param track Track to stream.
61
+ */
62
+ async stream(track) {
63
+ void track;
64
+ return undefined;
65
+ }
66
+ /** Resolve related metadata.
67
+ * @param track Seed track.
68
+ * @param history Optional queue history; local execution only.
69
+ */
70
+ async getRelatedTracks(track, history) {
71
+ void track;
72
+ void history;
73
+ return this.createResponse();
74
+ }
75
+ /** Explicit fallback for another source.
76
+ * @param track Track needing a stream.
77
+ * @param sourceExtractor Original extractor, if legacy.
78
+ */
79
+ async bridge(track, sourceExtractor) {
80
+ void track;
81
+ void sourceExtractor;
82
+ return null;
83
+ }
84
+ /** Transform an encoded local Readable. Raw codec overrides are unsupported.
85
+ * @param stream Encoded input.
86
+ * @param next Completion callback.
87
+ */
88
+ handlePostStream(stream, next) {
89
+ next(null, stream);
90
+ }
91
+ /** Build an anonymous bridge query.
92
+ * @param track Track metadata.
93
+ */
94
+ createBridgeQuery(track) {
95
+ return `${track.title} by ${track.author}`;
96
+ }
97
+ /** Construct a metadata response.
98
+ * @param playlist Optional playlist.
99
+ * @param tracks Ordered tracks, defaulting to playlist entries.
100
+ */
101
+ createResponse(playlist = null, tracks = playlist?.tracks ?? []) {
102
+ return { playlist, tracks };
103
+ }
104
+ /** Publish a diagnostic.
105
+ * @param message Diagnostic text.
106
+ */
107
+ debug(message) {
108
+ this.context.player.debug(message);
109
+ }
110
+ /** Forward supported Player events.
111
+ * @param event Player event.
112
+ * @param args Event arguments.
113
+ */
114
+ emit(event, ...args) {
115
+ return this.context.player.emit(event, ...args);
116
+ }
117
+ }
118
+ //# sourceMappingURL=base-extractor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"base-extractor.js","sourceRoot":"","sources":["../../src/compat/base-extractor.ts"],"names":[],"mappings":"AAQA,qFAAqF;AACrF,MAAM,OAAO,aAAa;IAgBf,OAAO;IAEP,OAAO;IAjBhB,sDAAsD;IAC/C,MAAM,CAAC,UAAU,GAAG,uBAAuB,CAAC;IAEnD,oDAAoD;IAC7C,QAAQ,GAAG,CAAC,CAAC;IAEpB,0CAA0C;IACnC,SAAS,GAAa,EAAE,CAAC;IAEhC;;;OAGG;IACH;IACE,0CAA0C;IACnC,OAAkC;IACzC,8CAA8C;IACvC,OAAO,GAAY,EAAa;uBAFhC,OAAO;uBAEP,OAAO;IACb,CAAC;IAEJ,kCAAkC;IAClC,IAAW,UAAU;QACnB,OAAQ,IAAI,CAAC,WAAoC,CAAC,UAAU,CAAC;IAC/D,CAAC;IAED,6CAA6C;IAC7C,IAAW,aAAa;QACtB,OAAO,KAAK,CAAC;IACf,CAAC;IAED,mCAAmC;IAC5B,KAAK,CAAC,QAAQ,KAAmB,CAAC;IAEzC,mCAAmC;IAC5B,KAAK,CAAC,UAAU,KAAmB,CAAC;IAE3C;;OAEG;IACI,KAAK,CAAC,WAAW,CAAC,OAAgB;QACvC,MAAM,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,IAAI,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;IAC3D,CAAC;IAED;;;OAGG;IACI,KAAK,CAAC,QAAQ,CAAC,KAAa,EAAE,IAA6B;QAChE,KAAK,KAAK,CAAC;QACX,KAAK,IAAI,CAAC;QAEV,OAAO,KAAK,CAAC;IACf,CAAC;IAED;;;OAGG;IACI,KAAK,CAAC,MAAM,CAAC,KAAa,EAAE,OAA+B;QAChE,KAAK,KAAK,CAAC;QACX,KAAK,OAAO,CAAC;QAEb,OAAO,IAAI,CAAC,cAAc,EAAE,CAAC;IAC/B,CAAC;IAED;;OAEG;IACI,KAAK,CAAC,MAAM,CAAC,KAAY;QAC9B,KAAK,KAAK,CAAC;QAEX,OAAO,SAAS,CAAC;IACnB,CAAC;IAED;;;OAGG;IACI,KAAK,CAAC,gBAAgB,CAAC,KAAY,EAAE,OAAiB;QAC3D,KAAK,KAAK,CAAC;QACX,KAAK,OAAO,CAAC;QAEb,OAAO,IAAI,CAAC,cAAc,EAAE,CAAC;IAC/B,CAAC;IAED;;;OAGG;IACI,KAAK,CAAC,MAAM,CAAC,KAAY,EAAE,eAA0C;QAC1E,KAAK,KAAK,CAAC;QACX,KAAK,eAAe,CAAC;QAErB,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;;OAGG;IACI,gBAAgB,CAAC,MAAgB,EAAE,IAAkB;QAC1D,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACrB,CAAC;IAED;;OAEG;IACI,iBAAiB,CAAC,KAAY;QACnC,OAAO,GAAG,KAAK,CAAC,KAAK,OAAO,KAAK,CAAC,MAAM,EAAE,CAAC;IAC7C,CAAC;IAED;;;OAGG;IACI,cAAc,CAAC,QAAQ,GAAoB,IAAI,EAAE,MAAM,GAAY,QAAQ,EAAE,MAAM,IAAI,EAAE;QAC9F,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC;IAC9B,CAAC;IAED;;OAEG;IACI,KAAK,CAAC,OAAe;QAC1B,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IACrC,CAAC;IAED;;;OAGG;IACI,IAAI,CAAsC,KAAQ,EAAE,GAAG,IAA4B;QACxF,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,GAAI,IAAc,CAAC,CAAC;IAC7D,CAAC;CACF"}
@@ -0,0 +1,69 @@
1
+ import type { LegacyPlayerContext } from '../legacy-player.js';
2
+ import type { Track } from '../structures/track.js';
3
+ import type { SourceStream } from '../plugins/types.js';
4
+ import { BaseExtractor } from './base-extractor.js';
5
+ import type { ExtractorConstructor } from './extractor-types.js';
6
+ /** Small legacy ownership adapter; all execution goes through player.plugins. */
7
+ export declare class ExtractorExecutionContext {
8
+ /** Player owning legacy registrations. */
9
+ readonly player: LegacyPlayerContext;
10
+ private bridgeAttempts;
11
+ private instances;
12
+ /** Read-only view of registered legacy instances. */
13
+ get store(): ReadonlyMap<string, BaseExtractor<any>>;
14
+ /** Number of legacy registrations. */
15
+ get size(): number;
16
+ /** Create an adapter owned by one Player.
17
+ * @param player Owning Player.
18
+ */
19
+ constructor(
20
+ /** Player owning legacy registrations. */
21
+ player: LegacyPlayerContext);
22
+ /** Look up a legacy registration.
23
+ * @param id Extractor identifier.
24
+ */
25
+ get(id: string): BaseExtractor<any> | undefined;
26
+ /** Test legacy registration membership.
27
+ * @param id Extractor identifier.
28
+ */
29
+ isRegistered(id: string): boolean;
30
+ /** Check the Player block list.
31
+ * @param id Extractor identifier.
32
+ */
33
+ isDisabled(id: string): boolean;
34
+ /** Check whether a source is allowed.
35
+ * @param id Extractor identifier.
36
+ */
37
+ isEnabled(id: string): boolean;
38
+ /** Register a legacy class through a source plugin.
39
+ * @param Extractor Legacy BaseExtractor subclass constructor.
40
+ * @param options Original options passed to the instance.
41
+ * @returns The owned compatibility instance.
42
+ */
43
+ register<C extends ExtractorConstructor>(Extractor: C, ...args: {} extends ConstructorParameters<C>[1] ? [options?: ConstructorParameters<C>[1]] : [options: ConstructorParameters<C>[1]]): Promise<InstanceType<C>>;
44
+ /** Remove an extractor and its source subscriptions.
45
+ * @param extractor ID or owned instance.
46
+ */
47
+ unregister(extractor: string | BaseExtractor<any>): Promise<boolean>;
48
+ /** Remove all legacy registrations. */
49
+ unregisterAll(): Promise<void>;
50
+ /** Replace an instance through the same registration path.
51
+ * @param id Legacy ID.
52
+ * @param options Replacement options.
53
+ */
54
+ reconfigure(id: string, options: object): Promise<BaseExtractor<any>>;
55
+ /** Register a bundle sequentially with deterministic precedence.
56
+ * @param bundle Legacy constructors.
57
+ * @param options Options keyed by identifier.
58
+ */
59
+ loadMulti(bundle: readonly ExtractorConstructor[], options?: Record<string, object>): Promise<void>;
60
+ /** Request explicit bridge fallback using the shared registry.
61
+ * @param track Track needing a bridge.
62
+ * @param sourceExtractor Original provider.
63
+ */
64
+ requestBridge(track: Track, sourceExtractor?: BaseExtractor<any> | null): Promise<{
65
+ result: SourceStream;
66
+ error: null;
67
+ extractor: BaseExtractor<any> | null;
68
+ }>;
69
+ }
@@ -0,0 +1,121 @@
1
+ import { AsyncLocalStorage } from 'node:async_hooks';
2
+ import { Collection } from '@discordjs/collection';
3
+ import { BaseExtractor } from './base-extractor.js';
4
+ import { createExtractorPlugin } from './extractor-plugin.js';
5
+ /** Small legacy ownership adapter; all execution goes through player.plugins. */
6
+ export class ExtractorExecutionContext {
7
+ player;
8
+ bridgeAttempts = new AsyncLocalStorage();
9
+ instances = new Collection();
10
+ /** Read-only view of registered legacy instances. */
11
+ get store() {
12
+ return this.instances;
13
+ }
14
+ /** Number of legacy registrations. */
15
+ get size() {
16
+ return this.instances.size;
17
+ }
18
+ /** Create an adapter owned by one Player.
19
+ * @param player Owning Player.
20
+ */
21
+ constructor(
22
+ /** Player owning legacy registrations. */
23
+ player) {
24
+ this.player = player;
25
+ }
26
+ /** Look up a legacy registration.
27
+ * @param id Extractor identifier.
28
+ */
29
+ get(id) {
30
+ return this.instances.get(id);
31
+ }
32
+ /** Test legacy registration membership.
33
+ * @param id Extractor identifier.
34
+ */
35
+ isRegistered(id) {
36
+ return this.instances.has(id);
37
+ }
38
+ /** Check the Player block list.
39
+ * @param id Extractor identifier.
40
+ */
41
+ isDisabled(id) {
42
+ return this.player.options.blockExtractors?.includes(id) ?? false;
43
+ }
44
+ /** Check whether a source is allowed.
45
+ * @param id Extractor identifier.
46
+ */
47
+ isEnabled(id) {
48
+ return !this.isDisabled(id);
49
+ }
50
+ /** Register a legacy class through a source plugin.
51
+ * @param Extractor Legacy BaseExtractor subclass constructor.
52
+ * @param options Original options passed to the instance.
53
+ * @returns The owned compatibility instance.
54
+ */
55
+ async register(Extractor, ...args) {
56
+ if (typeof Extractor !== 'function' ||
57
+ !(Extractor.prototype instanceof BaseExtractor) ||
58
+ typeof Extractor.identifier !== 'string') {
59
+ throw new TypeError('extractors.register expects a BaseExtractor subclass constructor.');
60
+ }
61
+ const instance = new Extractor(this, args[0] ?? {});
62
+ const plugin = createExtractorPlugin(instance, this, () => this.bridgeAttempts.getStore(), () => {
63
+ this.instances.set(instance.identifier, instance);
64
+ }, () => {
65
+ if (this.instances.get(instance.identifier) === instance) {
66
+ this.instances.delete(instance.identifier);
67
+ }
68
+ });
69
+ await this.player.plugins.register(plugin());
70
+ return instance;
71
+ }
72
+ /** Remove an extractor and its source subscriptions.
73
+ * @param extractor ID or owned instance.
74
+ */
75
+ async unregister(extractor) {
76
+ return this.player.plugins.unregister(typeof extractor === 'string' ? extractor : extractor.identifier);
77
+ }
78
+ /** Remove all legacy registrations. */
79
+ async unregisterAll() {
80
+ for (const id of this.instances.keys()) {
81
+ await this.unregister(id);
82
+ }
83
+ }
84
+ /** Replace an instance through the same registration path.
85
+ * @param id Legacy ID.
86
+ * @param options Replacement options.
87
+ */
88
+ async reconfigure(id, options) {
89
+ const instance = this.get(id);
90
+ if (!instance) {
91
+ throw new Error(`Unknown extractor: ${id}`);
92
+ }
93
+ const Constructor = instance.constructor;
94
+ await this.unregister(id);
95
+ return this.register(Constructor, options);
96
+ }
97
+ /** Register a bundle sequentially with deterministic precedence.
98
+ * @param bundle Legacy constructors.
99
+ * @param options Options keyed by identifier.
100
+ */
101
+ async loadMulti(bundle, options = {}) {
102
+ for (const Constructor of bundle) {
103
+ await this.register(Constructor, options[Constructor.identifier] ?? {});
104
+ }
105
+ }
106
+ /** Request explicit bridge fallback using the shared registry.
107
+ * @param track Track needing a bridge.
108
+ * @param sourceExtractor Original provider.
109
+ */
110
+ async requestBridge(track, sourceExtractor = track.extractor) {
111
+ const dispatch = () => this.player.plugins.execute('track.bridge', { track, sourcePluginId: sourceExtractor?.identifier }, { blocked: this.player.options.blockExtractors });
112
+ const result = this.bridgeAttempts.getStore()
113
+ ? await dispatch()
114
+ : await this.bridgeAttempts.run(new Set(), dispatch);
115
+ if (result === undefined) {
116
+ throw new Error('No source plugin handled track.bridge');
117
+ }
118
+ return { result, error: null, extractor: track.bridgedExtractor };
119
+ }
120
+ }
121
+ //# sourceMappingURL=extractor-execution-context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extractor-execution-context.js","sourceRoot":"","sources":["../../src/compat/extractor-execution-context.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};