ziplayer 0.1.3 → 0.1.4

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.
package/README.md CHANGED
@@ -1,212 +1,212 @@
1
- <img width="1175" height="305" alt="logo" src="https://github.com/user-attachments/assets/d4db4892-9c3d-4314-9228-701629555380" />
2
-
3
- # ziplayer
4
-
5
- A modular Discord voice player with plugin system for @discordjs/voice.
6
-
7
- ## Features
8
-
9
- - 🎵 **Plugin-based architecture** - Easy to extend with new sources
10
- - 🎶 **Multiple source support** - YouTube, SoundCloud, Spotify (with fallback)
11
- - 🔊 **Queue management** - Add, remove, shuffle, clear
12
- - 🎚️ **Volume control** - 0-200% volume range
13
- - ⏯️ **Playback control** - Play, pause, resume, stop, skip
14
- - 🔁 **Auto play** - Automatically replay the queue when it ends
15
- - 🔂 **Loop control** - Repeat a single track or the entire queue
16
- - 📊 **Progress bar** - Display playback progress with customizable icons
17
- - 🔔 **Event-driven** - Rich event system for all player actions
18
- - 🎭 **Multi-guild support** - Manage players across multiple Discord servers
19
- - 🗃️ **User data** - Attach custom data to each player for later use
20
- - 🔌 **Lavalink** - Support manage an external Lavalink JVM node
21
-
22
- ## Installation
23
-
24
- ```bash
25
- npm install ziplayer @ziplayer/plugin @ziplayer/extension @discordjs/voice discord.js
26
- ```
27
-
28
- ## Quick Start
29
-
30
- ```typescript
31
- import { PlayerManager } from "ziplayer";
32
- import { SoundCloudPlugin, YouTubePlugin, SpotifyPlugin } from "@ziplayer/plugin";
33
- import { voiceExt } from "@ziplayer/extension";
34
-
35
- const manager = new PlayerManager({
36
- plugins: [new SoundCloudPlugin(), new YouTubePlugin(), new SpotifyPlugin()],
37
- extensions: [
38
- new voiceExt(null, {
39
- lang: "vi-VN",
40
- minimalVoiceMessageDuration: 1,
41
- postSilenceDelayMs: 2000,
42
- }),
43
- ],
44
- });
45
-
46
- // Create player
47
- const player = manager.create(guildId, {
48
- leaveOnEnd: true,
49
- leaveTimeout: 30000,
50
- userdata: { channel: textChannel }, // store channel for events
51
- // Choose extensions for this player (by name or instances)
52
- extensions: ["voiceExt"],
53
- });
54
-
55
- // Connect and play
56
- await player.connect(voiceChannel);
57
- await player.play("Never Gonna Give You Up", userId);
58
-
59
- // Play a full YouTube playlist
60
- await player.play("https://www.youtube.com/playlist?list=PL123", userId);
61
-
62
- // Enable autoplay
63
- player.queue.autoPlay(true);
64
-
65
- // Play a full SoundCloud playlist
66
- await player.play("https://soundcloud.com/artist/sets/playlist", userId);
67
-
68
- // Events
69
- player.on("willPlay", (player, track) => {
70
- console.log(`Up next: ${track.title}`);
71
- });
72
- player.on("trackStart", (player, track) => {
73
- console.log(`Now playing: ${track.title}`);
74
- player.userdata?.channel?.send(`Now playing: ${track.title}`);
75
- });
76
-
77
- // Receive transcripts
78
- manager.on("voiceCreate", (player, evt) => {
79
- console.log(`User ${evt.userId} said: ${evt.content}`);
80
- });
81
- ```
82
-
83
- ### TTS (Interrupt Mode)
84
-
85
- Play short text-to-speech messages without losing music progress. The player pauses music, plays TTS on a dedicated AudioPlayer,
86
- then resumes.
87
-
88
- - Requirements: `@ziplayer/plugin` with `TTSPlugin` installed and registered in `PlayerManager`.
89
-
90
- ```ts
91
- import { PlayerManager } from "ziplayer";
92
- import { TTSPlugin, YouTubePlugin, SoundCloudPlugin, SpotifyPlugin } from "@ziplayer/plugin";
93
-
94
- const manager = new PlayerManager({
95
- plugins: [new TTSPlugin({ defaultLang: "vi" }), new YouTubePlugin(), new SoundCloudPlugin(), new SpotifyPlugin()],
96
- });
97
-
98
- // Create a player with TTS interrupt enabled
99
- const player = manager.create(guildId, {
100
- tts: {
101
- createPlayer: true, // pre-create the internal TTS AudioPlayer
102
- interrupt: true, // pause music, swap to TTS, then resume
103
- volume: 1, // 1 => 100%
104
- },
105
- });
106
-
107
- await player.connect(voiceChannel);
108
-
109
- // Trigger TTS by playing a TTS query (depends on your TTS plugin)
110
- await player.play("tts: xin chào mọi người", userId);
111
-
112
- // Listen to TTS lifecycle events
113
- manager.on("ttsStart", (plr, { track }) => console.log("TTS start", track?.title));
114
- manager.on("ttsEnd", (plr) => console.log("TTS end"));
115
- ```
116
-
117
- Notes
118
-
119
- - The detection uses track.source that includes "tts" or query starting with `tts:`.
120
- - If you need more control, call `player.interruptWithTTSTrack(track)` after building a TTS track via your plugin.
121
- - For CPU-heavy TTS generation, consider offloading to `worker_threads` or a separate process and pass a stream/buffer to the
122
- plugin.
123
-
124
- ### Player Lifecycle Overview
125
-
126
- ```
127
- PlayerManager.create(guild, opts)
128
-
129
-
130
- [Player constructor]
131
- - setup event listeners
132
- - freeze ExtensionContext { player, manager }
133
- - register plugins
134
-
135
-
136
- attachExtension(ext)
137
- - set ext.player
138
- - ext.onRegister?(context)
139
- - ext.active?(...) → false ⇒ detach
140
-
141
-
142
- player.play(query, by)
143
- - runBeforePlayHooks → extensions may mutate query/tracks/start Lavalink
144
- - resolve track list / queue updates / TTS interrupt check
145
- - extensionsProvideStream → extension stream overrides plugin pipeline
146
- - plugin.getStream / getFallback
147
-
148
-
149
- Audio playback
150
- - trackStart / queue events emitted
151
- - runAfterPlayHooks with final outcome
152
-
153
-
154
- player.destroy()
155
- - stop audio/voice / clear queue & plugins
156
- - ext.onDestroy?(context) for each attached extension
157
- - emit playerDestroy & cleanup references
158
- ```
159
-
160
- This diagram shows how custom extensions (voice, lyrics, Lavalink, etc.) integrate across the full player lifecycle and where
161
- their hooks are invoked.
162
-
163
- ### Lavalink Process
164
-
165
- Use `lavalinkExt` when you need ZiPlayer to manage an external Lavalink JVM node. The extension starts, stops, and optionally
166
- restarts the Lavalink jar and forwards lifecycle events through the manager/player.
167
-
168
- ```ts
169
- import { PlayerManager } from "ziplayer";
170
- import { lavalinkExt } from "@ziplayer/extension";
171
-
172
- const lavalink = new lavalinkExt(null, {
173
- nodes: [
174
- {
175
- identifier: "locallavalink",
176
- password: "youshallnotpass",
177
- host: "localhost",
178
- port: 2333,
179
- secure: false,
180
- },
181
- ],
182
- client: client,
183
- searchPrefix: "scsearch",
184
- });
185
-
186
- const manager = new PlayerManager({
187
- extensions: ["lavalinkExt"],
188
- });
189
- ```
190
-
191
- ## Events
192
-
193
- All player events are forwarded through the PlayerManager:
194
-
195
- - `trackStart` - When a track starts playing
196
- - `willPlay` - Before a track begins playing
197
- - `trackEnd` - When a track finishes
198
- - `queueEnd` - When the queue is empty
199
- - `playerError` - When an error occurs
200
- - `queueAdd` - When a track is added
201
- - `volumeChange` - When volume changes
202
- - And more...
203
-
204
- ## Useful Links
205
-
206
- [Example](https://github.com/ZiProject/ZiPlayer/tree/main/examples) | [Repo](https://github.com/ZiProject/ZiPlayer) |
207
- [Package](https://www.npmjs.com/package/ziplayer) | [Plugin](https://www.npmjs.com/package/@ziplayer/plugin) |
208
- [Extension](https://www.npmjs.com/package/@ziplayer/extension)
209
-
210
- ## License
211
-
212
- MIT License
1
+ <img width="1175" height="305" alt="logo" src="https://raw.githubusercontent.com/ZiProject/ZiPlayer/refs/heads/main/publish/logo.png" />
2
+
3
+ # ziplayer
4
+
5
+ A modular Discord voice player with plugin system for @discordjs/voice.
6
+
7
+ ## Features
8
+
9
+ - 🎵 **Plugin-based architecture** - Easy to extend with new sources
10
+ - 🎶 **Multiple source support** - YouTube, SoundCloud, Spotify (with fallback)
11
+ - 🔊 **Queue management** - Add, remove, shuffle, clear
12
+ - 🎚️ **Volume control** - 0-200% volume range
13
+ - ⏯️ **Playback control** - Play, pause, resume, stop, skip
14
+ - 🔁 **Auto play** - Automatically replay the queue when it ends
15
+ - 🔂 **Loop control** - Repeat a single track or the entire queue
16
+ - 📊 **Progress bar** - Display playback progress with customizable icons
17
+ - 🔔 **Event-driven** - Rich event system for all player actions
18
+ - 🎭 **Multi-guild support** - Manage players across multiple Discord servers
19
+ - 🗃️ **User data** - Attach custom data to each player for later use
20
+ - 🔌 **Lavalink** - Support manage an external Lavalink JVM node
21
+
22
+ ## Installation
23
+
24
+ ```bash
25
+ npm install ziplayer @ziplayer/plugin @ziplayer/extension @discordjs/voice discord.js
26
+ ```
27
+
28
+ ## Quick Start
29
+
30
+ ```typescript
31
+ import { PlayerManager } from "ziplayer";
32
+ import { SoundCloudPlugin, YouTubePlugin, SpotifyPlugin } from "@ziplayer/plugin";
33
+ import { voiceExt } from "@ziplayer/extension";
34
+
35
+ const manager = new PlayerManager({
36
+ plugins: [new SoundCloudPlugin(), new YouTubePlugin(), new SpotifyPlugin()],
37
+ extensions: [
38
+ new voiceExt(null, {
39
+ lang: "vi-VN",
40
+ minimalVoiceMessageDuration: 1,
41
+ postSilenceDelayMs: 2000,
42
+ }),
43
+ ],
44
+ });
45
+
46
+ // Create player
47
+ const player = await manager.create(guildId, {
48
+ leaveOnEnd: true,
49
+ leaveTimeout: 30000,
50
+ userdata: { channel: textChannel }, // store channel for events
51
+ // Choose extensions for this player (by name or instances)
52
+ extensions: ["voiceExt"],
53
+ });
54
+
55
+ // Connect and play
56
+ await player.connect(voiceChannel);
57
+ await player.play("Never Gonna Give You Up", userId);
58
+
59
+ // Play a full YouTube playlist
60
+ await player.play("https://www.youtube.com/playlist?list=PL123", userId);
61
+
62
+ // Enable autoplay
63
+ player.queue.autoPlay(true);
64
+
65
+ // Play a full SoundCloud playlist
66
+ await player.play("https://soundcloud.com/artist/sets/playlist", userId);
67
+
68
+ // Events
69
+ player.on("willPlay", (player, track) => {
70
+ console.log(`Up next: ${track.title}`);
71
+ });
72
+ player.on("trackStart", (player, track) => {
73
+ console.log(`Now playing: ${track.title}`);
74
+ player.userdata?.channel?.send(`Now playing: ${track.title}`);
75
+ });
76
+
77
+ // Receive transcripts
78
+ manager.on("voiceCreate", (player, evt) => {
79
+ console.log(`User ${evt.userId} said: ${evt.content}`);
80
+ });
81
+ ```
82
+
83
+ ### TTS (Interrupt Mode)
84
+
85
+ Play short text-to-speech messages without losing music progress. The player pauses music, plays TTS on a dedicated AudioPlayer,
86
+ then resumes.
87
+
88
+ - Requirements: `@ziplayer/plugin` with `TTSPlugin` installed and registered in `PlayerManager`.
89
+
90
+ ```ts
91
+ import { PlayerManager } from "ziplayer";
92
+ import { TTSPlugin, YouTubePlugin, SoundCloudPlugin, SpotifyPlugin } from "@ziplayer/plugin";
93
+
94
+ const manager = new PlayerManager({
95
+ plugins: [new TTSPlugin({ defaultLang: "vi" }), new YouTubePlugin(), new SoundCloudPlugin(), new SpotifyPlugin()],
96
+ });
97
+
98
+ // Create a player with TTS interrupt enabled
99
+ const player = await manager.create(guildId, {
100
+ tts: {
101
+ createPlayer: true, // pre-create the internal TTS AudioPlayer
102
+ interrupt: true, // pause music, swap to TTS, then resume
103
+ volume: 1, // 1 => 100%
104
+ },
105
+ });
106
+
107
+ await player.connect(voiceChannel);
108
+
109
+ // Trigger TTS by playing a TTS query (depends on your TTS plugin)
110
+ await player.play("tts: xin chào mọi người", userId);
111
+
112
+ // Listen to TTS lifecycle events
113
+ manager.on("ttsStart", (plr, { track }) => console.log("TTS start", track?.title));
114
+ manager.on("ttsEnd", (plr) => console.log("TTS end"));
115
+ ```
116
+
117
+ Notes
118
+
119
+ - The detection uses track.source that includes "tts" or query starting with `tts:`.
120
+ - If you need more control, call `player.interruptWithTTSTrack(track)` after building a TTS track via your plugin.
121
+ - For CPU-heavy TTS generation, consider offloading to `worker_threads` or a separate process and pass a stream/buffer to the
122
+ plugin.
123
+
124
+ ### Player Lifecycle Overview
125
+
126
+ ```
127
+ PlayerManager.create(guild, opts)
128
+
129
+
130
+ [Player constructor]
131
+ - setup event listeners
132
+ - freeze ExtensionContext { player, manager }
133
+ - register plugins
134
+
135
+
136
+ attachExtension(ext)
137
+ - set ext.player
138
+ - ext.onRegister?(context)
139
+ - ext.active?(...) → false ⇒ detach
140
+
141
+
142
+ player.play(query, by)
143
+ - runBeforePlayHooks → extensions may mutate query/tracks/start Lavalink
144
+ - resolve track list / queue updates / TTS interrupt check
145
+ - extensionsProvideStream → extension stream overrides plugin pipeline
146
+ - plugin.getStream / getFallback
147
+
148
+
149
+ Audio playback
150
+ - trackStart / queue events emitted
151
+ - runAfterPlayHooks with final outcome
152
+
153
+
154
+ player.destroy()
155
+ - stop audio/voice / clear queue & plugins
156
+ - ext.onDestroy?(context) for each attached extension
157
+ - emit playerDestroy & cleanup references
158
+ ```
159
+
160
+ This diagram shows how custom extensions (voice, lyrics, Lavalink, etc.) integrate across the full player lifecycle and where
161
+ their hooks are invoked.
162
+
163
+ ### Lavalink Process
164
+
165
+ Use `lavalinkExt` when you need ZiPlayer to manage an external Lavalink JVM node. The extension starts, stops, and optionally
166
+ restarts the Lavalink jar and forwards lifecycle events through the manager/player.
167
+
168
+ ```ts
169
+ import { PlayerManager } from "ziplayer";
170
+ import { lavalinkExt } from "@ziplayer/extension";
171
+
172
+ const lavalink = new lavalinkExt(null, {
173
+ nodes: [
174
+ {
175
+ identifier: "locallavalink",
176
+ password: "youshallnotpass",
177
+ host: "localhost",
178
+ port: 2333,
179
+ secure: false,
180
+ },
181
+ ],
182
+ client: client,
183
+ searchPrefix: "scsearch",
184
+ });
185
+
186
+ const manager = new PlayerManager({
187
+ extensions: ["lavalinkExt"],
188
+ });
189
+ ```
190
+
191
+ ## Events
192
+
193
+ All player events are forwarded through the PlayerManager:
194
+
195
+ - `trackStart` - When a track starts playing
196
+ - `willPlay` - Before a track begins playing
197
+ - `trackEnd` - When a track finishes
198
+ - `queueEnd` - When the queue is empty
199
+ - `playerError` - When an error occurs
200
+ - `queueAdd` - When a track is added
201
+ - `volumeChange` - When volume changes
202
+ - And more...
203
+
204
+ ## Useful Links
205
+
206
+ [Example](https://github.com/ZiProject/ZiPlayer/tree/main/examples) | [Repo](https://github.com/ZiProject/ZiPlayer) |
207
+ [Package](https://www.npmjs.com/package/ziplayer) | [Plugin](https://www.npmjs.com/package/@ziplayer/plugin) |
208
+ [Extension](https://www.npmjs.com/package/@ziplayer/extension)
209
+
210
+ ## License
211
+
212
+ MIT License
@@ -0,0 +1,22 @@
1
+ import { BasePlugin } from "./BasePlugin";
2
+ import { Track, SearchResult, StreamInfo } from "../types";
3
+ export declare class SoundCloudPlugin extends BasePlugin {
4
+ name: string;
5
+ version: string;
6
+ private client;
7
+ private ready;
8
+ constructor();
9
+ private init;
10
+ canHandle(query: string): boolean;
11
+ validate(url: string): boolean;
12
+ search(query: string, requestedBy: string): Promise<SearchResult>;
13
+ getStream(track: Track): Promise<StreamInfo>;
14
+ getRelatedTracks(trackURL: string | number, opts?: {
15
+ limit?: number;
16
+ offset?: number;
17
+ history?: Track[];
18
+ }): Promise<Track[]>;
19
+ getFallback(track: Track): Promise<StreamInfo>;
20
+ extractPlaylist(url: string, requestedBy: string): Promise<Track[]>;
21
+ }
22
+ //# sourceMappingURL=SoundCloudPlugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SoundCloudPlugin.d.ts","sourceRoot":"","sources":["../../src/plugins/SoundCloudPlugin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAI3D,qBAAa,gBAAiB,SAAQ,UAAU;IAC9C,IAAI,SAAgB;IACpB,OAAO,SAAW;IAClB,OAAO,CAAC,MAAM,CAAM;IACpB,OAAO,CAAC,KAAK,CAAgB;;YAOf,IAAI;IAKlB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO;IAOjC,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAIxB,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IAsEjE,SAAS,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,UAAU,CAAC;IAmB5C,gBAAgB,CACpB,QAAQ,EAAE,MAAM,GAAG,MAAM,EACzB,IAAI,GAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,KAAK,EAAE,CAAA;KAAO,GAChE,OAAO,CAAC,KAAK,EAAE,CAAC;IAoCb,WAAW,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,UAAU,CAAC;IAS9C,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC;CAsB1E"}
@@ -0,0 +1,171 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SoundCloudPlugin = void 0;
4
+ const BasePlugin_1 = require("./BasePlugin");
5
+ const SoundCloud = require("@zibot/scdl");
6
+ class SoundCloudPlugin extends BasePlugin_1.BasePlugin {
7
+ constructor() {
8
+ super();
9
+ this.name = "soundcloud";
10
+ this.version = "1.0.0";
11
+ this.ready = this.init();
12
+ }
13
+ async init() {
14
+ this.client = new SoundCloud({ init: false });
15
+ await this.client.init();
16
+ }
17
+ canHandle(query) {
18
+ return (query.includes("soundcloud.com") ||
19
+ (!query.startsWith("http") && !query.includes("youtube")));
20
+ }
21
+ validate(url) {
22
+ return url.includes("soundcloud.com");
23
+ }
24
+ async search(query, requestedBy) {
25
+ await this.ready;
26
+ try {
27
+ if (query.includes("soundcloud.com")) {
28
+ try {
29
+ const info = await this.client.getTrackDetails(query);
30
+ const track = {
31
+ id: info.id.toString(),
32
+ title: info.title,
33
+ url: info.permalink_url || query,
34
+ duration: info.duration,
35
+ thumbnail: info.artwork_url,
36
+ requestedBy,
37
+ source: this.name,
38
+ metadata: {
39
+ author: info.user?.username,
40
+ plays: info.playback_count,
41
+ },
42
+ };
43
+ return { tracks: [track] };
44
+ }
45
+ catch {
46
+ const playlist = await this.client.getPlaylistDetails(query);
47
+ const tracks = playlist.tracks.map((t) => ({
48
+ id: t.id.toString(),
49
+ title: t.title,
50
+ url: t.permalink_url,
51
+ duration: t.duration,
52
+ thumbnail: t.artwork_url || playlist.artwork_url,
53
+ requestedBy,
54
+ source: this.name,
55
+ metadata: {
56
+ author: t.user?.username,
57
+ plays: t.playback_count,
58
+ playlist: playlist.id?.toString(),
59
+ },
60
+ }));
61
+ return {
62
+ tracks,
63
+ playlist: {
64
+ name: playlist.title,
65
+ url: playlist.permalink_url || query,
66
+ thumbnail: playlist.artwork_url,
67
+ },
68
+ };
69
+ }
70
+ }
71
+ const results = await this.client.searchTracks({ query, limit: 15 });
72
+ const tracks = results.slice(0, 10).map((track) => ({
73
+ id: track.id.toString(),
74
+ title: track.title,
75
+ url: track.permalink_url,
76
+ duration: track.duration,
77
+ thumbnail: track.artwork_url,
78
+ requestedBy,
79
+ source: this.name,
80
+ metadata: {
81
+ author: track.user?.username,
82
+ plays: track.playback_count,
83
+ },
84
+ }));
85
+ return { tracks };
86
+ }
87
+ catch (error) {
88
+ throw new Error(`SoundCloud search failed: ${error?.message}`);
89
+ }
90
+ }
91
+ async getStream(track) {
92
+ await this.ready;
93
+ try {
94
+ const stream = await this.client.downloadTrack(track.url);
95
+ if (!stream) {
96
+ throw new Error("SoundCloud download returned null");
97
+ }
98
+ return {
99
+ stream,
100
+ type: "arbitrary",
101
+ metadata: track.metadata,
102
+ };
103
+ }
104
+ catch (error) {
105
+ throw new Error(`Failed to get SoundCloud stream: ${error.message}`);
106
+ }
107
+ }
108
+ async getRelatedTracks(trackURL, opts = {}) {
109
+ await this.ready;
110
+ try {
111
+ const tracks = await this.client.getRelatedTracks(trackURL, {
112
+ limit: 30,
113
+ filter: "tracks",
114
+ });
115
+ if (!tracks || !tracks?.length) {
116
+ return [];
117
+ }
118
+ const relatedfilter = tracks.filter((tr) => !(opts?.history ?? []).some((t) => t.url === tr.permalink_url));
119
+ const related = relatedfilter.slice(0, opts.limit || 1);
120
+ return related.map((t) => ({
121
+ id: t.id.toString(),
122
+ title: t.title,
123
+ url: t.permalink_url,
124
+ duration: t.duration,
125
+ thumbnail: t.artwork_url,
126
+ requestedBy: "auto",
127
+ source: this.name,
128
+ metadata: {
129
+ author: t.user?.username,
130
+ plays: t.playback_count,
131
+ },
132
+ }));
133
+ }
134
+ catch {
135
+ return [];
136
+ }
137
+ }
138
+ async getFallback(track) {
139
+ const trackfall = await this.search(track.title, track.requestedBy);
140
+ const fallbackTrack = trackfall.tracks?.[0];
141
+ if (!fallbackTrack) {
142
+ throw new Error(`No fallback track found for ${track.title}`);
143
+ }
144
+ return await this.getStream(fallbackTrack);
145
+ }
146
+ async extractPlaylist(url, requestedBy) {
147
+ await this.ready;
148
+ try {
149
+ const playlist = await this.client.getPlaylistDetails(url);
150
+ return playlist.tracks.map((t) => ({
151
+ id: t.id.toString(),
152
+ title: t.title,
153
+ url: t.permalink_url,
154
+ duration: t.duration,
155
+ thumbnail: t.artwork_url || playlist.artwork_url,
156
+ requestedBy,
157
+ source: this.name,
158
+ metadata: {
159
+ author: t.user?.username,
160
+ plays: t.playback_count,
161
+ playlist: playlist.id?.toString(),
162
+ },
163
+ }));
164
+ }
165
+ catch {
166
+ return [];
167
+ }
168
+ }
169
+ }
170
+ exports.SoundCloudPlugin = SoundCloudPlugin;
171
+ //# sourceMappingURL=SoundCloudPlugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SoundCloudPlugin.js","sourceRoot":"","sources":["../../src/plugins/SoundCloudPlugin.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ import { BasePlugin } from "./BasePlugin";
2
+ import { Track, SearchResult, StreamInfo } from "../types";
3
+ /**
4
+ * This minimal Spotify plugin:
5
+ * - Parses Spotify URLs/URIs (track/playlist/album)
6
+ * - Uses Spotify's public oEmbed endpoint to fetch *display metadata* (no auth, no SDK)
7
+ * - Does NOT provide audio streams (player is expected to redirect/fallback upstream)
8
+ * - Does NOT expand playlists/albums (no SDK; oEmbed doesn't enumerate items)
9
+ */
10
+ export declare class SpotifyPlugin extends BasePlugin {
11
+ name: string;
12
+ version: string;
13
+ canHandle(query: string): boolean;
14
+ validate(url: string): boolean;
15
+ search(query: string, requestedBy: string): Promise<SearchResult>;
16
+ extractPlaylist(_input: string, _requestedBy: string): Promise<Track[]>;
17
+ extractAlbum(_input: string, _requestedBy: string): Promise<Track[]>;
18
+ getStream(_track: Track): Promise<StreamInfo>;
19
+ private identifyKind;
20
+ private extractId;
21
+ private buildTrackFromUrlOrUri;
22
+ private buildHeaderItem;
23
+ private toShareUrl;
24
+ private fetchOEmbed;
25
+ }
26
+ //# sourceMappingURL=SpotifyPlugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SpotifyPlugin.d.ts","sourceRoot":"","sources":["../../src/plugins/SpotifyPlugin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAE3D;;;;;;GAMG;AACH,qBAAa,aAAc,SAAQ,UAAU;IAC3C,IAAI,SAAa;IACjB,OAAO,SAAW;IAElB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO;IAWjC,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAUxB,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IAyBjE,eAAe,CACnB,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,KAAK,EAAE,CAAC;IAIb,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC;IAIpE,SAAS,CAAC,MAAM,EAAE,KAAK,GAAG,OAAO,CAAC,UAAU,CAAC;IAInD,OAAO,CAAC,YAAY;IAsBpB,OAAO,CAAC,SAAS;YAeH,sBAAsB;YA6BtB,eAAe;IA8B7B,OAAO,CAAC,UAAU;YAeJ,WAAW;CAkB1B"}