Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

关于youtube的广告拦截问题 #74

Open
mq00fc opened this issue Jan 20, 2025 · 2 comments
Open

关于youtube的广告拦截问题 #74

mq00fc opened this issue Jan 20, 2025 · 2 comments

Comments

@mq00fc
Copy link

mq00fc commented Jan 20, 2025

尊敬的开发者您好

我将statsh的复写设置打算放入服务器上的mitm中,规则如下

#!desc =支持pip,后台播放
#!author = Maasea 
#!homepage=https://whatshub.top
#!icon = https://raw.githubusercontent.com/Koolson/Qure/master/IconSet/Color/YouTube.png
name: YouTubequguanggao
desc: 支持pip,后台播放
http:
  mitm:
    - "-redirector*.googlevideo.com"
    - "*.googlevideo.com"
    - "www.youtube.com"
    - "s.youtube.com"
    - "youtubei.googleapis.com"
  script:
    - match: ^https?:\/\/youtubei\.googleapis\.com\/youtubei\/v1\/(browse|next|player|search|reel\/reel_watch_sequence|guide|account\/get_setting|get_watch)
      name: YouTubequguanggao1
      type: response
      require-body: true
      binary-mode: true
      timeout: 10
  rewrite:
    - (^https?:\/\/[\w-]+\.googlevideo\.com\/(?!dclk_video_ads).+?)&ctier=L(&.+?),ctier,(.+) $1$2$3 302
    - ^https?:\/\/[\w-]+\.googlevideo\.com\/(?!(dclk_video_ads|videoplayback\?)).+&oad - reject-200
    - ^https?:\/\/(www|s)\.youtube\.com\/api\/stats\/ads - reject-200
    - ^https?:\/\/(www|s)\.youtube\.com\/(pagead|ptracking) - reject-200
    - ^https?:\/\/s\.youtube\.com\/api\/stats\/qoe\?adcontext - reject-200
script-providers:
  YouTubequguanggao1:
    url: https://raw.githubusercontent.com/Maasea/sgmodule/master/Script/Youtube/dist/youtube.response.preview.js
    interval: 86400

拦截工作可以进行,但是画中画(后台播放)没法实现

我参考了文档,没有发现可以插入js的效果,是否可以考虑增加实现?

谢谢,以上

@zu1k
Copy link
Owner

zu1k commented Jan 21, 2025

以下是大模型重写后的js脚本,但是本项目目前对js对支持还非常初级,还需要改进后才能用

(() => {
    const youtube = D.getInstance("YouTube");

    class YouTubeHandler {
        constructor(messageType, handlerName) {
            this.handlerName = handlerName;
            this.messageType = messageType;
            this.arguments = this.decodeArguments();
            youtube.isDebug = Boolean(this.arguments.debug);
            youtube.debug(this.handlerName);
            const config = youtube.getJSON("YouTubeAdvertiseInfo");
            youtube.debug(`currentVersion: ${this.version}`);
            youtube.debug(`storedVersion: ${config?.version}`);
            if (config?.version === this.version) Object.assign(this, config);
        }

        decodeArguments() {
            const defaultArgs = {
                lyricLang: "zh-Hans",
                captionLang: "zh-Hans",
                blockUpload: true,
                blockImmersive: true,
                debug: false
            };
            return youtube.decodeParams(defaultArgs);
        }

        fromBinary(data) {
            if (data instanceof Uint8Array) {
                this.message = this.messageType.fromBinary(data);
                youtube.debug(`raw: ${Math.floor(data.length / 1024)} kb`);
                return this;
            } else {
                youtube.log("YouTube cannot get binaryBody");
                youtube.exit();
                return this;
            }
        }

        async modify() {
            const result = this.pure();
            return result instanceof Promise ? await result : result;
        }

        toBinary() {
            return this.message.toBinary();
        }

        listUnknownFields(message) {
            return message instanceof E ? message.getType().runtime.bin.listUnknownFields(message) : [];
        }

        saveConfig() {
            if (this.needSave) {
                youtube.debug("Update Config");
                const config = {
                    version: this.version,
                    whiteNo: this.whiteNo,
                    blackNo: this.blackNo,
                    whiteEml: this.whiteEml,
                    blackEml: this.blackEml
                };
                youtube.debug(config);
                youtube.setJSON(config, "YouTubeAdvertiseInfo");
            }
        }

        finish() {
            this.saveConfig();
            if (this.needProcess) {
                youtube.timeStart("toBinary");
                const binaryData = this.toBinary();
                youtube.timeEnd("toBinary");
                youtube.debug(`modify: ${Math.floor(binaryData.length / 1024)} kb`);
                youtube.done({ bodyBytes: binaryData });
            }
            youtube.debug("use $done({})");
            youtube.exit();
        }

        iterateObject(obj, targetKey, callback) {
            const stack = typeof obj === "object" ? [obj] : [];
            while (stack.length) {
                const current = stack.pop();
                const keys = Object.keys(current);
                for (const key of keys) {
                    if (key === targetKey) {
                        callback(current, stack);
                    } else if (typeof current[key] === "object") {
                        stack.push(current[key]);
                    }
                }
            }
        }

        isAdvertise(message) {
            const field = this.listUnknownFields(message)[0];
            return field ? this.handleFieldNumber(field) : this.handleFieldEml(message);
        }

        handleFieldNumber(field) {
            const fieldNumber = field.no;
            if (this.whiteNo.includes(fieldNumber)) return false;
            if (this.blackNo.includes(fieldNumber)) return true;
            const isAd = this.checkBufferForAd(field);
            if (isAd) this.blackNo.push(fieldNumber);
            else this.whiteNo.push(fieldNumber);
            this.needSave = true;
            return isAd;
        }

        handleFieldEml(message) {
            let isAd = false;
            let eml = "";
            this.iterateObject(message, "renderInfo", (obj, stack) => {
                eml = obj.renderInfo.layoutRender.eml.split("|")[0];
                if (this.whiteEml.includes(eml)) isAd = false;
                else if (this.blackEml.includes(eml) || /shorts(?!_pivot_item)/.test(eml)) isAd = true;
                else {
                    const videoContent = obj?.videoInfo?.videoContext?.videoContent;
                    if (videoContent) {
                        isAd = this.checkUnknownField(videoContent);
                        if (isAd) this.blackEml.push(eml);
                        else this.whiteEml.push(eml);
                        this.needSave = true;
                    }
                }
                stack.length = 0;
            });
            return isAd;
        }

        checkBufferForAd(field) {
            return !field || field.data.length < 1000 ? false : this.decoder.decode(field.data).includes("pagead");
        }

        checkUnknownField(message) {
            return message ? this.listUnknownFields(message)?.some(field => this.checkBufferForAd(field)) ?? false : false;
        }

        isShorts(message) {
            let isShorts = false;
            this.iterateObject(message, "eml", (obj, stack) => {
                isShorts = /shorts(?!_pivot_item)/.test(obj.eml);
                stack.length = 0;
            });
            return isShorts;
        }
    }

    class BrowseHandler extends YouTubeHandler {
        constructor(messageType = Mt, handlerName = "Browse") {
            super(messageType, handlerName);
        }

        async pure() {
            this.iterateObject(this.message, "sectionListSupportedRenderers", section => {
                for (let i = section.sectionListSupportedRenderers.length - 1; i >= 0; i--) {
                    this.removeCommonAd(section, i);
                    this.removeShorts(section, i);
                }
            });
            await this.translate();
            return this;
        }

        removeCommonAd(section, index) {
            const items = section.sectionListSupportedRenderers[index]?.itemSectionRenderer?.richItemContent;
            for (let i = items?.length - 1; i >= 0; i--) {
                if (this.isAdvertise(items[i])) {
                    items.splice(i, 1);
                    this.needProcess = true;
                }
            }
        }

        removeShorts(section, index) {
            const shelf = section.sectionListSupportedRenderers[index]?.shelfRenderer;
            if (this.isShorts(shelf)) {
                section.sectionListSupportedRenderers.splice(index, 1);
                this.needProcess = true;
            }
        }

        getBrowseId() {
            let browseId = "";
            this.iterateObject(this.message?.responseContext, "key", (obj, stack) => {
                if (obj.key === "browse_id") {
                    browseId = obj.value;
                    stack.length = 0;
                }
            });
            return browseId;
        }

        async translate() {
            const lang = this.arguments.lyricLang?.trim();
            if (!(this.handlerName === "Browse" && this.getBrowseId().startsWith("MPLYt")) || lang === "off") return;

            let text = "";
            let target;
            let hasContent = false;

            this.iterateObject(this.message, "timedLyricsContent", (obj, stack) => {
                target = obj.timedLyricsContent;
                text = obj.timedLyricsContent.runs.map(run => run.text).join("\n");
                hasContent = true;
                stack.length = 0;
            });

            if (!hasContent) {
                this.iterateObject(this.message, "description", (obj, stack) => {
                    target = obj.description.runs[0];
                    text = obj.description.runs[0].text;
                    stack.length = 0;
                    hasContent = true;
                });
            }

            if (!hasContent) return;

            const langCode = lang.split("-")[0];
            const url = Yt(text, lang);
            const response = await youtube.fetch({ method: "GET", url });

            if (response.status === 200 && response.body) {
                const data = JSON.parse(response.body);
                const translatedText = data[0].map(line => line[0]).join("\r\n");
                target.text = translatedText;
                this.iterateObject(this.message, "footer", (obj, stack) => {
                    obj.footer.runs[0].text += " & Translated by Google";
                    stack.length = 0;
                });
                this.needProcess = true;
            }
        }
    }

    const handlerMap = new Map([
        ["browse", BrowseHandler]
    ]);

    function getHandler(url) {
        for (const [key, HandlerClass] of handlerMap.entries()) {
            if (url.includes(key)) return new HandlerClass();
        }
        return null;
    }

    async function main() {
        const handler = getHandler(youtube.request.url);
        if (handler) {
            const data = youtube.response.bodyBytes;
            youtube.timeStart("fromBinary");
            handler.fromBinary(data);
            youtube.timeEnd("fromBinary");
            youtube.timeStart("modify");
            await handler.modify();
            youtube.timeEnd("modify");
            handler.finish();
        } else {
            youtube.msg("YouTube Enhance", "脚本需要更新", "外部资源 -> 全部更新");
            youtube.exit();
        }
    }

    main().catch(err => {
        youtube.log(err.toString());
    }).finally(() => {
        youtube.exit();
    });
})();

@mq00fc
Copy link
Author

mq00fc commented Jan 21, 2025

以下是大模型重写后的js脚本,但是本项目目前对js对支持还非常初级,还需要改进后才能用

(() => {
const youtube = D.getInstance("YouTube");

class YouTubeHandler {
    constructor(messageType, handlerName) {
        this.handlerName = handlerName;
        this.messageType = messageType;
        this.arguments = this.decodeArguments();
        youtube.isDebug = Boolean(this.arguments.debug);
        youtube.debug(this.handlerName);
        const config = youtube.getJSON("YouTubeAdvertiseInfo");
        youtube.debug(`currentVersion: ${this.version}`);
        youtube.debug(`storedVersion: ${config?.version}`);
        if (config?.version === this.version) Object.assign(this, config);
    }

    decodeArguments() {
        const defaultArgs = {
            lyricLang: "zh-Hans",
            captionLang: "zh-Hans",
            blockUpload: true,
            blockImmersive: true,
            debug: false
        };
        return youtube.decodeParams(defaultArgs);
    }

    fromBinary(data) {
        if (data instanceof Uint8Array) {
            this.message = this.messageType.fromBinary(data);
            youtube.debug(`raw: ${Math.floor(data.length / 1024)} kb`);
            return this;
        } else {
            youtube.log("YouTube cannot get binaryBody");
            youtube.exit();
            return this;
        }
    }

    async modify() {
        const result = this.pure();
        return result instanceof Promise ? await result : result;
    }

    toBinary() {
        return this.message.toBinary();
    }

    listUnknownFields(message) {
        return message instanceof E ? message.getType().runtime.bin.listUnknownFields(message) : [];
    }

    saveConfig() {
        if (this.needSave) {
            youtube.debug("Update Config");
            const config = {
                version: this.version,
                whiteNo: this.whiteNo,
                blackNo: this.blackNo,
                whiteEml: this.whiteEml,
                blackEml: this.blackEml
            };
            youtube.debug(config);
            youtube.setJSON(config, "YouTubeAdvertiseInfo");
        }
    }

    finish() {
        this.saveConfig();
        if (this.needProcess) {
            youtube.timeStart("toBinary");
            const binaryData = this.toBinary();
            youtube.timeEnd("toBinary");
            youtube.debug(`modify: ${Math.floor(binaryData.length / 1024)} kb`);
            youtube.done({ bodyBytes: binaryData });
        }
        youtube.debug("use $done({})");
        youtube.exit();
    }

    iterateObject(obj, targetKey, callback) {
        const stack = typeof obj === "object" ? [obj] : [];
        while (stack.length) {
            const current = stack.pop();
            const keys = Object.keys(current);
            for (const key of keys) {
                if (key === targetKey) {
                    callback(current, stack);
                } else if (typeof current[key] === "object") {
                    stack.push(current[key]);
                }
            }
        }
    }

    isAdvertise(message) {
        const field = this.listUnknownFields(message)[0];
        return field ? this.handleFieldNumber(field) : this.handleFieldEml(message);
    }

    handleFieldNumber(field) {
        const fieldNumber = field.no;
        if (this.whiteNo.includes(fieldNumber)) return false;
        if (this.blackNo.includes(fieldNumber)) return true;
        const isAd = this.checkBufferForAd(field);
        if (isAd) this.blackNo.push(fieldNumber);
        else this.whiteNo.push(fieldNumber);
        this.needSave = true;
        return isAd;
    }

    handleFieldEml(message) {
        let isAd = false;
        let eml = "";
        this.iterateObject(message, "renderInfo", (obj, stack) => {
            eml = obj.renderInfo.layoutRender.eml.split("|")[0];
            if (this.whiteEml.includes(eml)) isAd = false;
            else if (this.blackEml.includes(eml) || /shorts(?!_pivot_item)/.test(eml)) isAd = true;
            else {
                const videoContent = obj?.videoInfo?.videoContext?.videoContent;
                if (videoContent) {
                    isAd = this.checkUnknownField(videoContent);
                    if (isAd) this.blackEml.push(eml);
                    else this.whiteEml.push(eml);
                    this.needSave = true;
                }
            }
            stack.length = 0;
        });
        return isAd;
    }

    checkBufferForAd(field) {
        return !field || field.data.length < 1000 ? false : this.decoder.decode(field.data).includes("pagead");
    }

    checkUnknownField(message) {
        return message ? this.listUnknownFields(message)?.some(field => this.checkBufferForAd(field)) ?? false : false;
    }

    isShorts(message) {
        let isShorts = false;
        this.iterateObject(message, "eml", (obj, stack) => {
            isShorts = /shorts(?!_pivot_item)/.test(obj.eml);
            stack.length = 0;
        });
        return isShorts;
    }
}

class BrowseHandler extends YouTubeHandler {
    constructor(messageType = Mt, handlerName = "Browse") {
        super(messageType, handlerName);
    }

    async pure() {
        this.iterateObject(this.message, "sectionListSupportedRenderers", section => {
            for (let i = section.sectionListSupportedRenderers.length - 1; i >= 0; i--) {
                this.removeCommonAd(section, i);
                this.removeShorts(section, i);
            }
        });
        await this.translate();
        return this;
    }

    removeCommonAd(section, index) {
        const items = section.sectionListSupportedRenderers[index]?.itemSectionRenderer?.richItemContent;
        for (let i = items?.length - 1; i >= 0; i--) {
            if (this.isAdvertise(items[i])) {
                items.splice(i, 1);
                this.needProcess = true;
            }
        }
    }

    removeShorts(section, index) {
        const shelf = section.sectionListSupportedRenderers[index]?.shelfRenderer;
        if (this.isShorts(shelf)) {
            section.sectionListSupportedRenderers.splice(index, 1);
            this.needProcess = true;
        }
    }

    getBrowseId() {
        let browseId = "";
        this.iterateObject(this.message?.responseContext, "key", (obj, stack) => {
            if (obj.key === "browse_id") {
                browseId = obj.value;
                stack.length = 0;
            }
        });
        return browseId;
    }

    async translate() {
        const lang = this.arguments.lyricLang?.trim();
        if (!(this.handlerName === "Browse" && this.getBrowseId().startsWith("MPLYt")) || lang === "off") return;

        let text = "";
        let target;
        let hasContent = false;

        this.iterateObject(this.message, "timedLyricsContent", (obj, stack) => {
            target = obj.timedLyricsContent;
            text = obj.timedLyricsContent.runs.map(run => run.text).join("\n");
            hasContent = true;
            stack.length = 0;
        });

        if (!hasContent) {
            this.iterateObject(this.message, "description", (obj, stack) => {
                target = obj.description.runs[0];
                text = obj.description.runs[0].text;
                stack.length = 0;
                hasContent = true;
            });
        }

        if (!hasContent) return;

        const langCode = lang.split("-")[0];
        const url = Yt(text, lang);
        const response = await youtube.fetch({ method: "GET", url });

        if (response.status === 200 && response.body) {
            const data = JSON.parse(response.body);
            const translatedText = data[0].map(line => line[0]).join("\r\n");
            target.text = translatedText;
            this.iterateObject(this.message, "footer", (obj, stack) => {
                obj.footer.runs[0].text += " & Translated by Google";
                stack.length = 0;
            });
            this.needProcess = true;
        }
    }
}

const handlerMap = new Map([
    ["browse", BrowseHandler]
]);

function getHandler(url) {
    for (const [key, HandlerClass] of handlerMap.entries()) {
        if (url.includes(key)) return new HandlerClass();
    }
    return null;
}

async function main() {
    const handler = getHandler(youtube.request.url);
    if (handler) {
        const data = youtube.response.bodyBytes;
        youtube.timeStart("fromBinary");
        handler.fromBinary(data);
        youtube.timeEnd("fromBinary");
        youtube.timeStart("modify");
        await handler.modify();
        youtube.timeEnd("modify");
        handler.finish();
    } else {
        youtube.msg("YouTube Enhance", "脚本需要更新", "外部资源 -> 全部更新");
        youtube.exit();
    }
}

main().catch(err => {
    youtube.log(err.toString());
}).finally(() => {
    youtube.exit();
});

})()

感谢您的回复,希望后续有所更新,新年快乐

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants