如何订阅Q外的RSS?方法来了

如何订阅Q外的RSS?方法来了

2024-01-06
#工具箱 , #学习

起因

前文有写到关于RSS订阅的一些事情,想了解的可以看看 打破信息茧房及一款RSS阅读器推荐 一文。

不过这两天在实际使用过程中发现有部分网站如:V2ex椒盐豆豉 等网站的订阅在通过yarr添加的时候会提示 No feeds found at the given url. 的提醒信息。

当时我还以为是yarr对这些网站的xml格式支持不全导致,所以跑去研究起了yarr关于解析xml文件部分的源代码,在尝试将2.4的部分代码和原作者最新的部分合并后我在本地测试发现似乎可以正常添加,所以重新build了一个镜像发布到docker上。

不过,奇怪的事情发生了。

我之前在本地测试明明可以,但是在自己的VPS中即便我重新拉取镜像、重新创建容器,却依旧提示上面的错误。这就让我有点「满头大汉」了,本来差点又要跑去研究源代码,不过忽然灵光一闪,想到这两个网站似乎都有一个共性——「在墙外」,而我的服务器又在国内,正常情况下自然是无法访问的,之前在本地测试通是因为我的路由器上有小猫做分流,所以自然能正常访问。

至此算是破案了,不过很想吐槽一句:yarr的错误信息提示真的很有问题,这种情况不是应该提示目标网站访问超时吗?

当时我能想到的只有一种方式:在服务器上装个小猫,让流量走代理就可以了,不过被我直接PASS了,因为环境的关系我并不想在这些大厂的服务器上安装这种东西。

CF Worker转发Rss

而后想到之前Memos使用tgbot机器人时通过CF Worker转发请求的折腾。那有没有可能也可以通过CF Worker转发RSS的呢?答案自然是有的,通过一些搜索,我在 蜜柑计划RSS无法访问的解决办法 一贴中找到了解决办法。

因为没有很复杂的功能,所以文内提到的代码几乎开箱即用,可以说有手就行。

在CF中新建一个Worker,并将下方的示例代码贴入新Workers中,并将代码中 const yourDomain = 'your.workers.dev'; 中后面的部分换成你的Worker链接(注意没有 https ),部署即可。

/*
* https://github.com/netnr/workers
*
* 2019-10-12 - 2022-05-05
* netnr
*
* https://github.com/Rongronggg9/rsstt-img-relay
*
* 2021-09-13 - 2022-05-29
* modified by Rongronggg9
* 
* 2023-4-21 
* modified by papersman
*/

export default {
   async fetch(request, _env) {
       return await handleRequest(request);
   }
}

/**
* Configurations
*/
const yourDomain = 'your.workers.dev';
const config = {
   // 是否丢弃请求中的 Referer,在目标网站应用防盗链时有用
   dropReferer: true,
};

/**
* Respond to the request
* @param {Request} request
*/
async function handleRequest(request) {
   //请求头部、返回对象
   let reqHeaders = new Headers(request.headers),
       outBody, outStatus = 200, outStatusText = 'OK', outCt = null, outHeaders = new Headers({
           "Access-Control-Allow-Origin": "*",
           "Access-Control-Allow-Methods": "GET, POST, PUT, PATCH, DELETE, OPTIONS",
           "Access-Control-Allow-Headers": reqHeaders.get('Access-Control-Allow-Headers') || "Accept, Authorization, Cache-Control, Content-Type, DNT, If-Modified-Since, Keep-Alive, Origin, User-Agent, X-Requested-With, Token, x-access-token"
       });

   try {
       //取域名第一个斜杠后的所有信息为代理链接
       let url = request.url.substr(8);
       url = decodeURIComponent(url.substr(url.indexOf('/') + 1));

       //需要忽略的代理
       if (request.method == "OPTIONS" || url.length < 3 || url.indexOf('.') == -1 || url == "favicon.ico" || url == "robots.txt") {
           //输出提示
           const invalid = !(request.method == "OPTIONS" || url.length === 0)
           outBody = JSON.stringify({
               code: invalid ? 400 : 0,
               usage: 'https://'+yourDomain+'/https://mikanani.me/...',
               source: '将 your.workers.dev 换成自己的workers的地址.使用的时候, RSS地址位置填入 '+'https://'+yourDomain+'/https://mikanani.me/RSS/...'
           });
           outCt = "application/json";
           outStatus = invalid ? 400 : 200;
       } else {
           url = fixUrl(url);

           //构建 fetch 参数
           let fp = {
               method: request.method,
               headers: {}
           }
           // 发起 fetch
           let fr = (await fetch(url, fp));
           outCt = fr.headers.get('content-type');

           //保留头部其它信息
           const dropHeaders = ['content-length', 'content-type', 'host'];
           if (config.dropReferer) dropHeaders.push('referer');
           let he = reqHeaders.entries();
           for (let h of he) {
               const key = h[0], value = h[1];
               if (!dropHeaders.includes(key)) {
                   fp.headers[key] = value;
               }
           }
           if (config.dropReferer && url.includes('.sinaimg.cn/')) fp.headers['referer'] = 'https://weibo.com/';

           // 当访问mikanani.me/RSS的时候,将返回的xml中的mikanani.me替换
           if (url.includes('mikanani.me/RSS')) {
               const response = await fetch(url, fp);
               const text = await response.text();
               outBody = text.replace(/mikanani.me\/Download\//g, yourDomain+'/https://mikanani.me/Download/');
               outCt = response.headers.get('content-type');
               outStatus = response.status;
               outStatusText = response.statusText;
           } else if (url.includes('acg.rip/.xml')) {  //当访问acg.rip/.xml的时候,将返回的xml中的acg.rip/t/替换
               const response = await fetch(url, fp);
               const text = await response.text();
               outBody = text.replace(/acg.rip\/t\//g, yourDomain+'/https://acg.rip/t/');
               outCt = response.headers.get('content-type');
               outStatus = response.status;
               outStatusText = response.statusText;
           } else if (url.includes('bangumi.moe/rss')) {  //当访问bangumi.moe/rss的时候,将返回的xml中的bangumi.moe/download替换
               const response = await fetch(url, fp);
               const text = await response.text();
               outBody = text.replace(/bangumi.moe\/download\//g, yourDomain+'/https://bangumi.moe/download/');
               outCt = response.headers.get('content-type');
               outStatus = response.status;
               outStatusText = response.statusText;
           } else {
               outBody = fr.body;
               outStatus = fr.status;
               outStatusText = fr.statusText;
           };

           if (["POST", "PUT", "PATCH", "DELETE"].indexOf(request.method) >= 0) {
               const ct = (reqHeaders.get('content-type') || "").toLowerCase();
               fp.headers['content-type'] = ct
               if (ct.includes('application/json')) {
                   fp.body = JSON.stringify(await request.json());
               } else if (ct.includes('application/text') || ct.includes('text/html')) {
                   fp.body = await request.text();
               } else if (ct.includes('form')) {
                   fp.body = await request.formData();
               } else {
                   fp.body = await request.blob();
               }
           };
       }
   } catch (err) {
       outBody = err.stack;
       outCt = "text/plain;charset=UTF-8";
       outStatus = 500;
       outStatusText = "Internal Server Error";
   }

   //设置类型
   if (outCt && outCt != "") {
       outHeaders.set("content-type", outCt);
   }
   
   let response = new Response(outBody, {
       status: outStatus,
       statusText: outStatusText,
       headers: outHeaders
   })

   return response;
}

/**
* Fix URL
* @param {string} url 
*/
function fixUrl(url) {
   if (url.startsWith('http://') || url.startsWith('https://')) {
       return url;
   } else {
       return 'https://' + url;
   }
}

使用方法也很简单,在你的worker地址后面跟上需要订阅的rss即可,这里以订阅v2ex为例:

https://your.worker.dev/https://www.v2ex.com/index.xml

加入评论