mirror of
https://github.com/LuanRT/YouTube.js.git
synced 2026-06-30 09:55:18 +00:00
feat: add support for generating sessions locally (#277)
* feat: add visitor data proto * feat: add support for generating session data locally * chore: add test
This commit is contained in:
@@ -1,12 +1,13 @@
|
|||||||
import UniversalCache from '../utils/Cache';
|
import UniversalCache from '../utils/Cache';
|
||||||
import Constants from '../utils/Constants';
|
import Constants, { CLIENTS } from '../utils/Constants';
|
||||||
import EventEmitterLike from '../utils/EventEmitterLike';
|
import EventEmitterLike from '../utils/EventEmitterLike';
|
||||||
import Actions from './Actions';
|
import Actions from './Actions';
|
||||||
import Player from './Player';
|
import Player from './Player';
|
||||||
|
|
||||||
import HTTPClient, { FetchFunction } from '../utils/HTTPClient';
|
import HTTPClient, { FetchFunction } from '../utils/HTTPClient';
|
||||||
import { DeviceCategory, getRandomUserAgent, InnertubeError, SessionError } from '../utils/Utils';
|
import { DeviceCategory, generateRandomString, getRandomUserAgent, InnertubeError, SessionError } from '../utils/Utils';
|
||||||
import OAuth, { Credentials, OAuthAuthErrorEventHandler, OAuthAuthEventHandler, OAuthAuthPendingEventHandler } from './OAuth';
|
import OAuth, { Credentials, OAuthAuthErrorEventHandler, OAuthAuthEventHandler, OAuthAuthPendingEventHandler } from './OAuth';
|
||||||
|
import Proto from '../proto';
|
||||||
|
|
||||||
export enum ClientType {
|
export enum ClientType {
|
||||||
WEB = 'WEB',
|
WEB = 'WEB',
|
||||||
@@ -21,7 +22,7 @@ export interface Context {
|
|||||||
client: {
|
client: {
|
||||||
hl: string;
|
hl: string;
|
||||||
gl: string;
|
gl: string;
|
||||||
remoteHost: string;
|
remoteHost?: string;
|
||||||
screenDensityFloat: number;
|
screenDensityFloat: number;
|
||||||
screenHeightPoints: number;
|
screenHeightPoints: number;
|
||||||
screenPixelDensity: number;
|
screenPixelDensity: number;
|
||||||
@@ -38,8 +39,8 @@ export interface Context {
|
|||||||
clientFormFactor: string;
|
clientFormFactor: string;
|
||||||
userInterfaceTheme: string;
|
userInterfaceTheme: string;
|
||||||
timeZone: string;
|
timeZone: string;
|
||||||
browserName: string;
|
browserName?: string;
|
||||||
browserVersion: string;
|
browserVersion?: string;
|
||||||
originalUrl: string;
|
originalUrl: string;
|
||||||
deviceMake: string;
|
deviceMake: string;
|
||||||
deviceModel: string;
|
deviceModel: string;
|
||||||
@@ -58,25 +59,72 @@ export interface Context {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface SessionOptions {
|
export interface SessionOptions {
|
||||||
|
/**
|
||||||
|
* Language.
|
||||||
|
*/
|
||||||
lang?: string;
|
lang?: string;
|
||||||
|
/**
|
||||||
|
* Geolocation.
|
||||||
|
*/
|
||||||
location?: string;
|
location?: string;
|
||||||
|
/**
|
||||||
|
* The account index to use. This is useful if you have multiple accounts logged in.
|
||||||
|
* **NOTE:**
|
||||||
|
* Only works if you are signed in with cookies.
|
||||||
|
*/
|
||||||
account_index?: number;
|
account_index?: number;
|
||||||
|
/**
|
||||||
|
* Specifies whether to retrieve the JS player. Disabling this will make session creation faster.
|
||||||
|
* **NOTE:** Deciphering formats is not possible without the JS player.
|
||||||
|
*/
|
||||||
retrieve_player?: boolean;
|
retrieve_player?: boolean;
|
||||||
|
/**
|
||||||
|
* Specifies whether to enable safety mode. This will prevent the session from loading any potentially unsafe content.
|
||||||
|
*/
|
||||||
enable_safety_mode?: boolean;
|
enable_safety_mode?: boolean;
|
||||||
|
/**
|
||||||
|
* Specifies whether to generate the session data locally or retrieve it from YouTube.
|
||||||
|
* This can be useful if you need more performance.
|
||||||
|
*/
|
||||||
|
generate_session_locally?: boolean;
|
||||||
|
/**
|
||||||
|
* Platform to use for the session.
|
||||||
|
*/
|
||||||
device_category?: DeviceCategory;
|
device_category?: DeviceCategory;
|
||||||
|
/**
|
||||||
|
* InnerTube client type.
|
||||||
|
*/
|
||||||
client_type?: ClientType;
|
client_type?: ClientType;
|
||||||
|
/**
|
||||||
|
* The time zone.
|
||||||
|
*/
|
||||||
timezone?: string;
|
timezone?: string;
|
||||||
|
/**
|
||||||
|
* Used to cache the deciphering functions from the JS player.
|
||||||
|
*/
|
||||||
cache?: UniversalCache;
|
cache?: UniversalCache;
|
||||||
|
/**
|
||||||
|
* YouTube cookies.
|
||||||
|
*/
|
||||||
cookie?: string;
|
cookie?: string;
|
||||||
|
/**
|
||||||
|
* Fetch function to use.
|
||||||
|
*/
|
||||||
fetch?: FetchFunction;
|
fetch?: FetchFunction;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SessionData {
|
||||||
|
context: Context;
|
||||||
|
api_key: string;
|
||||||
|
api_version: string;
|
||||||
|
}
|
||||||
|
|
||||||
export default class Session extends EventEmitterLike {
|
export default class Session extends EventEmitterLike {
|
||||||
#api_version;
|
#api_version: string;
|
||||||
#key;
|
#key: string;
|
||||||
#context;
|
#context: Context;
|
||||||
#account_index;
|
#account_index: number;
|
||||||
#player;
|
#player?: Player;
|
||||||
|
|
||||||
oauth: OAuth;
|
oauth: OAuth;
|
||||||
http: HTTPClient;
|
http: HTTPClient;
|
||||||
@@ -121,6 +169,7 @@ export default class Session extends EventEmitterLike {
|
|||||||
options.location,
|
options.location,
|
||||||
options.account_index,
|
options.account_index,
|
||||||
options.enable_safety_mode,
|
options.enable_safety_mode,
|
||||||
|
options.generate_session_locally,
|
||||||
options.device_category,
|
options.device_category,
|
||||||
options.client_type,
|
options.client_type,
|
||||||
options.timezone,
|
options.timezone,
|
||||||
@@ -135,30 +184,49 @@ export default class Session extends EventEmitterLike {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static async getSessionData(
|
static async getSessionData(
|
||||||
lang = 'en-US',
|
lang = '',
|
||||||
location = '',
|
location = '',
|
||||||
account_index = 0,
|
account_index = 0,
|
||||||
enable_safety_mode = false,
|
enable_safety_mode = false,
|
||||||
|
generate_session_locally = false,
|
||||||
device_category: DeviceCategory = 'desktop',
|
device_category: DeviceCategory = 'desktop',
|
||||||
client_name: ClientType = ClientType.WEB,
|
client_name: ClientType = ClientType.WEB,
|
||||||
tz: string = Intl.DateTimeFormat().resolvedOptions().timeZone,
|
tz: string = Intl.DateTimeFormat().resolvedOptions().timeZone,
|
||||||
fetch: FetchFunction = globalThis.fetch
|
fetch: FetchFunction = globalThis.fetch
|
||||||
) {
|
) {
|
||||||
|
let session_data: SessionData;
|
||||||
|
|
||||||
|
if (generate_session_locally) {
|
||||||
|
session_data = this.#generateSessionData({ lang, location, time_zone: tz, device_category, client_name, enable_safety_mode });
|
||||||
|
} else {
|
||||||
|
session_data = await this.#retrieveSessionData({ lang, location, time_zone: tz, device_category, client_name, enable_safety_mode }, fetch);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { ...session_data, account_index };
|
||||||
|
}
|
||||||
|
|
||||||
|
static async #retrieveSessionData(options: {
|
||||||
|
lang: string;
|
||||||
|
location: string;
|
||||||
|
time_zone: string;
|
||||||
|
device_category: string;
|
||||||
|
client_name: string;
|
||||||
|
enable_safety_mode: boolean;
|
||||||
|
}, fetch: FetchFunction = globalThis.fetch): Promise<SessionData> {
|
||||||
const url = new URL('/sw.js_data', Constants.URLS.YT_BASE);
|
const url = new URL('/sw.js_data', Constants.URLS.YT_BASE);
|
||||||
|
|
||||||
const res = await fetch(url, {
|
const res = await fetch(url, {
|
||||||
headers: {
|
headers: {
|
||||||
'accept-language': lang,
|
'accept-language': options.lang || 'en-US',
|
||||||
'user-agent': getRandomUserAgent('desktop'),
|
'user-agent': getRandomUserAgent('desktop'),
|
||||||
'accept': '*/*',
|
'accept': '*/*',
|
||||||
'referer': 'https://www.youtube.com/sw.js',
|
'referer': 'https://www.youtube.com/sw.js',
|
||||||
'cookie': `PREF=tz=${tz.replace('/', '.')}`
|
'cookie': `PREF=tz=${options.time_zone.replace('/', '.')}`
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!res.ok) {
|
if (!res.ok)
|
||||||
throw new SessionError(`Failed to get session data: ${res.status}`);
|
throw new SessionError(`Failed to retrieve session data: ${res.status}`);
|
||||||
}
|
|
||||||
|
|
||||||
const text = await res.text();
|
const text = await res.text();
|
||||||
const data = JSON.parse(text.replace(/^\)\]\}'/, ''));
|
const data = JSON.parse(text.replace(/^\)\]\}'/, ''));
|
||||||
@@ -172,22 +240,22 @@ export default class Session extends EventEmitterLike {
|
|||||||
const context: Context = {
|
const context: Context = {
|
||||||
client: {
|
client: {
|
||||||
hl: device_info[0],
|
hl: device_info[0],
|
||||||
gl: location || device_info[2],
|
gl: options.location || device_info[2],
|
||||||
remoteHost: device_info[3],
|
remoteHost: device_info[3],
|
||||||
screenDensityFloat: 1,
|
screenDensityFloat: 1,
|
||||||
screenHeightPoints: 720,
|
screenHeightPoints: 1080,
|
||||||
screenPixelDensity: 1,
|
screenPixelDensity: 1,
|
||||||
screenWidthPoints: 1280,
|
screenWidthPoints: 1920,
|
||||||
visitorData: device_info[13],
|
visitorData: device_info[13],
|
||||||
userAgent: device_info[14],
|
userAgent: device_info[14],
|
||||||
clientName: client_name,
|
clientName: options.client_name,
|
||||||
clientVersion: device_info[16],
|
clientVersion: device_info[16],
|
||||||
osName: device_info[17],
|
osName: device_info[17],
|
||||||
osVersion: device_info[18],
|
osVersion: device_info[18],
|
||||||
platform: device_category.toUpperCase(),
|
platform: options.device_category.toUpperCase(),
|
||||||
clientFormFactor: 'UNKNOWN_FORM_FACTOR',
|
clientFormFactor: 'UNKNOWN_FORM_FACTOR',
|
||||||
userInterfaceTheme: 'USER_INTERFACE_THEME_LIGHT',
|
userInterfaceTheme: 'USER_INTERFACE_THEME_LIGHT',
|
||||||
timeZone: device_info[79],
|
timeZone: device_info[79] || options.time_zone,
|
||||||
browserName: device_info[86],
|
browserName: device_info[86],
|
||||||
browserVersion: device_info[87],
|
browserVersion: device_info[87],
|
||||||
originalUrl: Constants.URLS.YT_BASE,
|
originalUrl: Constants.URLS.YT_BASE,
|
||||||
@@ -196,7 +264,7 @@ export default class Session extends EventEmitterLike {
|
|||||||
utcOffsetMinutes: new Date().getTimezoneOffset()
|
utcOffsetMinutes: new Date().getTimezoneOffset()
|
||||||
},
|
},
|
||||||
user: {
|
user: {
|
||||||
enableSafetyMode: enable_safety_mode,
|
enableSafetyMode: options.enable_safety_mode,
|
||||||
lockedSafetyMode: false
|
lockedSafetyMode: false
|
||||||
},
|
},
|
||||||
request: {
|
request: {
|
||||||
@@ -204,7 +272,53 @@ export default class Session extends EventEmitterLike {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return { context, api_key, api_version, account_index };
|
return { context, api_key, api_version };
|
||||||
|
}
|
||||||
|
|
||||||
|
static #generateSessionData(options: {
|
||||||
|
lang: string;
|
||||||
|
location: string;
|
||||||
|
time_zone: string;
|
||||||
|
device_category: DeviceCategory;
|
||||||
|
client_name: string;
|
||||||
|
enable_safety_mode: boolean
|
||||||
|
}): SessionData {
|
||||||
|
const id = generateRandomString(11);
|
||||||
|
const timestamp = Math.floor(Date.now() / 1000);
|
||||||
|
|
||||||
|
const context: Context = {
|
||||||
|
client: {
|
||||||
|
hl: options.lang || 'en',
|
||||||
|
gl: options.location || 'US',
|
||||||
|
screenDensityFloat: 1,
|
||||||
|
screenHeightPoints: 1080,
|
||||||
|
screenPixelDensity: 1,
|
||||||
|
screenWidthPoints: 1920,
|
||||||
|
visitorData: Proto.encodeVisitorData(id, timestamp),
|
||||||
|
userAgent: getRandomUserAgent('desktop'),
|
||||||
|
clientName: options.client_name,
|
||||||
|
clientVersion: CLIENTS.WEB.VERSION,
|
||||||
|
osName: 'Windows',
|
||||||
|
osVersion: '10.0',
|
||||||
|
platform: options.device_category.toUpperCase(),
|
||||||
|
clientFormFactor: 'UNKNOWN_FORM_FACTOR',
|
||||||
|
userInterfaceTheme: 'USER_INTERFACE_THEME_LIGHT',
|
||||||
|
timeZone: options.time_zone,
|
||||||
|
originalUrl: Constants.URLS.YT_BASE,
|
||||||
|
deviceMake: '',
|
||||||
|
deviceModel: '',
|
||||||
|
utcOffsetMinutes: new Date().getTimezoneOffset()
|
||||||
|
},
|
||||||
|
user: {
|
||||||
|
enableSafetyMode: options.enable_safety_mode,
|
||||||
|
lockedSafetyMode: false
|
||||||
|
},
|
||||||
|
request: {
|
||||||
|
useSsl: true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return { context, api_key: CLIENTS.WEB.API_KEY, api_version: CLIENTS.WEB.API_VERSION };
|
||||||
}
|
}
|
||||||
|
|
||||||
async signIn(credentials?: Credentials): Promise<void> {
|
async signIn(credentials?: Credentials): Promise<void> {
|
||||||
|
|||||||
@@ -2,9 +2,14 @@ import { CLIENTS } from '../utils/Constants';
|
|||||||
import { u8ToBase64 } from '../utils/Utils';
|
import { u8ToBase64 } from '../utils/Utils';
|
||||||
import { VideoMetadata } from '../core/Studio';
|
import { VideoMetadata } from '../core/Studio';
|
||||||
|
|
||||||
import { ChannelAnalytics, CreateCommentParams, GetCommentsSectionParams, InnertubePayload, LiveMessageParams, MusicSearchFilter, NotificationPreferences, PeformCommentActionParams, SearchFilter, SearchFilter_Filters } from './youtube';
|
import { ChannelAnalytics, CreateCommentParams, GetCommentsSectionParams, InnertubePayload, LiveMessageParams, MusicSearchFilter, NotificationPreferences, PeformCommentActionParams, SearchFilter, SearchFilter_Filters, VisitorData } from './youtube';
|
||||||
|
|
||||||
class Proto {
|
class Proto {
|
||||||
|
static encodeVisitorData(id: string, timestamp: number): string {
|
||||||
|
const buf = VisitorData.toBinary({ id, timestamp });
|
||||||
|
return encodeURIComponent(u8ToBase64(buf).replace(/\+/g, '-').replace(/\//g, '_'));
|
||||||
|
}
|
||||||
|
|
||||||
static encodeChannelAnalyticsParams(channel_id: string): string {
|
static encodeChannelAnalyticsParams(channel_id: string): string {
|
||||||
const buf = ChannelAnalytics.toBinary({
|
const buf = ChannelAnalytics.toBinary({
|
||||||
params: {
|
params: {
|
||||||
|
|||||||
@@ -3,12 +3,9 @@
|
|||||||
syntax = "proto2";
|
syntax = "proto2";
|
||||||
package youtube;
|
package youtube;
|
||||||
|
|
||||||
message ChannelAnalytics {
|
message VisitorData {
|
||||||
message Params {
|
required string id = 1;
|
||||||
required string channel_id = 1001;
|
required int32 timestamp = 5;
|
||||||
}
|
|
||||||
|
|
||||||
required Params params = 32;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message InnertubePayload {
|
message InnertubePayload {
|
||||||
@@ -91,6 +88,14 @@ message InnertubePayload {
|
|||||||
optional VideoThumbnail video_thumbnail = 20;
|
optional VideoThumbnail video_thumbnail = 20;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message ChannelAnalytics {
|
||||||
|
message Params {
|
||||||
|
required string channel_id = 1001;
|
||||||
|
}
|
||||||
|
|
||||||
|
required Params params = 32;
|
||||||
|
}
|
||||||
|
|
||||||
message SoundInfoParams {
|
message SoundInfoParams {
|
||||||
message Sound {
|
message Sound {
|
||||||
message Params {
|
message Params {
|
||||||
|
|||||||
@@ -15,22 +15,17 @@ import { reflectionMergePartial } from "@protobuf-ts/runtime";
|
|||||||
import { MESSAGE_TYPE } from "@protobuf-ts/runtime";
|
import { MESSAGE_TYPE } from "@protobuf-ts/runtime";
|
||||||
import { MessageType } from "@protobuf-ts/runtime";
|
import { MessageType } from "@protobuf-ts/runtime";
|
||||||
/**
|
/**
|
||||||
* @generated from protobuf message youtube.ChannelAnalytics
|
* @generated from protobuf message youtube.VisitorData
|
||||||
*/
|
*/
|
||||||
export interface ChannelAnalytics {
|
export interface VisitorData {
|
||||||
/**
|
/**
|
||||||
* @generated from protobuf field: youtube.ChannelAnalytics.Params params = 32;
|
* @generated from protobuf field: string id = 1;
|
||||||
*/
|
*/
|
||||||
params?: ChannelAnalytics_Params;
|
id: string;
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @generated from protobuf message youtube.ChannelAnalytics.Params
|
|
||||||
*/
|
|
||||||
export interface ChannelAnalytics_Params {
|
|
||||||
/**
|
/**
|
||||||
* @generated from protobuf field: string channel_id = 1001;
|
* @generated from protobuf field: int32 timestamp = 5;
|
||||||
*/
|
*/
|
||||||
channelId: string;
|
timestamp: number;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* @generated from protobuf message youtube.InnertubePayload
|
* @generated from protobuf message youtube.InnertubePayload
|
||||||
@@ -213,6 +208,24 @@ export interface InnertubePayload_VideoThumbnail_Thumbnail {
|
|||||||
*/
|
*/
|
||||||
imageData: Uint8Array;
|
imageData: Uint8Array;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* @generated from protobuf message youtube.ChannelAnalytics
|
||||||
|
*/
|
||||||
|
export interface ChannelAnalytics {
|
||||||
|
/**
|
||||||
|
* @generated from protobuf field: youtube.ChannelAnalytics.Params params = 32;
|
||||||
|
*/
|
||||||
|
params?: ChannelAnalytics_Params;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @generated from protobuf message youtube.ChannelAnalytics.Params
|
||||||
|
*/
|
||||||
|
export interface ChannelAnalytics_Params {
|
||||||
|
/**
|
||||||
|
* @generated from protobuf field: string channel_id = 1001;
|
||||||
|
*/
|
||||||
|
channelId: string;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* @generated from protobuf message youtube.SoundInfoParams
|
* @generated from protobuf message youtube.SoundInfoParams
|
||||||
*/
|
*/
|
||||||
@@ -646,26 +659,30 @@ export interface SearchFilter_Filters {
|
|||||||
featuresVr180?: number;
|
featuresVr180?: number;
|
||||||
}
|
}
|
||||||
// @generated message type with reflection information, may provide speed optimized methods
|
// @generated message type with reflection information, may provide speed optimized methods
|
||||||
class ChannelAnalytics$Type extends MessageType<ChannelAnalytics> {
|
class VisitorData$Type extends MessageType<VisitorData> {
|
||||||
constructor() {
|
constructor() {
|
||||||
super("youtube.ChannelAnalytics", [
|
super("youtube.VisitorData", [
|
||||||
{ no: 32, name: "params", kind: "message", T: () => ChannelAnalytics_Params }
|
{ no: 1, name: "id", kind: "scalar", T: 9 /*ScalarType.STRING*/ },
|
||||||
|
{ no: 5, name: "timestamp", kind: "scalar", T: 5 /*ScalarType.INT32*/ }
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
create(value?: PartialMessage<ChannelAnalytics>): ChannelAnalytics {
|
create(value?: PartialMessage<VisitorData>): VisitorData {
|
||||||
const message = {};
|
const message = { id: "", timestamp: 0 };
|
||||||
globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this });
|
globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this });
|
||||||
if (value !== undefined)
|
if (value !== undefined)
|
||||||
reflectionMergePartial<ChannelAnalytics>(this, message, value);
|
reflectionMergePartial<VisitorData>(this, message, value);
|
||||||
return message;
|
return message;
|
||||||
}
|
}
|
||||||
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: ChannelAnalytics): ChannelAnalytics {
|
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: VisitorData): VisitorData {
|
||||||
let message = target ?? this.create(), end = reader.pos + length;
|
let message = target ?? this.create(), end = reader.pos + length;
|
||||||
while (reader.pos < end) {
|
while (reader.pos < end) {
|
||||||
let [fieldNo, wireType] = reader.tag();
|
let [fieldNo, wireType] = reader.tag();
|
||||||
switch (fieldNo) {
|
switch (fieldNo) {
|
||||||
case /* youtube.ChannelAnalytics.Params params */ 32:
|
case /* string id */ 1:
|
||||||
message.params = ChannelAnalytics_Params.internalBinaryRead(reader, reader.uint32(), options, message.params);
|
message.id = reader.string();
|
||||||
|
break;
|
||||||
|
case /* int32 timestamp */ 5:
|
||||||
|
message.timestamp = reader.int32();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
let u = options.readUnknownField;
|
let u = options.readUnknownField;
|
||||||
@@ -678,10 +695,13 @@ class ChannelAnalytics$Type extends MessageType<ChannelAnalytics> {
|
|||||||
}
|
}
|
||||||
return message;
|
return message;
|
||||||
}
|
}
|
||||||
internalBinaryWrite(message: ChannelAnalytics, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
|
internalBinaryWrite(message: VisitorData, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
|
||||||
/* youtube.ChannelAnalytics.Params params = 32; */
|
/* string id = 1; */
|
||||||
if (message.params)
|
if (message.id !== "")
|
||||||
ChannelAnalytics_Params.internalBinaryWrite(message.params, writer.tag(32, WireType.LengthDelimited).fork(), options).join();
|
writer.tag(1, WireType.LengthDelimited).string(message.id);
|
||||||
|
/* int32 timestamp = 5; */
|
||||||
|
if (message.timestamp !== 0)
|
||||||
|
writer.tag(5, WireType.Varint).int32(message.timestamp);
|
||||||
let u = options.writeUnknownFields;
|
let u = options.writeUnknownFields;
|
||||||
if (u !== false)
|
if (u !== false)
|
||||||
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
|
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
|
||||||
@@ -689,56 +709,9 @@ class ChannelAnalytics$Type extends MessageType<ChannelAnalytics> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* @generated MessageType for protobuf message youtube.ChannelAnalytics
|
* @generated MessageType for protobuf message youtube.VisitorData
|
||||||
*/
|
*/
|
||||||
export const ChannelAnalytics = new ChannelAnalytics$Type();
|
export const VisitorData = new VisitorData$Type();
|
||||||
// @generated message type with reflection information, may provide speed optimized methods
|
|
||||||
class ChannelAnalytics_Params$Type extends MessageType<ChannelAnalytics_Params> {
|
|
||||||
constructor() {
|
|
||||||
super("youtube.ChannelAnalytics.Params", [
|
|
||||||
{ no: 1001, name: "channel_id", kind: "scalar", T: 9 /*ScalarType.STRING*/ }
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
create(value?: PartialMessage<ChannelAnalytics_Params>): ChannelAnalytics_Params {
|
|
||||||
const message = { channelId: "" };
|
|
||||||
globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this });
|
|
||||||
if (value !== undefined)
|
|
||||||
reflectionMergePartial<ChannelAnalytics_Params>(this, message, value);
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: ChannelAnalytics_Params): ChannelAnalytics_Params {
|
|
||||||
let message = target ?? this.create(), end = reader.pos + length;
|
|
||||||
while (reader.pos < end) {
|
|
||||||
let [fieldNo, wireType] = reader.tag();
|
|
||||||
switch (fieldNo) {
|
|
||||||
case /* string channel_id */ 1001:
|
|
||||||
message.channelId = reader.string();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
let u = options.readUnknownField;
|
|
||||||
if (u === "throw")
|
|
||||||
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
|
|
||||||
let d = reader.skip(wireType);
|
|
||||||
if (u !== false)
|
|
||||||
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
internalBinaryWrite(message: ChannelAnalytics_Params, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
|
|
||||||
/* string channel_id = 1001; */
|
|
||||||
if (message.channelId !== "")
|
|
||||||
writer.tag(1001, WireType.LengthDelimited).string(message.channelId);
|
|
||||||
let u = options.writeUnknownFields;
|
|
||||||
if (u !== false)
|
|
||||||
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
|
|
||||||
return writer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @generated MessageType for protobuf message youtube.ChannelAnalytics.Params
|
|
||||||
*/
|
|
||||||
export const ChannelAnalytics_Params = new ChannelAnalytics_Params$Type();
|
|
||||||
// @generated message type with reflection information, may provide speed optimized methods
|
// @generated message type with reflection information, may provide speed optimized methods
|
||||||
class InnertubePayload$Type extends MessageType<InnertubePayload> {
|
class InnertubePayload$Type extends MessageType<InnertubePayload> {
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -1456,6 +1429,100 @@ class InnertubePayload_VideoThumbnail_Thumbnail$Type extends MessageType<Innertu
|
|||||||
*/
|
*/
|
||||||
export const InnertubePayload_VideoThumbnail_Thumbnail = new InnertubePayload_VideoThumbnail_Thumbnail$Type();
|
export const InnertubePayload_VideoThumbnail_Thumbnail = new InnertubePayload_VideoThumbnail_Thumbnail$Type();
|
||||||
// @generated message type with reflection information, may provide speed optimized methods
|
// @generated message type with reflection information, may provide speed optimized methods
|
||||||
|
class ChannelAnalytics$Type extends MessageType<ChannelAnalytics> {
|
||||||
|
constructor() {
|
||||||
|
super("youtube.ChannelAnalytics", [
|
||||||
|
{ no: 32, name: "params", kind: "message", T: () => ChannelAnalytics_Params }
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
create(value?: PartialMessage<ChannelAnalytics>): ChannelAnalytics {
|
||||||
|
const message = {};
|
||||||
|
globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this });
|
||||||
|
if (value !== undefined)
|
||||||
|
reflectionMergePartial<ChannelAnalytics>(this, message, value);
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: ChannelAnalytics): ChannelAnalytics {
|
||||||
|
let message = target ?? this.create(), end = reader.pos + length;
|
||||||
|
while (reader.pos < end) {
|
||||||
|
let [fieldNo, wireType] = reader.tag();
|
||||||
|
switch (fieldNo) {
|
||||||
|
case /* youtube.ChannelAnalytics.Params params */ 32:
|
||||||
|
message.params = ChannelAnalytics_Params.internalBinaryRead(reader, reader.uint32(), options, message.params);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
let u = options.readUnknownField;
|
||||||
|
if (u === "throw")
|
||||||
|
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
|
||||||
|
let d = reader.skip(wireType);
|
||||||
|
if (u !== false)
|
||||||
|
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
internalBinaryWrite(message: ChannelAnalytics, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
|
||||||
|
/* youtube.ChannelAnalytics.Params params = 32; */
|
||||||
|
if (message.params)
|
||||||
|
ChannelAnalytics_Params.internalBinaryWrite(message.params, writer.tag(32, WireType.LengthDelimited).fork(), options).join();
|
||||||
|
let u = options.writeUnknownFields;
|
||||||
|
if (u !== false)
|
||||||
|
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
|
||||||
|
return writer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @generated MessageType for protobuf message youtube.ChannelAnalytics
|
||||||
|
*/
|
||||||
|
export const ChannelAnalytics = new ChannelAnalytics$Type();
|
||||||
|
// @generated message type with reflection information, may provide speed optimized methods
|
||||||
|
class ChannelAnalytics_Params$Type extends MessageType<ChannelAnalytics_Params> {
|
||||||
|
constructor() {
|
||||||
|
super("youtube.ChannelAnalytics.Params", [
|
||||||
|
{ no: 1001, name: "channel_id", kind: "scalar", T: 9 /*ScalarType.STRING*/ }
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
create(value?: PartialMessage<ChannelAnalytics_Params>): ChannelAnalytics_Params {
|
||||||
|
const message = { channelId: "" };
|
||||||
|
globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this });
|
||||||
|
if (value !== undefined)
|
||||||
|
reflectionMergePartial<ChannelAnalytics_Params>(this, message, value);
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: ChannelAnalytics_Params): ChannelAnalytics_Params {
|
||||||
|
let message = target ?? this.create(), end = reader.pos + length;
|
||||||
|
while (reader.pos < end) {
|
||||||
|
let [fieldNo, wireType] = reader.tag();
|
||||||
|
switch (fieldNo) {
|
||||||
|
case /* string channel_id */ 1001:
|
||||||
|
message.channelId = reader.string();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
let u = options.readUnknownField;
|
||||||
|
if (u === "throw")
|
||||||
|
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
|
||||||
|
let d = reader.skip(wireType);
|
||||||
|
if (u !== false)
|
||||||
|
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
internalBinaryWrite(message: ChannelAnalytics_Params, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
|
||||||
|
/* string channel_id = 1001; */
|
||||||
|
if (message.channelId !== "")
|
||||||
|
writer.tag(1001, WireType.LengthDelimited).string(message.channelId);
|
||||||
|
let u = options.writeUnknownFields;
|
||||||
|
if (u !== false)
|
||||||
|
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
|
||||||
|
return writer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @generated MessageType for protobuf message youtube.ChannelAnalytics.Params
|
||||||
|
*/
|
||||||
|
export const ChannelAnalytics_Params = new ChannelAnalytics_Params$Type();
|
||||||
|
// @generated message type with reflection information, may provide speed optimized methods
|
||||||
class SoundInfoParams$Type extends MessageType<SoundInfoParams> {
|
class SoundInfoParams$Type extends MessageType<SoundInfoParams> {
|
||||||
constructor() {
|
constructor() {
|
||||||
super("youtube.SoundInfoParams", [
|
super("youtube.SoundInfoParams", [
|
||||||
|
|||||||
@@ -35,7 +35,9 @@ export const OAUTH = Object.freeze({
|
|||||||
export const CLIENTS = Object.freeze({
|
export const CLIENTS = Object.freeze({
|
||||||
WEB: {
|
WEB: {
|
||||||
NAME: 'WEB',
|
NAME: 'WEB',
|
||||||
VERSION: '2.20220902.01.00'
|
VERSION: '2.20230104.01.00',
|
||||||
|
API_KEY: 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8',
|
||||||
|
API_VERSION: 'v1'
|
||||||
},
|
},
|
||||||
YTMUSIC: {
|
YTMUSIC: {
|
||||||
NAME: 'WEB_REMIX',
|
NAME: 'WEB_REMIX',
|
||||||
|
|||||||
@@ -141,6 +141,11 @@ describe('YouTube.js Tests', () => {
|
|||||||
expect(nop_yt.session.player).toBeUndefined();
|
expect(nop_yt.session.player).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should create a session from data generated locally', async () => {
|
||||||
|
const loc_yt = await Innertube.create({ generate_session_locally: true, retrieve_player: false });
|
||||||
|
expect(loc_yt.session.context).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
it('should resolve a URL', async () => {
|
it('should resolve a URL', async () => {
|
||||||
const url = await yt.resolveURL('https://www.youtube.com/@linustechtips');
|
const url = await yt.resolveURL('https://www.youtube.com/@linustechtips');
|
||||||
expect(url.payload.browseId).toBe(CHANNELS[0].ID);
|
expect(url.payload.browseId).toBe(CHANNELS[0].ID);
|
||||||
|
|||||||
Reference in New Issue
Block a user