Get rid of pesky youtube AI dubbing
// ==UserScript==
// @name Anti Youtube auto Dub
// @version 2.0
// @license MIT License
// @author leli8093
// @description Get rid of pesky youtube AI dubbing
// @match *://www.youtube.com/*
// @match *://youtube.com/*
// @grant none
// @run-at document-start
// @namespace https://gf.qytechs.cn/users/1534909
// ==/UserScript==
(function () {
'use strict';
// Helper: remove audioTrack.isAutoDubbed from any object/array
function stripIsAutoDubbed(obj) {
if (!obj || typeof obj !== 'object') return;
try {
if (Array.isArray(obj)) {
for (let i = 0; i < obj.length; i++) stripIsAutoDubbed(obj[i]);
return;
}
for (const k of Object.keys(obj)) {
const v = obj[k];
// Match path items used by uBlock rule: adaptiveFormats.*[?.audioTrack.isAutoDubbed]
if (k === 'adaptiveFormats' && Array.isArray(v)) {
for (const af of v) {
if (af && af.audioTrack && 'isAutoDubbed' in af.audioTrack) {
delete af.audioTrack.isAutoDubbed;
}
}
}
// Recurse
if (typeof v === 'object') stripIsAutoDubbed(v);
}
} catch (e) {
// fail silently
}
}
// 1) Intercept window.ytInitialPlayerResponse as early as possible.
// Define a property on window that proxies access and strips flag when set.
try {
let initialValue;
Object.defineProperty(window, 'ytInitialPlayerResponse', {
configurable: true,
enumerable: true,
get() { return initialValue; },
set(val) {
try {
// clone to avoid mutating external references unexpectedly
const cloned = JSON.parse(JSON.stringify(val));
stripIsAutoDubbed(cloned);
initialValue = cloned;
} catch (e) {
initialValue = val;
}
}
});
} catch (e) {
// fallback: set after load if defineProperty fails
window.addEventListener('DOMContentLoaded', () => {
try {
if (window.ytInitialPlayerResponse) {
const copy = JSON.parse(JSON.stringify(window.ytInitialPlayerResponse));
stripIsAutoDubbed(copy);
window.ytInitialPlayerResponse = copy;
}
} catch (e) {}
}, { once: true });
}
// 2) Wrap JSON.parse to scrub inline JSON strings before parse (best-effort).
// Only touch callers that pass strings containing "adaptiveFormats" to avoid breaking others.
(function () {
const origParse = JSON.parse;
JSON.parse = function (text, reviver) {
try {
if (typeof text === 'string' && text.indexOf('adaptiveFormats') !== -1) {
// Attempt to find and remove "isAutoDubbed":... occurrences before parsing.
// This is a text-level removal to catch inline JSON prior to parse.
// Be conservative: only remove the specific key name with boolean/null/number/string.
text = text.replace(/"isAutoDubbed"\s*:\s*(true|false|null|\d+|"[^"]*")\s*,?/g, '');
// Also clean up potential leftover trailing commas in objects/arrays.
text = text.replace(/,\s*([}\]])/g, '$1');
}
} catch (e) {}
return origParse.call(JSON, text, reviver);
};
})();
// 3) Intercept XHR responses: wrap XHR.prototype.open/send and process responseText when ready.
(function () {
const XHR = XMLHttpRequest;
const origOpen = XHR.prototype.open;
const origSend = XHR.prototype.send;
XHR.prototype.open = function (...args) {
this.__url_for_u_block = args[1] || '';
return origOpen.apply(this, args);
};
XHR.prototype.send = function (...args) {
// Only attach handler for likely YouTube player endpoints (youtubei, player, get_video_info, etc.)
const url = this.__url_for_u_block || '';
const shouldHook = /youtubei|player|get_video_info/i.test(url) || /\/youtubei\//i.test(url);
if (shouldHook) {
this.addEventListener('readystatechange', function () {
try {
if (this.readyState === 4 && this.responseType === '' /* default: responseText available */) {
let txt = this.responseText;
if (typeof txt === 'string' && txt.indexOf('adaptiveFormats') !== -1) {
// Remove isAutoDubbed occurrences in the JSON text
txt = txt.replace(/"isAutoDubbed"\s*:\s*(true|false|null|\d+|"[^"]*")\s*,?/g, '');
txt = txt.replace(/,\s*([}\]])/g, '$1');
// Try to redefine responseText via getter (non-standard): not possible on native XHR,
// but we can try to set response via overriding property on this instance.
try {
Object.defineProperty(this, 'responseText', { value: txt, configurable: true });
} catch (e) {
// If not allowed, try to set response / responseURL etc. otherwise leave as-is.
}
// Also try to update JSON-parsed piece if page stored it on assignment hooks
try {
const parsed = JSON.parse(txt);
stripIsAutoDubbed(parsed);
} catch (e) {}
}
}
} catch (e) {}
}, false);
}
return origSend.apply(this, args);
};
})();
// 4) Intercept fetch responses
(function () {
const origFetch = window.fetch;
window.fetch = function (input, init) {
// Call original fetch and then examine response bodies for JSON containing adaptiveFormats
return origFetch.call(this, input, init).then(async (response) => {
try {
const contentType = response.headers.get && response.headers.get('content-type') || '';
const url = (typeof input === 'string') ? input : (input && input.url) || '';
const shouldHook = /youtubei|player|get_video_info/i.test(url) || contentType.indexOf('application/json') !== -1;
if (!shouldHook) return response;
// Clone response so we can read body without consuming original stream
const clone = response.clone();
const text = await clone.text().catch(() => null);
if (typeof text === 'string' && text.indexOf('adaptiveFormats') !== -1) {
let newText = text.replace(/"isAutoDubbed"\s*:\s*(true|false|null|\d+|"[^"]*")\s*,?/g, '');
newText = newText.replace(/,\s*([}\]])/g, '$1');
// Try to create a new Response with same headers/status
const newBody = newText;
const headers = new Headers(response.headers);
// ensure correct content-length isn't required
const newResponse = new Response(newBody, {
status: response.status,
statusText: response.statusText,
headers: headers
});
return newResponse;
}
} catch (e) {
// ignore and return original
}
return response;
});
};
})();
// 5) Defensive: if any global playerResponse-like objects are created later, scrub them.
// Known keys: ytInitialPlayerResponse, ytcfg player, etc. This periodically scans common globals.
(function periodicScrub() {
const keys = ['ytInitialPlayerResponse', 'ytplayer', 'ytcfg'];
setInterval(() => {
try {
for (const k of keys) {
const v = window[k];
if (v) {
try {
const copy = JSON.parse(JSON.stringify(v));
stripIsAutoDubbed(copy);
window[k] = copy;
} catch (e) {}
}
}
} catch (e) {}
}, 1500);
})();
})();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址