discord-player-youtubei
Advanced tools
Comparing version 1.3.5 to 1.3.6-beta.0
import Innertube from 'youtubei.js'; | ||
import { PoTokenResult } from 'bgutils-js'; | ||
import * as googlevideo_dist_src_core from 'googlevideo/dist/src/core'; | ||
import { Track } from 'discord-player'; | ||
import { Y as YoutubeiExtractor } from '../Youtube-Cl8NAitT.js'; | ||
import { PassThrough } from 'stream'; | ||
import 'youtubei.js/dist/src/types'; | ||
import 'node:stream'; | ||
import 'youtubei.js/dist/src/parser/nodes'; | ||
import 'node:async_hooks'; | ||
declare function poTokenExtraction(innertube: Innertube): Promise<PoTokenResult>; | ||
export { poTokenExtraction }; | ||
declare function getGoogleVideoOrThrow(): typeof googlevideo_dist_src_core; | ||
declare function createServerAbrStream(track: Track, ext: YoutubeiExtractor, onError?: (err: Error) => any): Promise<PassThrough>; | ||
export { createServerAbrStream, getGoogleVideoOrThrow, poTokenExtraction }; |
@@ -23,2 +23,4 @@ "use strict"; | ||
__export(experimental_exports, { | ||
createServerAbrStream: () => createServerAbrStream, | ||
getGoogleVideoOrThrow: () => getGoogleVideoOrThrow, | ||
poTokenExtraction: () => poTokenExtraction | ||
@@ -62,2 +64,77 @@ }); | ||
// lib/common/extractVideoID.ts | ||
function extractVideoId(vid) { | ||
const YOUTUBE_REGEX = /^https:\/\/(www\.)?youtu(\.be\/[A-Za-z0-9]{11}(.+)?|be\.com\/watch\?v=[A-Za-z0-9]{11}(&.+)?)/; | ||
if (!YOUTUBE_REGEX.test(vid)) throw new Error("Invalid youtube url"); | ||
let id = new URL(vid).searchParams.get("v"); | ||
if (!id) id = vid.split("/").at(-1)?.split("?").at(0); | ||
return id; | ||
} | ||
// lib/experimental/ServerAbrStream/index.ts | ||
var import_stream = require("stream"); | ||
function getGoogleVideoOrThrow() { | ||
try { | ||
return require("googlevideo").default; | ||
} catch { | ||
throw new Error( | ||
'Unable to find googlevideo. Please install it via "npm install googlevideo"' | ||
); | ||
} | ||
} | ||
async function createServerAbrStream(track, ext, onError) { | ||
const innertube = ext.innerTube; | ||
const { ServerAbrStream } = getGoogleVideoOrThrow(); | ||
if (!innertube.session.player) | ||
throw new Error("ServerAbrStream does not work without a valid player."); | ||
const videoInfo = await innertube.getBasicInfo( | ||
extractVideoId(track.url), | ||
"WEB" | ||
); | ||
const fmt = videoInfo.chooseFormat({ type: "audio", quality: "best" }); | ||
const audio = { | ||
itag: fmt.itag, | ||
lastModified: fmt.last_modified_ms, | ||
xtags: fmt.xtags | ||
}; | ||
const sabrStreamUrl = innertube.session.player.decipher( | ||
videoInfo.page[0].streaming_data?.server_abr_streaming_url | ||
); | ||
const videoPlaybackUstreamerConfig = videoInfo.page[0].player_config?.media_common_config.media_ustreamer_request_config?.video_playback_ustreamer_config; | ||
if (!videoPlaybackUstreamerConfig) | ||
throw new Error("ustreamerConfig not found"); | ||
if (!sabrStreamUrl) throw new Error("serverAbrStreamingUrl not found"); | ||
const sabrStream = new ServerAbrStream({ | ||
fetch: innertube.session.http.fetch_function, | ||
serverAbrStreamingUrl: sabrStreamUrl, | ||
videoPlaybackUstreamerConfig, | ||
durationMs: (videoInfo.basic_info.duration ?? 0) * 1e3 | ||
}); | ||
const readable = new import_stream.PassThrough(); | ||
sabrStream.on("data", (data) => { | ||
for (const formatData of data.initializedFormats) { | ||
if (formatData.mimeType?.includes("video")) continue; | ||
const media = formatData.mediaChunks; | ||
for (const chunk of media) { | ||
readable.write(chunk); | ||
} | ||
} | ||
}); | ||
sabrStream.on("error", (err) => { | ||
if (onError) onError(err); | ||
}); | ||
sabrStream.on("end", () => { | ||
readable.end(); | ||
}); | ||
sabrStream.init({ | ||
audioFormats: [audio], | ||
videoFormats: [], | ||
clientAbrState: { | ||
startTimeMs: 0, | ||
mediaType: 1 | ||
} | ||
}); | ||
return readable; | ||
} | ||
// lib/experimental/index.ts | ||
@@ -69,3 +146,5 @@ console.log( | ||
0 && (module.exports = { | ||
createServerAbrStream, | ||
getGoogleVideoOrThrow, | ||
poTokenExtraction | ||
}); |
@@ -1,8 +0,4 @@ | ||
import { SearchQueryType, Track, BaseExtractor, ExtractorStreamable, ExtractorSearchContext, ExtractorInfo, Playlist, GuildQueueHistory } from 'discord-player'; | ||
import { Y as YoutubeiExtractor } from './Youtube-Cl8NAitT.js'; | ||
export { A as AsyncTrackingContext, Q as QueryBridgeModes, R as RefreshInnertubeOptions, S as StreamOptions, T as TrustedTokenConfig, a as YoutubeiOptions } from './Youtube-Cl8NAitT.js'; | ||
import Innertube, { OAuth2Tokens } from 'youtubei.js'; | ||
import { InnerTubeClient, DownloadOptions, InnerTubeConfig, FormatOptions } from 'youtubei.js/dist/src/types'; | ||
import { Readable } from 'node:stream'; | ||
import { Video } from 'youtubei.js/dist/src/parser/nodes'; | ||
import { AsyncLocalStorage } from 'node:async_hooks'; | ||
import { PoTokenResult } from 'bgutils-js'; | ||
import * as youtubei_js_agnostic from 'youtubei.js/agnostic'; | ||
@@ -14,65 +10,10 @@ import { VideoInfo } from 'youtubei.js/dist/src/parser/youtube'; | ||
import { ChatAction } from 'youtubei.js/dist/src/parser/youtube/LiveChat'; | ||
import { FormatOptions } from 'youtubei.js/dist/src/types'; | ||
import { PassThrough } from 'stream'; | ||
import 'discord-player'; | ||
import 'node:stream'; | ||
import 'youtubei.js/dist/src/parser/nodes'; | ||
import 'node:async_hooks'; | ||
import 'bgutils-js'; | ||
interface StreamOptions { | ||
useClient?: InnerTubeClient; | ||
highWaterMark?: number; | ||
} | ||
interface RefreshInnertubeOptions { | ||
filePath: string; | ||
interval?: number; | ||
} | ||
type TrustedTokenConfig = { | ||
poToken: string; | ||
visitorData: string; | ||
}; | ||
type QueryBridgeModes = Partial<Record<SearchQueryType, "yt" | "ytmusic">> & { | ||
default?: "yt" | "ytmusic"; | ||
}; | ||
interface YoutubeiOptions { | ||
authentication?: string; | ||
overrideDownloadOptions?: DownloadOptions; | ||
createStream?: (q: Track, extractor: BaseExtractor<object>) => Promise<string | Readable>; | ||
signOutOnDeactive?: boolean; | ||
streamOptions?: StreamOptions; | ||
overrideBridgeMode?: "ytmusic" | "yt" | QueryBridgeModes; | ||
disablePlayer?: boolean; | ||
ignoreSignInErrors?: boolean; | ||
innertubeConfigRaw?: InnerTubeConfig; | ||
trustedTokens?: TrustedTokenConfig; | ||
cookie?: string; | ||
} | ||
interface AsyncTrackingContext { | ||
useClient: InnerTubeClient; | ||
highWaterMark?: number; | ||
} | ||
declare class YoutubeiExtractor extends BaseExtractor<YoutubeiOptions> { | ||
#private; | ||
static identifier: string; | ||
innerTube: Innertube; | ||
_stream: (q: Track, extractor: BaseExtractor<object>) => Promise<ExtractorStreamable>; | ||
static instance?: YoutubeiExtractor; | ||
priority: number; | ||
static ytContext: AsyncLocalStorage<AsyncTrackingContext>; | ||
setInnertube(tube: Innertube): void; | ||
setPoToken(token: PoTokenResult, visitorData: string): Promise<void>; | ||
static getInstance(): YoutubeiExtractor | undefined; | ||
setClientMode(client: InnerTubeClient): void; | ||
static getStreamingContext(): AsyncTrackingContext; | ||
activate(): Promise<void>; | ||
signIn(tokens: string): Promise<void>; | ||
deactivate(): Promise<void>; | ||
validate(query: string, type?: SearchQueryType | null | undefined): Promise<boolean>; | ||
bridge(track: Track, ext: BaseExtractor | null): Promise<ExtractorStreamable | null>; | ||
bridgeFromYTMusic(query: string, track: Track): Promise<ExtractorStreamable | null>; | ||
bridgeFromYT(query: string, track: Track): Promise<ExtractorStreamable | null>; | ||
handle(query: string, context: ExtractorSearchContext): Promise<ExtractorInfo>; | ||
buildTrack(vid: Video, context: ExtractorSearchContext, pl?: Playlist): Track<any>; | ||
stream(info: Track<unknown>): Promise<ExtractorStreamable>; | ||
getRelatedTracks(track: Track<{ | ||
duration_ms: number; | ||
live: boolean; | ||
}>, history: GuildQueueHistory<unknown>): Promise<ExtractorInfo>; | ||
} | ||
declare function objectToToken(tokens: OAuth2Tokens): string; | ||
@@ -189,2 +130,2 @@ declare function tokenToObject(token: string): OAuth2Tokens; | ||
export { type AsyncTrackingContext, ChatMessageType, Errors, type If, LiveChatEvents, type QueryBridgeModes, type RefreshInnertubeOptions, type StreamOptions, type TrustedTokenConfig, YoutubeiExtractor, type YoutubeiOptions, generateOauthTokens, getLiveChat, getYoutubeiInstance, objectToToken, stream, tokenToObject, validateURL }; | ||
export { ChatMessageType, Errors, type If, LiveChatEvents, YoutubeiExtractor, generateOauthTokens, getLiveChat, getYoutubeiInstance, objectToToken, stream, tokenToObject, validateURL }; |
@@ -265,14 +265,18 @@ "use strict"; | ||
fetch: (input, init) => { | ||
const rotator = this.context.player.routePlanner?.getIP(); | ||
this.context.player.debug( | ||
"[EXT: discord-player-youtubei] APPLYING IP ROTATION CONFIG. ATTEMPTING TO USE " + rotator?.ip | ||
); | ||
return import_youtubei3.Platform.shim.fetch(input, { | ||
...init, | ||
// @ts-ignore | ||
dispatcher: new import_undici.Agent({ | ||
localAddress: rotator?.ip, | ||
autoSelectFamily: true | ||
}) | ||
}); | ||
let requestInit = { | ||
...init | ||
}; | ||
try { | ||
const rotator = this.context.player.routePlanner?.getIP(); | ||
if (rotator?.ip) { | ||
this.context.player.debug( | ||
`[EXT: discord-player-youtubei] APPLYING IP ROTATION CONFIG. ATTEMPTING TO USE ${rotator.ip}` | ||
); | ||
requestInit.dispatcher = new import_undici.Agent({ | ||
localAddress: rotator.ip | ||
}); | ||
} | ||
} catch { | ||
} | ||
return import_youtubei3.Platform.shim.fetch(input, requestInit); | ||
} | ||
@@ -279,0 +283,0 @@ }); |
{ | ||
"name": "discord-player-youtubei", | ||
"version": "1.3.5", | ||
"version": "1.3.6-beta.0", | ||
"description": "An unofficial package to test the use of youtubei in discord-player v6.", | ||
@@ -18,2 +18,3 @@ "main": "dist/index.js", | ||
"discord.js": "^14.16.3", | ||
"googlevideo": "^2.0.0", | ||
"happy-dom": "^15.7.4", | ||
@@ -20,0 +21,0 @@ "prettier": "^3.3.3", |
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
59260
9
1231
10
2