使用 CloudFlare Workers 搭建 telegram 频道镜像站
更新
- 2020-04-21:推荐食用 Telegram-Channel-Mirror 进行反代 telegram 频道
一次偶遇
昨天在咱的 让图片飞起来 oh-my-webp.sh ! 收到了 ChrAlpha 大佬的回复,咱就拜访了一下大佬的博客 😂,无意间发现 Cloudflare Worder 免费搭建镜像站 这篇博客。于是呐,咱也想着能不能玩一玩这个 Workers 。虽然之前听说过有用 Workers 做很多好玩的事儿,比如加速网站、代理 Google 镜像站什么的。不过这些对于咱来说不是很刚需就没有折腾。刚好咱的 telegram 电报频道 RSS Kubernetes 人也比较多了,为了提高一点影响力,咱就想着能不能把频道的预览界面 t.me/s/rss_kubernetes 反代到咱域名上。虽然之前尝试使用 nginx 进行反代,但是效果不尽人意,于是当时就弃坑了。在春节的时候咱也看到过有人反代 2019-nCoV 疫情实时播报 🅥 ,比如 2019ncov.ga,不过当时那个项目折腾起来也是很麻烦,咱这种菜鸡还是溜了溜了 😂。直到今天看到 Cloudflare Worder 免费搭建镜像站 这篇博客后就心血来潮,就搞一搞吧 😂
劝退三一连
首先你要有个 Cloudflare 账户,这是必须的。关于 Cloudflare 的注册咱就不多说啦,不过咱倒是建议大家伙把域名的 DNS 解析放到 Cloudflare 上来,好处多多:有把 https 小绿锁、免费的 加速 减速 CDN (墙内)、域名访问统计等等可玩性比较高 😋。需要注意的是 Cloudflare 的 Worker 一天 10 万次免费额度,也够咱喝一壶的啦,不用担心不够用。
新建 Worker
登录到 Cloudflare 的大盘面板,点击左上角的 Menu
—-> Workers
进入到 Workers 页面。新注册的用户会提示设置一个 workers.dev
顶级域名下的二级子域名,这个子域名设置好之后是不可更改的,之后你新创建的 Worker 就会使以这个域名而二级子域名开始的,类似于 WorkerName.yousetdomain.workers.dev
。yousetdomain
就是你要设置的二级子域名,WorkerName
可以自定义,默认是随机生成的。也可以给自己的域名添加一条 CNAME 到 WorkerName.yousetdomain.workers.dev
,这样使用自己的域名就可以访问到 Worker
了。
设置好二级子域名之后选择 free 套餐计划,然后进入到 Worker 管理界面,创建一个新的 Worker
然后在 Script
输入框里填入以下代码。俗话说代码写得好,同行抄到老,咱的 worker.js
代码是参考自 Workers-Proxy ,不过咱去掉了一些无关紧要的代码,原代码是加入了辨别移动设备适配域名、屏蔽某些 IP 和国家的功能。对于咱的 telegram 频道镜像站来说,这都是多余的,于是就去掉了。
// Website you intended to retrieve for users.
const upstream_me = 't.me';
const upstream_org = 'telegram.org';
// Custom pathname for the upstream website.
const upstream_path = '/s/rss_kubernetes';
// Whether to use HTTPS protocol for upstream address.
const https = true;
// Replace texts.
const replace_dict = {
$upstream: '$custom_domain'
};
addEventListener('fetch', event => {
event.respondWith(fetchAndApply(event.request));
});
async function fetchAndApply(request) {
let response = null;
let url = new URL(request.url);
let url_hostname = url.hostname;
if (https == true) {
url.protocol = 'https:';
} else {
url.protocol = 'http:';
}
var upstream_domain = upstream_me;
// Check telegram.org
let pathname = url.pathname;
console.log(pathname);
if (pathname.startsWith('/static')) {
console.log('here');
upstream_domain = upstream_org;
url.pathname = pathname.replace('/static', '');
} else {
if (pathname == '/') {
url.pathname = upstream_path;
} else {
url.pathname = upstream_path + url.pathname;
}
}
url.host = upstream_domain;
let method = request.method;
let request_headers = request.headers;
let new_request_headers = new Headers(request_headers);
new_request_headers.set('Host', url.hostname);
new_request_headers.set('Referer', url.hostname);
let original_response = await fetch(url.href, {
method: method,
headers: new_request_headers
});
let response_headers = original_response.headers;
let status = original_response.status;
response = new Response(original_response.body, {
status,
headers: response_headers
});
let text = await response.text();
// Modify it.
let modified = text.replace(
/telegram.org/g, 'tg.k8s.li/static'
);
// Return modified response.
return new Response(modified, {
status: response.status,
statusText: response.statusText,
headers: response.headers
});
}
需要注意的是,像 t.me
域名下的站点,比如我的 https://t.me/s/rss_kubernetes
,它的 js 和 css 样式文件是使用的 telegram.org 域名。所以我们需要在 将 replace_dict
那里定义好替换的正则表达式,https://t.me/s/rss_kubernetes
页面里的 telegram.org
同样进行反代才行,这需要为 telegram 建一个单独的 Worker 😑。这也是评论区 ChrAlpha 小伙伴提到的办法。
ubuntu@blog:~$ curl https://t.me/s/rss_kubernetes | grep "<script src="
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 141k 100 141k 0 0 85287 0 0:00:01 0:00:01 --:--:-- 85237
<script src="//telegram.org/js/jquery.min.js"></script>
<script src="//telegram.org/js/jquery-ui.min.js"></script>
<script src="//telegram.org/js/widget-frame.js?29"></script>
<script src="//telegram.org/js/telegram-web.js?8"></script>
修改下处代码为,将 https://t.me/s/rss_kubernetes
页面里的 telegram.org
同样进行一次反代。这样访问到 https://t.me/s/rss_kubernetes
页面时,把的 telegram.org 替换为另一个 Worker 的域名,比如我的 telegram.k8srss.workers.dev
。不过像频道里的图片、文件、视频等资源 telegram 是使用的 CDN ,而且有好几个域名……这点很僵硬,暂时找不到合适的办法。貌似一个 Worker 只能反代一个域名?如果汝有合适的办法,欢迎与咱交流,咱感激不尽 😋
let modified = text.replace(/telegram.org/g, "telegram.k8srss.workers.dev")
这样再使用 curl 访问测试一下,原来的 telegram.org 已经全部替换成 telegram.k8srss.workers.dev 了 😂。现在墙内用户也可以无痛访问啦。在此感谢 ChrAlpha 小伙伴 😂 提出宝贵的建议。
</main>
<script src="//telegram.k8srss.workers.dev/js/jquery.min.js"></script>
<script src="//telegram.k8srss.workers.dev/js/jquery-ui.min.js"></script>
<script src="//telegram.k8srss.workers.dev/js/widget-frame.js?29"></script>
<script src="//telegram.k8srss.workers.dev/js/telegram-web.js?8"></script>
<!-- page generated in 121.26ms -->
这个文本替换功能很好玩儿,在 Cloudflare 官方的博客里还有个 demo introducing-cloudflare-workers 。使用这个功能咱有解锁了一个玩具,稍后再讲 😂。
Here is a worker which performs a site-wide search-and-replace, replacing the word “Worker” with “Minion”. Try it out on this blog post.
剽窃修改好代码之后点击左下角的 Save and Deploy
然后 Preview 看看页面是否显示正常,如果显示正常恭喜你成功啦。
如果你想使用这种办法反代其他频道,只需要把 const upstream_path = '/s/rss_kubernetes'
后的 rss_kubernetes 替换为你想要代理的 telegram 频道 username 即可。之所以加上 upstream_path
而不反代整个 t.me
是为了防止别人滥用,毕竟 10W 次不经薅 😂
改进
周四晚上睡觉前在推特上发了个推文,向大家请教了一下之前一个 Worker 里只能反代一个域名的问题。第二天 @Echowxsy 就回复咱了,而且还特意注册了 CouldFare 账号使用 Workers 帮咱测试了一下。在此非常感谢 @Echowxsy 帮咱。按照 @Echowxsy 小伙伴所说的:
我没用过 CloudFlare,不过我看了一下你的 blog,貌似可以用两个 upstream 实现这个功能。 在 modified 那里替换为当前 worker 的地址,然后在后面加上一个不会重复的路径,例如 xxx。 然后在 fetchAndApply 里面判断,如果当前请求的 pathname=/xxx,使用 upstream2,否则使用 upstream1。 理论上是可以实现的。
就是一个 Workers 可以做很多事情,他实际上就是 Node.js 代码。 然后这里是将 http://telegram.org/xxxx 映射到 http://tg.k8s.li/static/xxxx 。 然后在 Workers 里面判断,如果有
/static
则从 http://telegram.org 获取,否则从 http://t.me 获取。
Woerker 首先我们获取完要反代的 https://t.me/s/rss_kubernetes
页面 html 源码,然后使用 replace 函数把 telegram.org 以及 cdn[1-5].telesco.pe 等域名进行替换,替换为 /static 、/cdn[1-5] 等不同的 url 路径。然后将修改后的 html 页面返回给客户端。客户端客户端在请求 /static
时 Worker 就会去 http://telegram.org 获取相应的资源返回给用户。这样就实现了一个 Worker 反代多个域名骚操作。修改后的代码如下:
const upstream_me = 't.me';
const upstream_org = 'telegram.org';
// Custom pathname for the upstream website.
const upstream_path = '/s/rss_kubernetes';
// Whether to use HTTPS protocol for upstream address.
const https = true;
// Replace texts.
const replace_dict = {
$upstream: '$custom_domain'
};
addEventListener('fetch', event => {
event.respondWith(fetchAndApply(event.request));
});
async function fetchAndApply(request) {
let response = null;
let url = new URL(request.url);
let url_hostname = url.hostname;
if (https == true) {
url.protocol = 'https:';
} else {
url.protocol = 'http:';
}
var upstream_domain = upstream_me;
// Check telegram.org
let pathname = url.pathname;
console.log(pathname);
if (pathname.startsWith('/static')) {
console.log('here');
upstream_domain = upstream_org;
url.pathname = pathname.replace('/static', '');
} else {
if (pathname == '/') {
url.pathname = upstream_path;
} else {
url.pathname = upstream_path + url.pathname;
}
}
url.host = upstream_domain;
let method = request.method;
let request_headers = request.headers;
let new_request_headers = new Headers(request_headers);
new_request_headers.set('Host', url.hostname);
new_request_headers.set('Referer', url.hostname);
let original_response = await fetch(url.href, {
method: method,
headers: new_request_headers
});
let original_response_clone = original_response.clone();
let response_headers = original_response.headers;
let new_response_headers = new Headers(response_headers);
let status = original_response.status;
response = new Response(original_response_clone.body, {
status,
headers: new_response_headers
});
let text = await response.text();
// Modify it.
let modified = text.replace(/telegram.org/g,'tg.k8s.li/static');
// Return modified response.
return new Response(modified, {
status: response.status,
statusText: response.statusText,
headers: response.headers
});
}
自定义域名
回到 Workers 的管理页面,点击 rename
即可修改 Worker 的三级子域名。不过咱还是不太喜欢 WorkerName.yousetdomain.workers.dev
这么长的域名,想使用咱自己的二级子域名访问。
首先回到域名管理的页面,进入到自己域名顶部那一栏里的 Workers
,在那里添加相应的路由即可。
点击 Add Route
,在 Route 那一栏输入好自己的域名,注意最后的 /*
也要加上,然后 Worker 选择刚刚创建的那个即可。接着再添加 CNAME
记录到自己的 WorkerName.yousetdomain.workers.dev
,这样就能使用自己的域名访问啦 😋
文本替换
前文提到的文本替换功能帮咱解决了一个小问题。咱友链关注的一个博客 chanshiyu.com 没有提供 RSS ,他是将博客内容放在 GitHub issue 上,所以只能通过 RSSHUB 来订阅 GitHub 的 issue 来获取博客的更新。但 RSSHUB 获取的是 GitHub issue 的链接而非原博客的链接,于是咱想了想就用 Worker
进行替换不得了。
通过 RSSHUB 获取到的 RSS 数据如下:
<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
<channel>
<title>
<![CDATA[ chanshiyucx/blog Issues ]]>
</title>
<link>https://github.com/chanshiyucx/blog/issues</link>
<atom:link href="http://rsshub.app/github/issue/chanshiyucx/blog" rel="self" type="application/rss+xml"/>
<description>
<![CDATA[
chanshiyucx/blog Issues - Made with love by RSSHub(https://github.com/DIYgod/RSSHub)
]]>
</description>
<generator>RSSHub</generator>
<webMaster>[email protected] (DIYgod)</webMaster>
<language>zh-cn</language>
<lastBuildDate>Thu, 26 Mar 2020 11:00:06 GMT</lastBuildDate>
<ttl>60</ttl>
<item>
<title>
<![CDATA[ Telegram 电报机器人 ]]>
</title>
<description>
<![CDATA[
]]>
</description>
<pubDate>Wed, 25 Mar 2020 03:54:04 GMT</pubDate>
<guid isPermaLink="false">https://github.com/chanshiyucx/blog/issues/108</guid>
<link>https://github.com/chanshiyucx/blog/issues/108</link>
其中的 <guid isPermaLink="false">
和 <link>
中的链接 github.com/chanshiyucx/blog/issues/ 替换为 chanshiyu.com/#/post/ 即可。于是还是同样的方法新建一个 Worker,然后修改一下 worker.js
的代码就可以啦。
// Website you intended to retrieve for users.
const upstream = 'rsshub.app'
// Custom pathname for the upstream website.
const upstream_path = '/github/issue/chanshiyucx/blog'
// Whether to use HTTPS protocol for upstream address.
const https = true
addEventListener('fetch', event => {
event.respondWith(fetchAndApply(event.request));
})
async function fetchAndApply(request) {
let response = null;
let url = new URL(request.url);
let url_hostname = url.hostname;
if (https == true) {
url.protocol = 'https:';
} else {
url.protocol = 'http:';
}
var upstream_domain = upstream;
url.host = upstream_domain;
if (url.pathname == '/') {
url.pathname = upstream_path;
} else {
url.pathname = upstream_path + url.pathname;
}
let method = request.method;
let request_headers = request.headers;
let new_request_headers = new Headers(request_headers);
new_request_headers.set('Host', url.hostname);
new_request_headers.set('Referer', url.hostname);
let original_response = await fetch(url.href, {
method: method,
headers: new_request_headers
})
let original_response_clone = original_response.clone();
let original_text = null;
let response_headers = original_response.headers;
let new_response_headers = new Headers(response_headers);
let status = original_response.status;
const content_type = new_response_headers.get('content-type');
if (content_type.includes('text/html') && content_type.includes('UTF-8')) {
original_text = await replace_response_text(original_response_clone, upstream_domain, url_hostname);
} else {
original_text = original_response_clone.body
}
response = new Response(original_text, {
status,
headers: new_response_headers
})
let text = await response.text()
// Modify it.
let modified = text.replace(/github.com\/chanshiyucx\/blog\/issues\//g, "chanshiyu.com\/#\/post\/")
// Return modified response.
return new Response(modified, {
status: response.status,
statusText: response.statusText,
headers: response.headers
})
return response;
}
部署好 Worker 之后就可以使用 RSS 来订阅啦 😋
解锁更多好玩儿的 😋
关于 Cloudflare 的 Workers 还有更多好玩的等待你去发现,咱就推荐一下啦:
- Cloudflare Worker 免费搭建镜像站
- 从现在起,任何人都可以在 Cloudflare 上使用 Workers 运行 JavaScript!
- 尽情编写代码吧:改善开发人员使用 Cloudflare Workers 的体验
- 使用 Cloudflare Workers 加速 Google Analytics
- 使用 Backblaze B2 和 Cloudflare Workers 搭建可以自定义域名的免费图床
- 使用 Cloudflare Workers 提高 WordPress 速度和效能教學
- 使用 Cloudflare Workers 反带 P 站图片
最后宣传一下咱的 @rss_kubernetes 频道,国内用户可以访问 tg.k8s.li,如果你对 docker 、K8s、云原生等感兴趣,就到咱碗里来吧 😂。不订阅咱的频道也可以通过咱的 tg.k8s.li 镜像站来查看 RSS 推送信息。
GitHub page 又被墙一波,rsshub.app 也被墙掉了。墙越来越高了,这个社会也来越可笑了……不知道未来的互联网会变成什么样子,但我们作为一只屁民能做的就是不为墙添砖加瓦,不为极权专制独裁暴政唱赞歌。最后一张自己制作 kindle 电子书时喜欢使用的封面图片送给大家。