newpipe-extractor-js
Version:
JavaScript/Node.js port of NewPipeExtractor
159 lines • 5.92 kB
JavaScript
;
// PoToken Provider - YouTube's Proof of Origin Token System
// This is essential for bypassing YouTube's modern anti-bot protection
Object.defineProperty(exports, "__esModule", { value: true });
exports.PoTokenCache = exports.PoTokenProvider = exports.WEBPO_CLIENTS = exports.PoTokenProviderRejectedRequest = exports.PoTokenError = exports.ContentBindingType = exports.PoTokenContext = void 0;
exports.getWebPoContentBinding = getWebPoContentBinding;
exports.generateWebPoCacheSpec = generateWebPoCacheSpec;
var PoTokenContext;
(function (PoTokenContext) {
PoTokenContext["GVS"] = "gvs";
PoTokenContext["PLAYER"] = "player";
PoTokenContext["SUBS"] = "subs";
})(PoTokenContext || (exports.PoTokenContext = PoTokenContext = {}));
var ContentBindingType;
(function (ContentBindingType) {
ContentBindingType["VISITOR_DATA"] = "visitor_data";
ContentBindingType["DATASYNC_ID"] = "datasync_id";
ContentBindingType["VIDEO_ID"] = "video_id";
ContentBindingType["VISITOR_ID"] = "visitor_id";
})(ContentBindingType || (exports.ContentBindingType = ContentBindingType = {}));
class PoTokenError extends Error {
constructor(message) {
super(message);
this.name = 'PoTokenError';
}
}
exports.PoTokenError = PoTokenError;
class PoTokenProviderRejectedRequest extends PoTokenError {
constructor(message) {
super(message);
this.name = 'PoTokenProviderRejectedRequest';
}
}
exports.PoTokenProviderRejectedRequest = PoTokenProviderRejectedRequest;
// WEBPO clients that support PO tokens
exports.WEBPO_CLIENTS = [
'WEB',
'MWEB',
'TVHTML5',
'WEB_EMBEDDED_PLAYER',
'WEB_CREATOR',
'WEB_REMIX',
'TVHTML5_SIMPLY_EMBEDDED_PLAYER'
];
function getWebPoContentBinding(request, webpoClients = exports.WEBPO_CLIENTS, bindToVisitorId = false) {
const clientName = request.innertubeContext.client.clientName;
if (!clientName || !webpoClients.includes(clientName)) {
return [null, null];
}
if (request.context === PoTokenContext.GVS || clientName === 'WEB_REMIX') {
if (request.isAuthenticated) {
return [request.dataSyncId || null, ContentBindingType.DATASYNC_ID];
}
else {
if (bindToVisitorId) {
const visitorId = extractVisitorId(request.visitorData);
if (visitorId) {
return [visitorId, ContentBindingType.VISITOR_ID];
}
}
return [request.visitorData || null, ContentBindingType.VISITOR_DATA];
}
}
else if (request.context === PoTokenContext.PLAYER || request.context === PoTokenContext.SUBS) {
return [request.videoId || null, ContentBindingType.VIDEO_ID];
}
return [null, null];
}
function extractVisitorId(visitorData) {
if (!visitorData)
return null;
try {
// Decode the visitor_data protobuf
const decoded = Buffer.from(decodeURIComponent(visitorData), 'base64');
const visitorId = decoded.slice(2, 13).toString();
// Check that visitor ID is valid format (11 chars, alphanumeric + _-)
if (/^[A-Za-z0-9_-]{11}$/.test(visitorId)) {
return visitorId;
}
}
catch (error) {
// Ignore errors in visitor ID extraction
}
return null;
}
function generateWebPoCacheSpec(request, bindToVisitorId = true) {
const [contentBinding, contentBindingType] = getWebPoContentBinding(request, exports.WEBPO_CLIENTS, bindToVisitorId);
if (!contentBinding || !contentBindingType) {
return null;
}
const writePolicy = contentBindingType === ContentBindingType.VIDEO_ID ? 'WRITE_FIRST' : 'WRITE_ALL';
return {
keyBindings: {
't': 'webpo',
'cb': contentBinding,
'cbt': contentBindingType,
'ip': request.innertubeContext.client.remoteHost || null,
'sa': request.requestSourceAddress || null,
'px': request.requestProxy || null
},
// Integrity token usually has 12 hour TTL, we use 6 hours to be safe
defaultTtl: 21600, // 6 hours in seconds
writePolicy
};
}
class PoTokenProvider {
constructor() {
this.supportedContexts = [PoTokenContext.GVS, PoTokenContext.PLAYER];
}
supportsContext(context) {
return this.supportedContexts.includes(context);
}
validateRequest(request) {
if (!this.supportsContext(request.context)) {
throw new PoTokenProviderRejectedRequest(`Provider ${this.providerName} does not support context: ${request.context}`);
}
}
}
exports.PoTokenProvider = PoTokenProvider;
// Simple in-memory cache for PO tokens
class PoTokenCache {
constructor() {
this.cache = new Map();
}
generateCacheKey(spec) {
const sortedEntries = Object.entries(spec.keyBindings)
.filter(([_, value]) => value !== null)
.sort(([a], [b]) => a.localeCompare(b));
return JSON.stringify(sortedEntries);
}
get(request) {
const spec = generateWebPoCacheSpec(request);
if (!spec)
return null;
const key = this.generateCacheKey(spec);
const cached = this.cache.get(key);
if (!cached)
return null;
// Check if expired
if (cached.expiresAt < Date.now()) {
this.cache.delete(key);
return null;
}
return cached.response;
}
store(request, response) {
const spec = generateWebPoCacheSpec(request);
if (!spec)
return;
const key = this.generateCacheKey(spec);
const expiresAt = response.expiresAt || (Date.now() + spec.defaultTtl * 1000);
this.cache.set(key, { response, expiresAt });
}
clear() {
this.cache.clear();
}
}
exports.PoTokenCache = PoTokenCache;
//# sourceMappingURL=PoTokenProvider.js.map