@ziplayer/plugin 0.0.1

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.
@@ -0,0 +1,302 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.YouTubePlugin = void 0;
4
+ const ziplayer_1 = require("ziplayer");
5
+ const youtubei_js_1 = require("youtubei.js");
6
+ class YouTubePlugin extends ziplayer_1.BasePlugin {
7
+ constructor() {
8
+ super();
9
+ this.name = "youtube";
10
+ this.version = "1.0.0";
11
+ this.ready = this.init();
12
+ }
13
+ async init() {
14
+ this.client = await youtubei_js_1.Innertube.create({
15
+ client_type: "ANDROID",
16
+ retrieve_player: false,
17
+ });
18
+ // Use a separate web client for search to avoid mobile parser issues
19
+ this.searchClient = await youtubei_js_1.Innertube.create({
20
+ client_type: "WEB",
21
+ retrieve_player: false,
22
+ });
23
+ youtubei_js_1.Log.setLevel(0);
24
+ }
25
+ canHandle(query) {
26
+ const q = query.toLowerCase();
27
+ const isUrl = q.startsWith("http://") || q.startsWith("https://");
28
+ return q.includes("youtube.com") || q.includes("youtu.be") || (!isUrl && q.includes("youtube"));
29
+ }
30
+ validate(url) {
31
+ const u = url.toLowerCase();
32
+ return u.includes("youtube.com") || u.includes("youtu.be");
33
+ }
34
+ async search(query, requestedBy) {
35
+ await this.ready;
36
+ if (this.validate(query)) {
37
+ const listId = this.extractListId(query);
38
+ if (listId) {
39
+ try {
40
+ const playlist = await this.client.getPlaylist(listId);
41
+ const videos = playlist?.videos || playlist?.items || [];
42
+ const tracks = videos.map((v) => {
43
+ const id = v.id || v.video_id || v.videoId;
44
+ const title = v.title?.text ?? v.title;
45
+ const duration = toSeconds(v.duration?.text ?? v.duration);
46
+ const thumb = v.thumbnails?.[0]?.url || v.thumbnail?.url;
47
+ const author = v.author?.name ?? v.channel?.name;
48
+ const views = v.view_count ?? v.views;
49
+ return {
50
+ id: String(id),
51
+ title,
52
+ url: `https://www.youtube.com/watch?v=${id}`,
53
+ duration: Number(duration) || 0,
54
+ thumbnail: thumb,
55
+ requestedBy,
56
+ source: this.name,
57
+ metadata: { author, views, playlist: listId },
58
+ };
59
+ });
60
+ return {
61
+ tracks,
62
+ playlist: {
63
+ name: playlist?.title || playlist?.metadata?.title || `Playlist ${listId}`,
64
+ url: query,
65
+ thumbnail: playlist?.thumbnails?.[0]?.url || playlist?.thumbnail?.url,
66
+ },
67
+ };
68
+ }
69
+ catch {
70
+ const withoutList = query.replace(/[?&]list=[^&]+/, "").replace(/[?&]$/, "");
71
+ return await this.search(withoutList, requestedBy);
72
+ }
73
+ }
74
+ const videoId = this.extractVideoId(query);
75
+ if (!videoId)
76
+ throw new Error("Invalid YouTube URL");
77
+ const info = await this.client.getBasicInfo(videoId);
78
+ const basic = info.basic_info ?? {};
79
+ const track = {
80
+ id: videoId,
81
+ title: basic.title ?? info.title ?? "Unknown title",
82
+ url: `https://www.youtube.com/watch?v=${videoId}`,
83
+ duration: toSeconds(basic.duration ?? info.duration) ?? 0,
84
+ thumbnail: basic.thumbnail?.[0]?.url || basic.thumbnail?.[basic.thumbnail?.length - 1]?.url || info.thumbnails?.[0]?.url,
85
+ requestedBy,
86
+ source: this.name,
87
+ metadata: {
88
+ author: basic.author ?? info.author?.name,
89
+ views: info.basic_info?.view_count ?? info.view_count,
90
+ },
91
+ };
92
+ return { tracks: [track] };
93
+ }
94
+ // Text search → return up to 10 video tracks
95
+ const res = await this.searchClient.search(query, {
96
+ type: "video",
97
+ });
98
+ const items = res?.items || res?.videos || res?.results || [];
99
+ const tracks = items.slice(0, 10).map((v) => {
100
+ const id = v.id || v.video_id || v.videoId || v.identifier;
101
+ const title = v.title?.text ?? v.title ?? v.headline ?? "Unknown title";
102
+ const duration = toSeconds(v.duration?.text ?? v.duration?.seconds ?? v.duration ?? v.length_text);
103
+ const thumbnail = v.thumbnails?.[0]?.url || v.thumbnail?.url || v.thumbnail?.thumbnails?.[0]?.url;
104
+ const author = v.author?.name ?? v.channel?.name ?? v.owner?.name;
105
+ const views = v.view_count ?? v.views ?? v.short_view_count ?? v.stats?.view_count;
106
+ const track = {
107
+ id: String(id),
108
+ title,
109
+ url: `https://www.youtube.com/watch?v=${id}`,
110
+ duration: Number(duration) || 0,
111
+ thumbnail,
112
+ requestedBy,
113
+ source: this.name,
114
+ metadata: { author, views },
115
+ };
116
+ return track;
117
+ });
118
+ return { tracks };
119
+ }
120
+ async extractPlaylist(url, requestedBy) {
121
+ await this.ready;
122
+ const listId = this.extractListId(url);
123
+ if (!listId)
124
+ return [];
125
+ try {
126
+ const playlist = await this.client.getPlaylist(listId);
127
+ const videos = playlist?.videos || playlist?.items || [];
128
+ return videos.map((v) => {
129
+ const id = v.id || v.video_id || v.videoId;
130
+ const title = v.title?.text ?? v.title;
131
+ const duration = toSeconds(v.duration?.text ?? v.duration);
132
+ const thumb = v.thumbnails?.[0]?.url || v.thumbnail?.url;
133
+ const author = v.author?.name ?? v.channel?.name;
134
+ const views = v.view_count ?? v.views;
135
+ const track = {
136
+ id: String(id),
137
+ title,
138
+ url: `https://www.youtube.com/watch?v=${id}`,
139
+ duration: Number(duration) || 0,
140
+ thumbnail: thumb,
141
+ requestedBy,
142
+ source: this.name,
143
+ metadata: { author, views, playlist: listId },
144
+ };
145
+ return track;
146
+ });
147
+ }
148
+ catch {
149
+ // If playlist fetch fails, return empty to keep optional contract intact
150
+ return [];
151
+ }
152
+ }
153
+ async getStream(track) {
154
+ await this.ready;
155
+ const id = this.extractVideoId(track.url) || track.id;
156
+ if (!id)
157
+ throw new Error("Invalid track id");
158
+ try {
159
+ const stream = await this.client.download(id, {
160
+ type: "audio",
161
+ quality: "best",
162
+ });
163
+ return {
164
+ stream,
165
+ type: "arbitrary",
166
+ metadata: track.metadata,
167
+ };
168
+ }
169
+ catch (e) {
170
+ try {
171
+ const info = await this.client.getBasicInfo(id);
172
+ // Prefer m4a audio-only formats first
173
+ let format = info?.chooseFormat?.({
174
+ type: "audio",
175
+ quality: "best",
176
+ });
177
+ if (!format && info?.formats?.length) {
178
+ const audioOnly = info.formats.filter((f) => f.mime_type?.includes("audio"));
179
+ audioOnly.sort((a, b) => (b.bitrate || 0) - (a.bitrate || 0));
180
+ format = audioOnly[0];
181
+ }
182
+ if (!format)
183
+ throw new Error("No audio format available");
184
+ let url = undefined;
185
+ if (typeof format.decipher === "function") {
186
+ url = format.decipher(this.client.session.player);
187
+ }
188
+ if (!url)
189
+ url = format.url;
190
+ if (!url)
191
+ throw new Error("No valid URL to decipher");
192
+ const res = await fetch(url);
193
+ if (!res.ok || !res.body) {
194
+ throw new Error(`HTTP ${res.status}`);
195
+ }
196
+ return {
197
+ stream: res.body,
198
+ type: "arbitrary",
199
+ metadata: {
200
+ ...track.metadata,
201
+ itag: format.itag,
202
+ mime: format.mime_type,
203
+ },
204
+ };
205
+ }
206
+ catch (inner) {
207
+ throw new Error(`Failed to get YouTube stream: ${inner?.message || inner}`);
208
+ }
209
+ }
210
+ }
211
+ async getRelatedTracks(trackURL, opts = {}) {
212
+ await this.ready;
213
+ const videoId = this.extractVideoId(trackURL);
214
+ const info = await await this.searchClient.getInfo(videoId);
215
+ const related = info?.watch_next_feed || [];
216
+ const offset = opts.offset ?? 0;
217
+ const limit = opts.limit ?? 5;
218
+ const relatedfilter = related.filter((tr) => !(opts?.history ?? []).some((t) => t.url === tr.url));
219
+ return relatedfilter.slice(offset, offset + limit).map((v) => {
220
+ const id = v.id || v.video_id || v.videoId || v.content_id;
221
+ const videometa = v?.metadata;
222
+ return {
223
+ id: String(id),
224
+ title: videometa.title.text ?? "Unknown title",
225
+ url: `https://www.youtube.com/watch?v=${id}`,
226
+ duration: Number(v.length_seconds || toSeconds(v.duration)) || 0,
227
+ thumbnail: v.thumbnails?.[0]?.url || v.thumbnail?.url || v.content_image?.image?.[0]?.url,
228
+ requestedBy: "auto",
229
+ source: this.name,
230
+ metadata: { author: v.author, views: v.view_count },
231
+ };
232
+ });
233
+ }
234
+ async getFallback(track) {
235
+ try {
236
+ const result = await this.search(track.title, track.requestedBy);
237
+ const first = result.tracks[0];
238
+ if (!first)
239
+ throw new Error("No fallback track found");
240
+ return await this.getStream(first);
241
+ }
242
+ catch (e) {
243
+ throw new Error(`YouTube fallback search failed: ${e?.message || e}`);
244
+ }
245
+ }
246
+ extractVideoId(input) {
247
+ try {
248
+ const u = new URL(input);
249
+ if (u.hostname.includes("youtu.be")) {
250
+ return u.pathname.split("/").filter(Boolean)[0] || null;
251
+ }
252
+ if (u.hostname.includes("youtube.com")) {
253
+ // watch?v=, shorts/, embed/
254
+ if (u.searchParams.get("v"))
255
+ return u.searchParams.get("v");
256
+ const path = u.pathname;
257
+ if (path.startsWith("/shorts/"))
258
+ return path.replace("/shorts/", "");
259
+ if (path.startsWith("/embed/"))
260
+ return path.replace("/embed/", "");
261
+ }
262
+ return null;
263
+ }
264
+ catch {
265
+ return null;
266
+ }
267
+ }
268
+ extractListId(input) {
269
+ try {
270
+ const u = new URL(input);
271
+ return u.searchParams.get("list");
272
+ }
273
+ catch {
274
+ return null;
275
+ }
276
+ }
277
+ }
278
+ exports.YouTubePlugin = YouTubePlugin;
279
+ function toSeconds(d) {
280
+ if (typeof d === "number")
281
+ return d;
282
+ if (typeof d === "string") {
283
+ // mm:ss or hh:mm:ss
284
+ const parts = d.split(":").map(Number);
285
+ if (parts.some((n) => Number.isNaN(n)))
286
+ return undefined;
287
+ if (parts.length === 3)
288
+ return parts[0] * 3600 + parts[1] * 60 + parts[2];
289
+ if (parts.length === 2)
290
+ return parts[0] * 60 + parts[1];
291
+ const asNum = Number(d);
292
+ return Number.isFinite(asNum) ? asNum : undefined;
293
+ }
294
+ if (d && typeof d === "object") {
295
+ if (typeof d.seconds === "number")
296
+ return d.seconds;
297
+ if (typeof d.milliseconds === "number")
298
+ return Math.floor(d.milliseconds / 1000);
299
+ }
300
+ return undefined;
301
+ }
302
+ //# sourceMappingURL=YouTubePlugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"YouTubePlugin.js","sourceRoot":"","sources":["../src/YouTubePlugin.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ export { YouTubePlugin } from "./YouTubePlugin";
2
+ export { SoundCloudPlugin } from "./SoundCloudPlugin";
3
+ export { SpotifyPlugin } from "./SpotifyPlugin";
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SpotifyPlugin = exports.SoundCloudPlugin = exports.YouTubePlugin = void 0;
4
+ var YouTubePlugin_1 = require("./YouTubePlugin");
5
+ Object.defineProperty(exports, "YouTubePlugin", { enumerable: true, get: function () { return YouTubePlugin_1.YouTubePlugin; } });
6
+ var SoundCloudPlugin_1 = require("./SoundCloudPlugin");
7
+ Object.defineProperty(exports, "SoundCloudPlugin", { enumerable: true, get: function () { return SoundCloudPlugin_1.SoundCloudPlugin; } });
8
+ var SpotifyPlugin_1 = require("./SpotifyPlugin");
9
+ Object.defineProperty(exports, "SpotifyPlugin", { enumerable: true, get: function () { return SpotifyPlugin_1.SpotifyPlugin; } });
10
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,iDAAgD;AAAvC,8GAAA,aAAa,OAAA;AACtB,uDAAsD;AAA7C,oHAAA,gBAAgB,OAAA;AACzB,iDAAgD;AAAvC,8GAAA,aAAa,OAAA"}
package/package.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "@ziplayer/plugin",
3
+ "version": "0.0.1",
4
+ "description": "A modular Discord voice player with plugin system",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "scripts": {
8
+ "build": "tsc",
9
+ "dev": "tsc --watch",
10
+ "prepare": "npm run build"
11
+ },
12
+ "dependencies": {
13
+ "@zibot/scdl": "^0.0.5",
14
+ "youtubei.js": "^15.0.1",
15
+ "ziplayer": "^0.0.1"
16
+ },
17
+ "devDependencies": {
18
+ "@types/node": "^20.0.0",
19
+ "typescript": "^5.0.0"
20
+ }
21
+ }
@@ -0,0 +1,191 @@
1
+ import { BasePlugin, Track, SearchResult, StreamInfo } from "ziplayer";
2
+
3
+ const SoundCloud = require("@zibot/scdl");
4
+
5
+ export class SoundCloudPlugin extends BasePlugin {
6
+ name = "soundcloud";
7
+ version = "1.0.0";
8
+ private client: any;
9
+ private ready: Promise<void>;
10
+
11
+ constructor() {
12
+ super();
13
+ this.ready = this.init();
14
+ }
15
+
16
+ private async init(): Promise<void> {
17
+ this.client = new SoundCloud({ init: false });
18
+ await this.client.init();
19
+ }
20
+
21
+ canHandle(query: string): boolean {
22
+ return (
23
+ query.includes("soundcloud.com") ||
24
+ (!query.startsWith("http") && !query.includes("youtube"))
25
+ );
26
+ }
27
+
28
+ validate(url: string): boolean {
29
+ return url.includes("soundcloud.com");
30
+ }
31
+
32
+ async search(query: string, requestedBy: string): Promise<SearchResult> {
33
+ await this.ready;
34
+
35
+ try {
36
+ if (query.includes("soundcloud.com")) {
37
+ try {
38
+ const info = await this.client.getTrackDetails(query);
39
+ const track: Track = {
40
+ id: info.id.toString(),
41
+ title: info.title,
42
+ url: info.permalink_url || query,
43
+ duration: info.duration,
44
+ thumbnail: info.artwork_url,
45
+ requestedBy,
46
+ source: this.name,
47
+ metadata: {
48
+ author: info.user?.username,
49
+ plays: info.playback_count,
50
+ },
51
+ };
52
+ return { tracks: [track] };
53
+ } catch {
54
+ const playlist = await this.client.getPlaylistDetails(query);
55
+ const tracks: Track[] = playlist.tracks.map((t: any) => ({
56
+ id: t.id.toString(),
57
+ title: t.title,
58
+ url: t.permalink_url,
59
+ duration: t.duration,
60
+ thumbnail: t.artwork_url || playlist.artwork_url,
61
+ requestedBy,
62
+ source: this.name,
63
+ metadata: {
64
+ author: t.user?.username,
65
+ plays: t.playback_count,
66
+ playlist: playlist.id?.toString(),
67
+ },
68
+ }));
69
+
70
+ return {
71
+ tracks,
72
+ playlist: {
73
+ name: playlist.title,
74
+ url: playlist.permalink_url || query,
75
+ thumbnail: playlist.artwork_url,
76
+ },
77
+ };
78
+ }
79
+ }
80
+
81
+ const results = await this.client.searchTracks({ query, limit: 15 });
82
+ const tracks: Track[] = results.slice(0, 10).map((track: any) => ({
83
+ id: track.id.toString(),
84
+ title: track.title,
85
+ url: track.permalink_url,
86
+ duration: track.duration,
87
+ thumbnail: track.artwork_url,
88
+ requestedBy,
89
+ source: this.name,
90
+ metadata: {
91
+ author: track.user?.username,
92
+ plays: track.playback_count,
93
+ },
94
+ }));
95
+
96
+ return { tracks };
97
+ } catch (error: any) {
98
+ throw new Error(`SoundCloud search failed: ${error?.message}`);
99
+ }
100
+ }
101
+
102
+ async getStream(track: Track): Promise<StreamInfo> {
103
+ await this.ready;
104
+
105
+ try {
106
+ const stream = await this.client.downloadTrack(track.url);
107
+ if (!stream) {
108
+ throw new Error("SoundCloud download returned null");
109
+ }
110
+
111
+ return {
112
+ stream,
113
+ type: "arbitrary",
114
+ metadata: track.metadata,
115
+ };
116
+ } catch (error: any) {
117
+ throw new Error(`Failed to get SoundCloud stream: ${error.message}`);
118
+ }
119
+ }
120
+
121
+ async getRelatedTracks(
122
+ trackURL: string | number,
123
+ opts: { limit?: number; offset?: number; history?: Track[] } = {}
124
+ ): Promise<Track[]> {
125
+ await this.ready;
126
+ try {
127
+ const tracks = await this.client.getRelatedTracks(trackURL, {
128
+ limit: 30,
129
+ filter: "tracks",
130
+ });
131
+
132
+ if (!tracks || !tracks?.length) {
133
+ return [];
134
+ }
135
+ const relatedfilter = tracks.filter(
136
+ (tr: any) =>
137
+ !(opts?.history ?? []).some((t) => t.url === tr.permalink_url)
138
+ );
139
+
140
+ const related = relatedfilter.slice(0, opts.limit || 1);
141
+
142
+ return related.map((t: any) => ({
143
+ id: t.id.toString(),
144
+ title: t.title,
145
+ url: t.permalink_url,
146
+ duration: t.duration,
147
+ thumbnail: t.artwork_url,
148
+ requestedBy: "auto",
149
+ source: this.name,
150
+ metadata: {
151
+ author: t.user?.username,
152
+ plays: t.playback_count,
153
+ },
154
+ }));
155
+ } catch {
156
+ return [];
157
+ }
158
+ }
159
+
160
+ async getFallback(track: Track): Promise<StreamInfo> {
161
+ const trackfall = await this.search(track.title, track.requestedBy);
162
+ const fallbackTrack = trackfall.tracks?.[0];
163
+ if (!fallbackTrack) {
164
+ throw new Error(`No fallback track found for ${track.title}`);
165
+ }
166
+ return await this.getStream(fallbackTrack);
167
+ }
168
+
169
+ async extractPlaylist(url: string, requestedBy: string): Promise<Track[]> {
170
+ await this.ready;
171
+ try {
172
+ const playlist = await this.client.getPlaylistDetails(url);
173
+ return playlist.tracks.map((t: any) => ({
174
+ id: t.id.toString(),
175
+ title: t.title,
176
+ url: t.permalink_url,
177
+ duration: t.duration,
178
+ thumbnail: t.artwork_url || playlist.artwork_url,
179
+ requestedBy,
180
+ source: this.name,
181
+ metadata: {
182
+ author: t.user?.username,
183
+ plays: t.playback_count,
184
+ playlist: playlist.id?.toString(),
185
+ },
186
+ }));
187
+ } catch {
188
+ return [];
189
+ }
190
+ }
191
+ }