关于 Diygod

RSSHub、xLog的作者。

RSS 地址: https://diygod.cc/feed

请复制 RSS 到你的阅读器,或快速订阅到 :

Diygod RSS 预览

一个六岁开源项目的崩溃与新生

2024-03-11 03:29:05

我有一个维护了六年的开源项目 —— RSSHub,它正在面临崩溃 背景# 表面上,它有接近 30k Stars、900 多 Contributors、每月 3 亿多次请求和数不清的用户、每月几十刀的赞助、有源源不断的 issue 和 pr、代码几乎每天更新,非常健康和充满活力,但在不可见的地方,持续数年高昂的维护时间成本、每月一千多刀的服务器费用、每天重复繁琐且逐渐积累的维护工作,都让它在崩溃的边缘反复横跳 项目是六年前开发的,不少当时以 Next Generation 为口号的时髦 Node.js 技术栈和依赖库已经成为时代眼泪,现在看非常陈旧,很多现在流行的新技术没法应用,比如 JSX、TypeScript、Serverless 等;它的架构也非常不合理,每个路由的信息散落在多个地方,开发或者变更一个路由需要多处修改,一个地方去注册路由,一个地方去编写路由脚本,一个地方去编写 Radar 规则,一个地方去编写文档...... 这增加了很多工作量,也很容易出错,之前路由少的时候并不是个问题,但现在已经变得难以忍受 在如此糟糕的基础架构下能保持现状已经是竭尽全力,开发新功能更是无本之木,只会增加以后更新的难度,所以我有时候脑子蹦出的新奇想法也很难实现 要解决这些问题,唯一的办法是使用现代化的框架和新设计的架构来重写内核,但随着路由越来越多,改造成本也越来越高,每个基础改动可能都需要多达数月的工作量,所以虽然问题越来越严重,但秉承着又不是不能用的原则一拖再拖 但这又是不得不做的事情,所以我抽空花了几个月的时间重新设计和重写了它 技术栈更新# koa -> Hono# 第一步也是最基础和难度最大的是换掉之前使用的 Web 框架 koa,作为六年前流行的下一代 Web 框架,作者早就弃坑了,调研之后决定换用对 JSX、TypeScript、Serverless 支持最好的 Hono 它们的 API 差异很大,需要重写所有中间件和替换所有路由中使用的 koa API 主要改动: https://github.com/DIYgod/RSSHub/pull/14295 Hono 作者也很喜欢这个改造 JavaScript -> TypeScript# 改用 TypeScript 可以避免很多类型问题和低级错误,最重要的是可以保证数百名贡献者保持一致难以出错和后续贡献的路由代码质量不至于太糟糕 主要改动: https://github.com/DIYgod/RSSHub/pull/14295 https://github.com/DIYgod/RSSHub/pull/14632 CommonJS -> ESM# ESM 是几年前一些 Node.js 核心开发者强推的规范,它有一些优点,但最多的是与之前 CommonJS 不兼容带来的生态割裂和功能简化带来的诟病 经过这几年的发展,现在可以说大部分场景勉强可用了,tsx 也为 CommonJS 和 ESM 混用的场景提供了支持 虽然已经尽了最大努力,但还是有一些 CommonJS 代码暂时难以迁移,导致现在只能使用 tsx 运行,与一些 Serverless 比如 Vercel 没法兼容,但也有机会后续慢慢解决 主要改动: https://github.com/DIYgod/RSSHub/pull/14619 https://github.com/DIYgod/RSSHub/pull/14691 https://github.com/DIYgod/RSSHub/pull/14632 art-template -> JSX# art-template 是一个支持 koa 的模板引擎,记得六年前还有一个更流行的模板引擎,但是不记得名字了,选用 art-template 是因为那个更流行的我当时没看懂,这个很简单 Hono 自带了 JSX 支持,JSX 就不用多介绍了,根正苗红的 JavaScript 的语法扩展,等同于用 React 主要改动: https://github.com/DIYgod/RSSHub/commit/3bfdf9427cb8cf063cf7d231ec621278495f5a44 https://github.com/DIYgod/RSSHub/commit/94cf0742afa8bf18510ad9ded9b76dcd2ad52c90 Jest -> Vitest# Jest 是曾经流行的测试框架,但是在 ESM 时代到来之后就越来越不行了,对 ESM 的支持一直是实现性「experimental support」,现在更流行的是 Vitest 了 主要改动: https://github.com/DIYgod/RSSHub/commit/38e42156a0622a2cd09f328d2d60623813b8df28 Got -> ?# 目前使用的 Got 也已经是不积极维护的状态了,也没有找到好的替代品,后续也许会换成原生 Fetch 或者自封装的 Fetch,还没有动手 新路由标准# 我自己能力还是不够的,在与社区开发者们讨论的过程中学习和改进了很多,过程很有意思:https://github.com/DIYgod/RSSHub/issues/14685 主要改动: https://github.com/DIYgod/RSSHub/pull/14718 历史# 新标准主要为了解决路由信息过于分散的问题,这次应该算第三版 第一版来自 RSSHub 开发阶段,当时没有预见到路由数量会有这么多,所以几乎没什么规划,所有路由在同一个文件中注册,然后再去增加路由脚本和文档,后来这个文件越来越大,很容易冲突,另外所有路由脚本都会在启动阶段被加载,程序性能越来越差 第二版来自 NeverBehave 维护的时期,引入了命名空间,切割了 router.js、radar.js,同命名空间的路由集中在了一个同文件夹中和一个或多个 Markdown 文档中,还实现了懒加载,极大提升了可维护性和性能,但还是会分散在多个文件中,不同文件的信息也容易出现不一致导致错误 现在# 本次把路由文件分为了两类,namespace.ts 和任意名字的路由文件 namespace.ts 会通过导出名为 namespace 的对象来定义命名空间的信息 Copyimport type { Namespace } from '@/types'; export const namespace: Namespace = { // ... }; namespace 包含的字段通过 TypeScript 限制为 Copyinterface Namespace { name: string; url?: string; categories?: string[]; description?: string; } 这些信息会经过编译后被文档和 RSSHub Radar 利用 路由文件会通过导出名为 route 的对象来定义路由的信息 Copyimport { Route } from '@/types'; export const route: Route = { // ... }; route 包含的字段通过 TypeScript 限制为 Copyinterface Route { path: string | string[]; name: string; url?: string; maintainers: string[]; handler: (ctx: Context) => Promise<Data> | Data; example: string; parameters?: Record<string, string>; description?: string; categories?: string[]; features: { requireConfig?: string[] | false; requirePuppeteer?: boolean; antiCrawler?: boolean; supportRadar?: boolean; supportBT?: boolean; supportPodcast?: boolean; supportScihub?: boolean; }; radar?: { source: string[]; target?: string; }; } 之前 route.js mantainer.js radar.js 和文档的信息都被集中在这一个文件中,减少了多处定义也减少了出错的可能 实现# 实现逻辑就是开发环境通过遍历整个 route 文件夹,找到所有 namespace.ts 和路由文件,读取信息,加载路由,在生成环境使用提前编译好的路径列表来避免遍历和不必要的加载过程,代码在:https://github.com/DIYgod/RSSHub/blob/master/lib/registry.ts 文档也是通过遍历 route 文件夹,找到所有需要的信息然后合成一系列的 Markdown 文件,不再需要手动维护,代码在:https://github.com/DIYgod/RSSHub/blob/master/scripts/workflow/build-routes.ts 当然使用之前路由标准开发的路由都需要迁移到新标准而不是直接放弃掉,已经通过脚本批量抓取整理信息后做了替换,但特别是文档比较混乱也有很多错误,所以抓取的信息也有很多错误,只能在后续逐渐人工修改了 未来# 通过这一系列改进,RSSHub 终于能够扔掉历史包袱,安心开发新功能了,这里列出我积累的一些想法抛砖引玉: 既然 RSSHub 是一个数据集合,用途不一定只有 RSS,JSON 输出功能可以做一些增强,作为通用的 RESTful API 来使用,比如可以提供获取下一页接口或者输出类似 Twitter 关注数的非 feed 数据 用户系统和用户自定义配置,生成自己的私有订阅地址 #14706 路由错误通知和健康度检测 #14712 与 RSS3 节点的联动和加密货币收益共享 https://twitter.com/rss3_/status/1731822029199094012 AI 翻译和摘要 更详细的实例数据分析及反向推导自动推荐的 Radar 规则 与本地浏览器或客户端绑定的 RSSHub 实例,有希望真正解决反爬难题 ... 最后,开源是一件很昂贵的事情,RSSHub 能活到现在离不开这些开发者的帮助 以及这些赞助的好心人 如果 RSSHub 正在帮助你,也希望你可以积极参与进来,为信息自由的未来贡献一份自己的微小力量

和帕鲁生活在一起的两周

2024-02-19 03:20:33

宝可梦幻想# 哪个小男孩没幻想过生活在这样一个充满宝可梦伙伴的世界,它们能听懂且愿意倾听我们说话,有着不同的特点和强大的技能,而且都很可爱,我们可以跟它们睡觉吃饭、并肩作战探索世界,永远不再孤独,不管是谁都可以找到最适合自己的宝可梦 这一切早就可以发生,但这一切终究没有发生,宝可梦复杂的版权问题和躺平在功劳簿上的版权公司 Game Freak 让我们的幻想一直也仅仅是幻想 当然也有人说最近的几个新作阿尔宙斯、朱紫比之前好多了,“虽然好多了,但只是臭味稀薄一点的狗屎罢了”,剑盾、阿尔宙斯、朱紫、Pokemon Sleep 游戏时长并不短的宝可梦玩家 DIYgod 义愤填膺地说道 帕鲁实现# 但只要幻想一直存在,变成现实也只是时间问题,横空出世的幻兽帕鲁给所有宝可梦粉丝带来了一个一本满足的平行幻想世界,终于苦于没有 Windows 的我也专门买了一台 ROG Ally 来沉迷其中 游戏中与帕鲁的互动是前无仅有的,衣食住行战斗探索的过程都与帕鲁深度融入,真的是和帕鲁生活在一起 从刚落地进入初始台地互动就开始了,我们的第一个落脚点不是自己造的,而是跟捣蛋猫一起造的 再到后来帕鲁包办我们的一切后勤,我们的每一顿大餐,都需要经历播种、浇水、收割、搬运、存储、烹饪,分别由有着不同特长和分工的帕鲁来完成,会制冷的企丸丸去运行冰箱,会喷火的燎火鹿去烹饪 在我们外出探索时,过河骑滑水蛇,赶路骑云海鹿,翻山越岭骑烽歌龙 在我们战斗时,举着火绒狐充当、喷火器,骑着暴电熊扫射机关枪,甚至虽然不太人道,可以把电棘鼠当作手榴弹扔出去,同时据点里的焰煌在帮我们炼钢造枪,阿努比斯在帮我们手搓子弹 从徒手撸树到机关枪横着走,从无人问津到人满为患,在不知不觉的建造中我已经在帕鲁世界中倾注了太多的感情 从饿着肚子的第一天 到现在 一些额外的探索# 数据展示# 毕竟是单机游戏,数据都在本地,可以很方便地做处理 有一个开源项目可以把存档文件解析成 JSON 方便第三方开发和展示 https://github.com/cheahjs/palworld-save-tools 还有一个开源项目可以把 JSON 再生成一个前端页面 https://github.com/zaigie/palworld-server-tool 于是我把存档同步到 NAS 上,再在 NAS 上部署一个展示页面,就可以很方便地查看我的帕鲁们了 https://pal.diygod.me 模组# 帕鲁有很丰富的社区模组,比如把皮皮鸡变成坤或者把帕鲁变成宝可梦什么的,还有一些可以在不影响游戏平衡的前提下极大提升游戏体验 我用的主要来自一个模组社区 Nexus Mods,他们有一个很方便的模组安装和管理工具 Vortex Mod Manager,以下是我正在使用的模组 Basic MiniMap:增加一个不知道为什么官方没做的感觉很基础的小地图功能,但是记得到设置里把旋转关掉 Better Night Light:晚上太眼瞎了,啥都看不见,像得了夜盲症一样,用这个模组可以变亮一些,还改了一个绝美的星空 Golden Statue of Power:把力量石像变得金灿灿的更有质感 Play as Zoe 和 Hide All Human Hair All female Head All HeadEquip:隐藏掉原有的装扮,自己变成老婆佐伊 Paimon Replace DaeDream:把寐魔变成派蒙 Pal Analyzer:捕捉前就可以看到帕鲁的属性和特质,掌机需要到设置里改下显示为 always on Stuck Pal Rescuer:救命模组,据点里的帕鲁经常被 bug 卡住然后饿死,这个模组可以自动检测帕鲁是否卡住并重置 FSR3 支持:官方不支持 A 卡的帧生成,用上这个 Roy Ally 可以在中等画质从 40 多帧提升到 70 多帧

Twitter 对开源项目发起 DDoS 攻击

2024-01-28 18:24:53

背景# Twitter 被马斯克收购后,从去年 8 月开始,他们对开源第三方集成和第三方客户端进行了一系列明里暗里的打压和攻击,这样做是为了阻止用户通过非官方客户端访问和使用 Twitter,来增加公司的广告和会员营收 而开源社区中以 Nitter 和 RSSHub 为代表的开源项目并没有放弃向信息自由的努力,通过众多聪明的开源开发者们想出来的一个个奇妙操作(issue),在一轮轮封锁和反封锁的对抗中短暂占了上风,其中最流行的做法是通过 Android 客户端使用的接口功能生成临时账号(细节) 经过# 在两天前(1 月 26 日),许多 Nitter 实例的运行者和开发者报告称,他们正在使用的接口已被封锁。与此同时,他们的实例也开始遭受报复性 DDoS 攻击 起初,我并没有太在意这件事情。毕竟,谁会相信 Twitter 官方会做出如此令人不耻且自降身段的 DDoS 行为呢?我对此深感怀疑 然而,昨天当我打开 RSSHub 的 GitHub 仓库时,却意外地发现了以下内容 最近一个月的请求数达到了 4.5 亿,比正常水平高出 50%(正常水平仅为 3 亿多) 然后登录 Cloudflare 查看日志 自从 26 日 0 点(当天官方接口被封锁和 Nitter 遭受 DDoS 攻击)以来,RSSHub 也一直遭受大规模的 DDoS 攻击。最近两天,请求量是平时的 170 多倍,每秒约 1 千次请求 尽管数量看起来可怕,但 Cloudflare 出色的缓存功能已经成功缓存了超过 99% 的 DDoS 请求,甚至没有触发报警 RSSHub 的负载均衡和自动扩容功能非常完善,没有受到太大的压力 🤣 就这样一直没有发现 进一步分析发现,所有请求都来自 IP 地址为 139.255.221.98 的设备。这些请求都是针对 /twitter/keyword 路由,并且后面跟着一串不同且无意义的参数 我很清楚为什么只针对 keyword 路由,尽管代码中没有明确表达,但根据我的使用经验,此路由使用的搜索接口受到最严格的访问频率限制,通过攻击该路由可以实现最大化效果。由此可以推断出 DDoS 攻击者也对 Twitter 的接口非常熟悉 虽然无法直接证明是官方人员所为,但各种无法解释的 “巧合” 已经清楚地表明了事情的真相,马斯克的简单粗暴行事风格也正在深刻影响这家公司 影响# 进一步封锁 API 和进行 DDoS 攻击这两个操作可以说非常有效 Nitter 开发者 zedeus 表示 Nitter 已死 Twitter Monitor 开发者 MANKA 表示不愿意再浪费时间 nitter-status 开发者更是直接放出了告别页面 看起来就到此为止了吗?不,这远非终点。自由无法被阻挡,我们还有很多事情可以做 29 日 更新:RSSHub 已经恢复

如何优雅编译一个 Markdown 文档

2024-01-18 20:50:37

Markdown 是一种广泛使用的轻量级标记语言,允许人们使用易读易写的纯文本格式编写文档,也是 xLog 主要使用的文章格式,本文就以 xLog Flavored Markdown 为例来说明如何优雅地解析一个 Markdown 文档 架构# 解析过程可以用这样一个架构来表示: flowchart TB subgraph input Markdown end subgraph unified subgraph remark Markdown:::inputClass --string--> remark-parse:::remarkClass --mdast--> remarkPlugins[remark plugins]:::remarkClass remarkPlugins --mdast--> remark-rehype:::remarkClass & mdast-util-toc:::remarkClass end subgraph rehype remark-rehype --hast--> rehypePlugins[rehype plugins]:::rehypeClass rehypePlugins --hast--> hast-util-to-text:::rehypeClass & hast-util-to-html:::rehypeClass & hast-util-to-jsx-runtime:::rehypeClass end rehypePlugins --hast--> unist-util-visit:::rehypeClass end subgraph output mdast-util-toc --tocResult--> TOC:::inputClass hast-util-to-text --string--> plainText[Plain Text]:::inputClass hast-util-to-html --string--> HTML:::inputClass hast-util-to-jsx-runtime --JSX.Element--> ReactElement[React Element]:::inputClass unist-util-visit --custom--> Metadata:::inputClass end style input fill:#bbf7d0,stroke:#4ade80,color:#15803d classDef inputClass fill:#22c55e,stroke:#16a34a style output fill:#bbf7d0,stroke:#4ade80,color:#15803d style unified fill:#bfdbfe,stroke:#60a5fa,color:#1d4ed8 style remark fill:#fecaca,stroke:#f87171,color:#b91c1c classDef remarkClass fill:#f87171,stroke:#dc2626 style rehype fill:#fef08a,stroke:##facc15,color:#a16207 classDef rehypeClass fill:#facc15,stroke:#ca8a04 关键概念: unified:通过语法树和插件来解析、检查、转换和序列化内容的库 remark:unified 的生态项目之一,由插件驱动的 Markdown 处理库 rehype:unified 的生态项目之一,由插件驱动的 HTML 处理库 mdast:remark 使用的用于表示 Markdown 的抽象语法树规范 hast:rehype 使用的用于表示 HTML 的抽象语法树规范 简单来说就是把 Markdown 文档交给一个 unified 生态的解析器解析成 unified 可识别的语法树,再通过一系列 unified 生态的插件转换为需要的内容,再通过一系列 unified 生态的工具库输出为需要的格式,下面就从 解析、转换、输出 这三个步骤来分别说明 解析 Parse# flowchart TB subgraph input Markdown end subgraph unified subgraph remark Markdown:::inputClass --string--> remark-parse:::remarkClass end end style input fill:#bbf7d0,stroke:#4ade80,color:#15803d classDef inputClass fill:#22c55e,stroke:#16a34a style unified fill:#bfdbfe,stroke:#60a5fa,color:#1d4ed8 style remark fill:#fecaca,stroke:#f87171,color:#b91c1c classDef remarkClass fill:#f87171,stroke:#dc2626 无论输入是 Markdown、HTML 还是纯文本,都需要将其解析为可操作的格式。这种格式被称为语法树。规范(例如 mdast)定义了这样一个语法树的外观。处理器(如 mdast 的 remark)负责创建它们。 最简单的一步,我们需要解析的是 Markdown,所以这里就应该使用 remark-parse 来把 Markdown 文档编译成 mdast 格式的语法树 对应 xLog Flavored Markdown 中的 Copyconst processor = unified().use(remarkParse) const file = new VFile(content) const mdastTree = processor.parse(file) 转换 Transform# flowchart TB subgraph remark remark-parse:::remarkClass --mdast--> remarkPlugins[remark plugins]:::remarkClass remarkPlugins --mdast--> remark-rehype:::remarkClass end subgraph rehype remark-rehype --hast--> rehypePlugins[rehype plugins]:::rehypeClass end style remark fill:#fecaca,stroke:#f87171,color:#b91c1c classDef remarkClass fill:#f87171,stroke:#dc2626 style rehype fill:#fef08a,stroke:##facc15,color:#a16207 classDef rehypeClass fill:#facc15,stroke:#ca8a04 这就是魔法发生的地方。用户组合插件以及它们运行的顺序。插件在此阶段插入并转换和检查它们获得的格式。 这一步最为关键,不仅包含了从 Markdown 到 HTML 的转换,还包含我们想在编译过程中夹带的私货,比如增加一些非标准的语法糖、清理 HTML 防止 XSS、增加语法高亮、嵌入自定义组件等 unified 的插件非常多,更新也比较及时,基本需求几乎都能满足,对于不能满足的特定需求,自己编写转换脚本也很容易实现 里面有一个特殊的插件是 remark-rehype,它会把 mdast 语法树转为 hast 语法树,所以在它之前必须使用处理 Markdown 的 remark 插件,在它之后必须使用处理 HTML 的 rehype 插件 xLog Flavored Markdown 中就加入了非常多的转换插件 Copyconst processor = unified() .use(remarkParse) .use(remarkGithubAlerts) .use(remarkBreaks) .use(remarkFrontmatter, ["yaml"]) .use(remarkGfm, { singleTilde: false, }) .use(remarkDirective) .use(remarkDirectiveRehype) .use(remarkCalloutDirectives) .use(remarkYoutube) .use(remarkMath, { singleDollarTextMath: false, }) .use(remarkPangu) .use(emoji) .use(remarkRehype, { allowDangerousHtml: true }) .use(rehypeRaw) .use(rehypeIpfs) .use(rehypeSlug) .use(rehypeAutolinkHeadings, { behavior: "append", properties: { className: "xlog-anchor", ariaHidden: true, tabIndex: -1, }, content(node) { return [ { type: "text", value: "#", }, ] }, }) .use(rehypeSanitize, strictMode ? undefined : sanitizeScheme) .use(rehypeTable) .use(rehypeExternalLink) .use(rehypeMermaid) .use(rehypeWrapCode) .use(rehypeInferDescriptionMeta) .use(rehypeEmbed, { transformers, }) .use(rehypeRemoveH1) .use(rehypePrism, { ignoreMissing: true, showLineNumbers: true, }) .use(rehypeKatex, { strict: false, }) .use(rehypeMention) const hastTree = pipeline.runSync(mdastTree, file) 下面介绍部分用到的插件 remarkGithubAlerts:增加 GitHub 风格的 Alerts 语法,演示 remarkBreaks:不再需要空一行才能被识别为新的自然段 remarkFrontmatter:支持前置内容(YAML、TOML 等) remarkGfm:支持非标准的 GitHub 在原版 Markdown 语法上扩展的一系列语法(但其实这系列语法已经被非常广泛使用,成为了事实意义上的标准) remarkDirective remarkDirectiveRehyp:支持非标准的 Markdown 通用指令提案 remarkMath rehypeKatex:支持复杂的数学公式,演示 rehypeRaw:支持 Markdown 中夹杂的自定义 HTML rehypeIpfs:自定义插件,为图片、音频、视频支持 ipfs:// 协议的地址 rehypeSlug:为标题添加 id rehypeAutolinkHeadings:为标题添加指向自身的链接 rel = "noopener noreferrer" rehypeSanitize:清理 HTML,用于确保 HTML 安全避免 XSS 攻击 rehypeExternalLink:自定义插件,给外部链接添加 target="_blank" 和 rel="noopener noreferrer" rehypeMermaid:自定义插件,渲染绘图和制表工具 Mermaid,本文的架构图就是通过 Mermaid 渲染的 rehypeInferDescriptionMeta:用于自动生成文档的描述 rehypeEmbed:自定义插件,用于根据链接自动嵌入 YouTube、Twitter、GitHub 等卡片 rehypeRemoveH1:自定义插件,用于把 h1 转为 h2 rehypePrism:支持语法高亮 rehypeMention:自定义插件,支持 @DIYgod 这样艾特其他 xLog 用户 输出 Stringify# flowchart TB subgraph unified subgraph remark remarkPlugins[remark plugins]:::remarkClass --mdast--> mdast-util-toc:::remarkClass end subgraph rehype rehypePlugins[rehype plugins]:::rehypeClass rehypePlugins --hast--> hast-util-to-text:::rehypeClass & hast-util-to-html:::rehypeClass & hast-util-to-jsx-runtime:::rehypeClass end rehypePlugins --hast--> unist-util-visit:::rehypeClass end subgraph output mdast-util-toc --tocResult--> TOC:::inputClass hast-util-to-text --string--> plainText[Plain Text]:::inputClass hast-util-to-html --string--> HTML:::inputClass hast-util-to-jsx-runtime --JSX.Element--> ReactElement[React Element]:::inputClass unist-util-visit --custom--> Metadata:::inputClass end classDef inputClass fill:#22c55e,stroke:#16a34a style output fill:#bbf7d0,stroke:#4ade80,color:#15803d style unified fill:#bfdbfe,stroke:#60a5fa,color:#1d4ed8 style remark fill:#fecaca,stroke:#f87171,color:#b91c1c classDef remarkClass fill:#f87171,stroke:#dc2626 style rehype fill:#fef08a,stroke:##facc15,color:#a16207 classDef rehypeClass fill:#facc15,stroke:#ca8a04 最后一步是将(调整后的)格式转换为 Markdown、HTML 或纯文本(可能与输入格式不同!) unified 的工具库也很多,可以输出各种我们需要的格式 比如 xLog 需要在文章右侧展示自动生成的目录、需要输出纯文本来计算预估阅读时间和生成 AI 摘要、需要生成 HTML 来给 RSS 使用、需要生成 React Element 来渲染到页面、需要提取文章的图片和描述来展示文章卡片,就分别使用了 mdast-util-toc、hast-util-to-text、hast-util-to-html、hast-util-to-jsx-runtime、unist-util-visit 这些工具 对应 xLog Flavored Markdown 中的 Copy{ toToc: () => mdastTree && toc(mdastTree, { tight: true, ordered: true, }), toHTML: () => hastTree && toHtml(hastTree), toElement: () => hastTree && toJsxRuntime(hastTree, { Fragment, components: { // @ts-expect-error img: AdvancedImage, mention: Mention, mermaid: Mermaid, // @ts-expect-error audio: APlayer, // @ts-expect-error video: DPlayer, tweet: Tweet, "github-repo": GithubRepo, "xlog-post": XLogPost, // @ts-expect-error style: Style, }, ignoreInvalidStyle: true, jsx, jsxs, passNode: true, }), toMetadata: () => { let metadata = { frontMatter: undefined, images: [], audio: undefined, excerpt: undefined, } as { frontMatter?: Record<string, any> images: string[] audio?: string excerpt?: string } metadata.excerpt = file.data.meta?.description || undefined if (mdastTree) { visit(mdastTree, (node, index, parent) => { if (node.type === "yaml") { metadata.frontMatter = jsYaml.load(node.value) as Record< string, any > } }) } if (hastTree) { visit(hastTree, (node, index, parent) => { if (node.type === "element") { if ( node.tagName === "img" && typeof node.properties.src === "string" ) { metadata.images.push(node.properties.src) } if (node.tagName === "audio") { if (typeof node.properties.cover === "string") { metadata.images.push(node.properties.cover) } if (!metadata.audio && typeof node.properties.src === "string") { metadata.audio = node.properties.src } } } }) } return metadata }, } 这样我们就优雅地从原始 Markdown 文档开始,获得了我们需要的各种格式的输出 除此之外,我们还能利用解析出的 unified 语法树来编写一个可以左右同步滚动和实时预览的 Markdown 编辑器,可以参考 xLog 的双栏 Markdown 编辑器(代码),有机会我们下次再聊

2023 平稳过渡

2024-01-08 22:23:18

</2023> <2024> 发小觉得今年经历了巨大的变化,从苏格兰搬到新加坡,从未婚到已婚,从一家两口变成了一家三口,但我觉得都在预料之中,所以感觉还算很平稳 身边的物品最能反映一个人的状态,所以还是跟 2022 一样从年度最佳开始 🏆 年度最佳# 🍿 影视# 看过的 28 部动漫和 12 部剧集和 8 部电影:https://movie.douban.com/people/62759792/collect?mode=list 而去年是 19 部动漫和 18 部剧集和 6 部电影,二次元含量显著上升了 DIY 年度番剧: 《鬼灭之刃 锻刀村篇》 .double-column { display: flex; gap: 10px; } .double-column > * { flex: 1; min-width: 0; } 尽管有数集节奏过缓,然而该剧的燃点皆燃,泪点亦催人泪目,尤以最后以为会是虐人的剧情却华丽逆转为甜蜜结局,令人心潮澎湃,难以自抑 去年最佳是《鬼灭之刃 游郭篇》,今年是《鬼灭之刃 锻刀村篇》,已经可以预料到了明年是《鬼灭之刃 柱训练篇》,希望可以保持下去 除此之外还有《天国大魔境》、《间谍过家家》、《【我推的孩子】》(第一集)、《为美好的世界献上爆焰!》也非常喜欢 DIY 年度剧集: 《最后生还者 第一季》 非常还原游戏,又胜于游戏,主角也非常契合角色,完美的一季,HBO 太强了 📚 图书# 今年看过的 22 本书: 《被讨厌的勇气》 《长安的荔枝》 《那些古怪又让人忧心的问题》 《上帝掷骰子吗》 《人类灭绝》 《来自新世界》 (两本) 《莱博维茨的赞歌》 《赡养人类》 《朝闻道》 《埃隆・马斯克传》 《巨人的陨落》(三本) 《世界的凛冬》(三本) 《翦商》(在读) 《自私的基因》(在读) 《一想到还有 95% 的问题留给人类,我就放心了》(在读) 《猫咪家庭医学大百科》(在读) 《未来简史》(在读) 去年是 9 本,没想到今年不知不觉看了这么多,大部分是旅游和 Boox Leaf 3 的功劳 DIY 年度图书:《人类灭绝》 差点被奇怪的标题和封面错过,是我看过最悬疑、血腥、脑洞的一本书,作者从三条看似不相关的线分别展开,最后再完美融合,揭露一个最大的阴谋 如果有一天智人不再是地球最聪明的物种,智人一定会毫不犹豫暴露出残忍丛林天性,不一定是因为书里假设的超人类新物种的出现,AI 的技术爆炸也许会让这一天马上到来 非常期待有一天被拍成电视剧 排名第二的是 《上帝掷骰子吗》 读到了很多难以置信的物理知识和猜想,比如空间和时间无法无限分割下去,它们不是连续的;所有观测者死掉的世界都没意义,所以观测者只会一直活下来,无法自杀,无法死亡,所有人都会量子永生;世界是不确定的,世界的底色不是因果律而是概率,上帝是掷骰子的。简直比科幻还要魔幻 排名第三的是《巨人的陨落》 世纪三部曲中的第一部,是那种开头比较难读下去但是一旦投入进去就无法自拔的书,学到了很多欧洲历史看到了时代变迁 🕹️ 好物# DIY 年度好物: Boox Leaf 3 第一次用电纸书,续航足够长,刷新速度也很快,内置墨水屏版微信读书,让我多看了很多书,缺点就是长时间拿着有点硌小拇指,还好买了 Randy 推荐的手机气囊支架解决了 其他还有一些值得推荐的小东西 Followcat 猫咪项圈 可以配套一个 AirTag 壳装入 AirTag 来定位猫咪,不仅可以预防出门跑丢,在家里不知道藏在哪里的时候也更好找了 博士眼镜 线下配眼镜要几千块,一直让我觉得眼镜是个很珍贵的物品,平时需要小心翼翼地保护,但发现差不多的蔡司镜片加镜框线上只要 200 多,差价很夸张,实际使用也感觉不出什么区别 索尼 ZV1 M2 Twitter 从第一代 ZV1 换到了第二代,虽然画质没什么升级,但解决了很多痛点,使用体验好了非常多,包括人类可以使用的新版菜单、更适合 vlog 的 18mm 焦距、内置指向性麦克风、视频拍照切换实体按键、USB-C 等 👩🏻‍💻 应用# DIY 年度应用:Arc 竖向标签栏、工作区用了就回不去,而且并不是简单地把标签栏竖过来,很多交互细节还是挺用心的 另外分享一些自己在使用的喜欢的应用 邮件客户端:Mimestream 浏览器:Arc 命令行:Warp Docker 管理:OrbStack 时间追踪:Toggl Track 笔记:Obsidian RSS 阅读:RSSHub、Reeder、Miniflux、RSS to Telegram Bot、RSSHub Radar 待办事项:TickTick 图片管理:Eagle 记账:MoneyWiz 影音:Plex、Alist、AutoBangumi、UnblockNeteaseMusic、Xiaoya 自动化:n8n 网站统计:Umami 加密钱包:OneKey、MetaMask 翻译:Bob、沉浸式翻译 广告拦截:AdGuard Home、AdGuard for Mac 截图:Snip 休息提醒:BreakTimer 足迹:世界迷雾 睡眠记录:Pokemon Sleep 应用管理:Homebrew Cask 🎮 游戏# DIY 年度游戏:《塞尔达传说:王国之泪》 今年玩的唯一的游戏 🧸 生活# 🏡 饮食居住# 11 月从贫瘠的苏格兰来到了生活便利的新加坡,生活质量还是高了一大大大截 饮食:跟英国比新加坡简直是天堂,终于有了便宜好吃的外卖和餐厅 住:租房更贵更小了,但多了游泳池和健身房,位置也很好,也算一丝慰藉 🛫 旅游# 发小去年 12 月滑雪扭伤了腿,所以前几个月哪都没去 5 月跟 Harry 去 Lochgelly 上山徒步 Twitter 我和 Harry 背着三大桶水累得在地上爬,发小健步如飞 跟粗粗去了土耳其 Twitter Twitter Twitter Twitter Twitter Twitter Twitter 景区里好玩的很多,山路自驾、游泳、跳海、跳伞、骑马、热气球、晒太阳,大海和热气球风景也超好看,就是价格很感人,景点物价很高,会宰游客,离开景点区域又很贫瘠 带着游泳圈在水里泡了几天,差一点学会了游泳 8 月去了伦敦、剑桥、利兹、约克 Twitter Twitter Twitter Twitter Twitter Twitter 带发小妈妈在英国到处玩 看悲惨世界话剧 Do You Hear the People Sing Twitter 唐顿庄园里面 Cosplay Twitter 被 Harry 带着逛了爱丁堡神奇的化石店和外科手术博物馆 外科手术博物馆展示了泡在福尔马林中成堆的尸体和各个部位肢体,发小看得津津有味 跟 Harry 在爱丁堡海边放火烤棉花糖 Twitter Twitter 烤棉花糖是外面脆脆的里面软绵绵的很奇妙,Harry 做的酥肉也超级好吃 9 月去了法国、德国、奥地利、匈牙利、捷克 该玩的都玩了但时间很赶,走马观花 10 月去了成都、重庆、郑州、乌鲁木齐、上海 在成都见了Tyzual 和 七夏浅笑,去看了大熊猫,在重庆大吃特吃,在乌鲁木齐吃羊肉配皮芽子 成都大熊猫基地 乌鲁木齐的大草原 11 月去了香港 香港金融科技周见到了很多同事,涨了很多见识,也是成立了两年多的公司的联合创始人们第一次团聚 🤼‍♀️ 社交# 今年不但去了很多地方还见了很多朋友 Harry、粗不粗、Tyzual、七夏浅笑、花生、ch、Tony、Bruce、Anni、Atlas、birdring、Dmoo、Jeff、Joshua、Kate、Maggie、Usagi、Yingzi、birdring、yingzi,大部分是同事,都是远程工作见一面着实不容易 🐱 家庭# 回老家结婚了,过程很累,但看到发小穿上婚纱打扮一番一切都值了 Twitter 一回国就跟酸奶团聚了,还一起来了新加坡 Twitter 被问的最多的是 “分开了两年多,酸奶还认识你么”,虽然我永远无法知道答案,但从黏人程度来看,我倾向于是认识的 🏊 技能# 游泳 从小学过几次,大学还上过游泳课,但都没有学会 如上所述,今年在土耳其旅游时候套个游泳圈泡了几天大海,克服了对游泳的恐惧,大概学会了自由泳的姿势但是不会换气 到新加坡之后又在小区的游泳池泡了几周后完全学会了,现在每天都会跟发小去游泳,运动强度足够大又有水冷,已经是我最喜欢的运动了,感觉之前错过了很多乐趣 播客 为了宣传 xLog,接受了 Web Worker 的邀请录了人生第一次播客,主播辛宝、刘威、小白菜都很专业,过程意外地非常顺利,也没有任何冷场 但是作为一个资深社恐,虽然第一次很顺利,应该也很难鼓起勇气进行下一次了 打麻将和打掼蛋 跟着发小各去了一次打麻将和打掼蛋,学会了这两种游戏,但是要说话所以都没有第二次了 🌌 印象深刻的日常# 过生日发小精心设计的 DIY 诱捕器 过生日 st 送的黑哥哥祝福 在发小学校穿苏格兰裙拍婚纱照 在格拉斯哥参加了发小的毕业典礼 在新加坡过圣诞节 在新加坡跨年看烟花表演,好像还是第一次看这么盛大的烟花表演,之前看过最大的是迪士尼 还有陪发小第一次去看演唱会,第一次去抓娃娃 🪄 生活管理# 跟去年差不多,主要还是在使用这一套基于 Obsidian 的生活记录系统,现在日记是这样的结构 当天发生的事情、正在进行中的事项、日程表、富兰克林美德表 再补充上用 TickTick 的 Habit 来记录每天每周各个 OKR 目标完成得怎么样,用 Toggl Track 记录做各个事情的时间,比之前的在 Obsidian 里 all in one 方便很多 🌿 输出# 👩🏻‍💻 代码# Contributions 快赶上 2019 年巅峰时期了 又是没有新项目的一年,今年中前期还是以 xLog 为主,后期也做了一些 RSSHub 和 RSSHub Radar 的工作,和为公司 2024 年的一些转变做准备 xLog 开发得热火朝天(相关 Twitter 列表),成功举办了 10000 刀的创作者激励活动,吸引了大批优秀的创作者,最多时候还每周有十几个社区开发者来贡献代码一起完善 xLog,完成了数不清的 Feature。现在有 3k 多创作者在上面发布了 26k 多高质量内容 RSSHub 重构了文档,让它更容易被维护,改造成了以英文为优先语言;用更现代化的架构 Plasmo 完全重写了 RSSHub Radar,变得更好看更稳定更容易维护,还能自动发布到各大应用商店。现在 RSSHub 官方实例每月承载三亿多次请求,docker 有一千五百多万次下载,RSSHub Radar 有差不多 9 万周活用户 📙 文字# 这一年文字也非常高产 认真写了 9 篇博客 2022 年终总结 你你你你要跳舞吗? 令我痛心的三部反乌托邦动漫 我得了软件更新强迫症 4 月新番太好看了!吹爆! 对 Newsletter 说不 在博客融入一个跨平台作品集 优雅使用 Cloudflare WARP 应对 RSSHub 反爬难题 轻松创建一万个 Twitter 账号 水了 243 条 Twitter 列表 🎥 影音# 今年在 B 站发布了两个 Vlog,旅游积攒了好多素材,至少还有 6 个视频还没剪,很惭愧 《女友浴室沉思录,非正常录音》 《英国旅游 VLOG | 爱丁堡圣诞节》 录了一个播客《和 xlog.app 的作者 DIYgod 聊区块链和博客平台、前端学习和生活感悟》 https://www.xiaoyuzhoufm.com/episode/645a76f67d934b85051081c8 🎊 2024# ⭐️ OKR# O1: In excellent health KR1: Body control, body weight 70kg and body fat 17% KR2: Early to bed and early to rise, 12:00-8:00 KR3: Healthy Eating, light and less, increase the proportion of white meat and reduce the sugar KR4: More exercise, at least 3 times per week O2: Highly productive like a sow KR1: Blog, at least 1 blog per month KR2: Vlog, publish the 6 vlogs from last year O3: Knowledge overflows my brain KR1: Reading, at least 7 hours per week KR2: Professional learning, at least 5 hours per week KR3: English learning, at least 5 hours per week O4: I am Franklin KR1: Guarantee 1 virtue per week KR2: Record virtue status daily O5: Run like clockwork KR1: Write bullet journal daily KR2: Bookkeep weekly and monthly KR3: Do things on a daily schedule O6: Writing code is like writing the poem of life KR1: Focused work for at least 7 hours per day KR2: Actively maintain and contribute to open source projects for at least 3 hours per week KR3: Develop a new RSS reader using Tauri, with comprehensive support for RSSHub and AI O7: Combat inflation KR1: Achieve a 10% annual return through simple and healthy financial management strategies

轻松创建一万个 Twitter 账号

2023-12-15 18:14:46

Information freedom does not naturally evolve, it degrades. —— Open Information Manifesto Twitter 在 8 月决定了全面限制公开访问和 API 接口,导致第三方集成均无法再正常工作。开放用户数据被绑架成私人敛财工具,曾经的 Open Web 标杆 Twitter 竟沦落到这种境地,数字奴隶制在最不应该的地方出现,令人唏嘘。这也致使许多用户流向 Fediverse,但社交关系和习惯一旦形成,要让其迅速改变并不易,更多人还是选择了忍受,Musk 也是看穿了这一点才有恃无恐 然而,我们也不能武断地说 Twitter 封闭,毕竟它仍然开放了一个起步价为每月 4 万美元,上限不设的企业 API 什么?你说用不起? 那么你可以像我一样,通过创建一万个账号以绕开封锁 尽管 Twitter 限制了所有公开访问,但我们发现新下载的 Twitter 移动客户端仍可以正常查看用户动态。这为我们提供了潜在的利用方法,通过抓包,我们可以看到客户端是通过请求一系列特殊接口来创建一个权限较低、频率限制严格的临时账号。我们可以用这个账号获取我们需要的大部分数据。然而,这种账号对请求频率的限制非常严格,因此需要大量的这样的账号才能满足基本的使用需求。同时,每个 IP 在一段时间内只能获取一个临时账号,因此我们也需要大量的 IP 代理 具体拆包和抓包过程可以参考 BANKA 的《怎么爬 Twitter(Android)》。站在 BANKA 肩膀上,我们可以写出一个这样的注册脚本(来自 Nitter - Guest Account Branch Deployment): Copy#!/bin/bash guest_token=$(curl -s -XPOST https://api.twitter.com/1.1/guest/activate.json -H 'Authorization: Bearer AAAAAAAAAAAAAAAAAAAAAFXzAwAAAAAAMHCxpeSDG1gLNLghVe8d74hl6k4%3DRUMF4xAQLsbeBhTSRrCiQpJtxoGWeyHrDb5te2jpGskWDFW82F' | jq -r '.guest_token') flow_token=$(curl -s -XPOST 'https://api.twitter.com/1.1/onboarding/task.json?flow_name=welcome' \ -H 'Authorization: Bearer AAAAAAAAAAAAAAAAAAAAAFXzAwAAAAAAMHCxpeSDG1gLNLghVe8d74hl6k4%3DRUMF4xAQLsbeBhTSRrCiQpJtxoGWeyHrDb5te2jpGskWDFW82F' \ -H 'Content-Type: application/json' \ -H "User-Agent: TwitterAndroid/10.10.0" \ -H "X-Guest-Token: ${guest_token}" \ -d '{"flow_token":null,"input_flow_data":{"flow_context":{"start_location":{"location":"splash_screen"}}}}' | jq -r .flow_token) curl -s -XPOST 'https://api.twitter.com/1.1/onboarding/task.json' \ -H 'Authorization: Bearer AAAAAAAAAAAAAAAAAAAAAFXzAwAAAAAAMHCxpeSDG1gLNLghVe8d74hl6k4%3DRUMF4xAQLsbeBhTSRrCiQpJtxoGWeyHrDb5te2jpGskWDFW82F' \ -H 'Content-Type: application/json' \ -H "User-Agent: TwitterAndroid/10.10.0" \ -H "X-Guest-Token: ${guest_token}" \ -d "{\"flow_token\":\"${flow_token}\",\"subtask_inputs\":[{\"open_link\":{\"link\":\"next_link\"},\"subtask_id\":\"NextTaskOpenLink\"}]}" | jq -c -r '.subtasks[0]|if(.open_account) then {oauth_token: .open_account.oauth_token, oauth_token_secret: .open_account.oauth_token_secret} else empty end' 以及这样的批量注册脚本(来自我自己): Copyconst got = require('got'); const { HttpsProxyAgent } = require('https-proxy-agent'); const fs = require('fs'); const path = require('path'); const concurrency = 5; // Please do not set it too large to avoid Twitter discovering our little secret const proxyUrl = ''; // Add your proxy here const baseURL = 'https://api.twitter.com/1.1/'; const headers = { Authorization: 'Bearer AAAAAAAAAAAAAAAAAAAAAFXzAwAAAAAAMHCxpeSDG1gLNLghVe8d74hl6k4%3DRUMF4xAQLsbeBhTSRrCiQpJtxoGWeyHrDb5te2jpGskWDFW82F', 'User-Agent': 'TwitterAndroid/10.10.0', }; const accounts = []; function generateOne() { // eslint-disable-next-line no-async-promise-executor return new Promise(async (resolve) => { const timeout = setTimeout(() => { // eslint-disable-next-line no-console console.log(`Failed to generate account, continue... timeout`); resolve(); }, 30000); const agent = { https: proxyUrl && new HttpsProxyAgent(proxyUrl), }; try { const response = await got.post(`${baseURL}guest/activate.json`, { headers: { Authorization: headers.Authorization, }, agent, timeout: { request: 20000, }, }); const guestToken = JSON.parse(response.body).guest_token; const flowResponse = await got.post(`${baseURL}onboarding/task.json?flow_name=welcome`, { json: { flow_token: null, input_flow_data: { flow_context: { start_location: { location: 'splash_screen', }, }, }, }, headers: { ...headers, 'X-Guest-Token': guestToken, }, agent, timeout: { request: 20000, }, }); const flowToken = JSON.parse(flowResponse.body).flow_token; const finalResponse = await got.post(`${baseURL}onboarding/task.json`, { json: { flow_token: flowToken, subtask_inputs: [ { open_link: { link: 'next_link', }, subtask_id: 'NextTaskOpenLink', }, ], }, headers: { ...headers, 'X-Guest-Token': guestToken, }, agent, timeout: { request: 20000, }, }); const account = JSON.parse(finalResponse.body).subtasks[0].open_account; if (account) { accounts.push({ t: account.oauth_token, s: account.oauth_token_secret, }); } else { // eslint-disable-next-line no-console console.log(`Failed to generate account, continue... no account`); } } catch (error) { // eslint-disable-next-line no-console console.log(`Failed to generate account, continue... ${error}`); } clearTimeout(timeout); resolve(); }); } (async () => { const oldAccounts = fs.readFileSync(path.join(__dirname, 'accounts.txt')); const tokens = oldAccounts.toString().split('\n')[0].split('=')[1].split(','); const secrets = oldAccounts.toString().split('\n')[1].split('=')[1].split(','); for (let i = 0; i < tokens.length; i++) { accounts.push({ t: tokens[i], s: secrets[i], }); } for (let i = 0; i < 1000; i++) { // eslint-disable-next-line no-console console.log(`Generating accounts ${i * concurrency}-${(i + 1) * concurrency - 1}, total ${accounts.length}`); // eslint-disable-next-line no-await-in-loop await Promise.all(Array.from({ length: concurrency }, () => generateOne())); fs.writeFileSync(path.join(__dirname, 'accounts.txt'), [`TWITTER_OAUTH_TOKEN=${accounts.map((account) => account.t).join(',')}`, `TWITTER_OAUTH_TOKEN_SECRET=${accounts.map((account) => account.s).join(',')}`].join('\n')); } })(); 这些脚本已放到了 RSSHub 仓库: https://github.com/DIYgod/RSSHub/blob/master/scripts/twitter-token/generate.js 在使用前,你需要填入你购买的 IP 代理服务地址。脚本会自动处理超时、请求等错误,并且以 5 并发来自动获取临时账号,当获取到 1000 个账号后将会停止。需注意并发不要设置得过高,我从观察发现,当 Twitter 发现大量请求时会暂停接口一段时间 我购买了 5 家代理服务以进行测试,感觉效果相差无几,选择一个最便宜的服务就可以。通常,最低价的 1G 套餐就足够获取大约几万到十几万个账号了。我目前找到的最便宜的服务是 proxy-cheap,如果你有更好的选择请告知我 这种方法已经在 Nitter 上稳定运行了一段时间,现在也已实装到了 RSSHub 及其官方示例上,我们可以宣布与邪恶 Twitter 奴隶主的战争已经阶段性胜利

优雅使用 Cloudflare WARP 应对 RSSHub 反爬难题

2023-08-18 06:01:51

🕊️ 本文送给更开放的互联网 起因是看到 @geekbb 介绍 Warp 的推文。尽管 Warp 已经发布了很长时间,就保护 IP 隐私而言,它并没有 iCloud Private Relay 好用,我也没有魔法上网的需求。但是我突然意识到,我还是有隐藏 IP 的需求。 在开发 RSSHub 的几年中,我发现提供公共 API 的站点非常少,许多站点还会采取严格的反爬控制来限制其平台内容的获取。有些站点会屏蔽同一 IP 发出过多请求,而还有一些站点则会全面屏蔽常见云服务器厂商的 IP 地址。因此,仅仅为了获取最新几条内容更新却变得非常困难。 这种情况需要使用代理,但是专门的爬虫代理通常价格昂贵,性价比极低,如果 Cloudflare WARP 的无限流量和丰富的 IP 资源能被 RSSHub 利用就太棒了。RSSHub 已经支持了通用的代理协议,只要能将 WARP 包装为通用的 proxy 就可以。 虽然无法直接在命令行环境中方便地使用官方客户端,但这么容易想到的点子肯定已经被别人实现过了。我在 GitHub 上找到了一个封装的 Docker。 然后只需要在 RSSHub 的 docker-compose.yml 中再添加这样一个 service 来启用代理服务 Copywarp-socks: image: monius/docker-warp-socks:latest privileged: true volumes: - /lib/modules:/lib/modules cap_add: - NET_ADMIN - SYS_ADMIN sysctls: net.ipv6.conf.all.disable_ipv6: 0 net.ipv4.conf.all.src_valid_mark: 1 healthcheck: test: ["CMD", "curl", "-f", "https://www.cloudflare.com/cdn-cgi/trace"] interval: 30s timeout: 10s retries: 5 最后给 RSSHub 加一个 PROXY_URI 环境变量来使用代理 CopyPROXY_URI: 'socks5h://warp-socks:9091' 我选取了一个我经常使用的 hotukdeals 路由(英国版的什么值得买)进行测试。该站点会屏蔽所有 DigitalOcean 的 IP,因此一直处于 403 状态。 加上 WARP 后可顺利访问 此外,我发现每次重启 WARP 时,都会输出新的 IP。尽管我没有时间验证,但我感觉 IP 应该会经常自动更改,这对解决反爬是一个好消息。 还可以进一步自定义 WireGuard 的配置,包括使用付费版 WARP+ 和自定义 endpoint,以获取可能更好的结果。 生成 WireGuard 配置文件可以使用 刷 WARP+ 流量和筛选 endpoint 可以使用 有说法是 WARP+ 的速度并无明显差异(《WARP、WARP + 速度对比,以及 WARP 速度上限》),但是是否影响反爬效果还需要进一步验证。 如果一切顺利,RSSHub 官方实例中许多严格反爬的路由应该能重新使用。我将在几天内进行验证并在此更新。

在博客融入一个跨平台作品集

2023-08-08 06:34:18

长久以来# 我一直将个人博客视为一个理想的展示个人 IP 的 “个人网站”,而不仅仅是发布文章的平台。我曾在 2014 年初学编程时使用 WordPress 建站 《世界,你好!》;入了前端坑后,在 2017 年我转向了 Hexo 《做了一点微小的改动》;Web3 飞升后 2022 年我换成了 xLog 《第一个开源链上博客系统 xLog》。然而,无论我使用什么博客系统,一直都存在一个问题,那就是如何优雅地汇集和展示我在其他平台发布的作品,最好还能直接显示外站的数据。我之前通常以文章形式发布作品,并在文章中附上链接,然而这样做显然不够优雅,读者还需要额外点击链接进行跳转。 灵感降临# 我在学习达芬奇剪辑时,发现了影视飓风的网站,它通过外链方式列出了他们在 B 站发布的视频,其中包括标题、封面图、发布时间、播放量等信息。这个发现给了我启发,我完全可以在 xLog 上制作一个装载了我在各个平台作品的作品集,这里面可以有我发布在 B 站的视频、我在 GitHub 上维护的仓库、我参与的小宇宙播客甚至是我在 pixiv 上创作的画作。这样,当人们访问我的博客时,将不只是看到文章,而是会看到更丰富多元的我,这让我的博客更接近一个真正意义上的 “个人网站”。 下手# 想法萌发后,实现就简单了。 对 xLog 后台进行了优化和清晰的分类:文章、页面、作品集,以消除类型增多后可能带来的用户困扰。 设计了一个全新的编辑页,不同于文章和页面,这里只保留封面、标题、摘要、发布时间,并新增外部链接字段。 实现了作品信息的自动填充功能,减轻了手动输入的负担。这是通过获取链接的 Open Graph 信息实现的,涉及到的字段包括 og:image og:title og:description og:date 把作品展示在首页和独立的作品集页 数据的获取和展示,对于 “偷数据” 经验丰富的 RSSHub 作者来说,这是得心应手的一环,首先针对 bilibili、小宇宙、GitHub、pixiv、Twitter 这几个平台进行了抓取,获取到播放量和评论数并在 xLog 的卡片上进行展示,同时考虑到源站可能的压力和反爬,我特别设置了足够长的数据缓存。 如今,这个简单实用的小功能已经落地实现了,可以看看我的作品集页,你是否也想要尝试在 xLog 建立属于自己的个人作品集呢?

对 Newsletter 说不

2023-07-13 02:31:13

衰退无处不在,这是很正常的现象。人们自然而然地更倾向于短平快的消费方式。然而,我一直无法忍受的一种奇怪趋势是,在一些地方,人们将 RSS 抛弃,转而使用 Newsletter。 本质上,电子邮件是一种私密的双向通信机制,而 RSS 是一种开放的单向通信机制。使用电子邮件进行私密的更新和文章推送是没有意义的,RSS 是更自然的选择。强行将这些功能应用于电子邮件可能会导致许多问题。这些问题让我觉得 Newsletter 就像一个没有能力但却拼命想证明自己的暴君,无法很好达到发布者期望的效果,又过分侵犯了用户的选择和效率。 五宗罪# 封闭限制# RSS 是一种开放协议,它允许用户自主订阅和拉取感兴趣的网站的 RSS 源,获取最新的更新和文章,无需他人的许可。用户还可以通过阅读器的个性化设置自由选择所需内容,并且可以使用多种渠道接收通知,甚至可以使用 Telegram Bot 进行订阅。 而 Newsletter 是由发布者推送到用户私人邮箱的订阅信息,整个过程依赖于平台方,渠道全程封闭。这种封闭极大地限制了用户的选择权,用户被迫在平台的许可下,通过特定渠道和特定格式接收固定信息。 繁琐低效# 相比之下,RSS 更加简洁高效。订阅源可以集中管理,分类、收藏、订阅和取消订阅的过程也非常简单。 而 Newsletter 则会将各种各样的邮件混合在一起,非常分散且难以管理。你很难知道自己到底订阅了哪些内容,它们什么时候会突然出现。而且,内容格式也是各种各样的,查看和阅读起来非常混乱,所以你也不能将一篇文章进行收藏,更不用说方便的第三方集成了。 信息过载# Newsletter 很难对内容进行有效分类和过滤,又与所有正常邮件混合在一起,需要花费精力手动整理。这很容易导致信息过载和垃圾邮件的问题。 而 RSS 可以很方便地进行分类和过滤,对于不重要的内容,你也可以一键全部标记为已读瞬间解脱,完全没有压力。 更新周期长# 对于 RSS 的更新虽然不算实时,但一般以小时计,类似 RSSHub 等自建服务,甚至可以做到每分钟更新。相比之下,Newsletter 的更新周期,以天甚至周月计,明显滞后了许多。 隐私和安全风险# RSS 的开放性体现在它不需要用户提供个人信息,从而确保了更好的隐私性和安全性。然而,Newsletter 至少需要提供一个邮箱地址,这增加了数据泄露或滥用的风险。更有甚者,电子邮件可能包含恶意链接或附件。 也有一些优点# 尽管我对 Newsletter 的低效和局限性持批评态度,但我也承认它有其优点,它的流行也有一些合理性,特别是在卖方市场的情况下对于一些发布者来说。Newsletter 可以让他们获得更多的控制权和点击率,可以更容易知道有谁订阅了他们的内容,并通过邮件通知更强烈地唤起用户的注意。 然而,站在用户的角度,我必须明确表示,我更偏爱 RSS。我不愿意以放弃自己的选择权和效率来迎合发布者的控制欲。在我看来,获取信息的权力应该掌握在我自己手中,而非其他人或机构。所以,我在这里对 Newsletter 说不。

4 月新番太好看了!吹爆!

2023-05-25 07:32:29

今年从 1 月就一直没什么好看的作品,到了 4 月突然爆发,让我非常激动。现在播出过半了,是时候好好说说感受了 以下当然不能囊括 4 月所有的好作品,因为实在太多了,只是说一说符合我口味的几部,按我个人的喜爱程度来排序 以下会包含大量剧透,还没看的小伙伴请酌情观看 鬼灭之刃刀匠村篇# 刀匠村是为鬼杀队锻造日轮刀的刀匠们居住的地方,位置及其隐蔽且高度保密,这部讲的是刀匠村被上弦之肆・半天狗和上弦之伍・玉壶找到并发动偷袭,正在刀匠村的炭治郎、恋柱・甘露寺蜜璃、霞柱・时透无一郎、不死川玄弥努力保护刀匠们,对抗上弦的故事 上弦之肆・半天狗这个角色非常有趣,本体又小又丑,非常弱小无助又胆小,总感觉像是来送人头的 但实际上非常扮猪吃老虎,控制的几个鬼(还不知道他们是什么关系)喜、怒、哀、乐、憎,都非常强,压迫感很足,本体防御力也无敌,被大家排着队砍也砍不死 弥豆子的表现也很亮眼,鬼化弥豆子超帅 还有可爱的蜜璃 虽然整体节奏还是略显拖沓,但经过游郭篇的洗礼,我本身预期也很低,目前已经远远超出了我的预期,文戏比之前明显少了好多,打斗也更加燃爆,看得非常上头 再加上我有粉丝加成,荣登我的周指活排名第一名 谢谢飞碟社款待 类别评分 / 10剧情7画面10个人加成10 为美好的世界献上爆焰!# 脑子有问题的红魔族第一天才中二病魔法师慧慧的故事,喜欢的东西是爆裂魔法,特技是爆裂魔法,兴趣是爆裂魔法,唯一的真爱也是爆裂魔法 慧慧小时候被一位使用爆裂魔法的巨乳魔法师所救而爱上了爆裂魔法 虽然爆裂魔法极不实用且难以操纵,是出了名的搞笑魔法,“只有学得上级魔法才能独当一面。爆裂魔法之流不过是搞笑魔法罢了”,但慧慧还是不顾嘲笑,为了爱好而独自努力 最后使用所有技能点数学到了爆裂魔法 比黑色还要黑 暗之漆黑 融合著我之真红吧 觉醒的时刻已经到来 坠入无谬之境界 形成无形之扭曲 出现吧! Explosion! 之后的外出冒险也很有趣 之前看《为美好的世界献上祝福!》就被慧慧的中二吸引了,结果在红魔乡发现慧慧的中二只是一个普通水平,印象最深的是魔法学校最重要的一节课是战斗前要摆什么样的中二姿势 缺点也是有的,作画穷了点,有不少崩坏的地方,有些画面还是挺出戏的,特别是扎堆在作画全都不要钱一样的 4 月新番中显得更惨了(但还算说得过去)(后面还有个说不过去的) 类别评分 / 10剧情8画面6个人加成10 天国大魔境# 刚说的精美作画就来了,从第一集就被震撼到了,不要钱一样的作画,末日废土乌托邦剧情,流畅的打戏完美的运镜,轻松愉快加简单的涩涩又不失紧张刺激的氛围把控,就像完美的考试标准答案一样,刚开始还担心开头质量过于高了后面会不会质量下降,但是完全没有! 剧情还没有完全揭秘,现在知道的是成为废墟的日本栖息着一种食人的怪物蛭子,人们艰难地生存着,还有一个号称 “天国” 的乌托邦,被安全的围墙覆盖,里面科技发达,被圈养的孩子们生活富足 墙外很危险 墙内看似天国但剧情暗示了更大的危险 生活在墙外的主角真流和斩子被托付前往天国寻找和真流长相一样的人,期间遇到了各种各样的人 不愿相信儿子已经变成没有人性的怪物蛭子的母亲,诱骗路人喂给儿子吃,最后被儿子变成的蛭子杀死 梦想跟看上的客人爱爱来赚大钱的酒店小老板,也会跟骗子强盗合作诱拐冒险者 做人体实验的冷血医生,又只是一个为了让妻子可以作为人类有尊严死去的痴情男,成功让妻子死去后抱着妻子的尸体自杀 而义正严词反对人体实验的伦理组织首领只是为了给医生下圈套抢夺资源 每个人都能被不长的篇幅刻画地如此鲜活,在这样的废土世界,努力艰难不择手段地活着,即使罪大恶极也能让我深深理解,感到同情和惋惜 让我对制作组献上最大的敬意,如果排除个人因素,这部作品将是遥遥领先的 4 月霸权 类别评分 / 10剧情10画面10个人加成5 地狱乐# 在德川幕府时期,有一个鲜花遍地,蝴蝶翩飞的名为 “神仙岛” 的地方,前往该岛的调查队只有极少数的残肢和鲜花能够随着小船漂流回来 最强死刑犯忍者画眉丸与处刑人山田浅右卫门佐切一同被派往此岛,寻找不老不死的仙药以换取无罪释放,并在岛上与怪物以及其他死刑犯互相残杀 里面的怪物是这样的画风 灶神 天仙 被天仙抓到会被炼成丹 非常诡异荒诞 每个死囚也都有自己的故事,并不是黑白分明,经历都很有趣 还出现了一个怪力萝莉 仙药和小岛的真相依然扑朔迷离,后面肯定还会有更诡异更强大的怪物,很期待 类别评分 / 10剧情8画面8个人加成0 【我推的孩子】# 妇科男医生和早逝女病人遇难转生为自己单推偶像星野爱的双胞胎孩子阿库亚和露比,三年后星野爱被害身亡,阿库亚立志寻找母亲遇害的真相和真凶,露比梦想着成为母亲那样的偶像,兄妹应对演艺圈的各种纷争和阴谋 一个多小时的首集太惊喜了,像看了一部电影,我觉得可以算得上 4 月最佳单集了 但可能是开头调起太高了,期待也拉得很高,后面几集略显平淡了一点,估计是高低高的节奏,现在是憋大招中,最后会有一个超感人哭爆的结局,准备好纸巾了 虽然如此,但如果中间也能再多给星野爱一些画面就更好了 另外它的 OP 也超好听,我单曲循环了好几天 类别评分 / 10剧情7画面7个人加成0 我家的英雄# 疼爱独生女零花的微不足道上班族・鸟栖哲雄,某天因为工作关系和开始一个人生活的零花相约见面,却发现她脸上有被殴打的痕迹。在回家的路上,哲雄看见了像是犯人的男人,并尾随在其後。隔天偷偷回到女儿的公寓,结果却发生了使整个家庭的命运为之一变的事件。父亲为了女儿、为了家庭,走上了修罗之道。高潮迭起的悬疑故事开幕。 剧情很精彩,但制作非常非常贫穷,挺可惜的,就是刚才说的说不过去的那个 好在剧情底子非常强,再垃圾的制作也能感受到挺强的紧张感,也能捏着鼻子津津有味地看下去 就不截图了,画面实在看不下去 类别评分 / 10剧情8画面1个人加成0 偶像大师 灰姑娘女孩 U149# 讲述一群小学生偶像在早期没有制作人、没有工作的情况下,仍然一步步向着梦想前进的故事。 其实也没什么剧情,就是过家家... 但是很可爱,非常刑 类别评分 / 10剧情1画面9个人加成0 跃动青春# 岩仓美津未从乡下的小初中,以第一名的成绩考入了东京的高升学率高中。 这位乡村神童心怀完美的人生蓝图、独自来到东京。她成绩优异,却与他人有着独特的距离感,稍显格格不入。 她虽然偶尔会失败,但还是凭借天真的性格一点点打动班上的同学,使他们那各不相同的性格逐渐交叠。 相遇、相知、最终心意相通。 人人都会有心烦和焦躁之时。而无可替代的朋友,定将带来互相理解的契机。 这是个偶有杂音却能让人不知不觉快乐起来的校园生活喜剧! 不管是画面还是剧情看着都很舒服,作品本身是非常棒的,喜欢,但这种平淡剧情不是我喜欢的类型,只能简单吹爆 类别评分 / 10剧情8画面8个人加成-5 其他# 另外还有《机动战士高达 水星的魔女》《我心里危险的东西》《放学后失眠的你》也是我正在追的,但我平时能看番的空闲时间没那么多,这几部进度还没跟上,等我全看完可能会写一篇 4 月新番的总结,有机会到时候再一起说 最后祝大家追番愉快!玩王国之泪之余也不要忘记追番哦!

我得了软件更新强迫症

2023-04-09 07:00:00

我必须让身边所有软件都保持最新版本,就像走路不能踩到地砖缝缝一样,没有意义,但也危害不大,所以我放任它发展 操作系统和固件# 最基础的,大到主力生产力 MacBook,小到吃灰的 Dockcase 扩展坞,都要更新,这是我的 Checklist,基本上靠更新提醒就够了 设备截图MacBookNAS路由器iPadiPhone小米手环小米家居相机PS5SwitchDockcase 扩展坞 应用# 操作系统下的应用也需要更新 MacBook# Homebrew Cask 出现之前 macOS 的应用更新一直是一个难题,App Store 的自动更新最好用但是应用很少,曾经困扰了我很久,但现在没问题了 对 macOS 的应用我秉持这样的原则: 第一顺位 Web 版,比如:Discord、Slack、Telegram、Spotify 第二顺位 App Store 版 第三顺位 Homebrew 除此之外不安装 其中 Web 版不需要更新,App Store 会自动更新,Homebrew 只需要每天执行下命令 Copybrew update && brew upgrade && brew cu -a -y && brew cleanup 但 App Store 有时候会好几天才能自动更新上,这不能忍,安装上 mas 用命令行触发强制更新,再加上鼠须管的词库更新和 alias,下面就是我的总命令了 Copyalias up="brew update && brew upgrade && brew cu -a -y && brew cleanup && mas upgrade && ~/plum/rime-install iDvel/rime-ice:others/recipes/full" NAS# 有两部分,群晖的套件会自动更新,Docker 部分用 Watchtower 自动更新 其他# iPad 和 iPhone 应用虽然也会自动更新,但也是经常好几天才更新,所以每天早上起床第一件事就是手动刷一下这两个的更新,其他不那么常用的设备就等提醒或自动更新,这个还是能忍的,毕竟我的强迫症还没那么严重 插件# 应用内的插件也需要更新 浏览器# 通常会自动更新,但也有遇到卡住几天都没更新的情况,还不是很懂,偶尔会去手动点下 Update,这些是我目前用到的浏览器插件 Obsidian# Obsidian 的 community plugins 和 themes 都需要更新,官方没有自动更新的方法,可以通过再装一个插件 Beta Reviewers Auto-update Tester 来实现自动更新,这些是我目前用到的 Obsidian 插件 VS Code# VS Code 有很好的自动更新,这些是我目前用到的 VS Code 插件 Home Assistant# Home Assistant 就没那么顺利了,我装了一个 Home Assistant Community Store (HACS) 来获取更多的集成,但它没有自动更新,也没有更新提醒,要点进去才能看到 项目依赖# 项目的依赖也需要更新,我用 Dependabot 来自动提交依赖更新的 pr,对于有完善自动化测试的项目,比如 RSSHub,就可以再用 Github Action Merge Dependabot 自动合并,一秒都不用多等 Copy automerge: if: github.actor == 'dependabot[bot]' && github.event_name == 'pull_request' needs: [ jest, puppeteer, docs, all ] runs-on: ubuntu-latest permissions: pull-requests: write contents: write steps: - uses: fastify/github-action-merge-dependabot@v3 with: github-token: ${{ secrets.GITHUB_TOKEN }} target: patch Changelog# 默默更新有时候还是不够的,对一些重要或者感兴趣的软件我会用 RSS 订阅它们的 Changelog,及时了解它们都更新了什么 目前想到的只有这些了,习惯了的话检查一遍很快的,就能换来一整天的舒适 不要学我,即使学了也不要说是我教的 .os-screenshots thead th:first-child { width: 10px; } .os-screenshots td > span { height: 100px; display: flex; align-items: center; overflow: hidden; } @media (min-width: 640px) { .os-screenshots td > span { height: 200px; } } .os-screenshots td > span > span { height: auto; }

令我痛心的三部反乌托邦动漫

2023-04-02 12:50:00

请欣赏 「DIY 的夺命歌单」 四月对我而言是一个充满悲伤的月份。尽管《四月是你的谎言》已经过去了很多年,但每当看到「四月」这两个字,仍会立刻触发我的 PTSD,再加上今天我刚读完一本反乌托邦题材的小说《来自新世界》。多种思绪糅杂在一起,让我不禁回想起了那些令我痛心的「重量级」反乌托邦动漫。 DARLING in the FRANXX# “如果把动画比作足球的话,这部动画大概就是日本国家队了,就是普通不看足球的人也会期待日本队的表现,也会去谈论足球,所以希望自己的动画也能被这样对待。不单单锦织敦史的粉丝、TRIGGER 的粉丝、A-1 的粉丝这些平时看动画的观众,希望平时不看动画的观众也能享受这部作品。” — 鸟羽洋典 制片人的这番话一点也不夸张。这部动漫的制作阵容极为豪华,集结了业界的大佬和顶级声优,因此在尚未播出之前就被所有人寄予厚望,以至于还没播出就已被推上神坛。 然而,神作的道路并非顺利。从爱奇艺和 B 站的霸权争夺,到驾驶姿势引发的女权争议,最后在 B 站 UP 主 Lexburner 的口嗨和群众的恶意举报中达到高潮,赵弹来袭。 这些历史背景现在看来其实都不值一提,只是说明当时的热度和争议之高。毕竟最后还是用作品说话,而这部作品简直太符合我的口味了,反乌托邦的背景、活泼可爱打破常规的 02、恰到好处的涩涩、科幻、鬼、悬疑... 当时的我茶不思饭不想,在一声声 Darling Darling 中逐渐迷失了自我,每周就指着 Darling 活了,我把博客主题改成了 Darling 的配色和配图,头像和名字也换成了 Darling,买了手办、画册一切能买的周边,甚至学着画画专门临摹 02,逢人就吹爆,只要看了 Darling 我们就是好朋友种种 再回过神来就是惨绝人寰的烂尾了,就像回应开播时的轰轰烈烈一样,烂得也彻彻底底没有任何余地,但可能是大脑对强烈刺激的自我保护,我现在已经不太记得当时的具体情景了,只记得一个巨大的花嫁扎古噩梦... 这部由如此豪华的制作团队打造的作品,竟然如此令人失望。至今我仍无法理解。 约定的梦幻岛# 一年后,我又发现了一部绝赞的反乌托邦番剧 在我吹爆《约定的梦幻岛》第一季的时候就有人提醒我:Darling 尸骨未寒,你怎么敢。我却不以为意,认为这只是巧合。毕竟,梦幻岛是根据广受好评的漫画改编的,有着坚实的剧情基础,与原创动画 Darling 根本不能相提并论,第一季播完我还沾沾自喜:看,这次没事吧。 然而,两年后,第二季开播了。 我仍然清晰地记得刚开播时,我坐在电脑前激动地第一时间观看。然而,越看越觉得不对劲。到了第四集,剧情已经无法挽回。后来我勉强捏着鼻子又看了几集,但发现我的意志力实在不足以支撑我继续观看。 按理说,漫画已经取得成功,剧情不成问题;第一季动画也获得了好评,拥有广泛的粉丝基础。只要按部就班地制作第二季,绝对会成为另一部神作。然而,动画剧情却砍掉了许多原创桥段,换上了毫无逻辑的编剧所编织的剧情。实在难以想象制作过程中究竟发生了什么。 还好,同期播放的巨人稳得很,让我没有过于悲伤。 进击的巨人# 这是一部我从 2013 年一直吹到 2022 年的作品。 作为一部自 2013 年就开播的动漫,《进击的巨人》在所有动漫中一直拥有极高的地位,堪称元老级存在。许多人正是从这部作品开始入坑看动漫,它是很多人的 “启蒙” 作品,也是我极力推荐给发小观看的第一部动漫。这部动漫也是根据漫画改编的,但与梦幻岛不同的是,其剧情、设定、丰富度和热度都高出好几个层次。更重要的是,接手最终季制作的 MAPPA 公司在质量和口碑上都绝对令人放心,不会出现梦幻岛那样的问题。它的剧情连载了那么长时间,久经时间考验,也不会出现 Darling 的情况,因此我一直信心满满地放心吹爆。 然而后来的事情许多人都知道了,动画史上空前绝后的恶劣事件发生了。这不是恶搞,不是开玩笑,更不是烂尾,而是一个充满仇恨、对爱、正义和和平的人类主流价值观怀有敌意的精神变态作者,对喜爱他和他的作品的观众进行了深思熟虑、蓄谋已久的极端恶意伤害。呕! 虽然已经过了很久了,但我还是久久无法平静,我恨啊,不仅仅是痛心,更是咬牙切齿的仇恨。 其它# 当然,这些只是玩梗。 最后,真诚祝愿四月新番开门大吉、一切顺利! .full-image span { max-width: 100% !important; } .half-image span { max-width: 563px !important; }