<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>infiniteblack.dev</title><description>写点技术，也写点别的。</description><link>https://infiniteblack.dev/</link><language>zh-cn</language><item><title>RSSHub 的 Twitter 路由：TWITTER_AUTH_TOKEN 到底该填什么</title><link>https://infiniteblack.dev/posts/rsshub-twitter-auth-token/</link><guid isPermaLink="true">https://infiniteblack.dev/posts/rsshub-twitter-auth-token/</guid><description>关于这个配置项，网上至少有三种互相矛盾的说法。我把 RSSHub 的源码读了一遍，把格式钉死了。</description><pubDate>Fri, 25 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;自建 RSSHub 之后想做的第一件事，通常就是把 Twitter 加进来。然后你会遇到这个：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;error: Error in /twitter/user/someone: ConfigNotFoundError: Twitter API is not configured
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;于是去搜。搜完你会更迷茫——因为你会同时看到三种说法：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;把&lt;strong&gt;整条 cookie&lt;/strong&gt; 填进 &lt;code&gt;TWITTER_COOKIE&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;只需要填 &lt;code&gt;TWITTER_AUTH_TOKEN&lt;/code&gt;，值是 &lt;code&gt;auth_token&lt;/code&gt; cookie 的内容&lt;/li&gt;
&lt;li&gt;要填 &lt;code&gt;TWITTER_USERNAME&lt;/code&gt; + &lt;code&gt;TWITTER_PASSWORD&lt;/code&gt; + &lt;code&gt;TWITTER_AUTHENTICATION_SECRET&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;这三种说法分别对应三个不同时期的设计，其中两个已经死了。&lt;/strong&gt; 更麻烦的是文档没跟上，所以老文章至今还在传播。&lt;/p&gt;
&lt;p&gt;我最后是直接读源码把答案钉死的。&lt;/p&gt;
&lt;h2&gt;TL;DR&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;TWITTER_AUTH_TOKEN&lt;/code&gt; &lt;strong&gt;只填 &lt;code&gt;auth_token&lt;/code&gt; 这个 cookie 的「值」本身&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-env&quot;&gt;TWITTER_AUTH_TOKEN=0123abcd...（一长串十六进制）
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;❌ 不要写 &lt;code&gt;auth_token=0123abcd...&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;❌ 不要写 &lt;code&gt;auth_token=0123abcd...; ct0=efgh...&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;✅ 多个账号用&lt;strong&gt;英文逗号&lt;/strong&gt;分隔：&lt;code&gt;值1,值2,值3&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;ct0&lt;/code&gt; 不需要你提供。&lt;/p&gt;
&lt;h2&gt;为什么会同时存在三种说法&lt;/h2&gt;
&lt;p&gt;这套认证换过三代，而换的时候文档没改：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;代&lt;/th&gt;
&lt;th&gt;变量&lt;/th&gt;
&lt;th&gt;现状&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;一&lt;/td&gt;
&lt;td&gt;&lt;code&gt;TWITTER_COOKIE&lt;/code&gt; = &lt;strong&gt;整条 cookie 串&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;已改名，且语义变了&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;二&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;TWITTER_AUTH_TOKEN&lt;/code&gt;&lt;/strong&gt; = &lt;strong&gt;只填 auth_token 的值&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ 现行，官方推荐&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;三&lt;/td&gt;
&lt;td&gt;&lt;code&gt;TWITTER_USERNAME&lt;/code&gt; + &lt;code&gt;TWITTER_PASSWORD&lt;/code&gt; + &lt;code&gt;TWITTER_AUTHENTICATION_SECRET&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;❌ 已废弃&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;第三种的废弃有官方原文（&lt;code&gt;lib/routes/twitter/namespace.ts&lt;/code&gt;）：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This no longer works since mobile client attestation has been implemented in October 2025.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;而第一种到第二种的迁移，坑的来源有官方 issue 背书（&lt;a href=&quot;https://github.com/DIYgod/RSSHub/issues/16184&quot;&gt;#16184&lt;/a&gt;）：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;目前底层配置从 TWITTER_COOKIE 切换到 TWITTER_AUTH_TOKEN 后，没有修改相应的文档，无法了解如何配置 TWITTER_AUTH_TOKEN&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;所以：你看到的&amp;quot;整条 cookie&amp;quot;教程，是上一个时代的东西。&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;读源码：为什么只能填「值」&lt;/h2&gt;
&lt;p&gt;关键函数在 &lt;code&gt;lib/routes/twitter/api/web-api/utils.ts&lt;/code&gt;，叫 &lt;code&gt;token2Cookie&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const jar = new CookieJar();
await jar.setCookie(`auth_token=${token}`, &apos;https://x.com&apos;);  // ← 它自己拼 auth_token=
await ofetch(&apos;https://x.com&apos;, { dispatcher: agent });          // ← 带着这个 cookie 访问一次 x.com
const cookie = JSON.stringify(jar.serializeSync());            // ← x.com 回种的 cookie 被整体捕获
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;三行就把事情说完了：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;它自己拼 &lt;code&gt;auth_token=&lt;/code&gt; 前缀&lt;/strong&gt;。所以如果你填的是 &lt;code&gt;auth_token=xxx&lt;/code&gt;，最终 cookie 会变成 &lt;code&gt;auth_token=auth_token=xxx&lt;/code&gt;——一个无效值。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;它带着这个 cookie 去访问一次 &lt;code&gt;https://x.com&lt;/code&gt;&lt;/strong&gt;。X 会在响应里补种 &lt;code&gt;ct0&lt;/code&gt;、&lt;code&gt;gt&lt;/code&gt; 等 cookie，这些都被 &lt;code&gt;CookieJar&lt;/code&gt; 捕获。&lt;/li&gt;
&lt;li&gt;之后真正发请求时，CSRF 头是这么来的：&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;&apos;x-csrf-token&apos;: jsonCookie.ct0,     // ct0 来自上面那次访问的结果
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;所以 &lt;code&gt;ct0&lt;/code&gt; 是自动换来的，不需要你填。&lt;/strong&gt; 这也解释了为什么老教程让你填整条 cookie——在旧实现里确实要自己提供 ct0，新实现把它自动化了。&lt;/p&gt;
&lt;h2&gt;怎么拿到 auth_token&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;浏览器登录 x.com&lt;/li&gt;
&lt;li&gt;打开开发者工具 → &lt;strong&gt;Application&lt;/strong&gt;（Firefox 是 &lt;strong&gt;Storage&lt;/strong&gt;）→ Cookies → &lt;code&gt;https://x.com&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;找到名为 &lt;strong&gt;&lt;code&gt;auth_token&lt;/code&gt;&lt;/strong&gt; 的那一条 → 复制它的 &lt;strong&gt;Value&lt;/strong&gt;（HttpOnly，一长串）&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;只复制值，别把 &lt;code&gt;auth_token=&lt;/code&gt; 或整行粘进去。&lt;/p&gt;
&lt;h2&gt;你的报错是怎么产生的&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;lib/routes/twitter/api/index.ts&lt;/code&gt; 的选择逻辑：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const enableThirdPartyApi = config.twitter.thirdPartyApi;
const enableWebApi        = config.twitter.authToken;
const enableDeveloperApi  = config.twitter.consumerKey &amp;amp;&amp;amp; config.twitter.consumerSecret;

let api = {
    init: () =&amp;gt; { throw new ConfigNotFoundError(&apos;Twitter API is not configured&apos;); },
    // ...
};

if (enableThirdPartyApi || enableWebApi) {
    api = webApi;
} else if (enableDeveloperApi) {
    api = devApi;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;三条路都没配 → &lt;code&gt;init()&lt;/code&gt; 直接抛 &lt;code&gt;ConfigNotFoundError(&apos;Twitter API is not configured&apos;)&lt;/code&gt;。&lt;strong&gt;这就是那个报错的全部来源。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;顺带记一下另外两个可能的报错：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;No valid Twitter token found&lt;/code&gt; —— 配了 token，但都被&amp;quot;用掉&amp;quot;了（见下一节）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Twitter cookie for token xxx***** is not valid&lt;/code&gt; —— 拿 auth_token 换 ct0 失败，通常是到不了 x.com，或 token 已失效&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;坑一：token 会被&amp;quot;用掉&amp;quot;&lt;/h2&gt;
&lt;p&gt;RSSHub 里 token 的消耗逻辑是硬编码的：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;多个 token 轮流用（&lt;code&gt;authTokenIndex++ % length&lt;/code&gt;），并发时给每个 token 加锁&lt;/li&gt;
&lt;li&gt;遇到 &lt;strong&gt;401/403&lt;/strong&gt;：把该 token 从内存数组里 &lt;strong&gt;&lt;code&gt;splice&lt;/code&gt; 掉&lt;/strong&gt;，并锁 3600 秒——相当于&lt;strong&gt;当场报废&lt;/strong&gt;，重启才恢复&lt;/li&gt;
&lt;li&gt;遇到 &lt;strong&gt;429&lt;/strong&gt;（或返回空用户 &lt;code&gt;{&amp;quot;user&amp;quot;:{}}&lt;/code&gt;）：锁 2000 秒；若响应头有 &lt;code&gt;x-rate-limit-reset&lt;/code&gt;，按 reset 时间 ×2 锁&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;也就是说：&lt;strong&gt;Twitter 的限流会实实在在地吃掉你的 token&lt;/strong&gt;，单账号跑一阵子就没了。&lt;/p&gt;
&lt;p&gt;→ &lt;strong&gt;一次多填几个账号的 auth_token&lt;/strong&gt;（逗号分隔），这是稳定性上最有效的一招。&lt;/p&gt;
&lt;h2&gt;坑二：&lt;code&gt;.env&lt;/code&gt; 不会自动进容器&lt;/h2&gt;
&lt;p&gt;这个坑跟 Twitter 无关，但会让你以为&amp;quot;我明明配了&amp;quot;。&lt;/p&gt;
&lt;p&gt;用 Docker Compose 时，把变量写进 &lt;code&gt;.env&lt;/code&gt; &lt;strong&gt;只是给 compose 做变量插值&lt;/strong&gt;，容器里看不到。必须在 service 里显式声明：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;services:
  rsshub:
    environment:
      TWITTER_AUTH_TOKEN: ${TWITTER_AUTH_TOKEN:-}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;:-&lt;/code&gt; 是给个空默认值，避免没配时报错。&lt;/p&gt;
&lt;p&gt;验证真的传进去了：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;docker compose exec rsshub sh -c &apos;printenv TWITTER_AUTH_TOKEN | head -c 8; echo &amp;quot;...(前8位)&amp;quot;&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;只打前 8 位。&lt;/p&gt;
&lt;h2&gt;坑三：必须能访问 x.com&lt;/h2&gt;
&lt;p&gt;因为要 GET 一次 &lt;code&gt;https://x.com&lt;/code&gt; 去换 ct0，所以：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;网络必须能到 x.com——国内部署需要代理&lt;/li&gt;
&lt;li&gt;如果配了 &lt;code&gt;PROXY_URI&lt;/code&gt; + &lt;code&gt;PROXY_URL_REGEX&lt;/code&gt;，&lt;strong&gt;正则要能匹配 &lt;code&gt;x.com&lt;/code&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;⚠️ &lt;code&gt;PROXY_URL_REGEX&lt;/code&gt; 匹配的是&lt;strong&gt;完整 URL 字符串&lt;/strong&gt;（不是 hostname），而且要自己加锚定：&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-env&quot;&gt;PROXY_URI=http://你的代理:7890
PROXY_URL_REGEX=^https?://([^/]+\.)?(twitter\.com|x\.com|youtube\.com|instagram\.com|reddit\.com)([:/])
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;不锚定的正则会误伤——只写 &lt;code&gt;twitter\.com&lt;/code&gt; 会同时命中 &lt;code&gt;https://twitter.com.evil.com/&lt;/code&gt; 和 &lt;code&gt;https://example.com/?u=twitter.com&lt;/code&gt;。&lt;/p&gt;
&lt;h2&gt;安全：请用专用小号&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;auth_token&lt;/code&gt; &lt;strong&gt;等同于账号的登录凭据&lt;/strong&gt;，谁拿到谁就能以你的身份操作该账号。所以：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;用专用小号，不要用主号&lt;/li&gt;
&lt;li&gt;它会以环境变量形式存在于容器配置里&lt;/li&gt;
&lt;li&gt;浏览器里&amp;quot;退出登录&amp;quot;或改密码会让它失效&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;排错：把 debug 日志打开&lt;/h2&gt;
&lt;p&gt;日志级别由 &lt;code&gt;LOGGER_LEVEL&lt;/code&gt; 控制。设成 &lt;code&gt;debug&lt;/code&gt; 后会输出一批 &lt;code&gt;twitter debug:&lt;/code&gt; 日志，包含 token 状态、限流余量、cookie jar 内容：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;environment:
  LOGGER_LEVEL: debug
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;docker compose logs --tail=80 rsshub | grep -i twitter
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;⚠️ &lt;strong&gt;debug 日志会明文打印 &lt;code&gt;auth_token&lt;/code&gt;&lt;/strong&gt;（源码里 &lt;code&gt;twitter debug: twitter cookie for token ${auth.token}&lt;/code&gt; 那句没做掩码）。&lt;strong&gt;别把 debug 日志贴到公开的地方&lt;/strong&gt;，贴之前先打码。&lt;/p&gt;
&lt;h2&gt;替代方案&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;1. X 官方开发者 API&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-env&quot;&gt;TWITTER_CONSUMER_KEY=...
TWITTER_CONSUMER_SECRET=...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;官方措辞是 &lt;em&gt;Pay-Per-Use developer API&lt;/em&gt;，也就是&lt;strong&gt;要花钱&lt;/strong&gt;。可选再加 &lt;code&gt;TWITTER_ACCESS_TOKEN&lt;/code&gt; / &lt;code&gt;TWITTER_ACCESS_SECRET&lt;/code&gt; 获得用户上下文。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;/twitter/user&lt;/code&gt; 与 &lt;code&gt;/twitter/keyword&lt;/code&gt; 还支持 &lt;code&gt;?forceWebApi=0/1&lt;/code&gt; 强制走哪条路。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. 第三方兼容 API&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-env&quot;&gt;TWITTER_THIRD_PARTY_API=https://你的服务
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;只对这几个端点生效：&lt;code&gt;UserByScreenName&lt;/code&gt; / &lt;code&gt;UserByRestId&lt;/code&gt; / &lt;code&gt;UserTweets&lt;/code&gt; / &lt;code&gt;UserTweetsAndReplies&lt;/code&gt; / &lt;code&gt;ListLatestTweetsTimeline&lt;/code&gt; / &lt;code&gt;SearchTimeline&lt;/code&gt; / &lt;code&gt;UserMedia&lt;/code&gt;。实现方式是把 &lt;code&gt;${base}${gqlMap[endpoint]}&lt;/code&gt; 拼起来，所以对方必须实现 x.com 的 GraphQL 路径——现成的公开服务我没找到。&lt;/p&gt;
&lt;h2&gt;一个诚实的补充&lt;/h2&gt;
&lt;p&gt;RSSHub 的 issue 区有过这样的报告（&lt;a href=&quot;https://github.com/DIYgod/RSSHub/issues/19420&quot;&gt;#19420&lt;/a&gt;）：token 格式正确、就是浏览器里那一份，却只返回 200 空内容。该 issue 已关闭，之后代码也重构过（&lt;code&gt;token2Cookie&lt;/code&gt; + 自动换 ct0 这套看着就是为修这类问题）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;本文只在源码层面验证了&amp;quot;格式&amp;quot;，长期稳定性需要你自己观察。&lt;/strong&gt; 如果填对了还是空的，先开 debug 日志看 token 有没有被限流吃掉。&lt;/p&gt;
</content:encoded><category>rsshub</category><category>self-hosted</category><category>troubleshooting</category><category>docker</category></item><item><title>如何搭建一个极简博客</title><link>https://infiniteblack.dev/posts/how-to-build-a-minimal-blog/</link><guid isPermaLink="true">https://infiniteblack.dev/posts/how-to-build-a-minimal-blog/</guid><description>用中文文件名，也能得到一个干净、可分享的短链接。</description><pubDate>Sun, 10 Nov 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;注意这一篇的&lt;strong&gt;文件名是中文&lt;/strong&gt;，但我在 Properties 里写了 &lt;code&gt;slug&lt;/code&gt;，所以它的网址是：&lt;/p&gt;
&lt;p&gt;&lt;code&gt;/posts/how-to-build-a-minimal-blog/&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;而不是一长串 &lt;code&gt;%E5%A6%82%E4%BD%95...&lt;/code&gt; 这样的百分号编码。&lt;/p&gt;
&lt;h2&gt;几个要点&lt;/h2&gt;
&lt;p&gt;一个极简博客，其实只需要三样东西：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;单栏、窄行宽的布局&lt;/li&gt;
&lt;li&gt;一套系统字体&lt;/li&gt;
&lt;li&gt;几乎不用颜色，让内容说话&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;关于短链接&lt;/h2&gt;
&lt;p&gt;分享链接是否短，取决于「域名 + 路径」：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;域名越短越好（比如 &lt;code&gt;x.pages.dev&lt;/code&gt;，或绑定自己的短域名）&lt;/li&gt;
&lt;li&gt;路径用短英文 slug（就是这一篇的做法）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;两者加起来，就是一个干净好分享的短链。&lt;/p&gt;
</content:encoded><category>web</category><category>tools</category></item><item><title>你好，世界</title><link>https://infiniteblack.dev/posts/afa27b/</link><guid isPermaLink="true">https://infiniteblack.dev/posts/afa27b/</guid><description>我的第一篇博客，聊聊这个站点是怎么搭起来的。</description><pubDate>Sun, 03 Nov 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;这是我的第一篇博客。这个站点用 &lt;a href=&quot;https://astro.build&quot;&gt;Astro&lt;/a&gt; 构建，文章用 Markdown 写在 Obsidian 里，每次推送到 GitHub 就会自动发布到 Cloudflare Pages。&lt;/p&gt;
&lt;h2&gt;为什么用 Markdown&lt;/h2&gt;
&lt;p&gt;在 Obsidian 里写笔记和写博客其实是同一件事：都是 Markdown，都带 YAML 属性。所以我只需要把笔记放到这个仓库里，剩下的交给构建流程。&lt;/p&gt;
&lt;h2&gt;一个代码块&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const hello = (name) =&amp;gt; `你好，${name}`;
console.log(hello(&apos;世界&apos;));
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;小结&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;内容用 Markdown 写&lt;/li&gt;
&lt;li&gt;标签用 Obsidian 的 &lt;code&gt;tags&lt;/code&gt; 属性&lt;/li&gt;
&lt;li&gt;发布用 Git + Cloudflare Pages&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;写作是为了思考，而不是为了记录。&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded><category>web</category><category>tutorial</category></item></channel></rss>