木匣子

Web/Game/Programming/Life etc.

以 Maildir 方式备份邮件

2023 年就这么过去了。AIGC 的出现让很多事情发生了改变。我使用搜索引擎的频率急剧降低,连查阅维基百科的次数也少了很多。AI 使得学习一些新的领域的门槛变得非常低,虽然它帮我打开了探索新世界的大门。但另一方面,不知道为什么我的表达欲也下降了很多。我觉得很多东西不再需要记录。大部分东西已经埋藏在那几千万亿的参数里。

十二月底,我没有像以往一样做点年终总结。相反,我翻了翻很多以往留下的文字、项目的备份。清理了网盘、NAS,把没用的东西都删除掉了。

有一些老帐号上的邮件我想把它们下载到本地,打包备份起来。我没像以往打开 Google 搜索别人总结的备份方案。而是很自然地打开 ChatGPT,询问了常见的邮件备份格式,其中 MailDir 以它及其简洁的方案吸引了我。只需要建立一个目录,里面再建三个空目录cur, new and tmp 即可。

Mails
└── your-account
    ├── cur
    ├── new
    └── tmp

然后 ChatGPT 还向我推荐了一些常用的支持 MailDir 的邮件备份工具,我选中了简单易用的 Getmail6 (v6 是使用 python3 实现的版本)。同时 ChatGPT 还根据我的需求提供了相应的 POP3 配置案例: ~/.config/getmail/getmailrc

[retriever]
type = SimplePOP3Retriever
server = your.pop3.server.here
username = your_username_here
password = your_password_here
port = 110

[destination]
type = Maildir
path = ~/Mails/your-account-folder

[options]
read_all = false
delete = false

配置完后,在终端运行 $ getmail 即可开始下载邮件,因为配置了 read_all = falsedelete = false 所以是纯备份模式,服务器上的邮件不会被改动。运行期间如果主动 Ctrl-c 终止运行,getmail 会将已下载的邮件 id 写入到 ~/.config/getmail/oldmail-host-port-your-accout 文件中,避免下次运行的时候重复下载。

但是如果期间出现服务器连接超时,getmail 似乎没能将缓存的数据写入文件中。这会导致重新运行 getmail 会重复下载邮件。以往遇到这样的事,往往要花很多时间去想办法处理。但现在有 ChatGPT,我只需要简单描述一下应当如何清理重复的邮件,它很快便能编写出一段 Shell 脚本来完成任务,用后即扔。

#!/bin/bash

# 创建一个空的临时文件来存储 Message-ID 和文件名的映射
temp_file=$(mktemp)

# 递归地遍历包含邮件的目录及其子目录
find /path/to/your/emails/ -type f -print0 | while IFS= read -r -d '' file; do
    # 提取 Message-ID 并将其与文件名一起添加到临时文件
    msg_id=$(grep "Message-ID:" "$file" | cut -d ' ' -f 2)
    echo "$msg_id $file" >> "$temp_file"
done

# 对临时文件进行排序
sort "$temp_file" -o "$temp_file"

# 使用 awk 处理重复的 Message-ID,保留第一个,删除其余的
awk '{if ($1 != last) {last = $1; next} print $2}' "$temp_file" | xargs rm

# 清理临时文件
rm "$temp_file"

花絮:ChatGPT 第一次给我的脚本会把所有重复的文件都删除,一个副本也没有留下。不过我在 Review 代码的时候特地留心看它怎么处理这个问题,结果发现它没处理。于是让它马上纠正了。

经过一个多小时,总算把 1500+ 邮件备份到本地了。中间清理重复邮件也算顺利。每个邮件正文和附件使用 MIME 格式以文本的形式存在 MailDir 的目录中。但是要怎么浏览它们呢?ChatGPT 向我推荐了一些命令行工具,其中 NeoMutt 深得我心。虽然 NeoMutt 客户端也可以配置邮件服务器进行收发邮件,但是我只需要它的浏览 MailDir 的功能。最重要的是,它可以零配置直接使用:

$ neomutt -f ~/Mails/your-account

NeoMutt 是一个纯命令行工具,非常易用。它还能帮你将 MIME 里的邮件附件导出到文件系统。超级给力!

ChatGPT 在我不熟悉的领域提供了极大的帮助,也改变了我的工作流程。2024年,期待 AIGC 更强大的功能。

后疫情之中国行

Plan

大流行(Pandemic)结束之后,我和 LP 就一直盼望着找个时间带四岁的曦仔回中国去见见从未某面的亲人。直到今年三月份我们才敲定行程,买了好六月份的机票,并请了一个月的年假。

曦仔两岁的时候就办好了护照,但是一直没能如愿出行。去年圣诞节的时候我们一家人和朋友相约去了新西兰,领略了自然风光的上限。发现曦仔对三个多小时飞行也没有很排斥。但这次他要面对的是九个半小时的飞行时间。去程是白天,于是我们早早准备了各种可以让他在飞机上打发时间的小玩意。

我的 Covid 经历

2020年中,坐标墨尔本,夫妻两人全职工作,娃刚满一周岁,工作日接送早教中心。在起初的一年多的严格封城下,如果没有开看世界各地的疫情短报,我甚至玩笑般地怀疑这个病毒是不是真的存在,毕竟也没有亲眼见症身边有谁被感染或者隔离。

2021年,看着曲线平缓,甚至一度有数周没有新增。感觉完全解封有了盼头,但是随即年底出现了新变种 Delta 和 Omicron 曲线激增。中间有三个月连早教中心都关闭了。娃两岁多,受疫情影响,我们俩都换了全职远程工作,但是在家带娃还是非常有挑战。身边开始有同事和朋友接连中招,多数是轻症,还有同事家里的老人因 Covid 离世。

2022年年初,维州政府放出了解封路线,随着大范围疫苗接种率接近95%,各种营业场所陆续开放。我完成了三针疫苗接种,老婆两针,娃不满五岁,无法接种。但曲线已经从早期最高一二十例,变成了日常五六千例。早教中心已经开放,但是老婆一开始还是拒绝把娃送去。因为在妈妈圈偶尔听闻有早教中心出现病例。经过一断时间的观察思考以及挣扎,还是决定开始每日接送。

2022年上半年,早教中心还算顺利,期间如果娃有轻微感冒症状,只需要在家使用 RAT 抗原自测,15分钟出结果。如果是阴性且没有流感迹象是可以继续接送。期间早教中心有邮件通知园期出现密接,但得益于园期的卫生工作做得挺好,大家都没有大碍。

2022年年中,政府解除了口罩令。终于可以自由呼吸了,在商场超市看到了久违的谈笑风生。久居家中的人开始呼朋唤友出来玩。于是我们一家人约了另一家小伙伴去乐高世界玩,期间妈妈们跑去一个封闭影院看了 4D 乐高短片,随后两家人一起在餐厅吃了晚餐。

两天后对方妈妈发来通知说自己阳了,然后老婆也觉得喉咙不适。这时候娃已经在早教中心,于是我开着车带着老婆去附近的 PCR 核酸检测点做了个筛查。凌晨三点老婆忐忑不安收到结果,她的短信内容很长:阳性 + 一堆后续注意事项,而我只有短短一行:结果为阴性。

隔日早上我查询了一下政府的 Covid 专题网站,里面提到了家庭密接的情况,以及能否将孩子接送早教中心的事项。答案是很明确的,只要父母方有一人当天是阴性,且孩子也是阴性,是可以接送的。于是接下来的每一天,除了家里做好隔离以外,每天早上都做了 RAT 抗原自测阴性后,就把娃送去早教并告知检测结果以及做好卫生工作。

第一天,老婆低烧 + 肌肉酸疼,简单请了个假,然后吃了点 Panadol 止痛药便于安稳休息。中午还收到政府人员的慰问电话,询问是否需要到隔离酒店免费住宿。但两人商量后还是觉得自己在家隔离比较舒服,也方便照顾。比较头疼的是晚上的需要隔离分开睡,但是娃坚持要跟妈妈一起,死活不肯跟我睡沙发,只能听天由命。老婆第二天已经好转,只剩下感冒症状,卧床刷了一天剧。第三天已经无聊到拿起电脑开始工作了,第五天感觉好了,但是 RAT 自测还是阳,只不过 T 线开始变浅。

接下来三天是小长假周末,因为不用接送娃,所以我也没有自测。从朋友家了解的情况是只有妈妈阳性,而爸爸和孩子一直阴性,现在妈妈已经转阴了,他们又可以带娃出去解闷。

可能有点松解,周末放松了警惕,结果周二的时候我开始嗓子不舒服,果然 RAT 自测阳了!好消息是老婆这时候自测转阴了。娃一直是阴,于是隔离和接送的任务倒置过来。我的症状也不重,第一天晚上稍微头晕,吃了止痛药然后就睡了。之后的几天只是感觉嗓子里有点东西。过完第二星期我也转阴了,但是嗓子里的东西持续了五天才完全消失,我一度以为是不是什么后遗症。

完事后我发了个朋友圈,记录一下最近的情况,说隔离给我的影响比得 Covid 还要严重,然后在国内老父亲的呵斥下删 (si) 除 (mi) 了。(可能老人家觉得我不到百来人的朋友圈影响力比卫健委还大吧 :doge: )

以上就是一位轻症患者的 Covid 流水。

Ref: https://www.v2ex.com/t/862622

创建自签名 SSL 证书

疫情仍然在继续,但是维州已经开始恢复生机。我大概又歇了小半年,除了工作时间,下班之余几乎没有碰与技术相关的东西。同时由于近来关注了太多社会新闻,反而让自己陷入了政治性抑郁。刷推的时候刷到一个无聊的放置类手游竟随手下载来玩了一个星期,也算放空一下自己,转移注意力,好重振旗鼓。 回顾了一下半年多以前开发的 Axidraw Web,有不少可以分享的点东西,准备挑几个记录一下。

AxiDraw 是一个双电机的二维绘图仪。可以将 SVG 绘制到纸面上。在研究完它的底层串口协议后,我尝试用 javascript 实现了一个能在网页上运行的 Web 小程序,能通过 WebUSB API 与 AxiDraw 单片机直接进行通讯,同时实现了 SVG 的绘制功能。

WebUSB

在开发 Axidraw Web 的时候,由于 WebUSB API 需要在安全的浏览器环境下(Secure Contexts)才能使用。在较早的 Chrome 浏览器上(version 83 之前),localhost 是否属于 Secure Contexts 尚未确定。而这类 API 默认只对 https://localhost 开放。在我写这篇文章的时候,文档中已经明确将 localhost 定义为安全环境。如果在开发环境中使用了较老的浏览器或者其它的自定域名,还是需要自行提供 TLS 支持。主要工作就是提供一个自签名证书,然后在 web server 中使用:

import express from 'express';
import https from 'https';
import fs from 'fs';

const app = express();
app.use(express.static('dist'));

const options = {};

try {
  options.key = fs.readFileSync('server/cert/dev.key');
  options.cert = fs.readFileSync('server/cert/dev.crt');
} catch (e) {
  console.error('Please create and install the SSL cert first.');
  process.exit(1);
}

const server = https.createServer(options, app).listen(8443);

写在 2021 年末

又过了没写博客的一年。曦仔已经两岁半了,这一年真是喜忧参半。然而疫情还是一眼望不到头。

家庭

一年前,休了一年半产假的 LP 重新回到工作岗位。这回如愿的换了个工作,离开了她在澳洲的第一个雇主。曦仔也开始了 Childcare 之旅,而我已经经历了大半年的远程办公(Working from home)。

虽然远程办公让我有更多的时间休息,免于通勤的奔波,不过这也是曦仔夜惊症(Night terror)非常严重的时期。他的睡眠不再像一岁的时候那样安稳。每天深夜都要哭闹数次,且处于非清醒状态。夜晚我们只好轮流值守,不过多半时间曦仔只要妈妈的怀抱。于是到了白天我们拖着疲惫的身驱把他送去日托,然后投入工作。而这样的情况持续了八九个月,直到最近才有所好转。这大概也是工作休息之余我再没有精力书写博客的原因。

在 macOS 上使用 eGPU 的体验

前阵子在处理动森二维码的服装预览模型的时候,玩了一下 Blender。于是又勾起了我小时候玩 3D Max 的回忆。于是好想给手上这台 Macbook Pro 2016 配个显卡,深入学习一下 3D 建模。

于是在网络上搜索了好一阵,发现可以使用 eGPU(外置显卡坞)将独立显卡接到 Macbook 上使用。苹果的官网有一篇文章介绍了目前各个版本的系统支持的 eGPU 以及显卡的情况。

仔细看了一圈下来,发现由于历史原因近几年新出的 Macbook 并不支持 nvidia 显卡。看来是没法享受最近要发布的 RTX 3080 系列,只能选择 AMD 显卡。经过简单的预算,最终选择了 Razer Core XAMD RX 5700 XT 来体验一番。

给 Apollo-Link 打补丁 II

继上回给 Apollo Link 打补丁过去了几个月,本想翻看一下之前的那个 Pull Request 是否已经被 Merge,结果没想到原来的 Apollo Link 库已经被弃用了。新版的 Apollo Link 被重构后集成到了 @apollo/client 包中。不过并没有修复前面 PR 提到的问题。更糟糕的是之前使用的打补丁的方式无法起作用了。新的 selectHttpOptionsAndBody() 函数被专门放置到一个独立的 module 文件中。并且在提供的 cjs bundle 里,它直接被 createHttpLink 函数引用。所以之前的替换方法没办法在函数被引用前进行「狸猫换太子」。

那么是否有其它成本比较低的方式进行补救?于是我简单的搜索了一下 Webpack 的官方文档,很快找到了一个叫 NormalModuleReplacementPlugin 的内置插件。使用它可以非常方便地替换指定的模块文件。

new webpack.NormalModuleReplacementPlugin(
  resourceRegExp,
  newResource
);

动森二维码生成器:服装类(后篇)

前篇写到动森二维码生成器的二维码划分规则以及预览模型的 UV 贴图。根据我的设想,接下来我要在 Aseprite 画板上将这个模型渲染出来,并将用户绘制的图案通过 UV 贴图的方式绘制到模型上。而目前我所拥有的东西只有:调整过 UV 贴图坐标的 6 个衣服模型、在 Aseprite 读取和写入像素的 Lua API,还有记忆中模糊的图形学知识。本文简单记录一下我当时的思路,以及遇到的一些细节问题,如果对实现有兴趣,可以直接访问 Github 上的该项目

动森二维码生成器:服装类(前篇)

在开发动森二维码生成器的时候,对于服装类设计的生成,一开始我并没有很好的想法。游戏中对贴图的是分块绘制的,并且有一个动态模型可以展示衣服绘制的效果。对比同类工具 Animal Crossing Pattern Tool(下称 ACPT),他们也提供了 3D 模型用于展示贴图效果。但要如何在 Aseprite 中实现展示 3D 模型,我并不是很有把握,毕竟这是一款用于绘制 2D 像素图的工具。另外如何给贴图分块,也需要深入分析。本文先记录一下贴图的划分,之后再写一篇分析 3D 模型的渲染。

动森二维码生成器 for Aseprite

最近翻到一本不错的像素书:由 Nostarch Press 出版的 Make Your Own Pixel Art。Nostrach Press 专门出版极客类的书籍,里面有很多看到标题就有兴趣读下去的书,非常适合我。

而这本 Make Your Own Pixel Art 的作者 Janeifer 拥有 30 年的像素画经验,在书中非常系统地讲解了像素画入门的各个方面,还提供了不错的练习素材。并且书中使用的编辑器正是我非常喜欢的 Aseprite。正好最近时间比较多,就又拿起来画板和鼠标瞎画点东西。

而更让我惊喜的是,这款买了很久的小而专业的像素绘画软件,居然支持自己编写插件。从 Aseprite v1.2.10-beta2 版本开始,作者引入了 Lua 语言来扩展编辑器,允许用户编写一些定制化的功能

于是我开始琢磨着用它做点什么。正好最近在玩动物森友会,游戏里允许玩家自己创做像素风格的图案用来装饰各种物品、衣服、地面。另外,新版的 Animal Crossing: New Horizons 可以通过手机 App 扫描早期版本的 Animal Crossing: New Leaf (下称 ACNL) 玩家绘制的图案的二维码,导入这些现存的图案。

但要知道,不像 3DS 上的 ACNL 可以使用双屏 + 画笔来做画。在 Nintendo Switch 上使用手柄做画有多难受,可想而知。虽然基础画板只有 32 x 32 像素,但是切换颜色、工具等操作,实再不如在电脑上作画来得舒服。我想,如果能直接在 Aseprite 这种专业软件中作画,然后直接生成二维码,再传送到游戏里使用,岂不是非常方便。