Surge 脚本 API 详解
前面几章学的都是 JavaScript 本身。这一章开始用在真地方:Surge 里的脚本。Surge 会给脚本一整套以 $ 开头的工具,这一章就把它们一个一个认全,再动手跑两个真脚本。
一这一章学什么
一句话目标:认识 Surge 脚本的三类工具——看现场、干活、交卷,会用 $done 的几种交法,会写「改 JSON」和「定时签到」两种脚本。
学完这一章,你应该能做到:
- 说清每个
$工具是干什么的:哪个是看请求的,哪个是发请求的,哪个是存东西的; - 背下
$done的几种交法,知道改响应体、改网址、伪造响应分别怎么写; - 看懂并写出一个「改 JSON 去广告」脚本和一个「定时签到」脚本骨架。
二概念:Surge 给脚本的工具箱
Surge 的脚本不是在空白环境里跑的。脚本一被叫起来,Surge 就先把一套工具递到它手里,这些工具的名字全都以 $ 开头。按用途分成三类,先记这个三分法,后面就不乱了:
- 看现场:
$request、$response——先看清这次请求和响应长什么样; - 干活:
$httpClient发请求、$persistentStore存数据、$notification发通知; - 交卷:
$done——活干完了,必须亲手交卷,脚本才算结束。
看现场的工具
当前这一次请求。http-request(请求发出前)和 http-response(响应回来后)两种脚本里都有它。
.url—— 网址.method—— 方法,比如 GET、POST.headers—— 请求头,是一个对象.body—— 请求体,只有脚本配置了 requires-body 时才有,没配就是空的
当前这一次响应,只有 http-response 脚本里才有(请求还没发出去时,当然没有响应)。
.status—— 状态码,比如 200 表示成功.headers—— 响应头,是一个对象.body—— 响应体,是一段字符串;想当对象用,先JSON.parse转一下
模块里 argument= 后面写的东西,原样传给脚本,是一个字符串。里面怎么组织格式由你自己约定:可以写成 JSON,也可以写成 a=1&b=2 这种用 & 拼接的格式,脚本里自己拆开用。
干活的工具
在脚本里再发一个网络请求出去(比如签到脚本要自己去请求签到接口)。
.get(选项, 回调)—— 发 GET 请求;.post(选项, 回调)—— 发 POST 请求- 回调函数的签名是
(error, response, data),三个参数按顺序记:error 为空就表示成功;response.status是状态码、response.headers是响应头;data是返回内容的字符串
一个持久小仓库:存进去的东西,Surge 重启、手机重启都不丢,下次脚本运行还能读出来。
.write(值, 键)—— 存:把值存到某个键名下.read(键)—— 读:按键名把值取出来- 只能存字符串。想存对象,先用
JSON.stringify把对象变成字符串再存;读出来再用JSON.parse变回去
发一条系统通知,就是手机顶部弹出来的那种。
$notification.post(标题, 副标题, 内容)—— 三个参数依次是标题、副标题、正文内容
这个不是 $ 开头的,但同样重要:打日志。写下的内容在 Surge 的脚本日志里看,脚本出问题时,靠它一步步找错在哪。
交卷的工具(最重要)
交卷。脚本结束时必须调用它,告诉 Surge「我处理完了」。这是本章最重要的一条规矩:不调 $done,脚本会一直挂到超时,这段时间请求被卡住,App 就一直转圈。
$done 有几种交法,交的东西不同,效果完全不同,逐个记:
什么都不改,原样放行。最常用的收尾。
同上,交一个空对象,意思一样:不改任何东西。
在 http-response 脚本里替换响应体。改 JSON 就靠它:把改好的内容变回字符串,塞进 body 交卷。
替换或增改头部,把要改的请求头/响应头写在对象里。
在 http-request 脚本里,把这次请求改发到另一个网址去。
直接伪造一个响应交回去,不等真正的服务器回复。拦截请求、模拟接口时常用。
$request,也没有 $response,只有干活的工具和 $done。签到脚本就是这种。
三动手试:真的跑一跑
先说清一件事:$response、$done 这些工具只存在于 Surge 里面,浏览器页面里没有。所以下面的练习都是先在页面里造两个「假的」顶上,再跑真正的脚本逻辑——逻辑部分和你以后写进 Surge 的一模一样。每段代码都能点「运行」,出错了也会把错误显示出来。
练习 1:改 JSON 去广告
服务器返回的 body 里有两条内容,第一条 isAd(是不是广告)是 true。脚本要把广告筛掉,再把新 body 交卷换回去。点「运行」,看改前改后的 body 对比。
// 第一步:造假现场。浏览器里没有真的 $response 和 $done,
// 先自己造两个假的顶上,逻辑部分和真脚本一模一样。
let $response = {
body: '{"data":[{"title":"a","isAd":true},{"title":"b","isAd":false}]}'
// body 是服务器返回的文字:一共 2 条,
// a 的 isAd 是 true(是广告),b 不是
};
let $done = function (结果) {
// 真 Surge 里 $done 是交卷;
// 这里把“交卷内容”交给页面显示出来
显示结果(结果);
};
// 第二步:真正的脚本逻辑(写进 Surge 的就是这一段)
try {
let obj = JSON.parse($response.body); // 把 body 文字转成对象
if (obj && obj.data) {
obj.data = obj.data.filter(item => !item.isAd); // 筛掉 isAd 为 true 的
}
$done({ body: JSON.stringify(obj) }); // 变回文字,交卷替换响应体
} catch (e) {
console.log("处理失败:" + e.message); // 出错先记日志
$done(); // 出错也要交卷放行
}
练习 2:小仓库 $persistentStore 的存和取
用普通对象模拟小仓库:先 write 存一条 cookie,再 read 读出来;然后试试存一个对象——记住,要先 JSON.stringify 变成字符串才能存。(这里是模拟:刷新页面就清空;真的 $persistentStore 重启都不丢。)
// 用一个普通对象模拟 $persistentStore(小仓库)
let 仓库 = {};
let $persistentStore = {
write: function (值, 键) { 仓库[键] = 值; }, // write(值, 键):存
read: function (键) { return 仓库[键]; } // read(键):取
};
// 存一条签到用的 cookie(字符串,直接存)
$persistentStore.write("session=abc123", "cookie");
// 再读出来看看
let 读到的 = $persistentStore.read("cookie");
console.log("读到的 cookie 是:" + 读到的);
// 想存对象?先用 JSON.stringify 把对象变成字符串再存
let 设置 = { 自动签到: true, 时间: "08:00" };
$persistentStore.write(JSON.stringify(设置), "设置");
console.log("存好的设置(字符串):" + $persistentStore.read("设置"));
// 读出来要用时,再用 JSON.parse 变回对象
let 读回的设置 = JSON.parse($persistentStore.read("设置"));
console.log("变回对象后,自动签到是:" + 读回的设置.自动签到);
练习 3:坏数据面前,包不包 try/catch 差多少
这次的 body 是坏掉的 JSON(结尾少了 }]},拼不完整)。同一份坏数据,两种写法各跑一遍:不包 try/catch 的会抛错崩掉,包了的会把错误接住、照样交卷。
// 这次的 $response 里装的是坏数据:结尾少了 }]}
// 不是合法 JSON,JSON.parse 转不动它
let $response = { body: '{"data":[{"title":"a"}' };
let $done = function () { console.log("→ $done() 被调用,请求放行"); };
// —— 写法一:不包 try/catch,直接转 ——
let obj = JSON.parse($response.body); // 就在这一行抛错
$done({ body: JSON.stringify(obj) }); // 出错后这行执行不到,等于没交卷
// —— 写法二:同样的活,包上 try/catch ——
try {
let obj2 = JSON.parse($response.body);
$done({ body: JSON.stringify(obj2) });
} catch (e) {
console.log("处理失败:" + e.message); // 错误被接住,先记下来
$done(); // 再交卷放行,App 不受影响
}
说明:点「运行」时,页面会把两种写法分开各跑一遍给你看——要是连在一起跑,写法一出错就直接崩了,轮不到写法二。
四练一练
一共 5 道题,点一个选项立刻判对错。已答对 0 / 5。
五适用案例
案例一去广告:筛掉信息流里的广告条目
很多 App 的信息流(feed)是服务器用 JSON 下发的,广告条目常常自带标记,比如 isAd: true。写一个 http-response 脚本:把 $response.body 转成对象,用 filter 把带广告标记的条目筛掉,再 $done({ body: 新字符串 }) 交卷换回去,App 拿到的就是干净的列表。上面「练习 1」跑的就是这个套路的最小版,真用的时候把判断条件换成那个 App 实际的广告标记就行。
案例二定时签到:脚本骨架长这样
签到脚本是 cron 定时脚本:到点被叫醒,先从小仓库读出以前存好的 cookie,用 $httpClient 发签到请求,最后 $notification.post 把结果弹个通知告诉你——不用打开 App 就知道成没成。
// 定时签到脚本骨架(cron 类型脚本,没有 $request)
let cookie = $persistentStore.read("cookie"); // 读出之前存好的 cookie
if (!cookie) {
// 没存过 cookie,先提醒自己去抓一次
$notification.post("签到", "", "还没存 cookie,先打开一次 App");
$done();
} else {
$httpClient.get({
url: "https://example.com/checkin", // 换成你自己的签到地址
headers: { Cookie: cookie }
}, function (error, response, data) { // 回调三参数:错、响应、内容
if (error) {
$notification.post("签到", "", "请求失败:" + error);
} else {
$notification.post("签到", "", "签到完成");
}
$done(); // 在回调里也要交卷,别漏了
});
}
注意最后一行:请求是异步发的,$done() 要写在回调函数里面,等结果回来再交卷;写在外面的话,请求还没回来脚本就结束了。
案例三出错也要交卷:别把人家的 App 卡死
你的脚本处理的是 App 的真实流量:body 格式变了、JSON 坏了、字段不存在,随时可能出错。一旦代码抛错又没有交卷,请求就卡到超时,用户看到的就是 App 打不开、一直转圈——锅全在你的脚本上。固定套路是:整个逻辑包上 try/catch,catch 里先 console.log 记下错误,再 $done() 放行。宁可这次不改,也不能把请求卡死;日志留着,事后慢慢修。
✓本章小结
$request / $response 看,用 $httpClient、$persistentStore、$notification 干,最后用 $done 交卷收尾,一次都不能少。