机场订阅聚合与转换

主要用到的工具是阿里云函数计算subconverter,前者用于机场订阅聚合,后者用于转换订阅。参考这篇博客和 subconverter 文档写成。

订阅聚合

进入函数计算后台,先切换到中国香港集群。在“服务及函数”中创建服务,填写名称,如 proxy,其他保持默认即可。

在刚创建的服务中新建函数,选择“使用内置运行时创建”,函数名称设置为 subscribe。

确保选中“处理 HTTP 请求”,然后在下方“高级配置”中将“实例并发度”设置为 100,“执行超时时间”设置为推荐的 600。

在“环境变量”中添加 URL 变量,值为订阅链接,有多个链接的话用|分隔。再添加一个 TOKEN 变量,值的格式为 <token_name>=<token>,同样以 | 分隔。上面博客中有相关设置截图。

完成后,点击“创建”会进入代码编辑页面,用的是 VS Code Server。调出终端,执行 npm install axios。修改 index.js 为以下代码,记得点击部署函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
const axios = require('axios');
axios.defaults.timeout = 3000;

const URLS = (process.env.URL || '')
.split('|')
.filter((item) => item.length > 0);
const TOKENS = Object.assign(
{},
...(process.env.TOKEN || '')
.split('|')
.filter((item) => item.length > 0)
.filter((item) => item.indexOf('=') !== -1)
.map((item) => item.split('='))
.map((item) => ({ [item[1]]: item[0] }))
);

const TESTSUBSCRIBE='dm1lc3M6Ly9ldzBLSUNBaWRpSTZJQ0l5SWl3TkNpQWdJbkJ6SWpvZ0l1YTFpK2l2bFNJc0RRb2dJQ0poWkdRaU9pQWlNVEkzTGpBdU1DNHhJaXdOQ2lBZ0luQnZjblFpT2lBaU1USXpORFVpTEEwS0lDQWlhV1FpT2lBaU16QXdaRGN6T1RZdE1tUXlPQzAwWmpKaUxUaG1PV1l0TXpjMU5UQTVZbVZpTVROaElpd05DaUFnSW1GcFpDSTZJQ0l3SWl3TkNpQWdJbk5qZVNJNklDSmhkWFJ2SWl3TkNpQWdJbTVsZENJNklDSjBZM0FpTEEwS0lDQWlkSGx3WlNJNklDSnViMjVsSWl3TkNpQWdJbWh2YzNRaU9pQWlJaXdOQ2lBZ0luQmhkR2dpT2lBaUlpd05DaUFnSW5Sc2N5STZJQ0lpTEEwS0lDQWljMjVwSWpvZ0lpSU5DbjA9DQo='

const fromBase64 = (s) => Buffer.from(s, 'base64').toString();
const toBase64 = (s) => Buffer.from(s).toString('base64');

const removePrefix = (s, p) => s.startsWith(p) ? s.slice(p.length) : s;

const get = (url, resolve, reject) =>
axios
.get(url)
.then((resp) => resp.data)
.then(resolve)
.catch(reject);

async function getData() {
try {
return toBase64(
(
await Promise.all(
URLS.map(
(url) => new Promise((resolve, reject) => get(url, resolve, reject))
)
)
)
.map((encoded) =>
fromBase64(encoded)
.split('\n')
.filter((line) => line.length > 0)
)
.reduce((pre, cur) => pre.concat(cur))
.join('\n')
);
} catch (e) {
return e.toString();
}
}

exports.handler = async (req, resp, context) => {
console.log(req.path);
if (removePrefix(req.path, "/subscribe") === '/test') {
console.log(req.clientIP);
resp.send(TESTSUBSCRIBE);
return;
}

const token = req.queries['token'];
console.log(URLS, TOKENS);
console.log(req.clientIP, token, TOKENS[token]);
const res = !!TOKENS[token] ? await getData() : '';
resp.send(res);
};

自定义域名

回到函数计算主管理界面,在“域名管理”中添加自定义域名。设置好域名解析后,修改路由配置为 /subscribe,其他栏下拉进行相应选择。完成后,再照此添加一条路由 /subscribe/*。你也可以为域名申请并启用证书,最后点击创建即可。

绑定域名后,可通过以下形式调用:

  • https://fc.example.com/subscribe/test
  • https://fc.example.com/subscribe?token=<token>

前者返回测试订阅,方便调试;后者返回打包后的节点信息。

搭建 subconverter

那篇博客中 subconverter 部分我试过,经常报错,也懒得去查阿里云的文档。干脆,直接用 Docker 搭建算了。你可以通过 Composerize 网站,将 Docker 命令转换成 Docker Compose 更方便些。

使用方式

根据相关文档,调用地址中的 url 参数和远程引用的 config 文件链接,都必须经过 URL Encode 处理。以下面为例进行简单说明。

https://<your_subconverter_server>/sub?target=clash&new_name=true&url=<your_subscription_link>&filename=<filename>&interval=21600&insert=false&config=https%3A%2F%2Fraw.githubusercontent.com%2FFDUZS%2Fsubconverter-config%2Fmain%2Fconfig.ini

  • <your_subconverter_server> 为上面搭建的 subconverter 后端,如果用 IP 访问或无证书,则为 http
  • url 为上面订阅聚合链接,记得带 token 参数并 URL Encode 处理
  • filename 是在 Clash for Windows 等软件中导入后显示的名字
  • interval 是在 Clash for Windows 等软件中导入后自动更新周期,单位为秒
  • config 即外部配置文件,需经 URL Encode 处理,可直接使用上述例子中的
  • 其他更多参数可查询相关文档