Good-MITM icon indicating copy to clipboard operation
Good-MITM copied to clipboard

关于youtube的广告拦截问题

Open mq00fc opened this issue 11 months ago • 2 comments

尊敬的开发者您好

我将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的效果,是否可以考虑增加实现?

谢谢,以上

mq00fc avatar Jan 20 '25 00:01 mq00fc

以下是大模型重写后的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();
    });
})();

zu1k avatar Jan 21 '25 03:01 zu1k

以下是大模型重写后的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 avatar Jan 21 '25 03:01 mq00fc