<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[喵内]]></title><description><![CDATA[楼外的蒹葭、傍晚的月亮和鸡鸣寺的樱花。]]></description><link>https://blog.caiths.com</link><image><url>https://pic.imgdb.cn/item/653e1b74c458853aef64d366.jpg</url><title>喵内</title><link>https://blog.caiths.com</link></image><generator>Yohaku (https://github.com/Innei/Yohaku)</generator><lastBuildDate>Tue, 04 Aug 2026 20:45:15 GMT</lastBuildDate><atom:link href="https://blog.caiths.com/feed" rel="self" type="application/rss+xml"/><pubDate>Tue, 04 Aug 2026 20:45:15 GMT</pubDate><language><![CDATA[zh-CN]]></language><item><title><![CDATA[自动化 Mac 之前先留一个停止按钮]]></title><description><![CDATA[<div><blockquote>此渲染由 Yohaku API 生成，或存排版之虞，最佳体验请往：<a href="https://blog.caiths.com/notes/33">https://blog.caiths.com/notes/33</a></blockquote><div><p>把 Mac 上一件重复操作交给脚本之前，我会先问一个不太像效率问题的问题：它要怎样停下来？</p><p>一个小脚本可以打开应用、读取一份本地文件、整理内容，再把结果写到指定目录。真正麻烦的是应用弹出确认框、页面还没加载完、文件格式变了，或者脚本跑到一半时我已经不想继续。没有停止条件的自动化，省下的几分钟很容易变成一小时排查。</p><p>我现在会把流程拆成四段：输入、转换、输出和记录。输入只读，不在脚本里保存账号或 Token；转换阶段先写到临时目录；输出前检查文件数量、大小和哈希；记录里只保留时间、步骤和错误原因。任何一段失败，都不继续往下一段传递半成品。</p><p>如果需要控制图形界面，优先寻找应用提供的快捷键、文件导入或本地接口，最后才用坐标点击。坐标会随着窗口大小变化，页面也可能出现登录、更新或权限提示。脚本最好有 <code>--dry-run</code> 和超时，执行前告诉我将读取什么、写到哪里，执行后能用一个退出码说明结果。</p><p>自动化也不等于把 Mac 变成没人照看的机器。它应该把重复动作变成一条可以暂停、检查和恢复的流程。先留一个停止按钮，再谈它能替我点多少次。</p></div><p style="text-align:right"><a href="https://blog.caiths.com/notes/33#comments">览毕，何不一言？</a></p></div>]]></description><link>https://blog.caiths.com/notes/33</link><guid isPermaLink="true">https://blog.caiths.com/notes/33</guid><dc:creator><![CDATA[喵内]]></dc:creator><pubDate>Mon, 03 Aug 2026 17:07:31 GMT</pubDate></item><item><title><![CDATA[把红外控制拆成 AP、状态和协议]]></title><description><![CDATA[<div><blockquote>此渲染由 Yohaku API 生成，或存排版之虞，最佳体验请往：<a href="https://blog.caiths.com/posts/tech/esp8266-ir-controller">https://blog.caiths.com/posts/tech/esp8266-ir-controller</a></blockquote><div><p>把一个传统空调接进网页，听起来像是给旧设备加一层智能家居。这个项目的实现其实更窄：ESP8266 自己开一个 Wi-Fi 热点，网页发出几个 HTTP 请求，固件把状态编码成美的空调能理解的红外脉冲。它没有云端账号，也没有复杂的家庭网络配置，所有链路都停留在一块小开发板附近。</p><p>我更喜欢从这条短链路看它，而不是从“智能”两个字看它。问题一旦被缩小，网络、状态、协议和硬件之间的关系就能逐个核对；某一步没有反应，也能知道应该回到哪一层。</p><h2 id="">先让网络退到设备旁边</h2><p>仓库使用 ESP8266 的 AP 模式，设备自己提供热点。<code>DNSServer</code> 把连接进来的域名请求引向本地地址，<code>ESP8266WebServer</code> 提供网页和控制接口。手机连上热点后可以打开本地页面，即使没有家庭路由器和外网，控制路径也仍然存在。</p><p>这是一种很朴素的取舍。接入家里的 Wi-Fi，当然可以让设备出现在更多场景里，但也会引入密码、配网、路由器隔离和网络变化。这个项目只服务于近距离控制，AP 模式已经足够。先把依赖删到最低，调试时面对的变量也会少很多。</p><p style="padding:6px 12px;border-left:2px solid #C56473;background:#C5647350;font-style:italic;font-weight:500">Not support render this content in RSS render</p>
<h2 id="">页面发送的不是一个按钮</h2><p>网页上的开关、温度、模式、风速和睡眠操作，最后都会回到一份状态。固件用几个变量保存开关状态、温度索引、模式索引、风速索引和睡眠状态；每个接口更新其中一项后，再用 <code>buildStateString()</code> 返回完整状态。</p><p>这个返回值很重要。前端显示的是设备刚刚确认过的状态，而不是“按钮已经被点击”的猜测。对一个只服务于单个设备的小网页来说，完整状态字符串已经够用，不需要为了看起来实时就先引入 WebSocket 或复杂的状态管理库。</p><p>状态也给故障排查留出了顺序：请求有没有到达，参数有没有被处理，状态变量有没有改变，红外发送函数有没有被调用，最后才检查发射管方向和距离。每一层都能单独观察，问题就不会全部变成“网页点了没反应”。</p><h2 id="">红外信号有自己的语法</h2><p>红外发射不是把 GPIO 拉高就结束。仓库里把美的协议拆成三个字节：A 是固定用户码，B 表示风速或特殊功能，C 由模式和温度组合得到。固件再把这三个字节转换为红外脉冲，并按项目约定发送双帧。</p><p>这层编码把网页上的词汇翻译成设备能理解的格式。温度不是直接传一个数字，模式也不是字符串；它们要先查表，再组合成协议字段。关机还有独立的负载，不能和普通状态更新共用同一条假设。</p><p>协议表越具体，越应该同时写出不确定的部分。仓库给睡眠模式的字节留了占位值 <code>0xCE</code>，要求使用真实遥控器重新捕获后替换。这个说明比“睡眠模式已支持”更有价值，因为它告诉后来的人，哪一段仍然没有被当前设备证明。</p><p>同样的边界也适用于型号。代码和 README 针对特定的美的协议做了实现，并不代表所有品牌、所有型号都能收到同样的帧。把一次设备上的成功扩大成通用兼容，会让故障排查从协议差异退化成猜测。</p><h2 id="loop-"><code>loop()</code> 保持轻一点</h2><p>固件的主循环只负责处理 Web 请求和 DNS 请求，没有把耗时工作塞进 <code>loop()</code>。各个处理函数完成参数更新、红外发送和状态回写，职责比较短。这样的代码看起来没有太多技巧，却让检查路径变得直接：页面请求、路由、状态、编码、发射，按顺序往回走就能找到断点。</p><p>网页端也保留了错误显示。如果请求失败，页面会把 HTTP 错误写到调试区域，而不是静默地保持旧状态。它不能替代串口日志和示波器，但至少让“浏览器没有收到响应”和“空调没有识别红外帧”不再混在一起。</p><h2 id="">能力范围要写在项目里</h2><p>这个实现没有 MQTT、远程控制和断电恢复，也没有证明睡眠指令适配所有手里的遥控器。它只完成了一条近距离的本地链路：连热点、打开页面、提交状态、发出红外帧。剩下的功能可以继续做，但不能在已有代码上自动推导出已经完成。</p><p>我越来越愿意把这种边界写进项目说明。小设备的价值不在于拥有一张很长的路线图，而在于每一个已经完成的动作都能回到代码和硬件上核对。先让一个 HTTP 请求稳定地变成一串脉冲，再决定是否需要更大的系统，通常比一开始接入所有平台更容易走完。</p><p>这个仓库最后留下的经验也很具体：先把网络依赖缩小，再把状态写清楚，接着把协议字段和未验证的占位符标出来。硬件项目并不因为体积小就少了工程问题，只是它把这些问题放得更近，近到每一行代码都能对应一次按键和一次闪烁。</p></div><p style="text-align:right"><a href="https://blog.caiths.com/posts/tech/esp8266-ir-controller#comments">览毕，何不一言？</a></p></div>]]></description><link>https://blog.caiths.com/posts/tech/esp8266-ir-controller</link><guid isPermaLink="true">https://blog.caiths.com/posts/tech/esp8266-ir-controller</guid><dc:creator><![CDATA[喵内]]></dc:creator><pubDate>Mon, 03 Aug 2026 16:00:00 GMT</pubDate></item><item><title><![CDATA[一个博客系统的边界，从页面一直到回滚]]></title><description><![CDATA[<div><blockquote>此渲染由 Yohaku API 生成，或存排版之虞，最佳体验请往：<a href="https://blog.caiths.com/posts/projects/build-personal-blog">https://blog.caiths.com/posts/projects/build-personal-blog</a></blockquote><div><p>一个博客最开始只需要一个 Markdown 文件和一个静态托管。文章多起来以后，后台、图片、搜索、RSS、手记和评论逐渐加入，系统也从“把页面发出去”变成了一条需要长期照看的链路。</p><p>我现在会先把它画成几层。浏览器访问 Vercel 上的前端，前端通过公开 API 读取内容，API 由主机上的 Nginx 转给 MX Space，数据落在 PostgreSQL，缓存和搜索又有各自的更新时机。两台备用服务器站在旁边，平时不接流量，只有主机故障或维护窗口才参与切换。层次写清以后，看到 500 时不必立刻重启所有服务。</p><p>日常发文也应该沿着这条边界走。先在后台或 API 创建草稿，写完以后读回标题、正文、slug 和发布状态，再让首页、详情页、归档和 RSS 各读一次。直接改数据库虽然看起来快，却会跳过缓存失效、搜索索引和内容更新事件；数据库里的值正确，页面仍可能是旧的。</p><p>日期是另一种容易被忽略的字段。文章的创建时间决定归档顺序，手记还可能有独立的公开时间；只改 <code>publicAt</code> 不会修复列表里的同日堆叠。没有原始证据时，我宁愿把稿件留成私有，也不为了让时间线好看而假装它在某个日期发生过。</p><p>回滚要提前存在。每次批处理前保存原正文哈希、标题、摘要、日期和发布状态，写回后逐条读回；一条失败就按照相反顺序恢复已经尝试的条目。API 的 <code>modifiedAt</code> 可能无法回到旧值，但内容和公开状态必须能恢复。这样的回滚包比一句“数据库有备份”更接近实际。</p><p>RSS 和前端挑战页也不能只在出问题时看。RSS route 如果把可选的 aggregate 当成必选依赖，手记为空时就可能连文章订阅一起 500；前端若出现浏览器检查页，则要分别检查 Vercel、CDN 和源站，而不是把它归咎于文章内容。每一层都有自己的验收方法。</p><p>博客的维护很少有戏剧性的瞬间，更多是一次次确认：状态码、时间、哈希、日志、回滚目录。它们不适合写成热闹的教程，却能让几年后的自己在升级前知道应该先看哪里。页面只是最外面的一层，真正属于自己的，是这套可以重新解释和恢复的边界。</p><p>因此我把运维动作分成读、写和切换三类。读操作可以随时做，写操作必须有快照和逐条回读，切换操作则要有维护窗口和观察时间。分类以后，很多看似紧急的动作其实可以先用只读证据把范围缩小。</p></div><p style="text-align:right"><a href="https://blog.caiths.com/posts/projects/build-personal-blog#comments">览毕，何不一言？</a></p></div>]]></description><link>https://blog.caiths.com/posts/projects/build-personal-blog</link><guid isPermaLink="true">https://blog.caiths.com/posts/projects/build-personal-blog</guid><dc:creator><![CDATA[喵内]]></dc:creator><pubDate>Sun, 02 Aug 2026 16:00:00 GMT</pubDate></item><item><title><![CDATA[把一批旧文撤下来以后]]></title><description><![CDATA[<div><blockquote>此渲染由 Yohaku API 生成，或存排版之虞，最佳体验请往：<a href="https://blog.caiths.com/posts/thoughts/why-i-write-after-unpublishing-old-posts">https://blog.caiths.com/posts/thoughts/why-i-write-after-unpublishing-old-posts</a></blockquote><div><p>这次重新整理博客，我先把一批旧文撤了下来。</p><p>它们还在，只是不再出现在公开列表里。那些标题原来很显眼，挤在一起像一排说得太满的话。撤下以后，重写也有了一个明确的起点：先确认哪些内容还能认下来，哪些已经没有足够材料支撑。</p><p>有些句子并不差，放在那篇文章里却让我觉得陌生。场景写得完整，情绪也有起伏，我却找不到当时的笔记，想不起那些细节从哪里来。继续改几个形容词没有太大意义，于是把文章收起来。以后若还能找到材料，就换一张空白页重写；找不到，也不必勉强补回去。</p><h2 id="">旧文章不需要一起翻新</h2><p>整理旧文章时，很容易想把文字全部修成现在喜欢的样子。标题统一一点，句子收敛一点，段落重新排得顺眼一些。做完以后，归档会显得整齐，过去的我也像是忽然变得会写了。</p><p>有一篇旧文按要求原样保留。本轮没有读取它，也没有拿现在的标准去改它。</p><p>旧文之间本来就有差别。有些只是表达生硬，内容仍然认得；有些剩下一个还愿意继续想的问题，原来的叙述却接不上记忆；有些到今天依然属于写下它的那一刻。全部翻新成同一种语气，最后留下的可能只有现在。</p><p>博客无需像简历一样随时更新措辞。早年的犹豫、笨拙和没说完，都可以在原处待着。我撤下一篇文章，和它是否稚嫩无关；我只是已经无法坦然认下里面那段第一人称。</p><h2 id="">短的就让它短一点</h2><p>整理备忘录时，我看到很多很小的记录：早上蒸了一个紫薯，刚做出一道积分题，买了清单软件，时间却没有因此变多。它们没有完整的开头，也不负责告诉我这一年学会了什么。</p><p>以前我总觉得，这样的内容撑不起一个页面。真要写，就得在后面接一段关于时间、选择或成长的话。几句话一路加长，最后看起来像一篇文章了，那件小事反而被压在最下面。</p><p>这一次，我把它们放进手记。紫薯就是一顿简单的早饭，积分题只记刚刚想通的片刻，清单软件也可以停在“买了，但日子没有自动整齐”这里。它们各自很短，放在一起却比一个统一的结论更像生活。</p><p>手记不用等着长大。两三句话能够留下当天注意到的东西，就已经够了。隔几年再读，也许想不起那天后来发生了什么，至少还认得那个念头。</p><p>长文则需要更多耐心。一个项目经过几次变化，旧记录和代码能接起前后，才有东西值得慢慢展开。如果只剩一句话，篇幅不够就先不写。归档页少一篇，不会让那段生活跟着减少。</p><h2 id="">重写时，先把那一天找回来</h2><p>有些记忆要靠旧东西才能接上。备忘录里的日期、仓库里留下的修改、今天还能打开的页面，各自带回一小段上下文。它们不会把整件事原样还给我，却能提醒我哪些细节当时已经存在，哪些解释可能是后来才补上的。</p><p>我喜欢这种慢一点的重写。看到一条旧记录，先沿着它往前后翻；遇到说不准的地方，就留一点空白。文章因此少了几个漂亮转折，却多了一些我愿意承认的停顿。</p><p>记忆本来就不整齐。一个项目可能只剩某次修改特别清楚，一段生活也可能只记得一个午后，其他部分已经模糊。把模糊写成模糊，并不会妨碍文章成立。有时正是那句“我已经记不清了”，让我重新认出写字的人。</p><h2 id="">别人的生活留在别人的文章里</h2><p>我还是会逛很多个人博客。有人把几年生活写得很充实，也愿意留下没有完成的计划；有人只写最近的零散近况，读完却能记住其中一个很轻的瞬间。</p><p>这些文章让我知道，章节不用排得一样长，结尾也可以没有答案。读到喜欢的文字，我会留意作者怎样铺开一段经历，在哪里停住，为什么挑中这个细节。</p><p>但我喜欢它，常常正因为那里面是另一个人的生活。大学、项目、旅行和关系都带着各自的来路。把一段经历搬过来，换掉地点和名字，表面也许仍然流畅，里面的人却不见了。</p><p>所以参考到最后，还是要回到自己的记录。若提到别人的观点，就留下出处；若只是喜欢某种松弛的节奏，就关掉网页，从自己记得的那件小事开始。没有材料时空着，也比借一段人生来填满轻松。</p><h2 id="">两三年的空白不用逐格补齐</h2><p>博客搁置了两三年，重新打开时，很容易把“补回来”理解成一张时间表：每一年都该有总结，每段经历都要在归档里占一个位置。这样排下去，空白会显得像欠下的作业。</p><p>可那几年并没有因为少了博文就变空。事情散在别处，可能是一条很短的备忘录，也可能是一个后来没有继续的仓库。它们保存下来的分量各不相同。强行按年份补齐，反而会让材料很少的月份承担太多解释。</p><p>我更愿意从还认得的部分写起。某个项目的前后能接起来，就把那段过程整理清楚；某天只留下几句小记，也让它保持原来的大小。至于已经模糊的月份，归档里留一段空白并没有关系。</p><p>时间线本来就会漏掉东西。博客能做的是捡回其中一些，而不是替过去补拍一套完整的记录。接受这一点以后，“这三年要写多少篇”便没有那么重要了。哪一件事今天仍然让我愿意坐下来回想，哪一段有足够材料可以说清楚，我就从那里写。</p><h2 id="">让归档慢慢长回来</h2><p>旧文章撤下以后，这次整理也不是把过去清空后重新开始。过去几年没有因此消失，博客也不会一夜之间换一副模样。只是一些暂时说不清的内容先离开了公开页面。</p><p>以后归档会慢慢长回来。有足够前后的事，写成长文；只留下一个瞬间，放进手记；仍然对不上记忆的，继续等一等。文章不需要同时出现，也不需要每一篇都很长。</p><p>我还想写博客，是因为许多事当时不记，后来就只剩一个模糊的标题。写下来也不必每次得到答案。几年后重新打开，能认出那是自己做过的事、犹豫过的问题，以及当时没有说完的话，对我已经够了。</p></div><p style="text-align:right"><a href="https://blog.caiths.com/posts/thoughts/why-i-write-after-unpublishing-old-posts#comments">览毕，何不一言？</a></p></div>]]></description><link>https://blog.caiths.com/posts/thoughts/why-i-write-after-unpublishing-old-posts</link><guid isPermaLink="true">https://blog.caiths.com/posts/thoughts/why-i-write-after-unpublishing-old-posts</guid><dc:creator><![CDATA[喵内]]></dc:creator><pubDate>Sun, 02 Aug 2026 00:44:56 GMT</pubDate></item><item><title><![CDATA[这些早期练习还留在仓库里]]></title><description><![CDATA[<div><blockquote>此渲染由 Yohaku API 生成，或存排版之虞，最佳体验请往：<a href="https://blog.caiths.com/notes/31">https://blog.caiths.com/notes/31</a></blockquote><div><p>2023 年建立的公开仓库里，还放着图书管理、茶叶商城和 2048 这类早期练习。现有证据只能说明仓库与代码存在，不能替它们补上“课程作业”的来历。</p><p>我还是愿意留着几个。它们不代表现在的水平，也不必硬塞进作品集。偶尔点开，能看见自己当时怎样命名变量、怎样组织页面，这就够了。</p></div><p style="text-align:right"><a href="https://blog.caiths.com/notes/31#comments">览毕，何不一言？</a></p></div>]]></description><link>https://blog.caiths.com/notes/31</link><guid isPermaLink="true">https://blog.caiths.com/notes/31</guid><dc:creator><![CDATA[喵内]]></dc:creator><pubDate>Sat, 01 Aug 2026 10:54:15 GMT</pubDate></item><item><title><![CDATA[AirScript 不会替你解决边界问题]]></title><description><![CDATA[<div><blockquote>此渲染由 Yohaku API 生成，或存排版之虞，最佳体验请往：<a href="https://blog.caiths.com/posts/tech/wps-script-journey">https://blog.caiths.com/posts/tech/wps-script-journey</a></blockquote><div><h1 id="airscript-">AirScript 不会替你解决边界问题</h1><p>我曾经把签到脚本理解得很简单：把接口请求写进一个定时任务，每天让它自己跑。脚本越写越多，费心的部分逐渐显出来：先确认这段代码在什么环境里运行，哪些行为可以默认执行，以及“成功”到底意味着什么。</p><p>现在这个仓库把任务放进金山文档的 AirScript 里，配置表保存账号信息和执行开关，脚本读取表格后逐个调用任务处理器，再把结果写回表格。看起来像一个小型调度系统，实际却有很清楚的限制：AirScript 的语法、HTTP API、表格 API 和第三方服务的接口变化，都决定了代码能不能按预期运行。</p><h2 id="">先区分运行时</h2><p>普通 Node.js 里习惯使用的能力，在 AirScript 里并不一定存在。这里使用的是同步的 <code>HTTP.fetch/get/post</code>、官方提供的哈希和 HMAC、<code>Buffer</code>、<code>Time.sleep()</code> 以及工作表的 <code>Text</code> 和 <code>Value</code>。<code>class</code>、<code>import/export</code>、<code>await</code>、生成器和部分语法糖都不能想当然地写进去。</p><p>这件事表面像语法检查，落到部署时却是一条硬约束。代码在本地 Node 里能解析，并不能说明它能在 AirScript 编辑器里执行。为此我在本地测试里专门把脚本放进一个模拟运行时，限制可用的全局对象，拦截每次请求，并检查请求选项是否落在官方文档允许的范围内。测试无法证明第三方接口永远不变，但能先把运行时错误挡在账号请求之前。</p><h2 id="">表格也是接口</h2><p>配置表的列并不是随手放的几格。任务标识、凭据、是否执行、账号名称、最近结果和执行时间共同组成了脚本的输入输出。<code>UPDATE.js</code> 负责补齐缺失的工作表和字段，但不会覆盖已有单元格，这让第一次初始化和后续升级之间有了一个比较温和的边界。</p><p>我更愿意把这张表看成一个很小的配置 API。它有输入格式，有默认值，也有错误情况。一个任务被设为“否”时，不应该因为脚本更新就突然开始运行；一条旧结果留在 E 列，也不能被当成今天的成功。每次任务都应该把本次响应和执行时间写回去，读表的人才知道它究竟发生过什么。</p><h2 id="">“已适配”不等于“真实账号成功”</h2><p>仓库目前把任务分成代码验证、受限可用和不纳入三类。模拟契约测试可以确认请求结构、分支和响应解析，但它不会登录真实账号，也不能证明平台下一周还保留同一个接口。把这个区别写在文档里，比列出一张看起来全是绿色的任务表更诚实。</p><p>尤其是带有资产消耗或公开互动的操作，默认应该关闭。投币、点赞、分享、抽奖、兑换等行为带着明确的副作用，可能消耗账号资产，也可能触发平台风控。脚本能做到，不代表脚本应该默认做。</p><p>我也不会把 Cookie、密码或令牌放进示例。配置表会以原文保存凭据，文档、Issue、截图和日志都不能出现真实值。更合适的做法是使用可撤销的凭据，限制表格访问权限，并在任务失败时先查看回写结果，而不是把原始响应发到公共群里。</p><h2 id="">高频任务需要一点耐心</h2><p>有些接口需要分页，有些接口对请求频率很敏感。贴吧之类的任务如果不设上限，很容易从“每天做一次小事”变成一轮没有节制的请求。代码里把页数、数量和重试次数写成显式限制，并让扩展功能保持关闭，这些限制比追求一次运行覆盖更多内容更重要。</p><p>定时任务也不该全部安排在同一分钟。错开执行时间，可以减少瞬时请求，也让出问题时更容易从日志判断是哪一个任务先失败。自动化省下来的时间，来自把重复动作放进一个不会悄悄扩大范围的盒子里。</p><h2 id="">失败也要能读懂</h2><p>最有用的回写结果不一定是“成功”。它还应该告诉我失败发生在哪一步：登录过期、接口返回变化、请求超时、响应解析失败，还是脚本根本没有被触发。如果所有错误最后都变成一条模糊的推送消息，第二天还是要重新打开代码猜。</p><p>所以我会把每个处理器拆成几段：读取配置、构造请求、检查状态、解析响应、格式化结果。某个平台改了字段，只需要看对应处理器和模拟响应；某个任务需要临时停用，也可以通过表格开关完成，不用把整个聚合脚本注释掉。</p><h2 id="">这套脚本的边界</h2><p>它适合个人、低频、明确授权的自动化，不适合绕过平台限制，也不适合把所有账号动作都交给一个没有审计记录的黑盒。AirScript 的限制并没有让项目变得无用，反而提醒我：运行环境、凭据、请求频率、失败处理和真实验证，都是功能的一部分。</p><p>写到最后，签到本身已经不太重要了。最后留下的是一套关于边界的记录：哪些行为经过代码验证，哪些只等自己的账号测试，哪些因为风险而明确不纳入。自动化可以替我重复动作，却不能替我决定什么值得重复。</p><p style="padding:6px 12px;border-left:2px solid #C56473;background:#C5647350;font-style:italic;font-weight:500">Not support render this content in RSS render</p></div><p style="text-align:right"><a href="https://blog.caiths.com/posts/tech/wps-script-journey#comments">览毕，何不一言？</a></p></div>]]></description><link>https://blog.caiths.com/posts/tech/wps-script-journey</link><guid isPermaLink="true">https://blog.caiths.com/posts/tech/wps-script-journey</guid><dc:creator><![CDATA[喵内]]></dc:creator><pubDate>Fri, 31 Jul 2026 08:00:00 GMT</pubDate></item><item><title><![CDATA[旧仓库不会自己变新]]></title><description><![CDATA[<div><blockquote>此渲染由 Yohaku API 生成，或存排版之虞，最佳体验请往：<a href="https://blog.caiths.com/notes/32">https://blog.caiths.com/notes/32</a></blockquote><div><p>一个 2023 年建立的招聘数据爬虫，在 2026 年又有了提交。8 月 1 日的公开快照里，它有 65 个 Star。仅凭这些元数据，还不能判断三年前的代码现在能稳定跑到哪一步。</p><p>Star 不会替代码更新。旧仓库重新动起来时，第一件事未必是加功能；先把现在能确认的范围、没有验证的部分写清楚，也算一次维护。</p></div><p style="text-align:right"><a href="https://blog.caiths.com/notes/32#comments">览毕，何不一言？</a></p></div>]]></description><link>https://blog.caiths.com/notes/32</link><guid isPermaLink="true">https://blog.caiths.com/notes/32</guid><dc:creator><![CDATA[喵内]]></dc:creator><pubDate>Fri, 31 Jul 2026 05:35:16 GMT</pubDate></item><item><title><![CDATA[痕迹不是计数器]]></title><description><![CDATA[<div><blockquote>此渲染由 Yohaku API 生成，或存排版之虞，最佳体验请往：<a href="https://blog.caiths.com/posts/thoughts/trace">https://blog.caiths.com/posts/thoughts/trace</a></blockquote><div><h1 id="">痕迹不是计数器</h1><p>我有一段时间很在意提交记录的数字。</p><p>打开仓库，先看绿色的方块，再点开一条 commit。数字往上走的时候，会有一种事情没有白做的错觉：今天留下了一条记录，明天再留一条，某个阶段就被证明存在过。可提交记录只是一个计数器，它能告诉我做过多少次保存，却不能替我解释这些保存为什么发生。</p><p>后来我开始重新看那些旧仓库。它们并不整齐，也没有一条漂亮的成长曲线。早期的代码会把界面、数据和事件处理揉在同一个文件里，函数名常常只够让当时的自己看懂；文档只写了“怎么运行”，没有写“为什么这样做”。有些仓库很久没有再打开，另一些却在隔了很久之后又出现了修补。</p><p>这些缺口并不丢人。它们只是说明代码曾经处在一个具体的时间里。人往往要先把东西做出来，等下一次遇到同样的问题，才知道上一次哪里留下了债。</p><h2 id="">三个小仓库</h2><p><code>BookManager</code> 是一个用 Python 和 PyQt5 写的图书信息管理系统，仓库带有 MIT 许可证，最早的提交在 2023 年 12 月，后来又有一轮文档和小修补。它没有被包装成什么大型系统，项目目录里能看到窗口、表单和数据操作各自留下的痕迹。现在回头看，我更在意它让我第一次必须面对的那个问题：“界面上的一个动作到底会改动什么数据？”</p><p><code>wps_script</code> 是一组放在金山文档 AirScript 环境中运行的日常任务。仓库同样以 MIT 许可证发布，最近的公开提交集中在任务适配和表格验证上。它和课堂作业不太一样：代码不是写完就结束，外部接口变了，原本能工作的任务就会沉默。维护它需要先确认输入、输出和模拟测试能证明什么，再决定哪些变化值得写进脚本。很多时候，所谓“修好了”只意味着契约测试重新通过，并不等于真实账号已经验证完毕。</p><p><code>bosszhipin_spider</code> 的公开说明把它描述为 Python 与 Pyppeteer 组成的招聘数据采集工具，仓库有 MIT 许可证，也保留了爬虫与数据合并测试。这个项目让我对“能抓到数据”和“应该怎样使用数据”之间的距离更敏感。代码可以公开，测试可以公开，真实职位数据、个人信息和站点规则却不能因为出现在一个仓库里就被随意搬走。留下代码，不等于留下所有上下文的授权。</p><p>这三个仓库没有共同的技术栈，也没有组成什么宏大的产品线。它们只是分别记录了界面、接口适配和数据处理的几个阶段。把它们放在一起看，反而能看到一条更可靠的线：从“让页面动起来”，到“让任务在变化中继续工作”，再到“先问数据是否应该被处理”。</p><h2 id="">旧代码没有替我成长</h2><p>有时我会把仓库更新误认成成长。只要最近还有 commit，就好像自己仍然在前进；很久没有提交，就像某段时间被从履历里删掉了。可代码不会因为被推送到远端就自动变得成熟。一个仓库也不会因为拥有更多星标就替我承担维护责任。</p><p>真正留下来的，是一些更细小的判断：把一次重复的修补写成测试，把外部接口的假设记在文档里，把不能公开的数据挡在仓库之外，在没有必要时不再增加一个功能。它们没有明显的仪式感，甚至很少出现在项目介绍里，却比一个好看的数字更接近工程经验。</p><p>我现在打开旧仓库，先看的不再是提交总数，而是三个问题。这个项目当时想解决的具体麻烦是什么？哪些地方只在我的电脑上成立？如果今天交给另一个人，他能不能从文档和测试里判断边界？答案往往不够漂亮，但至少可以继续往下做。</p><h2 id="">让痕迹保留原来的重量</h2><p>代码和照片有一点相似。照片能证明某个瞬间被按下快门，却不能自动说明当时的人在想什么；提交能证明文件被保存过，却不能替我补写当时的动机。若要把旧项目写成文章，最容易犯的错就是给它加上一条顺滑的故事线，把零散的修补说成早已规划好的路线。</p><p>我不想再这样处理自己的记录。能从公开仓库核对的，就写仓库实际呈现的内容；没有证据的数字和场景，就删掉；涉及他人和第三方数据的部分，宁可留在原处。文章可以有情绪，但情绪不能替事实增加一条提交记录。</p><p>这些项目以后也许还会被打开，也许会一直停在某个旧版本。它们不会替我证明已经成为怎样的人，只会安静地告诉我：在某些具体的下午，我曾经把问题拆开，试着让下一次运行少一点意外。</p><p>这就够了。痕迹不是计数器，也不是一份需要不断美化的成绩单。它只是让过去保持可核对，让后来的人，包括未来的我，知道一段代码从哪里来，又在哪些地方需要重新开始。</p></div><p style="text-align:right"><a href="https://blog.caiths.com/posts/thoughts/trace#comments">览毕，何不一言？</a></p></div>]]></description><link>https://blog.caiths.com/posts/thoughts/trace</link><guid isPermaLink="true">https://blog.caiths.com/posts/thoughts/trace</guid><dc:creator><![CDATA[喵内]]></dc:creator><pubDate>Thu, 30 Jul 2026 01:00:00 GMT</pubDate></item><item><title><![CDATA[一个数字不会替你证明什么]]></title><description><![CDATA[<div><blockquote>此渲染由 Yohaku API 生成，或存排版之虞，最佳体验请往：<a href="https://blog.caiths.com/notes/13">https://blog.caiths.com/notes/13</a></blockquote><div><p>仓库页面上那个数字又变了。</p><p>它从一个个位数慢慢走到三位数，中间没有掌声，也没有哪一次提交因此变得更好。数字只是 GitHub 在某个时间点显示出来的统计，能证明有人点过关注，不能证明有人真的把项目用进了自己的生活。</p><p>我以前会把 Star 当成结果，后来更愿意把它当成提醒：README 要不要更新，安装命令还能不能跑，Issue 有没有回复，下一次打开仓库时自己是否还看得懂。一个项目被看见以后，维护才刚刚开始。</p><p>所以这条记录不写“终于成功”，只记下我看到数字时停了一会儿。代码真正留下来的证据，还是提交、文档和有人遇到问题时能否得到回应。</p></div><p style="text-align:right"><a href="https://blog.caiths.com/notes/13#comments">览毕，何不一言？</a></p></div>]]></description><link>https://blog.caiths.com/notes/13</link><guid isPermaLink="true">https://blog.caiths.com/notes/13</guid><dc:creator><![CDATA[喵内]]></dc:creator><pubDate>Sun, 26 Jul 2026 07:06:55 GMT</pubDate></item><item><title><![CDATA[把一个签到脚本重新捡起来]]></title><description><![CDATA[<div><blockquote>此渲染由 Yohaku API 生成，或存排版之虞，最佳体验请往：<a href="https://blog.caiths.com/posts/tech/wps-script">https://blog.caiths.com/posts/tech/wps-script</a></blockquote><div><p>2026 年 7 月底，我又打开了 <code>wps_script</code>。</p><p>仓库的第一批提交在 2023 年 7 月 19 日。当时放进去的是运行在金山文档 AirScript 里的签到脚本，README 很短，提交也很密集：加一个任务，修一个失效接口，再补几行说明。</p><p>三年后的快照里，它有 145 个 Star、18 个 Fork，Issue 列表有 11 条记录，其中 10 条还开着。这些数字没有大到能证明什么“成功”，但至少说明，这个很小的仓库没有完全停在 2023 年。</p><p>还有人在配置时卡住，还有人问某个平台为什么报错。所以我回来了。</p><h2 id="">先把账记对</h2><p>隔了几年，人很容易把一个断断续续的项目记成一条完整的线。真正对照提交历史时，我才发现自己原来写下的起源故事，与仓库留下的记录对不上。</p><p>可确认的起点其实很普通：最早的 README 已经把它写成金山文档 AirScript 脚本集合。2023 年的我整理和维护过这批脚本，2024 年有过零星更新，2026 年 7 月又做了一轮 AirScript 适配、文档和测试整理。</p><p>还有一件更应该说清楚的事：项目中的原始脚本基础和 DailyCheckIn 参考实现，包含“在虎”与 Sitoi 以 MIT 许可开放的代码。我负责当前的 AirScript 适配、文档和后续维护。这层关系现在写进了 <code>THIRD_PARTY_NOTICES.md</code>。</p><p>整理、改造和长期维护本来就是开源工作的一部分，没有必要把它包装成一个人从空文件开始的故事。来源写清楚之后，这个仓库反而更像它自己了。</p><h2 id="">提交历史把三年切成了几段</h2><p>仓库记录没有给出一条持续更新的漂亮曲线。2023 年 7 月 19 日放进最早的一批脚本，8 月开始整理聚合版本；2024 年 4 月有过一轮脚本和文档更新，之后又安静了很久。直到 2026 年 7 月 25 日和 26 日，提交记录里才重新连续出现 AirScript 适配、任务整理和表格验证。</p><p>这样看，它更像三段相隔很远的维护，而不是一个坚持三年的连续项目。文章如果只写起点和今天，很容易把中间的空白自动抹平。可空白也属于仓库：接口在变，Issue 在积累，而我没有一直跟着它。</p><p>重新开始时，我没有办法假装自己还记得所有决定。最早为什么这样组织配置，某项任务为何默认开启，一段请求是不是从参考实现沿用过来的，都得回到提交、README 和许可文件里重查。隔了几年再维护自己的代码，体验和接手别人的项目并没有想象中那么不同。</p><h2 id="">这次没有继续加菜单</h2><p>2023 年整理签到脚本时，最直接的成就感来自列表变长：这个平台的请求跑通了，就再接下一个。到 2026 年回来时，我更在意的却是哪些不应该默认运行。</p><p>因为有些所谓“签到”，实际会改变账号状态。投币、抽奖、兑换、分享或其他公开互动，不是一行“执行成功”就可以一笔带过的副作用。即使用户已经配好凭据，脚本也不该替他默认做完所有可做的事。</p><p>所以这次的聚合版本只纳入 13 项当时仍适合 AirScript 的任务，并把投币、抽奖、兑换、观看与公开互动放在显式开关后面。对请求量大的流程，也要有上限。</p><p>有几项直接没有放进去。公开任务接口已经提示下线的，不继续做一个形同虚设的按钮；需要复杂加密、设备信息、定位或真实申购的，不塞进普通签到；需要伪造个人运动数据的，也不做。</p><p>从功能表上看，这类删减不像升级。对一个要拿到自己账号凭据的脚本来说，它们比多几个平台重要。</p><p>这也是我对“功能数量”看法变化最大的地方。聚合脚本很容易把列表长度当成卖点，平台越多，README 看起来越完整。但每加一个平台，也多了一份凭据处理、接口变化和副作用说明。用户真正承担的风险不会因为它们被装进同一个按钮就消失。</p><p>所以 13 项不是“目前只能做 13 项”的遗憾数字，它只是那次整理里能够在既定边界内留下的范围。以后如果再加，应该先回答它需要什么凭据、会不会改变账号状态、失败后怎样看见，而不是先让表格多一行。</p><h2 id="">测试通过，不等于真实账号一定能跑</h2><p>7 月 25 日到 26 日的那轮提交，主要处理了 DailyCheckIn 到 AirScript 的适配、表格配置、文档和模拟测试。当前测试覆盖 13 项任务的核心分支，也检查了几个可选操作。</p><p>但这里有一条不能被“测试全绿”盖过去的线：这轮整理没有使用用户的 Cookie、密码或令牌去做线上真实账号测试。</p><p>因此，README 里的“已适配”，只能说对当时代码的语法、请求结构、分支和模拟响应做过检查。它不保证每个第三方私有接口今天都有效，更不保证真实账号不会遇到风控。</p><p>以前写项目介绍，我很容易把“测试跑过了”写成“功能已稳定”。但这类工具同时依赖十几个外部平台，仓库能控制的范围其实很小。外部接口什么时候改，账号处在什么环境，风控规则怎样变，都不在代码仓库里。</p><p>能做的是把检查过的部分写得窄一点，同时把不知道的部分留在文档上。</p><h2 id="-issue">再打开仓库时，先看 Issue</h2><p>145 个 Star 是一个容易被写进标题的数字。这次重新打开仓库，我先看到的却是 10 条还没关闭的 Issue。</p><p>它们有些缺少运行环境，有些已经指向改掉的接口，也有些只是文档没说清楚。不一定每一条都能修，它们至少比一个总数更接近这个项目被使用时的样子：有人配置失败，有人看不懂一个参数，有人仍在等一个过期接口恢复。</p><p>Star 和 Fork 也会保留很久，Issue 却更像现在时。发布这篇草稿前的快照里，仓库是 145 个 Star、18 个 Fork，10 条 Issue 仍然打开。这些数字以后还会变，所以它们只能说明 2026 年 8 月 1 日我回来时看到的状态，不能继续留在标题里充当永久结论。</p><p>这次重新维护，我给自己定的最低标准也变了。刷新最后提交时间没有多大意义，后来的人需要看得出哪些任务只做过模拟检查、哪些行为默认关闭、哪些外部接口可能已经失效。如果一条 Issue 暂时解决不了，就留下可复现环境或明确边界，别用“后续优化”把它推远。</p><p><code>wps_script</code> 到现在仍然只是一组小脚本。它没有变成一个庞大的开源项目，也不需要被讲成个人成长的证明。</p><p>它只是在 2023 年被留下，三年后还有人打开，于是我回来对了一遍提交，补上许可声明，把能确认的部分重新整理，也把不敢承诺的部分写出来。</p><p>下一个打开它的人，至少不用先穿过一个比仓库本身更精彩的故事。</p><p>项目地址：<a href="https://github.com/poboll/wps_script">github.com/poboll/wps_script</a></p></div><p style="text-align:right"><a href="https://blog.caiths.com/posts/tech/wps-script#comments">览毕，何不一言？</a></p></div>]]></description><link>https://blog.caiths.com/posts/tech/wps-script</link><guid isPermaLink="true">https://blog.caiths.com/posts/tech/wps-script</guid><dc:creator><![CDATA[喵内]]></dc:creator><pubDate>Sun, 26 Jul 2026 01:45:28 GMT</pubDate></item></channel></rss>