@ziplayer/ytexecplug 0.0.2 → 0.0.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/dist/index.d.ts +0 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -45
- package/dist/index.js.map +1 -1
- package/package.json +38 -38
- package/src/index.ts +3 -42
- package/tsconfig.json +27 -23
- package/dist/YouTubePlugin.d.ts +0 -1
- package/dist/YouTubePlugin.d.ts.map +0 -1
- package/dist/YouTubePlugin.js +0 -343
- package/dist/YouTubePlugin.js.map +0 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
import { BasePlugin } from "ziplayer";
|
|
2
2
|
import { Track, SearchResult, StreamInfo } from "ziplayer";
|
|
3
|
-
import { Readable } from "stream";
|
|
4
|
-
/**
|
|
5
|
-
* Converts a Web ReadableStream to a Node.js Readable stream
|
|
6
|
-
*/
|
|
7
|
-
export declare function webStreamToNodeStream(webStream: ReadableStream): Readable;
|
|
8
3
|
export declare class YTexec extends BasePlugin {
|
|
9
4
|
name: string;
|
|
10
5
|
version: string;
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AACtC,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AACtC,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AA6C3D,qBAAa,MAAO,SAAQ,UAAU;IACrC,IAAI,SAAY;IAChB,OAAO,SAAW;IAElB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO;IAe3B,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IAIjE,SAAS,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,UAAU,CAAC;CAgBlD"}
|
package/dist/index.js
CHANGED
|
@@ -4,41 +4,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.YTexec = void 0;
|
|
7
|
-
exports.webStreamToNodeStream = webStreamToNodeStream;
|
|
8
7
|
const ziplayer_1 = require("ziplayer");
|
|
9
|
-
const stream_1 = require("stream");
|
|
10
8
|
const youtube_dl_exec_1 = __importDefault(require("youtube-dl-exec"));
|
|
11
|
-
/**
|
|
12
|
-
* Converts a Web ReadableStream to a Node.js Readable stream
|
|
13
|
-
*/
|
|
14
|
-
function webStreamToNodeStream(webStream) {
|
|
15
|
-
const nodeStream = new stream_1.Readable({
|
|
16
|
-
read() {
|
|
17
|
-
// This will be handled by the Web Stream reader
|
|
18
|
-
},
|
|
19
|
-
});
|
|
20
|
-
// Create a reader from the Web Stream
|
|
21
|
-
const reader = webStream.getReader();
|
|
22
|
-
// Read chunks and push to Node.js stream
|
|
23
|
-
const pump = async () => {
|
|
24
|
-
try {
|
|
25
|
-
while (true) {
|
|
26
|
-
const { done, value } = await reader.read();
|
|
27
|
-
if (done) {
|
|
28
|
-
nodeStream.push(null); // End the stream
|
|
29
|
-
break;
|
|
30
|
-
}
|
|
31
|
-
nodeStream.push(Buffer.from(value));
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
catch (error) {
|
|
35
|
-
nodeStream.destroy(error);
|
|
36
|
-
}
|
|
37
|
-
};
|
|
38
|
-
// Start pumping data
|
|
39
|
-
pump();
|
|
40
|
-
return nodeStream;
|
|
41
|
-
}
|
|
42
9
|
function extractVideoId(input) {
|
|
43
10
|
try {
|
|
44
11
|
const u = new URL(input);
|
|
@@ -80,11 +47,8 @@ async function getYoutubeStream(url) {
|
|
|
80
47
|
return videourl;
|
|
81
48
|
}
|
|
82
49
|
class YTexec extends ziplayer_1.BasePlugin {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
this.name = "YTexec";
|
|
86
|
-
this.version = "1.0.0";
|
|
87
|
-
}
|
|
50
|
+
name = "YTexec";
|
|
51
|
+
version = "1.0.0";
|
|
88
52
|
canHandle(query) {
|
|
89
53
|
const q = (query || "").trim().toLowerCase();
|
|
90
54
|
const isUrl = q.startsWith("http://") || q.startsWith("https://");
|
|
@@ -109,14 +73,9 @@ class YTexec extends ziplayer_1.BasePlugin {
|
|
|
109
73
|
if (!youtubeUrl) {
|
|
110
74
|
throw new Error("Failed to get YouTube stream URL");
|
|
111
75
|
}
|
|
112
|
-
const response = await fetch(youtubeUrl);
|
|
113
|
-
if (!response.ok || !response.body) {
|
|
114
|
-
throw new Error("Failed to fetch YouTube stream");
|
|
115
|
-
}
|
|
116
|
-
const stream = webStreamToNodeStream(response.body);
|
|
117
76
|
return {
|
|
118
|
-
|
|
119
|
-
type: "
|
|
77
|
+
url: youtubeUrl,
|
|
78
|
+
type: "url",
|
|
120
79
|
metadata: track.metadata,
|
|
121
80
|
};
|
|
122
81
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,38 +1,38 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@ziplayer/ytexecplug",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "A modular Discord voice player with plugin system",
|
|
5
|
-
"keywords": [
|
|
6
|
-
"ZiPlayer",
|
|
7
|
-
"@ziplayer/plugin",
|
|
8
|
-
"discord",
|
|
9
|
-
"music",
|
|
10
|
-
"player",
|
|
11
|
-
"voice"
|
|
12
|
-
],
|
|
13
|
-
"homepage": "https://player.ziji.
|
|
14
|
-
"bugs": {
|
|
15
|
-
"url": "https://github.com/ZiProject/ZiPlayer/issues"
|
|
16
|
-
},
|
|
17
|
-
"repository": {
|
|
18
|
-
"type": "git",
|
|
19
|
-
"url": "git+https://github.com/ZiProject/ZiPlayer.git"
|
|
20
|
-
},
|
|
21
|
-
"license": "MIT",
|
|
22
|
-
"author": "Ziji",
|
|
23
|
-
"main": "dist/index.js",
|
|
24
|
-
"types": "dist/index.d.ts",
|
|
25
|
-
"scripts": {
|
|
26
|
-
"build": "tsc",
|
|
27
|
-
"dev": "tsc --watch",
|
|
28
|
-
"prepare": "npm run build"
|
|
29
|
-
},
|
|
30
|
-
"dependencies": {
|
|
31
|
-
"youtube-dl-exec": "^3.1.1",
|
|
32
|
-
"ziplayer": "^0.
|
|
33
|
-
},
|
|
34
|
-
"devDependencies": {
|
|
35
|
-
"@types/node": "^20.0.0",
|
|
36
|
-
"typescript": "^5.0.0"
|
|
37
|
-
}
|
|
38
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@ziplayer/ytexecplug",
|
|
3
|
+
"version": "0.0.4",
|
|
4
|
+
"description": "A modular Discord voice player with plugin system",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"ZiPlayer",
|
|
7
|
+
"@ziplayer/plugin",
|
|
8
|
+
"discord",
|
|
9
|
+
"music",
|
|
10
|
+
"player",
|
|
11
|
+
"voice"
|
|
12
|
+
],
|
|
13
|
+
"homepage": "https://player.ziji.best",
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/ZiProject/ZiPlayer/issues"
|
|
16
|
+
},
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+https://github.com/ZiProject/ZiPlayer.git"
|
|
20
|
+
},
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"author": "Ziji",
|
|
23
|
+
"main": "dist/index.js",
|
|
24
|
+
"types": "dist/index.d.ts",
|
|
25
|
+
"scripts": {
|
|
26
|
+
"build": "tsc",
|
|
27
|
+
"dev": "tsc --watch",
|
|
28
|
+
"prepare": "npm run build"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"youtube-dl-exec": "^3.1.1",
|
|
32
|
+
"ziplayer": "^0.3.11"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/node": "^20.0.0",
|
|
36
|
+
"typescript": "^5.0.0"
|
|
37
|
+
}
|
|
38
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -2,41 +2,7 @@ import { BasePlugin } from "ziplayer";
|
|
|
2
2
|
import { Track, SearchResult, StreamInfo } from "ziplayer";
|
|
3
3
|
import { Readable } from "stream";
|
|
4
4
|
import youtubedl from "youtube-dl-exec";
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Converts a Web ReadableStream to a Node.js Readable stream
|
|
8
|
-
*/
|
|
9
|
-
export function webStreamToNodeStream(webStream: ReadableStream): Readable {
|
|
10
|
-
const nodeStream = new Readable({
|
|
11
|
-
read() {
|
|
12
|
-
// This will be handled by the Web Stream reader
|
|
13
|
-
},
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
// Create a reader from the Web Stream
|
|
17
|
-
const reader = webStream.getReader();
|
|
18
|
-
|
|
19
|
-
// Read chunks and push to Node.js stream
|
|
20
|
-
const pump = async () => {
|
|
21
|
-
try {
|
|
22
|
-
while (true) {
|
|
23
|
-
const { done, value } = await reader.read();
|
|
24
|
-
if (done) {
|
|
25
|
-
nodeStream.push(null); // End the stream
|
|
26
|
-
break;
|
|
27
|
-
}
|
|
28
|
-
nodeStream.push(Buffer.from(value));
|
|
29
|
-
}
|
|
30
|
-
} catch (error) {
|
|
31
|
-
nodeStream.destroy(error as Error);
|
|
32
|
-
}
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
// Start pumping data
|
|
36
|
-
pump();
|
|
37
|
-
|
|
38
|
-
return nodeStream;
|
|
39
|
-
}
|
|
5
|
+
import { url } from "inspector/promises";
|
|
40
6
|
|
|
41
7
|
function extractVideoId(input: string): string | null {
|
|
42
8
|
try {
|
|
@@ -107,15 +73,10 @@ export class YTexec extends BasePlugin {
|
|
|
107
73
|
if (!youtubeUrl) {
|
|
108
74
|
throw new Error("Failed to get YouTube stream URL");
|
|
109
75
|
}
|
|
110
|
-
const response = await fetch(youtubeUrl);
|
|
111
|
-
if (!response.ok || !response.body) {
|
|
112
|
-
throw new Error("Failed to fetch YouTube stream");
|
|
113
|
-
}
|
|
114
|
-
const stream = webStreamToNodeStream(response.body as ReadableStream) as unknown as Readable;
|
|
115
76
|
|
|
116
77
|
return {
|
|
117
|
-
|
|
118
|
-
type: "
|
|
78
|
+
url: youtubeUrl,
|
|
79
|
+
type: "url",
|
|
119
80
|
metadata: track.metadata,
|
|
120
81
|
};
|
|
121
82
|
} catch (error) {
|
package/tsconfig.json
CHANGED
|
@@ -1,23 +1,27 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "
|
|
4
|
-
"module": "
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
"
|
|
10
|
-
"
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
"allowSyntheticDefaultImports": true,
|
|
17
|
-
"experimentalDecorators": true,
|
|
18
|
-
"emitDecoratorMetadata": true,
|
|
19
|
-
"resolveJsonModule": true
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"lib": ["ES2022", "DOM"],
|
|
7
|
+
"outDir": "./dist",
|
|
8
|
+
"rootDir": "./src",
|
|
9
|
+
"strict": true,
|
|
10
|
+
"esModuleInterop": true,
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"forceConsistentCasingInFileNames": true,
|
|
13
|
+
"declaration": true,
|
|
14
|
+
"declarationMap": true,
|
|
15
|
+
"sourceMap": true,
|
|
16
|
+
"allowSyntheticDefaultImports": true,
|
|
17
|
+
"experimentalDecorators": true,
|
|
18
|
+
"emitDecoratorMetadata": true,
|
|
19
|
+
"resolveJsonModule": true,
|
|
20
|
+
"paths": {
|
|
21
|
+
"googlevideo/sabr-stream": ["./node_modules/googlevideo/dist/src/exports/sabr-stream"],
|
|
22
|
+
"googlevideo/utils": ["./node_modules/googlevideo/dist/src/exports/utils"]
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"include": ["src/*"],
|
|
26
|
+
"exclude": ["node_modules", "dist", "examples"]
|
|
27
|
+
}
|
package/dist/YouTubePlugin.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
//# sourceMappingURL=YouTubePlugin.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"YouTubePlugin.d.ts","sourceRoot":"","sources":["../src/YouTubePlugin.ts"],"names":[],"mappings":""}
|
package/dist/YouTubePlugin.js
DELETED
|
@@ -1,343 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
// import { BasePlugin, Track, SearchResult, StreamInfo, Player } from "ziplayer";
|
|
3
|
-
// export interface PluginOptions {
|
|
4
|
-
// player: Player;
|
|
5
|
-
// debug?: boolean;
|
|
6
|
-
// }
|
|
7
|
-
// /**
|
|
8
|
-
// * A plugin for handling YouTube audio content including videos, playlists, and search functionality.
|
|
9
|
-
// *
|
|
10
|
-
// * This plugin provides comprehensive support for:
|
|
11
|
-
// * - YouTube video URLs (youtube.com, youtu.be, music.youtube.com)
|
|
12
|
-
// * - YouTube playlist URLs and dynamic mixes
|
|
13
|
-
// * - YouTube search queries
|
|
14
|
-
// * - Audio stream extraction from YouTube videos
|
|
15
|
-
// * - Related track recommendations
|
|
16
|
-
// *
|
|
17
|
-
// * @example
|
|
18
|
-
// * const youtubePlugin = new YouTubePlugin();
|
|
19
|
-
// *
|
|
20
|
-
// * // Add to PlayerManager
|
|
21
|
-
// * const manager = new PlayerManager({
|
|
22
|
-
// * plugins: [youtubePlugin]
|
|
23
|
-
// * });
|
|
24
|
-
// *
|
|
25
|
-
// * // Search for videos
|
|
26
|
-
// * const result = await youtubePlugin.search("Never Gonna Give You Up", "user123");
|
|
27
|
-
// *
|
|
28
|
-
// * // Get audio stream
|
|
29
|
-
// * const stream = await youtubePlugin.getStream(result.tracks[0]);
|
|
30
|
-
// *
|
|
31
|
-
// * @since 1.0.0
|
|
32
|
-
// */
|
|
33
|
-
// export class YouTubePlugin extends BasePlugin {
|
|
34
|
-
// name = "youtube";
|
|
35
|
-
// version = "1.0.0";
|
|
36
|
-
// private ready: Promise<void>;
|
|
37
|
-
// private player: Player | undefined;
|
|
38
|
-
// private options: PluginOptions;
|
|
39
|
-
// /**
|
|
40
|
-
// * Creates a new YouTubePlugin instance.
|
|
41
|
-
// *
|
|
42
|
-
// * The plugin will automatically initialize YouTube clients for both video playback
|
|
43
|
-
// * and search functionality. Initialization is asynchronous and handled internally.
|
|
44
|
-
// *
|
|
45
|
-
// * @example
|
|
46
|
-
// * const plugin = new YouTubePlugin();
|
|
47
|
-
// * // Plugin is ready to use after initialization completes
|
|
48
|
-
// */
|
|
49
|
-
// constructor(options: PluginOptions) {
|
|
50
|
-
// super();
|
|
51
|
-
// this.player = options?.player ?? undefined;
|
|
52
|
-
// this.options = options ?? {};
|
|
53
|
-
// this.ready = this.init();
|
|
54
|
-
// }
|
|
55
|
-
// private async init(): Promise<void> {}
|
|
56
|
-
// private debug(message?: any, ...optionalParams: any[]): void {
|
|
57
|
-
// if (this.options?.debug && this?.player && this.player?.listenerCount("debug") > 0) {
|
|
58
|
-
// this.player.emit("debug", `[YouTubePlugin] ${message}`, ...optionalParams);
|
|
59
|
-
// }
|
|
60
|
-
// }
|
|
61
|
-
// // Build a Track from various YouTube object shapes (search item, playlist item, watch_next feed, basic_info, info)
|
|
62
|
-
// private buildTrack(raw: any, requestedBy: string, extra?: { playlist?: string }): Track {
|
|
63
|
-
// const pickFirst = (...vals: any[]) => vals.find((v) => v !== undefined && v !== null && v !== "");
|
|
64
|
-
// // Try to resolve from multiple common shapes
|
|
65
|
-
// const id = pickFirst(
|
|
66
|
-
// raw?.id,
|
|
67
|
-
// raw?.video_id,
|
|
68
|
-
// raw?.videoId,
|
|
69
|
-
// raw?.content_id,
|
|
70
|
-
// raw?.identifier,
|
|
71
|
-
// raw?.basic_info?.id,
|
|
72
|
-
// raw?.basic_info?.video_id,
|
|
73
|
-
// raw?.basic_info?.videoId,
|
|
74
|
-
// raw?.basic_info?.content_id,
|
|
75
|
-
// );
|
|
76
|
-
// const title = pickFirst(
|
|
77
|
-
// raw?.metadata?.title?.text,
|
|
78
|
-
// raw?.title?.text,
|
|
79
|
-
// raw?.title,
|
|
80
|
-
// raw?.headline,
|
|
81
|
-
// raw?.basic_info?.title,
|
|
82
|
-
// "Unknown title",
|
|
83
|
-
// );
|
|
84
|
-
// const durationValue = pickFirst(
|
|
85
|
-
// raw?.length_seconds,
|
|
86
|
-
// raw?.duration?.seconds,
|
|
87
|
-
// raw?.duration?.text,
|
|
88
|
-
// raw?.duration,
|
|
89
|
-
// raw?.length_text,
|
|
90
|
-
// raw?.basic_info?.duration,
|
|
91
|
-
// );
|
|
92
|
-
// const duration = Number(toSeconds(durationValue)) || 0;
|
|
93
|
-
// const thumb = pickFirst(
|
|
94
|
-
// raw?.thumbnails?.[0]?.url,
|
|
95
|
-
// raw?.thumbnail?.[0]?.url,
|
|
96
|
-
// raw?.thumbnail?.url,
|
|
97
|
-
// raw?.thumbnail?.thumbnails?.[0]?.url,
|
|
98
|
-
// raw?.content_image?.image?.[0]?.url,
|
|
99
|
-
// raw?.basic_info?.thumbnail?.[0]?.url,
|
|
100
|
-
// raw?.basic_info?.thumbnail?.[raw?.basic_info?.thumbnail?.length - 1]?.url,
|
|
101
|
-
// raw?.thumbnails?.[raw?.thumbnails?.length - 1]?.url,
|
|
102
|
-
// );
|
|
103
|
-
// const author = pickFirst(raw?.author?.name, raw?.author, raw?.channel?.name, raw?.owner?.name, raw?.basic_info?.author);
|
|
104
|
-
// const views = pickFirst(
|
|
105
|
-
// raw?.view_count,
|
|
106
|
-
// raw?.views,
|
|
107
|
-
// raw?.short_view_count,
|
|
108
|
-
// raw?.stats?.view_count,
|
|
109
|
-
// raw?.basic_info?.view_count,
|
|
110
|
-
// );
|
|
111
|
-
// const url = pickFirst(raw?.url, id ? `https://www.youtube.com/watch?v=${id}` : undefined);
|
|
112
|
-
// this.debug("Track build:", {
|
|
113
|
-
// id: String(id),
|
|
114
|
-
// title: String(title),
|
|
115
|
-
// url: String(url),
|
|
116
|
-
// duration,
|
|
117
|
-
// thumbnail: thumb,
|
|
118
|
-
// requestedBy,
|
|
119
|
-
// source: this.name,
|
|
120
|
-
// });
|
|
121
|
-
// return {
|
|
122
|
-
// id: String(id),
|
|
123
|
-
// title: String(title),
|
|
124
|
-
// url: String(url),
|
|
125
|
-
// duration,
|
|
126
|
-
// thumbnail: thumb,
|
|
127
|
-
// requestedBy,
|
|
128
|
-
// source: this.name,
|
|
129
|
-
// metadata: {
|
|
130
|
-
// author,
|
|
131
|
-
// views,
|
|
132
|
-
// ...(extra?.playlist ? { playlist: extra.playlist } : {}),
|
|
133
|
-
// },
|
|
134
|
-
// } as Track;
|
|
135
|
-
// }
|
|
136
|
-
// /**
|
|
137
|
-
// * Determines if this plugin can handle the given query.
|
|
138
|
-
// *
|
|
139
|
-
// * @param query - The search query or URL to check
|
|
140
|
-
// * @returns `true` if the plugin can handle the query, `false` otherwise
|
|
141
|
-
// *
|
|
142
|
-
// * @example
|
|
143
|
-
// * plugin.canHandle("https://www.youtube.com/watch?v=dQw4w9WgXcQ"); // true
|
|
144
|
-
// * plugin.canHandle("Never Gonna Give You Up"); // true
|
|
145
|
-
// * plugin.canHandle("spotify:track:123"); // false
|
|
146
|
-
// */
|
|
147
|
-
// canHandle(query: string): boolean {
|
|
148
|
-
// const q = (query || "").trim().toLowerCase();
|
|
149
|
-
// const isUrl = q.startsWith("http://") || q.startsWith("https://");
|
|
150
|
-
// if (isUrl) {
|
|
151
|
-
// try {
|
|
152
|
-
// const parsed = new URL(query);
|
|
153
|
-
// const allowedHosts = ["youtube.com", "www.youtube.com", "music.youtube.com", "youtu.be", "www.youtu.be"];
|
|
154
|
-
// return allowedHosts.includes(parsed.hostname.toLowerCase());
|
|
155
|
-
// } catch (e) {
|
|
156
|
-
// return false;
|
|
157
|
-
// }
|
|
158
|
-
// }
|
|
159
|
-
// // Avoid intercepting explicit patterns for other extractors
|
|
160
|
-
// if (q.startsWith("tts:") || q.startsWith("say ")) return false;
|
|
161
|
-
// if (q.startsWith("spotify:") || q.includes("open.spotify.com")) return false;
|
|
162
|
-
// if (q.includes("soundcloud")) return false;
|
|
163
|
-
// // Treat remaining non-URL free text as YouTube-searchable
|
|
164
|
-
// return true;
|
|
165
|
-
// }
|
|
166
|
-
// /**
|
|
167
|
-
// * Validates if a URL is a valid YouTube URL.
|
|
168
|
-
// *
|
|
169
|
-
// * @param url - The URL to validate
|
|
170
|
-
// * @returns `true` if the URL is a valid YouTube URL, `false` otherwise
|
|
171
|
-
// *
|
|
172
|
-
// * @example
|
|
173
|
-
// * plugin.validate("https://www.youtube.com/watch?v=dQw4w9WgXcQ"); // true
|
|
174
|
-
// * plugin.validate("https://youtu.be/dQw4w9WgXcQ"); // true
|
|
175
|
-
// * plugin.validate("https://spotify.com/track/123"); // false
|
|
176
|
-
// */
|
|
177
|
-
// validate(url: string): boolean {
|
|
178
|
-
// try {
|
|
179
|
-
// const parsed = new URL(url);
|
|
180
|
-
// const allowedHosts = ["youtube.com", "www.youtube.com", "music.youtube.com", "youtu.be", "www.youtu.be", "m.youtube.com"];
|
|
181
|
-
// return allowedHosts.includes(parsed.hostname.toLowerCase());
|
|
182
|
-
// } catch (e) {
|
|
183
|
-
// return false;
|
|
184
|
-
// }
|
|
185
|
-
// }
|
|
186
|
-
// /**
|
|
187
|
-
// * Retrieves the audio stream for a YouTube track using sabr download.
|
|
188
|
-
// *
|
|
189
|
-
// * This method extracts the audio stream from a YouTube video using the sabr download
|
|
190
|
-
// * method which provides better quality and more reliable streaming.
|
|
191
|
-
// *
|
|
192
|
-
// * @param track - The Track object to get the stream for
|
|
193
|
-
// * @returns A StreamInfo object containing the audio stream and metadata
|
|
194
|
-
// * @throws {Error} If the track ID is invalid or stream extraction fails
|
|
195
|
-
// *
|
|
196
|
-
// * @example
|
|
197
|
-
// * const track = { id: "dQw4w9WgXcQ", title: "Never Gonna Give You Up", ... };
|
|
198
|
-
// * const streamInfo = await plugin.getStream(track);
|
|
199
|
-
// * console.log(streamInfo.type); // "arbitrary"
|
|
200
|
-
// * console.log(streamInfo.stream); // Readable stream
|
|
201
|
-
// */
|
|
202
|
-
// async getStream(track: Track): Promise<StreamInfo> {
|
|
203
|
-
// await this.ready;
|
|
204
|
-
// const id = this.extractVideoId(track.url) || track.id;
|
|
205
|
-
// if (!id) throw new Error("Invalid track id");
|
|
206
|
-
// try {
|
|
207
|
-
// this.debug("🚀 Attempting sabr download for video ID:", id);
|
|
208
|
-
// // Use sabr download for better quality and reliability
|
|
209
|
-
// const { streamResults } = await createSabrStream(id, DEFAULT_SABR_OPTIONS);
|
|
210
|
-
// const { audioStream, selectedFormats, videoTitle } = streamResults;
|
|
211
|
-
// this.debug("✅ Sabr download successful, converting Web Stream to Node.js Stream");
|
|
212
|
-
// // Convert Web Stream to Node.js Readable Stream
|
|
213
|
-
// const nodeStream = webStreamToNodeStream(audioStream);
|
|
214
|
-
// this.debug("✅ Stream conversion complete, returning Node.js stream");
|
|
215
|
-
// // Return the converted Node.js stream
|
|
216
|
-
// return {
|
|
217
|
-
// stream: nodeStream,
|
|
218
|
-
// type: "arbitrary",
|
|
219
|
-
// metadata: {
|
|
220
|
-
// ...track.metadata,
|
|
221
|
-
// itag: selectedFormats.audioFormat.itag,
|
|
222
|
-
// mime: selectedFormats.audioFormat.mimeType,
|
|
223
|
-
// },
|
|
224
|
-
// };
|
|
225
|
-
// } catch (e: any) {
|
|
226
|
-
// this.debug("⚠️ Sabr download failed, falling back to youtubei.js:", e.message);
|
|
227
|
-
// // Fallback to original youtubei.js method if sabr download fails
|
|
228
|
-
// try {
|
|
229
|
-
// const stream: any = await (this.client as any).download(id, {
|
|
230
|
-
// type: "audio",
|
|
231
|
-
// quality: "best",
|
|
232
|
-
// });
|
|
233
|
-
// // Check if it's a Web Stream and convert it
|
|
234
|
-
// this.debug("🔍 Checking stream type:", typeof stream, stream?.constructor?.name);
|
|
235
|
-
// if (stream && typeof stream.getReader === "function") {
|
|
236
|
-
// this.debug("🔄 Converting Web Stream to Node.js Stream");
|
|
237
|
-
// const nodeStream = webStreamToNodeStream(stream);
|
|
238
|
-
// this.debug("✅ Stream converted successfully");
|
|
239
|
-
// return {
|
|
240
|
-
// stream: nodeStream,
|
|
241
|
-
// type: "arbitrary",
|
|
242
|
-
// metadata: track.metadata,
|
|
243
|
-
// };
|
|
244
|
-
// } else {
|
|
245
|
-
// this.debug("⚠️ Stream is not a Web Stream or is null");
|
|
246
|
-
// }
|
|
247
|
-
// return {
|
|
248
|
-
// stream,
|
|
249
|
-
// type: "arbitrary",
|
|
250
|
-
// metadata: track.metadata,
|
|
251
|
-
// };
|
|
252
|
-
// } catch (fallbackError: any) {
|
|
253
|
-
// try {
|
|
254
|
-
// const info: any = await (this.client as any).getBasicInfo(id);
|
|
255
|
-
// // Prefer m4a audio-only formats first
|
|
256
|
-
// let format: any = info?.chooseFormat?.({
|
|
257
|
-
// type: "audio",
|
|
258
|
-
// quality: "best",
|
|
259
|
-
// });
|
|
260
|
-
// if (!format && info?.formats?.length) {
|
|
261
|
-
// const audioOnly = info.formats.filter((f: any) => f.mime_type?.includes("audio"));
|
|
262
|
-
// audioOnly.sort((a: any, b: any) => (b.bitrate || 0) - (a.bitrate || 0));
|
|
263
|
-
// format = audioOnly[0];
|
|
264
|
-
// }
|
|
265
|
-
// if (!format) throw new Error("No audio format available");
|
|
266
|
-
// let url: string | undefined = undefined;
|
|
267
|
-
// if (typeof format.decipher === "function") {
|
|
268
|
-
// url = format.decipher((this.client as any).session.player);
|
|
269
|
-
// }
|
|
270
|
-
// if (!url) url = format.url;
|
|
271
|
-
// if (!url) throw new Error("No valid URL to decipher");
|
|
272
|
-
// const res = await fetch(url);
|
|
273
|
-
// if (!res.ok || !res.body) {
|
|
274
|
-
// throw new Error(`HTTP ${res.status}`);
|
|
275
|
-
// }
|
|
276
|
-
// // Convert Web Stream to Node.js Stream
|
|
277
|
-
// this.debug("🔄 Converting fetch response Web Stream to Node.js Stream");
|
|
278
|
-
// const nodeStream = webStreamToNodeStream(res.body);
|
|
279
|
-
// return {
|
|
280
|
-
// stream: nodeStream,
|
|
281
|
-
// type: "arbitrary",
|
|
282
|
-
// metadata: {
|
|
283
|
-
// ...track.metadata,
|
|
284
|
-
// itag: format.itag,
|
|
285
|
-
// mime: format.mime_type,
|
|
286
|
-
// },
|
|
287
|
-
// };
|
|
288
|
-
// } catch (inner: any) {
|
|
289
|
-
// throw new Error(`Failed to get YouTube stream: ${inner?.message || inner}`);
|
|
290
|
-
// }
|
|
291
|
-
// }
|
|
292
|
-
// }
|
|
293
|
-
// }
|
|
294
|
-
// async getFallback(track: Track): Promise<StreamInfo> {
|
|
295
|
-
// try {
|
|
296
|
-
// const result = await this.search(track.title, track.requestedBy);
|
|
297
|
-
// const first = result.tracks[0];
|
|
298
|
-
// this.debug("Fallback track:", first);
|
|
299
|
-
// if (!first) throw new Error("No fallback track found");
|
|
300
|
-
// return await this.getStream(first);
|
|
301
|
-
// } catch (e: any) {
|
|
302
|
-
// throw new Error(`YouTube fallback search failed: ${e?.message || e}`);
|
|
303
|
-
// }
|
|
304
|
-
// }
|
|
305
|
-
// private extractVideoId(input: string): string | null {
|
|
306
|
-
// try {
|
|
307
|
-
// const u = new URL(input);
|
|
308
|
-
// const allowedShortHosts = ["youtu.be"];
|
|
309
|
-
// const allowedLongHosts = ["youtube.com", "www.youtube.com", "music.youtube.com", "m.youtube.com"];
|
|
310
|
-
// if (allowedShortHosts.includes(u.hostname)) {
|
|
311
|
-
// return u.pathname.split("/").filter(Boolean)[0] || null;
|
|
312
|
-
// }
|
|
313
|
-
// if (allowedLongHosts.includes(u.hostname)) {
|
|
314
|
-
// // watch?v=, shorts/, embed/
|
|
315
|
-
// if (u.searchParams.get("v")) return u.searchParams.get("v");
|
|
316
|
-
// const path = u.pathname;
|
|
317
|
-
// if (path.startsWith("/shorts/")) return path.replace("/shorts/", "");
|
|
318
|
-
// if (path.startsWith("/embed/")) return path.replace("/embed/", "");
|
|
319
|
-
// }
|
|
320
|
-
// return null;
|
|
321
|
-
// } catch {
|
|
322
|
-
// return null;
|
|
323
|
-
// }
|
|
324
|
-
// }
|
|
325
|
-
// }
|
|
326
|
-
// function toSeconds(d: any): number | undefined {
|
|
327
|
-
// if (typeof d === "number") return d;
|
|
328
|
-
// if (typeof d === "string") {
|
|
329
|
-
// // mm:ss or hh:mm:ss
|
|
330
|
-
// const parts = d.split(":").map(Number);
|
|
331
|
-
// if (parts.some((n) => Number.isNaN(n))) return undefined;
|
|
332
|
-
// if (parts.length === 3) return parts[0] * 3600 + parts[1] * 60 + parts[2];
|
|
333
|
-
// if (parts.length === 2) return parts[0] * 60 + parts[1];
|
|
334
|
-
// const asNum = Number(d);
|
|
335
|
-
// return Number.isFinite(asNum) ? asNum : undefined;
|
|
336
|
-
// }
|
|
337
|
-
// if (d && typeof d === "object") {
|
|
338
|
-
// if (typeof (d as any).seconds === "number") return (d as any).seconds;
|
|
339
|
-
// if (typeof (d as any).milliseconds === "number") return Math.floor((d as any).milliseconds / 1000);
|
|
340
|
-
// }
|
|
341
|
-
// return undefined;
|
|
342
|
-
// }
|
|
343
|
-
//# sourceMappingURL=YouTubePlugin.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"YouTubePlugin.js","sourceRoot":"","sources":["../src/YouTubePlugin.ts"],"names":[],"mappings":"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"}
|