javascript-illustration.png

I. 基础知识(Surge/QuantumultX 到底能干啥?)

Surge 是一个网络开发和代理工具。它是为开发者打造的工具,所以使用时需要具备一定的专业知识。以下四种是 Surge 的核心工作流。

接管:接管设备发起的网络连接。Surge 支持代理服务器和虚拟网卡两种接管模式。
处理:可以修改已经接管的网络请求和响应。包括 URL 重定向、本地文件映射、使用 JavaScript 自定义修改等多种方法。
转发:可以将已接管的网络请求转发到其他代理服务器。这可以是全局转发,也可以通过灵活的规则系统来确定出站策略。
拦截:可以拦截并保存网络请求和响应的数据,也可以通过 MITM 解密 HTTPS 流量。

via Surge 使用手册,本文正是使用的是处理,QuantumultX 同样支持该核心作业流程;

另外你可能需要了解一些前端的知识,包括但不限于 HTTPHTMLCSSJavaScript,当然,还有在代理工具使用过程中必不可少的正则表达式;其中,JavaScript 和 正则表达式是 QuantumultX / Surge 等代理工具进阶的基础知识储备;

II. 开始吧(这里有个油猴脚本)

现在,我们有一个 JavaScript 脚本,遵循 油猴用户脚本规范编写,可以直接通过油猴脚本管理器(Tampermonkey/UserScript)进行安装,并应用于相应网站;

油猴脚本哔滴影视广告屏蔽 (该脚本包含内容已经集成到 Adblock4limbo.user.js,在后文的示例中将 Adblock4limbo.user.js 代替哔滴影视广告屏蔽脚本),你现在可以在电脑上安装该脚本并打开 https://www.btbdys.com/ 体验试试;

对应源码如下(需要注意的是,油猴脚本一般以 .user.js 结尾,只有以该后缀结果才能被油猴脚本管理器识别;):

// ==UserScript==
// @name         哔滴影视广告屏蔽
// @namespace    http://tampermonkey.net/
// @version      0.1.2
// @description  哔滴影视广告屏蔽(图片广告屏蔽及视频广告跳过)
// @author       limbopro
// @match        https://www.btbdys.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=btbdys.com
// @grant        none
// @run-at document-end
// @license GNU GPLv3
// ==/UserScript==

const btbdys_css = ".ayx[style^=\"position\: fixed;bottom\"],#ad-index,#adsbox,.ayx[style=\"display:block;\"],.ayx[style^=\"position: fixed;bottom\"],a[target*=_new] {display:none !important;}" // 哔滴影视广告样式
const url = document.location.href;
const url_regx = "/play/";

css_adsRemove(btbdys_css, 500);
hrefAttribute_set();
if (url.indexOf(url_regx) !== -1) {
    videoAds_accelerateSkip(0.1);
}

// 动态创建引用内部资源 内嵌式样式 内嵌式脚本
function css_adsRemove(newstyle, delaytime, id) {
    setTimeout(() => {
        var creatcss = document.createElement("style");
        creatcss.id = id;
        creatcss.innerHTML = newstyle;
        document.getElementsByTagName('head')[0].appendChild(creatcss)
    }, delaytime);
}

/* 视频页广告加速跳过 */
function videoAds_accelerateSkip(fasterx) {
    // https://github.com/gorhill/uBlock/wiki
    /// nano-setInterval-booster.js
    /// alias nano-sib.js
    let needleArg = '{{1}}';
    if (needleArg === '{{1}}') { needleArg = ''; }
    let delayArg = '{{2}}';
    if (delayArg === '{{2}}') { delayArg = ''; }
    let boostArg = '{{3}}';
    if (boostArg === '{{3}}') { boostArg = ''; }
    if (needleArg === '') {
        needleArg = '.?';
    } else if (needleArg.charAt(0) === '/' && needleArg.slice(-1) === '/') {
        needleArg = needleArg.slice(1, -1);
    } else {
        needleArg = needleArg.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
    }
    const reNeedle = new RegExp(needleArg);
    let delay = delayArg !== '*' ? parseInt(delayArg, 10) : -1;
    if (isNaN(delay) || isFinite(delay) === false) { delay = 1000; }
    let boost = parseFloat(boostArg);
    boost = isNaN(boost) === false && isFinite(boost)
        ? Math.min(Math.max(boost, fasterx), 50)
        : fasterx;
    self.setInterval = new Proxy(self.setInterval, {
        apply: function (target, thisArg, args) {
            const [a, b] = args;
            if (
                (delay === -1 || b === delay) &&
                reNeedle.test(a.toString())
            ) {
                args[1] = b * boost;
            }
            return target.apply(thisArg, args);
        }
    });
};

// 禁止新页面跳转
function hrefAttribute_set() {
    var href = document.querySelectorAll("a");
    var i;
    if (href.length > 0) {
        for (i = 0; i < href.length; i++) {
            href[i].target = "_self";
        }
    }
}

好了,现在我们用苹果手机,该如何将本脚本应用到 哔滴影视 网站?如果你是 Safari 用户,那么你可以通过 Userscript 等油猴脚本管理器进行安装;

如果你是非Safari用户,那么你可以使用 Surge/QuantumultX 作为油猴脚本管理器使用;

III. QuantumultX 示例(如何在网页中嵌入油猴脚本)

示例中有涉及两个脚本:Adblock4limbo.js、Adblock4limbo.user.js;前者将通过 Surge/QuantumultX 提供的方法作用于任意网络请求/响应,即响应体/返回体(HTTP Response)和请求体(HTTP Request),使得后者得以插入到(我们想要插入的任意)网页(百度/知乎/CSDN/Google搜索等等,像 Tampermonkey/UserScript 所能做到的那样)的任意部分,而后者正是一个油猴脚本

hostname = www.btbdys.com
# 哔嘀影视播放页(www.btbdys.com)
^https?:\/\/(\w{0,3}(\.){0,1}(btbdys)(\.)\w{0,3})\/play\/.*?\.htm.* url script-response-body https://limbopro.com/Adguard/Adblock4limbo.js
# 哔嘀影视展示页(www.btbdys.com)
^https?:\/\/(\w{0,3}(\.){0,1}(btbdys)(\.)\w{0,3})(?!.*?(/(cdn-cgi)))(?!.*?(\.(css|js|jpeg|jpg|png|php|gif|ico|mp3|mp4|svg|tff|PNG|woff|woff2|m3u8))).* url script-response-body https://limbopro.com/Adguard/Adblock4limbo.js

如何在网页中插入油猴脚本

像PC端油猴管理器(tampermonkey)那样,将 JavaScript/油猴脚本 (嵌入)至网页的加载过程中,使其对网页的元素、事件、CSS样式进行控制;其中,在Surge/QuantumultX 的示例中,我们需要重点理解的是:Adblock4limbo.js 在其中的作用,我们利用 Adblock4limbo.js 实现 油猴管理器(tampermonkey)所能实现的功能;

JavaScript® (通常简写为JS)是一种轻量的、解释性的、面向对象的头等函数语言,其最广为人知的应用是作为网页的脚本语言,但同时它也在很多非浏览器环境下使用(在本文示例中,Adblock4limbo.js 是在 Surge/QuantumultX 代理工具环境下使用,代理工具是其宿主;而 Adblock4limbo.user.js 则是在浏览器环境下使用,浏览器是其宿主;不同宿主除了提供通用的JavaScript 语言结构如条件语句(if)、循环(for,while),方法如 ,也提供自己特有的方法;充分理解这一点,对认识 JavaScript 很有必要;)。JS是一种动态的基于原型和多范式的脚本语言,支持面向对象、命令式和函数式的编程风格。 via MDN

Adblock4limbo.js 源码为:

// 定义 CSS/JS
const regex = '</heda>';
const replace_str = '<link rel="stylesheet" href="https://limbopro.com/CSS/Adblock4limbo.user.css" type="text/css" />\
<script type="text/javascript" async="async" src="https://limbopro.com/Adguard/Adblock4limbo.user.js"></script></head>\
'

// 定义响应体
if ($response.body) {
    var body = $response.body.replaceAll('</HEAD>', '</head>').replaceAll(regex, replace_str) // 获取本次请求的响应体/返回体,此名为 body 的变量非 $done({ body }) 的 body;
};

// 定义响应头
const headers = $response.headers;
headers['Content-Security-Policy'] = '*'; // 重新定义内容安全策略(CSP) 使浏览器可以执行嵌入的第三方脚本
$done({ headers: headers, body: body }); // 在代理工具环境下,使用 $done({}); 表示不对该请求进行修改;$done({ body }); 则意味着使用该 body 覆盖原来的响应 body;

参阅 Surge 使用手册:HTTP 响应脚本;这一点 Quantumult X 是与 Surge 一样的,所以大多数时候 surge 可以用的模块或脚本,Quantumult x 也能用,只是他们的方法命名不一样而已:Surge,http-response;Quantumult X,script-response-body;

咩意思?我们重新走一遍,先从 hostname 说起:

hostname = www.btbdys.com

定义好主机名之后,则意味着我们要对该域名进行 MITM(中间人攻击),而不会匹配到别的未定义的域名;在所有代理工具及抓包工具中,正则表达式是非常常见的,如果你想快速上手 Surge/Quantumultx 等代理工具,学习好正则表达式会成为你的敲门砖(正则表达式学习包);总而言之,不管分流也好,重写也好,都是一次次匹配匹配再匹配的过程,在这里,我希望你能看懂这些重写规则示例中的表达式的各个部分意味着什么

正则表达式是由普通字符(例如字符 a 到 z)以及特殊字符(称为"元字符")组成的文字模式。模式描述在搜索文本时要匹配的一个或多个字符串。正则表达式作为一个模板,将某个字符模式与所搜索的字符串进行匹配。 via 菜鸟教程

紧接着,我们会看到重写规则,正则表达式,选用的 HTTP 重写类型,操作脚本;

# 哔嘀影视播放页(www.btbdys.com)
^https?:\/\/(\w{0,3}(\.){0,1}(btbdys)(\.)\w{0,3})\/play\/.*?\.htm.* url script-response-body https://limbopro.com/Adguard/Adblock4limbo.js

我们先从播放页这则说起,前面这段正则表达式,意味着我们要匹配哔滴影视网站下的符合某个正则表达式的网页,例如:先蝙蝠侠, https://www.btbdys.com/play/21643-0.htm

^https?:\/\/(\w{0,3}(\.){0,1}(btbdys)(\.)\w{0,3})\/play\/.*?\.htm.*

此时,我们选用的是 script-response-body 方法,即使用 JavaScript 对 response-body 进行相应操作,而 response-body(responsebody)正是HTTP中所谓的响应体/返回体

response-body 是什么

响应体/返回体是什么:(以访问网站举例)当你在浏览器地址栏输入链接并回车后,浏览器向网站服务器发起请求(查看http请求方法),网站服务器在收到该请求后进行匹配、解释,并返回你需要的内容,而这个内容正是响应体/返回体,浏览器作为用户端,网站服务器做为服务端,我们通过用户端向服务器拉取内容,于App而言,App作为用户端,也会向其服务器拉取内容,其抓包会更难一些,而在浏览网页时抓包http请求及其响应则是非常简单的事,你只需要一台能运行 Chrome/Safari 的电脑,打开开发者工具就可以操作,你可以了解到网页的全部细节,包括其包含的元素、标签、加载的第三方资源等等;我相信大家都有过类似经验,同一个网站在手机浏览器打开和在PC浏览器打开呈现的细节是不一样的,除了自适应网页设计的原因外,另一个原因是服务端会查看用户端发来的请求所携带的附加信息(Request header),进行匹配、解释,并返回事先约定的内容,而代理工具如 Surge/QuantumultX 位于服务端和用户端之间,通过中间人攻击(MITM)便可实现对请求/返回内容进行改写,请求通过代理工具后,我们可以改变其请求头,例如修改 userAgent(一般手机浏览器的UA都包含 Mobile 的字眼) 为 pc 端的UA,让服务端误以为该请求来自PC浏览器;同理,由于代理工具起到中间人的作用,服务端返回内容会被代理工具先接收,Surge/QuantumultX 可以先对其替换、删减、增加内容,尔后再释放给浏览器,最终浏览器对内容进行解析,渲染,而此时,渲染后得到的内容跟服务器所返回的内容已经是不同的两个版本了;

哔滴影视-蜘蛛侠页面对应的-请求头-userAgent键值对.png

哔滴影视-蜘蛛侠页面对应的-响应体/返回体.png

它对应媒体格式 content-type: text/html;charset=UTF-8,浏览器会根据该格式进行解析,此处为html,故最终根据响应体/返回体渲染出相应网页内容;

OK,我们现在要对这个响应体/返回体做些操作:我们要在 head 标签后面嵌入一个脚本(即前面示例的油猴脚本),使之作用于该网页;现在再回过头来看看 Adblock4limbo.js,我们正是将它用于操作响应体/返回体:修改/增加/删除/替换内容等等操作;

Adblock4limbo.js 源码如下:

// 定义 CSS/JS
const regex = '</heda>';
const replace_str = '<link rel="stylesheet" href="https://limbopro.com/CSS/Adblock4limbo.user.css" type="text/css" />\
<script type="text/javascript" async="async" src="https://limbopro.com/Adguard/Adblock4limbo.user.js"></script></head>\
'

// 定义响应体
if ($response.body) {
    var body = $response.body.replaceAll('</HEAD>', '</head>').replaceAll(regex, replace_str) // 获取本次请求的响应体/返回体,此名为 body 的变量非 $done({ body }) 的 body;
};

// 定义响应头
const headers = $response.headers;
headers['Content-Security-Policy'] = '*'; // 重新定义内容安全策略(CSP) 使浏览器可以执行嵌入的第三方脚本
$done({ headers: headers, body: body }); // 在代理工具环境下,使用 $done({}); 表示不对该请求进行修改;$done({ body }); 则意味着使用该 body 覆盖原来的响应 body;

这是一个简单的,将脚本(油猴脚本/自制JS脚本)嵌入响应体/返回体的方法,上面示例中,我们定义一个变量 ele(正则表达式),用于匹配捕获响应体/返回体中的 head 元素,通过 .replace() 方法将之(捕获到的文本)替换成一个新的文本,这样便完成了我们在网页中嵌入脚本(在这个示例中,我们嵌入的脚本名称为 Adblock4limbo.user.js,这正是一个符合油猴脚本写作规范的JavaScript脚本)的目的(注意!我们一般将脚本嵌入到 body 标签结束前;);

Adblock4limbo.jslet body = $response.body 意味着什么?对响应体/返回体进行捕获,捕获后在对其进行操作,删减、增加、替换等等操作(JavaScript提供的一些方法,包括但不限于 JSON.parse()、JSON.stringify(),App 中部分请求响应涉及的 content-type,如 JSON,需要对其进行转换操作,以修改其内容,达到伪造服务端响应的目的;);

此时,如果你对前端有所了解,相信你可以很快将其应用到实践当中。

IV. Surge 示例(如何在网页中嵌入油猴脚本)

以 Surge 模块配置举例:

#!name=毒奶特供(05.07.2022更新)
#!desc=毒奶去网页广告计划(稳定版)For Surge

[Script]
哔嘀影视播放页(www.btbdys.com) = type=http-response,pattern="^https?:\/\/(\w{0,3}(\.){0,1}(btbdys)(\.)\w{0,3})\/play\/.*?\.htm.*",requires-body=1,max-size=0,script-path=https://limbopro.com/Adguard/Adblock4limbo.js,script-update-interval=0
哔嘀影视展示页(www.btbdys.com) = type=http-response,pattern="^https?:\/\/(\w{0,3}(\.){0,1}(btbdys)(\.)\w{0,3})(?!.*?(/(cdn-cgi|verifyCode|member\/|zzzzz)))(?!.*?(\.(css|js|jpeg|jpg|png|php|gif|ico|mp3|mp4|svg|tff|PNG|woff|woff2|m3u8|ddr))).*",requires-body=1,max-size=0,script-path=https://limbopro.com/Adguard/Adblock4limbo.js,script-update-interval=0

[MITM]
hostname = %APPEND% www.btbdys.com

以上示例中,

#!name=毒奶特供(05.07.2022更新)// 模块命名
#!desc=毒奶去网页广告计划(稳定版)For Surge // 模块描述

其它即相应的脚本配置示例,基本上与Quantumult X 重写大同小异;

以上。

V. 附注

如果你想了解 如何通过 JavaScript 操作网页,对其进行增删查改,可以查看 客户端 Web API 以及如下一些前端入门资料:

最后修改:2023 年 11 月 01 日 09 : 33 AM