野声

Hey, 野声!

谁有天大力气可以拎着自己飞呀
twitter
github

一個免費的 Newsletter 轉 RSS 方案

我之前都是使用自己的郵箱訂閱 Newsletter,但是每天好多郵件發送給你,混雜在一些提醒和交流郵件中,怪麻煩的。

也嘗試過好幾個訂閱 newsletter 的應用,也了解了 mail2rss 這方面的網站和開源項目,一直沒有啥好的解決方案。

有一天偶然看到 testmail.app 這個可以讓開發者測試郵件服務的網站,每個用戶可以有一個 namespace,然後可以構造出無限個郵件地址,也有相應的 API 用來過濾郵件等等。

testmail 免費版每個月可以接收 100 封郵件,郵件能保存一天。但是只需要設置 RSS 閱讀器請求頻率低於一天,肯定就能接收到所有郵件了。

那麼項目思路就有了,使用 serverless 實現一個函數,這個函數每次接受 RSS 閱讀器請求時都去抓取最新的郵件列表,然後響應一個 RSS 格式的 XML 文檔即可。

Mermaid Loading...

有了整個項目的思路之後,現在就開始根據 testmail.app 的 API 來寫功能就行了,Serverless 選擇使用 Cloudflare workers,每天十萬次請求數,I/O 時間不限。

開始使用 testmail.app#

名詞解釋#

註冊登錄後就能看到一個你自己的 namespace。

testmail.app 裡對 namespace 是這麼描述的:
testmail.app 會接收發送到 {namespace}.{tag}@inbox.testmail.app 的郵件,{namespace} 是分配給每個人的獨自的一個 id,{tag} 是你自己隨便輸入的,之後我們可以通過 API 來過濾 tag 從而獲取我們需要的郵件。

namespace#

每個 namespace 都支持無限數量的電子郵件地址。

舉個例子:假設你的 namespace 是 acmeinc;然後你發一封郵件到 [email protected],再發另一封郵件到 [email protected],然後你都能在 acmeinc 這個 namespace 下查到這兩封郵件,只是兩封郵件帶的 tag 不同。

tag#

tag 可以是任意內容。

舉個例子:假設你要測試一下新用戶註冊的功能,你就可以使用 [email protected] 這個郵箱創建用戶名為 John 的新用戶,通過 [email protected] 這個郵箱創建用戶名為 Albert 的新用戶。

之後查詢 API 的時候,可以過濾指定標籤來查看發給每個用戶的郵件。

namespace

使用 API#

文檔地址:https://testmail.app/docs/

testmail.app 支持直接參數查詢和 graphql 兩種查詢方式。
兩種方式都是直接獲取一個 json 格式的響應,裡面有郵件列表,每個郵件的內容,附件,元信息等等。

進行查詢之前要獲取一個 API Key,也是登錄之後在網頁上就能看到了,把 API Key 填到 headers 的 Authorization 即可。

這裡就不詳細說了,就是查 API 獲取想要的郵件的內容,比如我們使用 quartz 這個 tag 訂閱 QuartZ,那我們就只查這個 tag 的郵件即可。

js 實現的請求郵件:

const testmailNamespace = 'xxxxx';
const testmailToken = 'xxxxxxxxxxxxxxx';

class TestMail {
  static testmailApi = 'https://api.testmail.app/api/graphql';

  static async getMails(tag) {
    const query = `{
      inbox (
        namespace: "${testmailNamespace}"
        tag: "${tag}"
        limit: 99
      ) {
        emails {
          id
          subject
          html
          from
          timestamp
          downloadUrl
          attachments {
            cid
            downloadUrl
          }
        }
      }
    }`;

    const init = {
      method: 'POST',
      headers: {
        'content-type': 'application/json;charset=UTF-8',
        Authorization: `Bearer ${testmailToken}`,
        Accept: 'application/json',
      },

      body: JSON.stringify({
        operationName: null,
        query,
        variables: {},
      }),
    };
    return fetch(this.testmailApi, init);
  }
}

注意一下這裡用的是 GraphQL 的請求方式即可。之後只需要調用 await TestMail.getMails(tag) 即可獲取相應 tag 的郵件。

開始使用 Cloudflare Workers#

按照我們的想法,我們需要 Serverless 函數幫我們做一個中間層,根據 RSS 閱讀器的請求生成 RSS 內容。

所以我們就需要:

  1. 獲取用戶請求的 tag
  2. 請求 tag 對應的郵件列表
  3. 返回一個 XML 格式的網頁。

第一步就是解析用戶請求的地址,然後獲取出 tag 就行。
比如用戶請求的 mail2rss.test.workers.dev/tenjs,我們就要把 tenjs 提取出來即可。

Cloudflare Workers 中可以通過 event.request.url 拿到用戶請求的完整 URL,如果你使用的是其他的 Serverless 服務,如果是 koa-like 的話,一般都是也是通過 Request 拿到 url。

const {request} = event;
let url = new URL(request.url);
// parse tag
const requestTag = url.pathname.substring(1);

第二步請求郵件列表我們已經寫好了。

第三步就是生成一個 XML 格式的響應,再返回即可。

實現生成 XML#

這裡我們沒有使用模板引擎,直接拼接字符串就行了。

首先我們得知道 RSS 的文件格式,這裡參考了 RSSHub 的文件內容
然後寫一個 makeRss 函數,拼接生成字符串就行。

async function makeRss(emails, tag) {
  let items = emails.map((value) => {
    if (value.attachments.length > 0) {
      for (let i of value.attachments) {
        // update the image link
        value.html = value.html.replace(`cid:${i.cid}`, i.downloadUrl);
      }
    }
    return `<item>
    <title><![CDATA[${value.subject}]]></title>
    <description><![CDATA[${value.html}]]></description>
    <pubDate>${new Date(value.timestamp).toGMTString()}</pubDate>
    <guid isPermaLink="false">${value.id}</guid>
    <link>${value.downloadUrl}</link>
    <author><![CDATA[${value.from}]]></author>
</item>`;
  });

  return `<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
    <channel>
        <title><![CDATA[${tag}郵件訂閱]]></title>
        <link>${deployUrl + tag}</link>
        <atom:link href="${
          deployUrl + tag
        }" rel="self" type="application/rss+xml" />
        <description><![CDATA[${tag}郵件訂閱]]></description>
        <generator>mail2rss</generator>
        <webMaster>[email protected] (Artin)</webMaster>
        <language>zh-cn</language>
        <lastBuildDate>${new Date().toGMTString()}</lastBuildDate>
        <ttl>300</ttl>
        ${items.join('\n')}
    </channel>
</rss>`;
}

然後返回響應:

let responseXML = await makeRss(data.data.inbox.emails, requestTag);
let response = new Response(responseXML, {
  status: 200,
  headers: {
    'content-type': 'application/xml; charset=utf-8',
  },
});
response.headers.append('Cache-Control', 'max-age=600');
return response;

總結#

其實思路也很簡單,代碼也很簡單。

完整代碼見:https://github.com/bytemain/mail2rss

參考鏈接#

  1. DIYgod/RSSHub
    https://github.com/DIYgod/RSSHub
  2. testmail.app
    https://testmail.app/
  3. testmail.app Documentation
    https://testmail.app/docs/
  4. Cloudflare Workers
    https://workers.cloudflare.com/
載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。