distube-invidious 1.0.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 techtimefor
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 ADDED
@@ -0,0 +1,157 @@
1
+ # DisTube Invidious Plugin
2
+
3
+ A [DisTube](https://github.com/skick1234/DisTube) extractor plugin that routes all YouTube URLs through an [Invidious](https://invidious.io/) instance for privacy-friendly music playback.
4
+
5
+ This is an ExtractorPlugin, find out more about it [here](https://github.com/skick1234/DisTube/wiki/Projects-Hub#extractorplugin).
6
+
7
+ ## Features
8
+
9
+ - 🎵 Play YouTube videos through any Invidious instance
10
+ - 🔒 Privacy-friendly - no direct YouTube API calls
11
+ - 📝 Playlist support
12
+ - 🔍 Search functionality
13
+ - 🎼 Related songs recommendations
14
+ - ⚙️ Configurable timeout
15
+ - 🌐 Supports all YouTube URL formats (watch, shorts, live, embed, youtu.be, playlists)
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ npm install distube-invidious
21
+ ```
22
+
23
+ ## Usage
24
+
25
+ ```typescript
26
+ import { DisTube } from "distube";
27
+ import { InvidiousPlugin } from "distube-invidious";
28
+
29
+ const distube = new DisTube({
30
+ plugins: [
31
+ new InvidiousPlugin({
32
+ instance: "yewtu.be", // or any other Invidious instance
33
+ }),
34
+ ],
35
+ });
36
+ ```
37
+
38
+ ### Basic Example
39
+
40
+ ```typescript
41
+ import { DisTube } from "distube";
42
+ import { InvidiousPlugin } from "distube-invidious";
43
+
44
+ // Initialize DisTube with Invidious plugin
45
+ const distube = new DisTube({
46
+ plugins: [
47
+ new InvidiousPlugin({
48
+ instance: "yewtu.be",
49
+ timeout: 10000, // Optional: request timeout in milliseconds (default: 10000)
50
+ }),
51
+ ],
52
+ });
53
+
54
+ // Play a YouTube video
55
+ distube.play(voiceChannel, "https://youtube.com/watch?v=dQw4w9WgXcQ");
56
+
57
+ // Play a playlist
58
+ distube.play(voiceChannel, "https://youtube.com/playlist?list=PLxyz123");
59
+
60
+ // Search for a song
61
+ distube.play(voiceChannel, "never gonna give you up");
62
+ ```
63
+
64
+ ## Configuration Options
65
+
66
+ ### `InvidiousPluginOptions`
67
+
68
+ | Option | Type | Required | Default | Description |
69
+ |--------|------|----------|---------|-------------|
70
+ | `instance` | `string` | Yes | - | Invidious instance URL (e.g., "yewtu.be", "https://yewtu.be", "http://localhost:8095") |
71
+ | `timeout` | `number` | No | `10000` | Request timeout in milliseconds |
72
+
73
+ ### Public Invidious Instances
74
+
75
+ You can find a list of public Invidious instances at:
76
+ - https://docs.invidious.io/instances/
77
+
78
+
79
+ ## Supported YouTube URLs
80
+
81
+ The plugin supports all YouTube URL formats:
82
+
83
+ - **Videos**: `youtube.com/watch?v=ID`
84
+ - **Short URLs**: `youtu.be/ID`
85
+ - **Shorts**: `youtube.com/shorts/ID`
86
+ - **Live**: `youtube.com/live/ID`
87
+ - **Embed**: `youtube.com/embed/ID`
88
+ - **Playlists**: `youtube.com/playlist?list=ID`
89
+ - **Channels**: `youtube.com/channel/UCxxxx`, `youtube.com/@handle`, `youtube.com/c/CustomName`, `youtube.com/user/username`
90
+
91
+ It also handles shared YouTube links with tracking parameters:
92
+ - `https://youtu.be/ID?si=xxx` ✅
93
+
94
+ ## Advanced Usage
95
+
96
+ ### Direct Playlist Resolution
97
+
98
+ ```typescript
99
+ import { InvidiousPlugin } from "distube-invidious";
100
+
101
+ const plugin = new InvidiousPlugin({ instance: "yewtu.be" });
102
+
103
+ // Resolve a playlist directly
104
+ const playlist = await plugin.playlist(
105
+ "https://youtube.com/playlist?list=PLxyz123",
106
+ {}
107
+ );
108
+
109
+ console.log(`Playlist: ${playlist.name}`);
110
+ console.log(`Songs: ${playlist.songs.length}`);
111
+ ```
112
+
113
+ ### Stream URL Extraction
114
+
115
+ ```typescript
116
+ // Get the audio stream URL for a song
117
+ const streamUrl = await plugin.getStreamURL(song);
118
+ console.log(`Stream URL: ${streamUrl}`);
119
+ ```
120
+
121
+ ### Related Songs
122
+
123
+ ```typescript
124
+ // Get related songs for a video
125
+ const relatedSongs = await plugin.getRelatedSongs(song);
126
+ console.log(`Found ${relatedSongs.length} related songs`);
127
+ ```
128
+
129
+ ## API
130
+
131
+ ### `InvidiousPlugin`
132
+
133
+ #### Methods
134
+
135
+ - **`validate(url: string): boolean`** - Checks if a URL is a valid YouTube URL
136
+ - **`resolve(url, options): Promise<Song \| Playlist>`** - Resolves a URL to a Song or Playlist
137
+ - **`searchSong(query, options): Promise<Song \| null>`** - Searches for a song
138
+ - **`getStreamURL(song): Promise<string>`** - Gets the audio stream URL
139
+ - **`getRelatedSongs(song): Promise<Song[]>`** - Gets related songs
140
+ - **`playlist(url, options): Promise<Playlist>`** - Resolves a playlist directly
141
+
142
+ ## Requirements
143
+
144
+ - Node.js 16+
145
+ - DisTube 4.0.0 or 5.0.0+
146
+
147
+ ## License
148
+
149
+ MIT
150
+
151
+ ## Contributing
152
+
153
+ Contributions are welcome! Please feel free to submit a Pull Request.
154
+
155
+ ## Disclaimer
156
+
157
+ This plugin is not affiliated with, endorsed by, or connected to YouTube, DisTube or Invidious. Also this plugin is an unofficial plugin to help enhance your DisTube experience!
@@ -0,0 +1,52 @@
1
+ import { ExtractorPlugin, Playlist, ResolveOptions, Song } from "distube";
2
+ import type { InvidiousPluginOptions } from "./types.js";
3
+ export declare class InvidiousPlugin extends ExtractorPlugin {
4
+ readonly instance: string;
5
+ private readonly timeout;
6
+ constructor(options: InvidiousPluginOptions);
7
+ /**
8
+ * Validates if URL is a YouTube URL.
9
+ * Calls stripQuery() first to remove tracking parameters.
10
+ */
11
+ validate(url: string): boolean;
12
+ /**
13
+ * Resolves a URL to a Song or Playlist.
14
+ * Calls stripQuery() first to remove tracking parameters.
15
+ */
16
+ resolve<T>(url: string, options: ResolveOptions<T>): Promise<Song<T> | Playlist<T>>;
17
+ /**
18
+ * Searches for a song and returns the first result.
19
+ */
20
+ searchSong<T>(query: string, options: ResolveOptions<T>): Promise<Song<T> | null>;
21
+ /**
22
+ * Gets the stream URL for a song.
23
+ * Fetches fresh video data and extracts highest-quality audio stream.
24
+ */
25
+ getStreamURL(song: Song): Promise<string>;
26
+ /**
27
+ * Gets related songs for a given song.
28
+ */
29
+ getRelatedSongs(song: Song): Promise<Song[]>;
30
+ /**
31
+ * Public method for direct playlist resolution.
32
+ */
33
+ playlist<T>(url: string, options: ResolveOptions<T>): Promise<Playlist<T>>;
34
+ /**
35
+ * Wraps fetch with AbortController for timeout handling.
36
+ */
37
+ private fetchWithTimeout;
38
+ /**
39
+ * Returns highest-resolution thumbnail URL.
40
+ */
41
+ private getBestThumbnail;
42
+ /**
43
+ * Creates a Song object from Invidious video data.
44
+ */
45
+ private createSong;
46
+ /**
47
+ * Creates a Song object from video data (for related videos).
48
+ */
49
+ private createSongFromData;
50
+ }
51
+ export default InvidiousPlugin;
52
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EACf,QAAQ,EACR,cAAc,EACd,IAAI,EAEL,MAAM,SAAS,CAAC;AAEjB,OAAO,KAAK,EACV,sBAAsB,EAIvB,MAAM,YAAY,CAAC;AAWpB,qBAAa,eAAgB,SAAQ,eAAe;IAClD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;gBAErB,OAAO,EAAE,sBAAsB;IAe3C;;;OAGG;IAEH,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAI9B;;;OAGG;IAEG,OAAO,CAAC,CAAC,EACb,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,cAAc,CAAC,CAAC,CAAC,GACzB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;IAsBjC;;OAEG;IAEG,UAAU,CAAC,CAAC,EAChB,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,cAAc,CAAC,CAAC,CAAC,GACzB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IAsB1B;;;OAGG;IAEG,YAAY,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC;IA2C/C;;OAEG;IAEG,eAAe,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;IAgBlD;;OAEG;IAEG,QAAQ,CAAC,CAAC,EACd,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,cAAc,CAAC,CAAC,CAAC,GACzB,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;IAsDvB;;OAEG;YAEW,gBAAgB;IA2B9B;;OAEG;IAEH,OAAO,CAAC,gBAAgB;IAcxB;;OAEG;IAEH,OAAO,CAAC,UAAU;IA4BlB;;OAEG;IAEH,OAAO,CAAC,kBAAkB;CAqB3B;AAED,eAAe,eAAe,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,248 @@
1
+ import { ExtractorPlugin, Playlist, Song, DisTubeError, } from "distube";
2
+ import fetch from "node-fetch";
3
+ import { stripQuery, isYouTubeUrl, extractVideoId, extractPlaylistId, } from "./url-utils.js";
4
+ // InvidiousPlugin Class
5
+ export class InvidiousPlugin extends ExtractorPlugin {
6
+ instance;
7
+ timeout;
8
+ constructor(options) {
9
+ super();
10
+ // Normalize instance:
11
+ // 1. Remove trailing slash
12
+ // 2. Prepend "https://" if missing
13
+ let normalized = options.instance.replace(/\/+$/, "");
14
+ if (!normalized.startsWith("http://") && !normalized.startsWith("https://")) {
15
+ normalized = "https://" + normalized;
16
+ }
17
+ this.instance = normalized;
18
+ this.timeout = options.timeout ?? 10000;
19
+ }
20
+ // Required ExtractorPlugin Methods
21
+ /**
22
+ * Validates if URL is a YouTube URL.
23
+ * Calls stripQuery() first to remove tracking parameters.
24
+ */
25
+ validate(url) {
26
+ return isYouTubeUrl(url);
27
+ }
28
+ /**
29
+ * Resolves a URL to a Song or Playlist.
30
+ * Calls stripQuery() first to remove tracking parameters.
31
+ */
32
+ async resolve(url, options) {
33
+ const cleanUrl = stripQuery(url);
34
+ // Check if it's a playlist URL
35
+ if (cleanUrl.includes("list=") && !cleanUrl.includes("v=")) {
36
+ return this.playlist(url, options);
37
+ }
38
+ // Extract video ID
39
+ const videoId = extractVideoId(cleanUrl);
40
+ if (!videoId) {
41
+ throw new DisTubeError("CANNOT_RESOLVE_SONG", "Invalid YouTube URL");
42
+ }
43
+ // Fetch video data from Invidious API
44
+ const apiUrl = `${this.instance}/api/v1/videos/${videoId}`;
45
+ const data = (await this.fetchWithTimeout(apiUrl));
46
+ // Convert to Song
47
+ return this.createSong(data, options);
48
+ }
49
+ /**
50
+ * Searches for a song and returns the first result.
51
+ */
52
+ async searchSong(query, options) {
53
+ const apiUrl = `${this.instance}/api/v1/search?q=${encodeURIComponent(query)}&type=video`;
54
+ const data = (await this.fetchWithTimeout(apiUrl));
55
+ if (!data || data.length === 0) {
56
+ return null;
57
+ }
58
+ const firstResult = data[0];
59
+ if (!firstResult.videoId) {
60
+ return null;
61
+ }
62
+ // Fetch full video data
63
+ const videoUrl = `${this.instance}/api/v1/videos/${firstResult.videoId}`;
64
+ const videoData = (await this.fetchWithTimeout(videoUrl));
65
+ return this.createSong(videoData, options);
66
+ }
67
+ /**
68
+ * Gets the stream URL for a song.
69
+ * Fetches fresh video data and extracts highest-quality audio stream.
70
+ */
71
+ async getStreamURL(song) {
72
+ const apiUrl = `${this.instance}/api/v1/videos/${song.id}`;
73
+ const data = (await this.fetchWithTimeout(apiUrl));
74
+ // Priority 1: Audio-only from adaptiveFormats
75
+ // Check both 'type' and 'mimeType' fields as different instances may use either
76
+ if (data.adaptiveFormats && data.adaptiveFormats.length > 0) {
77
+ // First try to find opus audio
78
+ let bestAudio = data.adaptiveFormats.find((format) => {
79
+ const mimeType = format.mimeType || format.type || "";
80
+ return mimeType.includes("audio") && mimeType.includes("opus");
81
+ });
82
+ // If no opus, try AAC
83
+ if (!bestAudio) {
84
+ bestAudio = data.adaptiveFormats.find((format) => {
85
+ const mimeType = format.mimeType || format.type || "";
86
+ return mimeType.includes("audio") && mimeType.includes("mp4");
87
+ });
88
+ }
89
+ // If still no audio, find any audio format
90
+ if (!bestAudio) {
91
+ bestAudio = data.adaptiveFormats.find((format) => {
92
+ const mimeType = format.mimeType || format.type || "";
93
+ return mimeType.includes("audio");
94
+ });
95
+ }
96
+ if (bestAudio && bestAudio.url) {
97
+ return bestAudio.url;
98
+ }
99
+ }
100
+ // Priority 2: Fallback to first entry in formatStreams
101
+ if (data.formatStreams && data.formatStreams.length > 0) {
102
+ return data.formatStreams[0].url;
103
+ }
104
+ // Priority 3: Throw error if no valid stream found
105
+ throw new DisTubeError("CANNOT_GET_STREAM_URL", "No playable stream found");
106
+ }
107
+ /**
108
+ * Gets related songs for a given song.
109
+ */
110
+ async getRelatedSongs(song) {
111
+ const apiUrl = `${this.instance}/api/v1/videos/${song.id}`;
112
+ const data = (await this.fetchWithTimeout(apiUrl));
113
+ if (!data.recommendedVideos || data.recommendedVideos.length === 0) {
114
+ return [];
115
+ }
116
+ // Return up to 10 related songs
117
+ return data.recommendedVideos.slice(0, 10).map((video) => this.createSongFromData(video));
118
+ }
119
+ // Playlist Resolution
120
+ /**
121
+ * Public method for direct playlist resolution.
122
+ */
123
+ async playlist(url, options) {
124
+ const cleanUrl = stripQuery(url);
125
+ const playlistId = extractPlaylistId(cleanUrl);
126
+ if (!playlistId) {
127
+ throw new DisTubeError("CANNOT_RESOLVE_SONG", "Invalid playlist URL");
128
+ }
129
+ const apiUrl = `${this.instance}/api/v1/playlists/${playlistId}`;
130
+ const data = (await this.fetchWithTimeout(apiUrl));
131
+ // Create Song objects from playlist videos
132
+ const songs = data.videos.map((video) => this.createSongFromData({
133
+ videoId: video.videoId,
134
+ title: video.title,
135
+ author: video.author,
136
+ authorId: data.authorId,
137
+ authorUrl: `/channel/${data.authorId}`,
138
+ lengthSeconds: video.lengthSeconds,
139
+ videoThumbnails: video.videoThumbnails.map((t) => ({
140
+ quality: "",
141
+ url: t.url,
142
+ width: t.width,
143
+ height: t.height,
144
+ })),
145
+ viewCount: 0,
146
+ likeCount: 0,
147
+ liveNow: false,
148
+ recommendedVideos: [],
149
+ formatStreams: [],
150
+ adaptiveFormats: [],
151
+ }));
152
+ // Create Playlist object
153
+ const playlist = new Playlist({
154
+ source: "youtube",
155
+ name: data.title,
156
+ id: data.playlistId,
157
+ url: `${this.instance}/playlist?list=${data.playlistId}`,
158
+ thumbnail: this.getBestThumbnail(data.videos[0]?.videoThumbnails || []),
159
+ songs,
160
+ }, options);
161
+ return playlist;
162
+ }
163
+ // Private Helper Methods
164
+ /**
165
+ * Wraps fetch with AbortController for timeout handling.
166
+ */
167
+ async fetchWithTimeout(url) {
168
+ const controller = new AbortController();
169
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
170
+ try {
171
+ const response = await fetch(url, {
172
+ signal: controller.signal,
173
+ });
174
+ if (!response.ok) {
175
+ throw new DisTubeError("CANNOT_RESOLVE_SONG", `HTTP ${response.status}: ${response.statusText}`);
176
+ }
177
+ return await response.json();
178
+ }
179
+ catch (error) {
180
+ if (error.name === "AbortError") {
181
+ throw new DisTubeError("CANNOT_RESOLVE_SONG", "Request timeout");
182
+ }
183
+ throw error;
184
+ }
185
+ finally {
186
+ clearTimeout(timeoutId);
187
+ }
188
+ }
189
+ /**
190
+ * Returns highest-resolution thumbnail URL.
191
+ */
192
+ getBestThumbnail(thumbnails) {
193
+ if (!thumbnails || thumbnails.length === 0) {
194
+ return "";
195
+ }
196
+ // Sort by resolution (width * height) and return the highest
197
+ const sorted = [...thumbnails].sort((a, b) => b.width * b.height - a.width * a.height);
198
+ return sorted[0].url;
199
+ }
200
+ /**
201
+ * Creates a Song object from Invidious video data.
202
+ */
203
+ createSong(data, options) {
204
+ const song = new Song({
205
+ plugin: this,
206
+ source: "youtube",
207
+ playFromSource: true,
208
+ id: data.videoId,
209
+ name: data.title,
210
+ url: `${this.instance}/watch?v=${data.videoId}`,
211
+ thumbnail: this.getBestThumbnail(data.videoThumbnails),
212
+ duration: data.lengthSeconds,
213
+ isLive: data.liveNow,
214
+ views: data.viewCount,
215
+ likes: data.likeCount,
216
+ uploader: {
217
+ name: data.author,
218
+ url: `${this.instance}${data.authorUrl}`,
219
+ },
220
+ }, options);
221
+ return song;
222
+ }
223
+ /**
224
+ * Creates a Song object from video data (for related videos).
225
+ */
226
+ createSongFromData(data) {
227
+ const song = new Song({
228
+ plugin: this,
229
+ source: "youtube",
230
+ playFromSource: true,
231
+ id: data.videoId,
232
+ name: data.title,
233
+ url: `${this.instance}/watch?v=${data.videoId}`,
234
+ thumbnail: this.getBestThumbnail(data.videoThumbnails),
235
+ duration: data.lengthSeconds,
236
+ isLive: data.liveNow,
237
+ views: data.viewCount,
238
+ likes: data.likeCount,
239
+ uploader: {
240
+ name: data.author,
241
+ url: `${this.instance}/channel/${data.authorId}`,
242
+ },
243
+ });
244
+ return song;
245
+ }
246
+ }
247
+ export default InvidiousPlugin;
248
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,72 @@
1
+ export interface InvidiousPluginOptions {
2
+ instance: string;
3
+ timeout?: number;
4
+ }
5
+ export interface InvidiousVideoResponse {
6
+ videoId: string;
7
+ title: string;
8
+ videoThumbnails: Array<{
9
+ quality: string;
10
+ url: string;
11
+ width: number;
12
+ height: number;
13
+ }>;
14
+ lengthSeconds: number;
15
+ author: string;
16
+ authorId: string;
17
+ authorUrl: string;
18
+ viewCount: number;
19
+ likeCount: number;
20
+ liveNow: boolean;
21
+ recommendedVideos: InvidiousVideoResponse[];
22
+ formatStreams: Array<{
23
+ url: string;
24
+ type: string;
25
+ mimeType?: string;
26
+ quality: string;
27
+ }>;
28
+ adaptiveFormats: Array<{
29
+ url: string;
30
+ type: string;
31
+ mimeType?: string;
32
+ encoding?: string;
33
+ }>;
34
+ hlsUrl?: string;
35
+ dashUrl?: string;
36
+ }
37
+ export interface InvidiousPlaylistResponse {
38
+ title: string;
39
+ playlistId: string;
40
+ author: string;
41
+ authorId: string;
42
+ videoCount: number;
43
+ videos: Array<{
44
+ title: string;
45
+ videoId: string;
46
+ author: string;
47
+ lengthSeconds: number;
48
+ videoThumbnails: Array<{
49
+ url: string;
50
+ width: number;
51
+ height: number;
52
+ }>;
53
+ }>;
54
+ }
55
+ export interface InvidiousSearchResponse {
56
+ type: string;
57
+ title: string;
58
+ videoId?: string;
59
+ author: string;
60
+ videoThumbnails: Array<{
61
+ url: string;
62
+ width: number;
63
+ height: number;
64
+ }>;
65
+ lengthSeconds?: number;
66
+ }
67
+ export interface InvidiousChannelResponse {
68
+ author: string;
69
+ authorId: string;
70
+ latestVideos: InvidiousVideoResponse[];
71
+ }
72
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,eAAe,EAAE,KAAK,CAAC;QACrB,OAAO,EAAE,MAAM,CAAC;QAChB,GAAG,EAAE,MAAM,CAAC;QACZ,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC,CAAC;IACH,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,OAAO,CAAC;IACjB,iBAAiB,EAAE,sBAAsB,EAAE,CAAC;IAC5C,aAAa,EAAE,KAAK,CAAC;QACnB,GAAG,EAAE,MAAM,CAAC;QACZ,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC,CAAC;IACH,eAAe,EAAE,KAAK,CAAC;QACrB,GAAG,EAAE,MAAM,CAAC;QACZ,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,CAAC,CAAC;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,yBAAyB;IACxC,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,KAAK,CAAC;QACZ,KAAK,EAAE,MAAM,CAAC;QACd,OAAO,EAAE,MAAM,CAAC;QAChB,MAAM,EAAE,MAAM,CAAC;QACf,aAAa,EAAE,MAAM,CAAC;QACtB,eAAe,EAAE,KAAK,CAAC;YAAE,GAAG,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,MAAM,CAAC;YAAC,MAAM,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;KACxE,CAAC,CAAC;CACJ;AAED,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,eAAe,EAAE,KAAK,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACvE,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,wBAAwB;IACvC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,sBAAsB,EAAE,CAAC;CACxC"}
package/dist/types.js ADDED
@@ -0,0 +1,3 @@
1
+ // TypeScript interfaces for Invidious API responses
2
+ export {};
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,oDAAoD"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Strips query parameters from URL (everything from "?" onward).
3
+ * Must be called FIRST in all URL parsing functions to handle shared
4
+ * YouTube links with tracking parameters (e.g., https://youtu.be/ID?si=xxx).
5
+ */
6
+ export declare function stripQuery(url: string): string;
7
+ /**
8
+ * Tests if URL matches any YouTube URL pattern.
9
+ */
10
+ export declare function isYouTubeUrl(url: string): boolean;
11
+ /**
12
+ * Extracts video ID from URL patterns.
13
+ */
14
+ export declare function extractVideoId(url: string): string | null;
15
+ /**
16
+ * Extracts playlist ID using pattern: [?&]list=
17
+ */
18
+ export declare function extractPlaylistId(url: string): string | null;
19
+ /**
20
+ * Extracts channel identifier from patterns.
21
+ */
22
+ export declare function extractChannel(url: string): string | null;
23
+ /**
24
+ * Converts any YouTube URL to Invidious equivalent using normalized instance URL.
25
+ */
26
+ export declare function toInvidious(url: string, instance: string): string;
27
+ //# sourceMappingURL=url-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"url-utils.d.ts","sourceRoot":"","sources":["../src/url-utils.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAG9C;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAejD;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAwBzD;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAI5D;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAoBzD;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAqDjE"}
@@ -0,0 +1,143 @@
1
+ // URL Parser Helper Functions for YouTube/Invidious URLs
2
+ /**
3
+ * Strips query parameters from URL (everything from "?" onward).
4
+ * Must be called FIRST in all URL parsing functions to handle shared
5
+ * YouTube links with tracking parameters (e.g., https://youtu.be/ID?si=xxx).
6
+ */
7
+ export function stripQuery(url) {
8
+ const q = url.indexOf("?");
9
+ return q === -1 ? url : url.substring(0, q);
10
+ }
11
+ /**
12
+ * Tests if URL matches any YouTube URL pattern.
13
+ */
14
+ export function isYouTubeUrl(url) {
15
+ const cleanUrl = stripQuery(url);
16
+ const patterns = [
17
+ /^https?:\/\/(www\.)?youtube\.com\/watch\?v=/,
18
+ /^https?:\/\/youtu\.be\//,
19
+ /^https?:\/\/(www\.)?youtube\.com\/shorts\//,
20
+ /^https?:\/\/(www\.)?youtube\.com\/live\//,
21
+ /^https?:\/\/(www\.)?youtube\.com\/embed\//,
22
+ /^https?:\/\/(www\.)?youtube\.com\/playlist/,
23
+ /^https?:\/\/(www\.)?youtube\.com\/channel\//,
24
+ /^https?:\/\/(www\.)?youtube\.com\/c\//,
25
+ /^https?:\/\/(www\.)?youtube\.com\/user\//,
26
+ /^https?:\/\/(www\.)?youtube\.com\/@/,
27
+ ];
28
+ return patterns.some((pattern) => pattern.test(cleanUrl));
29
+ }
30
+ /**
31
+ * Extracts video ID from URL patterns.
32
+ */
33
+ export function extractVideoId(url) {
34
+ const cleanUrl = stripQuery(url);
35
+ // Standard watch URL: ?v=ID
36
+ const watchMatch = cleanUrl.match(/[?&]v=([^&]+)/);
37
+ if (watchMatch)
38
+ return watchMatch[1];
39
+ // Short URL: youtu.be/ID
40
+ const shortMatch = cleanUrl.match(/youtu\.be\/([^/?]+)/);
41
+ if (shortMatch)
42
+ return shortMatch[1];
43
+ // Shorts: /shorts/ID
44
+ const shortsMatch = cleanUrl.match(/youtube\.com\/shorts\/([^/?]+)/);
45
+ if (shortsMatch)
46
+ return shortsMatch[1];
47
+ // Live: /live/ID
48
+ const liveMatch = cleanUrl.match(/youtube\.com\/live\/([^/?]+)/);
49
+ if (liveMatch)
50
+ return liveMatch[1];
51
+ // Embed: /embed/ID
52
+ const embedMatch = cleanUrl.match(/youtube\.com\/embed\/([^/?]+)/);
53
+ if (embedMatch)
54
+ return embedMatch[1];
55
+ return null;
56
+ }
57
+ /**
58
+ * Extracts playlist ID using pattern: [?&]list=
59
+ */
60
+ export function extractPlaylistId(url) {
61
+ const cleanUrl = stripQuery(url);
62
+ const match = cleanUrl.match(/[?&]list=([^&]+)/);
63
+ return match ? match[1] : null;
64
+ }
65
+ /**
66
+ * Extracts channel identifier from patterns.
67
+ */
68
+ export function extractChannel(url) {
69
+ const cleanUrl = stripQuery(url);
70
+ // /channel/UCxxxx
71
+ const channelMatch = cleanUrl.match(/\/channel\/([^/?]+)/);
72
+ if (channelMatch)
73
+ return channelMatch[1];
74
+ // /c/CustomName
75
+ const customMatch = cleanUrl.match(/\/c\/([^/?]+)/);
76
+ if (customMatch)
77
+ return customMatch[1];
78
+ // /user/username
79
+ const userMatch = cleanUrl.match(/\/user\/([^/?]+)/);
80
+ if (userMatch)
81
+ return userMatch[1];
82
+ // /@handle
83
+ const handleMatch = cleanUrl.match(/\/@([^/?]+)/);
84
+ if (handleMatch)
85
+ return handleMatch[1];
86
+ return null;
87
+ }
88
+ /**
89
+ * Converts any YouTube URL to Invidious equivalent using normalized instance URL.
90
+ */
91
+ export function toInvidious(url, instance) {
92
+ const cleanUrl = stripQuery(url);
93
+ const patterns = [
94
+ {
95
+ regex: /^https?:\/\/(www\.)?youtube\.com\/watch\?v=([^&]+)/,
96
+ replace: `${instance}/watch?v=$2`,
97
+ },
98
+ {
99
+ regex: /^https?:\/\/youtu\.be\/([^/?]+)/,
100
+ replace: `${instance}/watch?v=$1`,
101
+ },
102
+ {
103
+ regex: /^https?:\/\/(www\.)?youtube\.com\/shorts\/([^/?]+)/,
104
+ replace: `${instance}/watch?v=$2`,
105
+ },
106
+ {
107
+ regex: /^https?:\/\/(www\.)?youtube\.com\/live\/([^/?]+)/,
108
+ replace: `${instance}/watch?v=$2`,
109
+ },
110
+ {
111
+ regex: /^https?:\/\/(www\.)?youtube\.com\/embed\/([^/?]+)/,
112
+ replace: `${instance}/watch?v=$2`,
113
+ },
114
+ {
115
+ regex: /^https?:\/\/(www\.)?youtube\.com\/playlist\?list=([^&]+)/,
116
+ replace: `${instance}/playlist?list=$2`,
117
+ },
118
+ {
119
+ regex: /^https?:\/\/(www\.)?youtube\.com\/channel\/([^/?]+)/,
120
+ replace: `${instance}/channel/$2`,
121
+ },
122
+ {
123
+ regex: /^https?:\/\/(www\.)?youtube\.com\/c\/([^/?]+)/,
124
+ replace: `${instance}/channel/$2`,
125
+ },
126
+ {
127
+ regex: /^https?:\/\/(www\.)?youtube\.com\/user\/([^/?]+)/,
128
+ replace: `${instance}/user/$2`,
129
+ },
130
+ {
131
+ regex: /^https?:\/\/(www\.)?youtube\.com\/@([^/?]+)/,
132
+ replace: `${instance}/@$2`,
133
+ },
134
+ ];
135
+ for (const pattern of patterns) {
136
+ const match = cleanUrl.match(pattern.regex);
137
+ if (match) {
138
+ return pattern.replace;
139
+ }
140
+ }
141
+ return url;
142
+ }
143
+ //# sourceMappingURL=url-utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"url-utils.js","sourceRoot":"","sources":["../src/url-utils.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "distube-invidious",
3
+ "version": "1.0.0",
4
+ "author": "techtimefor",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/techtimefor/distube-invidious.git"
8
+ },
9
+ "description": "An unofficial Invidious extractor plugin for DisTube",
10
+ "type": "module",
11
+ "main": "./dist/index.js",
12
+ "types": "./dist/index.d.ts",
13
+ "exports": "./dist/index.js",
14
+ "files": [
15
+ "dist"
16
+ ],
17
+ "directories": {
18
+ "lib": "src"
19
+ },
20
+ "scripts": {
21
+ "build": "tsc",
22
+ "typecheck": "tsc --noEmit",
23
+ "test": "npm run typecheck",
24
+ "prepack": "npm run build"
25
+ },
26
+ "keywords": [
27
+ "distube",
28
+ "invidious",
29
+ "youtube",
30
+ "music",
31
+ "bot",
32
+ "plugin",
33
+ "discord",
34
+ "distube-invidious",
35
+ "distube-invidious-plugin",
36
+ "invidious-plugin"
37
+ ],
38
+ "license": "MIT",
39
+ "publishConfig": {
40
+ "access": "public"
41
+ },
42
+ "peerDependencies": {
43
+ "distube": "^4.0.0 || ^5.0.0"
44
+ },
45
+ "dependencies": {
46
+ "node-fetch": "^3.3.2"
47
+ },
48
+ "devDependencies": {
49
+ "@types/node": "^20.0.0",
50
+ "typescript": "^5.0.0"
51
+ }
52
+ }