This blog is rated 🔞, viewer discretion is advised

Little known facts about Han

I was planning to publish this article on 2024-04-05 00:01, but rejected because it looked silly for this blog. On that day, butt hurt as usual, I watched this short-vid called Why Sichuan people were chill, by the influencer 罗胖. I was quite amazed by his 3.79M many fans and more strangely, IP location: Sichuan. I followed the article he mentioned: 1001 A.D. and How Sichuan got its name? and find it quite interesting, as all history lessons are.

So let's explore the little known details and find out why history of Sichuan is somewhat special (Whig-style narrative alert!)

The birthplace of Taoism

Records shows that the birthplace of Taoism, the most chill religion ever, points to 鹤鸣山 at 大邑, and the 青城山 nearby was considered a holy mountain. Some say 龙虎山 at 江西 as an alternative, but in my opinion its where the tianshi (prophet) 张道陵 pactice his alchemy rather than the developing the belief,The real deal including Wudoumi movement clearly originates from 龙门 mountain ranges on the western of Chengdu plain.

Inspired by Buddhism

When I was searching for tourist attactions I found that 鹤鸣山 were suspiciously connected to Buddhism. Chinese Buddhism were first landed at 白马寺 at then capital 洛阳 at 68 B.C. But 白马寺 itself wasn't supposed to be a temple per se, but rather a settlement sponsored by the imperial court. There were nine houses of governance established by Han court covering Justice, Sacrifices, Royal affairs, Carriages and so on, one of them is 鸿胪寺 for Royal Hospitality. The 白马寺 was built by the order of the Second Emperor of E. Han as a guest house to translate the 42 chapters of Sutra acting like a 鸿胪寺. Some 6 years later, after the job were done, the two buddhist monks, 迦什摩腾 and 竺法兰, went to Sichuan seeking a legendary mountain called 雾中山 according a prophecy. The prophecy were carved on a Ming-era stele 开化寺碑记, describes the Buddha Shakyamuni, when about to enter Nirvana in the city of Kushinagar, once said to his disciple Sariputta:

“我灭去七百年,尔往震旦雾中大光明山。山脉发源于昆仑,有七十二峰,为古佛弥陀道化之所。严密保护,嗣后圣者来居。”

So the two Indian monks built a real Buddhism temple 开化寺 on 雾中山 and began spreading the religion there. It's said the temple had a collection of Pāli Canon transcribing Theravada Buddhism, one of the O.G. version of two Buddhism major branches in Asia.

So what's the connection anyway? The first prophet of Taoism, tianshi Zhang, chose 鹤鸣山 somewhere 10km near the first Buddhism temple

And later Taoism flourished in Sichuan, like Wudoumi, and ultimately, its variant Taiping-Tao, wrecked the mighty Han empire.

I also commented this travia on HN

The doctrine of religious Taoism and Buddhism were strikingly similar, which leds to series of conflicts and accusations over the millennia. The most famous example, Laozi Converted the Barbarians 老子化胡经 were publicly debated in Mongol-era imperial court and Taoist lost the battle.

Silk

Also an interesting article from HN Evidence of the use of silk by Bronze Age civilization, which I also commented, because some guy suggests Sichuan were once non-Han, it triggered me. TFA states (with my edits):

HuangDi lived on the hill of Xuanyuan and married the daughter of Xiling clan. HuangDi's consort Leizu of Xiling taught others to raise silkworms, and the legend Leizu is from the people of Chengdu in Sichuan. Shu refers to “mulberry worms” aka “silkworm larvae”. The character Shu is related to the initial sericulture, referring to the beginning of the Shu State and the people, for whom sericulture was central to their economic activities.

HuangDi

For those who aren't familiar, HuangDi the Yellow Emperor was considered the ancester of the Han, the Hun, the Hmong and Xianbei of the Siberians. The second son of HuangDi, married ChangPu from the Shu clan. Their son zhuānxū was another Di, out of the 5 Di of Ancient Kings 五帝

Baijiu

Baijiu, the Kaoliang liquor, strong distilled from great millet, were first introduced in Sichuan known as 蜀黍. Even today top notch brands like Maotai were from the Chishui Valley of Sichuan (administratively divided to Guizhou by purpose)

Han

But what does Han has anything to do with Sichuan? Well everything does. The literal meaning of "Han" traces back to LiuBang, the Great King of Han, founder and first ruler of the Han dynasty, literally settled his fief over Sichuan (Ba, Shu, Hanzhong and its 41 counties). Liubang spent his next 5 years elimilated all his enemies and began the first Pax Sinica of 300 years.

The second Pax Sinica, led by the Great Worrier Li Shimin, also titled Yizhou Acting Desk of the Supreme Book. To explain, the Supreme Book refers to 尚书, the source of great power, the source of ultimate authority, one and only text that describes how Chinese civilization came into being since ancient times. The Desk is the government body where the book operates, Yizhou Acting Desk is like the copy that covers Sichuan, aliased Sichuan. The soldiers of Sichuan, as the last reserve force led by general 窦轨, joined Li Shimin for the final attack on Dou JianDe on 620 A.D. at the Hulao Pass, which leds to the live capture of two kings, and helped Li Shimin rose to throne.

8 Partners of Oath

The fighting spirit of Sichuan soldiers dates back. An oath was taken between Zhou and 8 partner states, including Shu, as described in the Supreme Book on Zhou chapter 4, to rebel against the cruel Shang dynasty. The battle took place at 牧野 on 1046 B.C. where the Ba people performed a war dance on the frontline and Shang army collapsed upon witness.

Libai and the Great Prose Masters

Libai, the God of Poetry, was raised in Sichuan, but where he's born is debatable. However, four out of the Eight Great Prose Masters, were born in Sichuan. So it's either Libai a Sichuanese or Ouyang Xiu, or both.

... and there's more

That's all of the trivia I could think of, for now! Leave a comment if you think otherwise. Feel free to correct my English mistakes please, as I am an ESL blogger without use of any AI

Posted

stdout

ss命令抓linux下偶发端口访问

Linux服务器一直有个TCP连上来发数据,跑到对应的机器上发现连接已经断了,对应的进程也退出了。估计是某种定时任务。

排查代码无果,只能通过命令行来监控。这里直接上ss命令

  while true; do pid=$(ss -tanpe state established 'dst 10.11.22.33:4455'  | awk 'match($0,/pid=([0-9]+)/,a){print a[1]}'); [[ -n $pid ]] && tr '\0' ' ' </proc/$pid/cmdline ; sleep 0.2; done;

解释下:

  1. while true; do ...; sleep 0.2; done;每0.2s反复刷新执行指定命令。
  2. ss -tanpe state established 'dst 10.11.22.33:4455'
    - -t 选项表示显示 TCP 连接。
    - -a 显示所有连接。
    - -n 不解析主机名、端口。
    - -p 显示进程信息。
    - -e 显示额外的详细信息。
    - state established TCP已连接
    - dst 10.11.22.33:4455 过滤TCP目标地址+端口
  3. awk 'match($0,/pid=([0-9]+)/,a){print a[1]}' 提取出 pid
  4. [[ -n $pid ]] && tr '\0' ' ' </proc/$pid/cmdline 从procfs读取该进程启动时的命令和参数。且把空字符 \0替换为空格

综合起来:不断地查询目标 IP 地址和端口的网络连接,找到与之相关的进程 ID,并显示该进程的命令行。每隔 0.2 秒刷新一次,持续监控这个连接对应的进程。

跑了一阵子,发现 $pid 可能有多行。囧,只能用双层 while 了:

  while true; do ss -tanp state established 'dport = 2333' | awk 'match($0,/pid=([0-9]+)/,m){print m[1]}' | while read -r pid; do echo $(date '+%F %T') $pid $(readlink -f /proc/$pid/cwd) $(tr '\0' ' ' </proc/$pid/cmdline); done ;  done;

Posted

stdout

白嫖百度网盘“单次转存数”500限制

找了个资源,点击保存,居然提示充SVIP。看了下免费用户每次最多保存500份文件。本来找资源就白嫖,让我充钱?

研究了一会儿,发现可以搞。

  1. 首先你去你网盘里建立个目录 0000-0500,准备存文件。这么起名字是因为你在自己网盘里多选也是最多选500个,所以每次存一个目录用来对照数量
  2. 分享链接里文件列表默认只展示前100条,是惰性加载,所以请用鼠标一直反复向下滚,务必拉到底,然后点击「名称」排序。这样方便对比有没有漏掉
  3. F12打开 console 并粘贴下面的js。如果这一步看不懂建议直接放弃
    Array.prototype.slice.apply(document.querySelectorAll(
    '.share-list dd.open-enable>span'
    ), [0, 500]).forEach((x)=>x.click())
    这样就等效点击了前500个文件。点击保存。
  4. 等全部存好,再次执行上一段代码,取消点击前500个文件。
  5. 去你的网盘里新建个目录 0500-1000 ,用来保存下一批
  6. 把上面代码里的 [0,500] 改成 [500,1000] 然后再执行一遍,保存到刚才创建的目录里。
  7. 如果有更多的文件,在执行一遍 [500,1000] 这一批,取消点击;然后改成 [1000,1500] 如此反复。

Posted

stdout

Linux删除了 .ru 帐号?我查了一下

发生了什么事

日前,Linux 内核主要维护者之一 Greg Kroah-Hartman (Greg K-H) 提交了一项不寻常的“文档”更新,将数名具有 <.ru> 顶级域名邮箱的维护者,和一名明确为俄罗斯身份的维护者从 MAINTAINERS(维护者名录)文件除名。
这一提交已于上周日被 Linus Torvalds 拉取并包含于 6.12-rc4 版本的代码中。
已有来自于 AOSC 的开发者提交了 revert(尚未合并),而 Linus 本人在评论中声明其对先前的 commit 知情并支持

一开始,我以为是 “反向” 厚脸皮PR

一般来说, github 上混一个 contributor 得贡献代码,很多人就改改文档、翻译就拿到一个“贡献者”称号(我也干过哈哈哈)。甚至还有离了大谱的,直接提一个 pull request 内容是把自己加到 contributors 文件里。。

我以为是 Linux Foundation 发疯了,为了某种 “合规” 或者 sanction,掩耳盗铃,把 作者名字列表.txt 改改掩人耳目

仔细看了下,发现好像并不是那么简单。这里是diff

https://github.com/torvalds/linux/commit/6e90b675cf942e50c70e8394dfb5862975c3b3b2

这里是更改后完整的 MAINTAINERS 内容:

https://github.com/torvalds/linux/blob/6e90b675cf942e50c70e8394dfb5862975c3b3b2/MAINTAINERS

直接通过 github API 拿到原始的 diff

curl -H'Accept: application/vnd.github.diff' -sk 'https://api.github.com/repos/torvalds/linux/commits/6e90b675cf942e50c70e8394dfb5862975c3b3b2'

乱写几个 prompt 丢给 gpt-4o,可以看到大概做了下列几处更改:

Device/Module Probable Date Author Email
Acer Aspire 1 Embedded Controller ~2018 Nikita Travkin nikita@trvn.ru
Alpha Port (Ivan Kokshaysky removed) ~1992 Ivan Kokshaysky (removed) ink@jurassic.park.msu.ru
ARM/Cirrus Logic CLPS711X Architecture ~1996 Alexander Shiyan shc_work@mail.ru
Baikal-T1 PVT Hardware Monitor ~2015 Serge Semin fancer.lancer@gmail.com
GRE Demultiplexer Driver ~2000 Dmitry Kozlov xeb@mail.ru
LIBATA PATA Drivers ~2003 Sergey Shtylyov s.shtylyov@omp.ru
LIBATA SATA AHCI Synopsys DWC Driver ~2014 Serge Semin fancer.lancer@gmail.com
Media Drivers for Ascot2E ~2010 Sergey Kozlov, Abylay Ospan serjk@netup.ru, aospan@netup.ru
Media Drivers for CXD2841ER ~2015 Sergey Kozlov, Abylay Ospan serjk@netup.ru, aospan@netup.ru
Media Drivers for Helene ~2017 Abylay Ospan aospan@netup.ru
Media Drivers for Horus3A ~2018 Sergey Kozlov, Abylay Ospan serjk@netup.ru, aospan@netup.ru
Media Drivers for LNBH25 ~2012 Sergey Kozlov, Abylay Ospan serjk@netup.ru, aospan@netup.ru
NetUP PCI Universal DVB Devices ~2010 Sergey Kozlov, Abylay Ospan serjk@netup.ru, aospan@netup.ru
MIPS Baikal-T1 Platform ~2015 Serge Semin fancer.lancer@gmail.com
NTB IDT Driver ~2014 Serge Semin fancer.lancer@gmail.com
PPTP Driver ~1996 Dmitry Kozlov xeb@mail.ru
Renesas Ethernet AVB Driver ~2013 Sergey Shtylyov s.shtylyov@omp.ru
Renesas R-Car SATA Driver ~2015 Sergey Shtylyov s.shtylyov@omp.ru
Renesas SuperH Ethernet Driver ~2000 Sergey Shtylyov s.shtylyov@omp.ru
Synopsys DesignWare APB SSI Driver ~2010 Serge Semin fancer.lancer@gmail.com
UFS Filesystem ~1974 Evgeniy Dushistov dushistov@gmail.com

如果说作者 @gregkh 作出这个改动的动机说

Remove some entries due to various compliance requirements

那么 BAIKAL 这个是明显会去掉。其余的改动,我能认出来的比较重要的有 PPTP Driver,其它都是五花八门驱动的维护者,感觉就算去掉, impact 的功能也不多。

当然你也可以说 first they came for the ... 我就不继续展开了

下面列举几个可能不那么重要,但是我觉得很容易被忽略的事实:

一、 .ru 的邮箱被全部移除了吗?

是的。最新版本 MAINTAINERS 文件里我搜了 .rurussia 关键词没找到了。干干净净。当然后期会不会加回来我不知道。

二、 俄国人对 Linux 的贡献被抹掉了吗?

第一,上面这些人的对应代码贡献,目前并没有删掉。

第二,Linux 源码根目录还有一个 CREDITS 文件,里面包含了下列俄国人:

N: Yuri Per
E: yuri@pts.mipt.ru
D: Some smbfs fixes
S: Demonstratsii 8-382
S: Tula 300000
S: Russia

N: Alexey Kuznetsov
E: kuznet@ms2.inr.ac.ru
D: Author and maintainer of large parts of the networking stack

N: Stas Sergeev
E: stsp@users.sourceforge.net
D: PCM PC-Speaker driver
D: misc fixes
S: Russia

三、 俄国人不能参与 Linux 项目了吗?

我在这里想补充一个背景知识,什么是 Linux Kernel 的 maintainer

内核功能繁多,Linus 老爷子一个人照看不过来,得把拆分成各个模块,授权给相应的 负责人(maintainer),给予他们检查、合并代码的权限。Linux基金会(注册地:美国)会给他们配置一个专用邮件列表用来主持专项议事

上面这句话,如何理解?可能对某些简中读者没看明白。简单的说:.ru 用户还是可以以 developer 身份向别的 maintainer 提交代码。这里不得不提到那个出名的 kernel git workflow

这是普通 git 项目的工作流:

这个是 linux kernel 的(图很老,大概这个意思):

普通人是没法直接向 mainline https://github.com/torvalds/linux 提交代码的。这个社区甚至会给你一个长长的文档教你一步一步如何提代码

不要说新功能了,一些老功能的 patch 要赶上主线发车窗口,你手续流程都很长。

想给内核做贡献,你要面对的甚至都不是 Linus 本尊,恰好是这些 MAINTAINERS 里列举的各位把关人。世界上绝大部分的人对 Linux 内核做贡献都是这样。 maintainer 是极少数,他们可以直接向 Linus 提改动。

Linus 自己也说了,他是芬兰人,意思就是说他不想看到俄国人。啊这。能理解,不提倡!

四、maintainer 名单变化多吗?

从提交历史来看,

https://github.com/torvalds/linux/commits/master/MAINTAINERS

人员进进出出很频繁。但是 @gregkh 这种在 char-misc 里搭车进去,的确偷偷摸摸。

五、 .cn 会被波及吗?

这个问题我无法回答,但是可以列一下现在有的:

  1. 一大堆 LOONGARCH 龙芯的驱动和各种模块
  2. 海光 HYGON 驱动
  3. 简中翻译 Alex Shi alexs@kernel.org Yanteng Si siyanteng@loongson.cn
  4. 繁体翻译 Hu Haowen 2023002089@link.tyut.edu.cn https://github.com/srcres258/linux-doc 太原理工的人才!
  5. RADOS BLOCK DEVICE (RBD) Ilya Dryomov idryomov@gmail.com Dongsheng Yang dongsheng.yang@easystack.cn ceph-devel@vger.kernel.org
  6. 一大堆 @huawei.com @linux.alibaba.com的各种驱动和模块
  7. 可能有别的大陆+港澳台公司邮箱我漏掉了。懒得看了。

CREDITS 里鸣谢的:

N: Chih-Jen Chang
E: chihjenc@scf.usc.edu
E: chihjen@iis.sinica.edu.tw
D: IGMP(Internet Group Management Protocol) version 2
S: 3F, 65 Tajen street
S: Tamsui town, Taipei county,
S: Taiwan 251
S: Republic of China

N: Li Yang
E: leoli@freescale.com
D: Freescale Highspeed USB device driver
D: Freescale QE SoC support and Ethernet driver
S: B-1206 Jingmao Guojigongyu
S: 16 Baliqiao Nanjie, Beijing 101100
S: People's Repulic of China

N: Tsu-Sheng Tsao
E: tsusheng@scf.usc.edu
D: IGMP(Internet Group Management Protocol) version 2
S: 2F 14 ALY 31 LN 166 SEC 1 SHIH-PEI RD
S: Taipei
S: Taiwan 112
S: Republic of China
S: 24335 Delta Drive
S: Diamond Bar, California 91765
S: USA

六、结束语

总的来说 Linux内核项目,包括不限于很多别的社区项目,还是英雄主义色彩很重的,这样的个人主义既可以说出“nVidia fuck you”,也完全有可能在某一天把主语丝滑切成 “Russia”。这压根就是同一个硬币的同一面(甚至都不是另一面)

在国际地缘政治热火朝天、段视频AI诵读、情绪控制终端满天飞的今天,讨论这些重要吗?有用吗?

回答:不重要,没用

但是对我个人而言,或许有那么一丢丢重要,有一丁点用。

希望对你也有点用。

Posted

stdout

“分封制” 代替RBAC权限管理模型

作为开发狗,手上堆的系统和任务多了,开权限就变成了一件麻烦的事。

一开始做一个功能X,给甲用,就做一个简单的权限关联表:功能key人员id就完事大吉

后来人员调整流动,换人换得勤快,得考虑一个功能给一个团队使用,于是引入 RBAC,基于角色的鉴权控制。说到底就是个三元组 功能key角色成员id

我也一度迷信这样的设计。用了这么久还是麻烦了。在一个正常官僚程度的大中型组织里,压根没人知道自己角色是啥。

你说他是管理人员吧,他也就管一部分;你说他负责这事吧,也就负责一阵。该给他放到哪个角色呢?

如果做一个自动化角色申请审批流程,他会跑来问你,该申请哪个角色啊?

所以问题又回到了原点。因此我琢磨出来一套具有大汉封建主义特色的,「分封制」权限控制体系。

  • 系统超管:普天之下,莫非王土;率土之宾,莫非王臣。拥有系统一切权限。但是不负责,不执行,不搞具体管理。超管只干一件事:分封
  • 如何分封:把「事」交给一个信得过的领主(serf)。领主对一块业务全权负责。但代价得自己把封地采邑打下来。具体在系统层面,系统所有权限、功能都直接开放,所谓 zero-trust。但是你得从零开始搭建和配置。一旦你配置好、跑起来,这快权限和功能就是你独有的。(owner)
  • 线上既有的业务权限,那也是有人配置管理和主导,你只能从别那里抢夺,或者过继。
  • 领主可以给自己封臣划分部分管理权限
  • 如果一个领主挂了,超管指定一个接盘的领主

感觉这样开发和超管就可以不用每天处理谁负责哪个功能的麻烦事了。哈哈哈

Posted

stdout

Install mosh for macOS arm64 with Miniconda3

I always hate compiling and building. It's an error-prone path filled with riddles and unknowns.

mosh is a great tool replacing ssh in many cases, sadly it does not provide an arm64 binary download or installer for M1/M2/M3 MacBooks. Moreover I dont like XCode and protected /usr/bin shit so I prefer isolated envs like miniconda3.

Anyway, for such a detour, following the official mobile-shell build wiki, type these commands:

conda install autoconf automake libprotobuf pkg-config tmux
# use libncursesw 6.5 since  6.4 is buggy, rpath and dyld not load LC_RPATH shit
conda install conda-forge::ncurses
git clone --depth=1 https://github.com/mobile-shell/mosh.git`
cd mosh

./autogen.sh
./configure --prefix $CONDA_PREFIX
make check
make
make install

And it's done. All thanks to authors for open source project and butter-smooth build scripts.

Posted

stdout

精准 javascript 节拍器

娃学琴,节奏感糊成一坨了。老师让抖腿打节拍,显然不work。码农特有的穷B气质让我舍不得买个实体的;去找个节拍器app,结果各大市场都是各种广告,怕给娃用不正经;甚至找了很多公众号版的,小程序版的,网页版的,普遍都是各种跳转诱导点击。

失望之余,想起来上次JS播放DTMF对 WebAudio 有点感觉,要不这次自己干脆再手搓一个。调研了一圈发现这玩意没那么容易,主要就是javascript在浏览器里 setInterval 是有时间抖动(jitter)的,不能保证节奏精确。下面的图来自 Monica Dinculescu

即便采用了 requestAnimationFrame 也不行

因为js的执行无论如何trick,本质都是在只有一个主线程的单线程。如何才能做到精准采样打节拍呢?连续精心编排 AudioContext 时间轴!因为音频播放是系统调度跟js无关

搜了一圈发现一位叫 Grant James 已经写好放在 github 了。那还等什么直接拿来~~针对性下列需求改了一番

  1. 无广告
  2. 去掉 .js .css 依赖,单文件更快
  3. 精简不必要的代码结构,方便以后魔改

改完之后发现两三个坑也跟着一起修补了:

  1. wakeLock 防止手机上使用息屏
  2. 突破固定的4拍子,加上可以增减的节拍控制
  3. 增减按钮禁止调到小于 0

最后成果就放在 https://lab.est.im/metronome/ 这里了。有需要的拿去用。协议 CC BY-NC 4.0

20240929更新:

  1. 支持了长按按钮一直增加减少 bpm
  2. 禁止长按出现菜单
  3. 禁止双击缩放

Posted

stdout

Edge找回丢失的右上角Copilot侧边栏图标

右上角图标和侧边栏的官方截图:

如果不小心关掉了,找回办法:

  1. 打开 edge://settings/sidebar/appSettings?hubApp=cd4688a9-e888-48ea-ad81-76193d56b1be
  2. F12 或者 Opt+Cmd+I,找到第一个“Show Copilot”开关,把它改成关闭。
  3. 如果第二步不会搞,输入 document.querySelector('input[aria-label="Show Copilot"]').removeAttribute('disabled') 可破

为什么会有这个问题,因为政策的原因,Microsoft Copilot 不能在简中使用,于是右上角的按钮点一下会消失;但是开发或者集成测试的为了省事儿,就直接给隐藏掉,导致侧边栏无法点开

这串 hubApp 的id如何找到呢?新建一个浏览器profile,这个时候 Copilot 是有图标的。不要去点,去设置里翻 edge://settings/sidebar 就能看到 Copilot 的选项。

Posted

stdout

php是最好的……serverless

php是不是最好的语言先不说,但是如果从 serverless 的角度看php:

  1. 语法简单,功能丰富,10分钟就能学会
  2. 通过 FTP 之类的标准工具部署,而不是脑残的命令行
  3. 不用管理麻烦的服务器。基于非常成熟的 php-fpm 去做 scaling。也有大量巨型网站验证过这一方案
  4. 虽然不是 docker,但是 vhost 够用了。而且要容器化 vhost 也很容易
  5. 让人梦寐以求的“热更新”技术
  6. 超级便宜的月租。(还记得 dreamhost 吗?)
  7. 不会吊死在一颗树(vendor lock-in)上。全球遍地的厂商都支持

你就说好不好吧。同时评论在 Hacker News

Posted

stdout

2024年玩xml真是笑死了(xsl)

昨天大早上的6点就醒了,翻来覆去睡不着,就开始胡思乱想。忘记是什么缘起了,反正突然想到一个问题,博客这种静态站,又要输出html,又要输出rss。太冗余了。要不直接把内容存在RSS里。然后用js去fetch()。。。。

等等,死去的记忆突然攻击我。RSS不就是XML,XML似乎有个加皮肤的技术。于是就更睡不着了,起床一查,原来是 XSLT 转换成 HTML 然后就可以自由发挥了。

可行性没问题,准备找个前人的例子开抄。比如 2021年Nathan Clark的Styling an RSS Feed With XSLT2023年Darek Kay的Style your RSS feed

思路就很匹配,执行效果也不错。我想百尺竿头更进一步,把 XSL 给 inline 到 RSS 里,避免额外的加载,节省一次网络请求。吗,没想到这下麻烦大了,折腾到现在才有个初步结果。直接贴源码:

<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="#stylesheet"?>

<some_xml_root xmlns:shit="http://xxx">

<xsl:stylesheet xml:id="stylesheet" version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" doctype-system="about:legacy-compat" version="5.0" encoding="UTF-8" indent="yes" />
<xsl:template match="/">
  <html>
    ...
  </html>

</xsl:template>
</xsl:stylesheet>

</some_xml_root>

这里的 href="#stylesheet" 方法,是官方推荐的 。但是要起效果,一定注意 type="text/xsl" 别抄成网上的 application/xslt+xml

主要浪费时间的坑点主要在根的 xmlns 上。这玩意把 xpath 给搞挂了。比如 sitemap你写

<xsl:for-each select="/urlset/url">

好好的,加上 xmlns 就得写成:

<xsl:for-each select="/*[local-name() = 'urlset']/*[local-name() = 'url']">

怪不得现在没人玩XML了。这破玩意没法直观的本地调试,还得靠各种第三方 validator,反人类啊。

于是把根的 xmlns 直接改成 tag namespace。也就是 xmlns:shit 。反正你namespace冲突了关我什么事?而且说实话,xmlns这玩意除了坑各种解析器不好写之外,并没有什么卵用。我以前写都是想办法去掉 xmlns 再解析。感觉这一块真的 over-engineering 了。 好像有点明白 namespace 是干嘛的了。这玩意主要解决一个sb需求:允许多个XML合并到一个文件里。这不就得考虑如何分清楚tag谁是谁的。。。。并且标准已经挖好坑了,一个XML有且只有一个root。。你把别的 tag 合并进来,那的确不容易区分。还有这样写不会造成文件体积膨胀吗?真是xsl笑死了。

接下来的问题就是如何生成一个规规矩矩的 html5 doctype。搜了下 doctype-system就可以了。本来其实可以在<xsl:template>后面加上

<xsl:text disable-output-escaping='yes'>&lt;!DOCTYPE html&gt;</xsl:text>

但实际只加这个是不行的。

另外吐槽下 Chrome,默认的viewer把xml渲染成html了,在 devtool 里通过 $x() 无法直接调试xpath,还得靠别的第三方工具。(⊙﹏⊙)

这可能就是XSL的核心问题了——没法方便直观的调试,挂了就给你白屏。有点react那种白屏的 ptsd 感觉了。

Posted

stdout

关于 cn.cyberIdentity.certification 的一些技术信息

搜索该app名字可以得到

https://apps.apple.com/cn/app/_/id1588080869

各大app市场都有隐私政策页,点击可以发现网址是:

https://cdnrefresh.ctdidcii.cn/w1/WHClient_H5/pages/secret.html

甚至有一个 apk 下载页面

https://cdnrefresh.ctdidcii.cn/w1/WHClient_H5/Install/InstallGuide.html

https://cdnrefresh.ctdidcii.cn/w1/version/cyberIdentity-android.apk

whois ctdidcii.cn 可以得到

Registrant: XX部第一研究所
Registrant Contact Email: songjie@anicert.cn

可以发现该公司官网

有限公司(以下简称“ZDAX”)是**部第一研究所下属全资子公司。
**部第一研究所以国家“互联网+”可信身份认证平台(简称“CTID平台”)核心技术为依托,致力于为各级政府部门、互联网运营商、各行业应用提供权威、可信、安全、便捷的身份认证服务,并开展相关业务创新。在**部的直接领导下,**办、国家发改委、科技部等部委的大力支持下,**部第一研究所积极开展居民身份证网上应用研究,组织承建CTID平台,助力新时代社会治理现代化。
2017年10月,**部第一研究所成立ZDAX公司,并授权ZDAX为CTID平台唯一合法的运营服务商,为各行业提供权威、可信、安全、便捷的网络身份认证服务,为我国“互联网+行动战略”提供强有力的支撑保障。

以及平台介绍说明

https://www.anicert.cn/platform.html

● 一次一码,超时失效
● 通过国密算法加密、签名防止数据泄露、篡改和伪造
根据相关行政许可设立的第三方签发、面向系统应用可作为司法采证使用的身份凭证,如电子认证服务提供者、CA机构等第三方签发的电子签名认证证书、eID等;

得出一个结论,这玩意不是eID换皮那么简单。

点开发展历程可以看到

2020.12 《基于可信数字身份的区块链应用服务白皮书》正式发布

上面提到的厦门公司,看上去是专门做 CDIT 运营的,搜了下官网是

https://easyctid.cn/about

其中有一张最重要的架构图

目前不知道这个 PID 是唯一的还是可变的。要实用还是得做类似微信那种 OpenID/UnionID 才行。因为技术对接SDK需要付费签协议才能拿到,细节就无从而知了。

Posted

stdout

Valid HTTP verbs

这里 想到,fetch/xhr 可以发起哪些 http verb 呢?

python -m http.server 随手测试了下,发现连 !$ 这种符号都可以作为 http verb,比如 fetch('/asdf', {method:"$"}) 这样的请求是能发起的。于是去 chromium 搜了下 "is not a valid HTTP method" 相关的解析放在 blink/renderer/platform/network/http_parsers.cc

// See RFC 7230, Section 3.2.6.
bool IsValidHTTPToken(const String& characters) {
  if (characters.empty())
    return false;
  for (unsigned i = 0; i < characters.length(); ++i) {
    UChar c = characters[i];
    if (c > 0x7F || !net::HttpUtil::IsTokenChar(c))
      return false;
  }
  return true;
}

bool HttpUtil::IsTokenChar(char c) {
  return !(c >= 0x7F || c <= 0x20 || c == '(' || c == ')' || c == '<' ||
           c == '>' || c == '@' || c == ',' || c == ';' || c == ':' ||
           c == '\\' || c == '"' || c == '/' || c == '[' || c == ']' ||
           c == '?' || c == '=' || c == '{' || c == '}');
}

原来如此,RFC 7230呀。于是把这些字符打出来:' '.join(chr(x) for x in range(0x20, 0x7f))

! # $ % & ' * + - . 0 1 2 3 4 5 6 7 8 9 A B C D E F G H I J K L M N O P Q R S T U V W X Y Z ^ _ ` a b c d e f g h i j k l m n o p q r s t u v w x y z | ~

那还搞个屁的 RESTful 啊。直接用特殊符号一把梭。不过各种服务端框架可能不是很容易做兼容。哈哈。

Posted

stdout

Cappy in a Haystack locations

For the mission from the beloved Fallout 4 Nuka-World DLC

coc DLC04KiddieKingdomFunHouse01; player.moveto 06015A94
coc NukaWorldWildWestExt02; player.moveto 06015A8A
coc DLC04BottlingPlant01; player.moveto 060445DF
coc DLC04BottlingPlant01; player.moveto 0604A812
coc DLC04GalacticZoneExt04; player.moveto 06015A8E
coc DLC04GalacticZoneExt04; player.moveto 0604AD3F
coc DLC04GalacticZoneExt08; player.moveto 06015A91
coc DLC04GalacticZoneExt08; player.moveto 0604AD40
coc DLC04KiddieKingdomFunHouse01; player.moveto 0604AD3D
coc DLC04WWMineCart01; player.moveto 06015A8B
coc DLC04WWMineCart01; player.moveto 0604AA39
coc KiddieKingdomExt06; player.moveto 06015A97
coc KiddieKingdomExt06; player.moveto 0604AD3E
coc NukaWorldNukaTownUSA06; player.moveto 0604A814
coc NukaWorldNukaTownUSA06; player.moveto 0604A813
coc NukaWorldWildWestExt02; player.moveto 0604A815
coc SafariAdventure03; player.moveto 06015A9A 
coc SafariAdventure03; player.moveto 0604AA3A
coc SafariAdventure07; player.moveto 06015A9D
coc SafariAdventure07; player.moveto 06015A9D
coc SafariAdventure07; player.moveto 0604AA3B

idk hope it works lol.

Posted

stdout

All Star Core locations in Fallout 4

Arent nobody got time for this shit.

Open FO4Edit, locate 604a053 and find all refs:

0601FAD1
060255A7
060255D2
06025774
060257F1
06026C7B
06026D23
06026D70
060278F7
06027917
0602791A
06027A3C
06027ACC
06027C0D
06027D5E
06027E43
06027E4C
060319E1
060319E2
060327BA
060327C4
060327D0
060327E2
06034EC6
06034F97
06034FD4
0603522E
06037CA7
060380CB
0603816F

And for external star cores, locate 0601F0E5 and 0601F0E6 for more refs:

coc NWJunkyardExt; player.moveto 060319D6;
coc NukaWorldWildWestExt03; player.moveto 06031995;
coc DLC04BottlingPlant01; player.moveto 060319CE;
coc DLC04Nukacade01; player.moveto 060319CF;
coc DLC04GalacticZoneExt07; player.moveto 060319E1;
coc NukaTownMarket01; player.moveto 06054CA8;

Just type player.moveto with the code above and you can grab all star cores, and return to console player.moveto 0601E359, so you get the Quantum X-01 power armor from Nuka-World DLC of Fallout 4.

Also there's coc TestCory01; 06044B4B; for testing purposes.

Posted

stdout

Making subprocess async friendly in Python

It's been a while since i wrote something in English, mostly because there's nothing really interesting, until now.

Occasionally, when facing a long running task in Python, I would choose either a distrubuted tasks queue system, or for the convenience, just the subprocess module. It's built-in and well designed for grabbing outputs of a child process running for a short period of time.

But what if the child-process takes a really, really long time? In my case it's an expensive query, or some CPU/GPU intensive task, which needs to be launched from a running Web framework, like FastAPI.

Popen() fire and forget

if child-process's output, end state and the return code are irrelevant, a simple Popen would do

subprocess.Popen(..., stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

The problem is, after the child-process finishes, it will hang as a zombie, because the parents refused to claim its exit status. To fix this, add an extra parameter in Popen() like subprocess.Popen(..., start_new_session=True)

And write a simple loop periodically check for WNOHANG

while 1: try: chpid, retcode, res = os.wait3(os.WNOHANG) except ChildProcessError: break sleep(5) if chpid == os.getpid(): do_sth() break

This can be done using BackgroundTasks in FastAPI/Starlette.

In a way, the child-process hebaves like nohup or screen/tmux, running in a detatched fashion.

If you hate this many lines of code, just subprocess.run("blah.sh &", shell=True) and wrap your commands in blah.sh

Make .communicate() async

Sometimes I need to monitor and handle the stdout/stderr of a child-process, like forward the outputs as an EventSource response to the browser.

First I tried .communicate() it will block until the process quites.

Then I tried Popen.stdout.read(), it will also block. Eventually I found a great hack from Stackoverlow like this:

p = subprocess.Popen(
    cmd, bufsize=0, text=True, stdin=subprocess.PIPE,
    stderr=subprocess.PIPE,  stdout=subprocess.PIPE, close_fds=True)
fcntl.fcntl(p.stdout.fileno(), fcntl.F_SETFL, os.O_NONBLOCK)

Now p.stdout.read(1024) would return immediately, or with a TypeError bnecause internal messed up with None as non-blocking empty return.

Wrap it with try...except inside a loop, it worked fine as expected.

When the parent process crashes unexpectedly, the child-process is still working, to detect this, just check for BrokenPipeError carefully in child-process and gracefully shutdown.

Solved with asyncio

I tried harder reading the official Python docs, turns out the most easy solution is already there:

proc = await asyncio.create_subprocess_exec(
    sys.executable, '-c', code,
    stdout=asyncio.subprocess.PIPE)

# Read one line of output.
data = await proc.stdout.readline()
line = data.decode('ascii').rstrip()

# Wait for the subprocess exit.
await proc.wait()
return line

I guess another lesson learned today.

Posted

stdout

人声分离简单评测

娃要去表演唱歌,老师要求提供MTV。。 视频是这个,需要去掉人声部分

先用 ffmpeg 提音轨 ffmpeg -i v.mp4 -ac 1 1.mp3

微信小程序上的全是骗充值的,网上的服务:

  1. 没卵用 https://vocalremover.org/ 名气很大,搜出来第一就这个
  2. 没用 https://www.conversion-tool.com/karaoke/
  3. 卡死 https://www.songpeel.com/song-to-karaoke
  4. 要安装 https://github.com/adefossez/demucs
  5. 要6G显存 https://github.com/Anjok07/ultimatevocalremovergui
  6. 效果很好! https://tunebat.com/Vocal-Remover 推荐

看来人声分离,特别是童声,还是有点难。

最后把得到的 a.mp3 塞回去

ffmpeg -i v.mp4 -i a.mp3 -c:v copy -map 0:v:0 -map 1:a:0 new.mp4

btw 打了这么多网址,该不会被误认为SEO吧。囧。。。。

Posted

stdout

吐槽pynsq,另外给subprocess糊了一个异步

pynsq太烂了

起初是因为需要在 Web API 里消费一个消息队列(nsq),给浏览器返回 EventSource 做实时输出。但是没想到官方库pynsq居然写得这么渣:

def _handle_term_signal(sig_num, frame):
    logging.getLogger(__name__).info(
        'TERM Signal handler called with signal %r', sig_num)
    tornado.ioloop.IOLoop.current().stop()

def run():
    """
    Starts any instantiated :class:`nsq.Reader` or :class:`nsq.Writer`
    """
    signal.signal(signal.SIGTERM, _handle_term_signal)
    signal.signal(signal.SIGINT, _handle_term_signal)
    tornado.ioloop.IOLoop.current().start()

这都什么玩意儿?一个库何德何能也敢去乱劫持 signal?最他妈逗的是这个库的推荐写法:

import nsq
r = nsq.Reader(message_handler=handler,
        lookupd_http_addresses=['http://127.0.0.1:4161'],
        topic='nsq_reader', channel='asdf', lookupd_poll_interval=15)
nsq.run()

这个 r 莫名其妙的就能跑起来了。真是魔法。anyway,在Web API兼容这一坨是失败了,爆了一堆错,对付魔法的办法就是隔离,所以打算糊个脚本,通过多进程+PIPE来得到数据。

给 subprocess 加一个异步补丁

糊的时候发现,subprocess 模块的 .communicate() 方法是 blocking的, 居然不支持 async/await,于是只能继续糊:

import subprocess, fcntl, asyncio
p = subprocess.Popen(
    cmd, bufsize=0, text=True, stdin=subprocess.PIPE,
    stderr=subprocess.PIPE,  stdout=subprocess.PIPE, close_fds=True)
fcntl.fcntl(p.stdout.fileno(), fcntl.F_SETFL, os.O_NONBLOCK)
buf = ''
while p.poll() is None:
    try:
        # read() with O_NONBLOCK might gets None thus TypeError
        buf += (p.stdout.read(1024) or '')
        contents = buf.split('\n')
        if len(contents) < 2:
            raise TypeError('Not enough')
    except TypeError:
        await asyncio.sleep(1)
        yield None
        continue
    buf = contents.pop(-1)
    for x in contents:
        yield x

p.terminate()

这里用 bufcontents 是因为直接调用消息队列,每个消息都是分割好的;但是通过管道来读。得自己去处理字节流如何分割的问题,也就是传说简中特供的「粘包」问题。

另外子进程可能因为Web框架的种种原因,不自觉退出或者变僵尸进程,需要向 stdout/stderr 做输出的时候检查一下 BrokenPipeError 处理后事主动退出即可。

还有子进程如果输出完毕,需要代码里调用一下 p.terminate(),才能回收,避免变zombie

试了一下,也不是不能用,成功!正觉得自己又行了的时候,发现官方:

https://docs.python.org/3.8/library/asyncio-subprocess.html

啊这。白忙活。

超长待机 subprocess

想起来之前还有个问题,一个API调用一个subprocess,然后等它执行,不想管。这个时候可以加一个参数 detach 模式:

subprocess.Popen(..., start_new_session=True)

然后在同进程用里跑一个后台检查循环去检查子进程的 WNOHANG

while 1:
    try:
        chpid, retcode, res = os.wait3(os.WNOHANG)
    except ChildProcessError:
        break
    sleep(5)
    if chpid == os.getpid():
        do_sth()
        break

想了下好像也搞复杂了。要不直接跑一个 sh -c "blah.sh &" 算了。

Posted

stdout

如何删除 C:\Windows\WinSxS 文件

天气热得心慌,看Win10不爽,决定删几个文件玩玩

  1. 首先你需要以 SYSTEM 用户启动 cmd.exe
    Win+R 敲入:
    "\\live.sysinternals.com@SSL\DavWWWRoot\tools\PsExec.exe" -i -s cmd.exe
    回车。确认当前身份敲入 whoami
  2. 夺权:takeown /F "C:\Windows\WinSxS\Backup" /A /R /D Y
  3. 授权:cacls "C:\Windows\WinSxS\Backup" /T /G SYSTEM:F /C
  4. 开删:rd /s /q "C:\Windows\WinSxS\Backup"

爽!

当然,你也可以让系统代替你清理:

Dism.exe /online /Cleanup-Image /StartComponentCleanup /ResetBase

很老的磁盘清理工具 cleanmgr.exe 也可以试试,删不要的程序 appwiz.cpl

老黄家的 DXCache 也十分让人不解。这破玩意还存2个地方:

  1. %LOCALAPPDATA%\NVIDIA\
  2. %APPDATA%\NVIDIA

老夫当年给 C: 装了个最慢的SSD,失策。去年(2023)那一波M.2降价潮一直等等结果错过了,拍断大腿追悔莫及。

Posted

stdout

从 Leanote 迁移到 Obsidian

差不多积累了10年的笔记,因为软件开发商跑路去做 2B 了,这玩意也没人维护了。换 Obsidian

Leanote 不支持导出 .md 但是它自己的格式就是 json,转换一下很简单。

  1. 把一个目录下的笔记批量导出为 Leanote 格式,比如导出到 /tmp/folder1
  2. 执行Python代码
    import json dir = '/tmp/folder1' for n in os.listdir(dir): if not n.endswith('.leanote'): continue with open(f"{dir}{n.replace('.leanote', '.md')}", "w") as f2, open(dir+n) as f1: f2.write(json.load(f1)['notes'][0]['content']) os.remove(dir+n)
  3. 你会发现该目录下的 .leanote 都变成了 .md。拖拽到 Obsidian 即可

希望对搜到这篇文章的人有所帮助。

Posted

stdout

快速入睡的技巧和原理

随着年龄的增长,加上工作和生活的恶习,睡眠越来越不好。很多时候躺在床上明明已经非常困了,但是大脑一不小心陷入胡思乱想,然后越想越睡不着。

很多人说把大脑放空就能睡着了,我也试过,放空是不可能的,大脑时时刻刻总得琢磨点什么;既然要琢磨点什么,那我们能否找一部分最容易睡着的来琢磨?这几年我倒琢磨出一个非常行之有效的方法用来入睡:

停止理性推演!强行带入感性的、放松、舒服的回忆体验占据大脑活动

具体展开,首先我这里对睡不着的「胡思乱想」作了一个二元分类:

  1. 一种是「理性」,逻辑推演:工作上的任务如何完成,生活中的矛盾如何化解,职业和家庭面对的困难,身边人群的压力,社会环境如何改进,民族国家的命运如何?电车会是能源革命吗?巴以冲突俄乌战争如何化解?等等,这类胡思乱想基本模式是「因果」:如果我做到A,那么B就会发生;如果我避免C,那么D这个糟糕的结果就能挽回。这类事,千万不要躺在床上想,否则越想越气。
  2. 另一种思绪我们称之为感性,温暖的阳光下微风吹过皮肤,恋人的手轻轻搂着你的腰,碧蓝的海平面上星星点点的船帆,你躺在椅子上无忧无虑的闭上眼睛享受这一切,只要把自己带入这种你亲身经历过的让你舒服,放松的体会中,假想你现在正在亲身经历这种愉快的事,那么你很快就能入睡。

或许,有人会说我命苦啊~没那么多享受可以回味。我之前也是这样,总觉得过得很匆匆没这么留下的美好回忆,有一次下班路过郊外,看到天边块落山的太阳,光线没那么刺眼,橙红色的余晖给白云描了个边,倒影在一处水塘里,没有了中午那么热的暴晒,微风刮过岸边那些稀稀拉拉的花朵和绿树。感受到美的瞬间迫使我立刻停下脚步,找个草坪坐下欣赏并且仔细观察,深怕错过任何一个细节,我当时决定把这个时刻深深记录在脑海里并反复加深印象,以备将来再也遇不到了。后来这个「场景」的确帮助我度过了很多个艰难的夜晚。

要训练这个区分能力没有想象那么难,但需要长期的刻意练习,大脑胡思乱想的时候很容易不自觉陷入「理性」部分,要想睡觉,就得避免。不要尝试去思考「why」。时刻对当前的大脑活动行为进行「鉴定」和归类。对事物分析、解释和总结类的事,躺着就不要去想;一旦躺下,就多回忆和假象一些「体会」和「感受」。需要额外注意,情感的大类里,有一种叫做「害怕」和「忧虑」的东西,也要特意避免,Zizek说过 “The only emotion that doesn't deceive is anxiety”,焦虑是惟一不骗人的情感,这句话的意思是,焦虑的本质是理性和归因分析,而不是情感,所以它很「真」而且很「重」。入睡这种事就不要当「真」啦,想点轻松愉快的骗骗自己做个美梦不好么。

对思绪的简单的二元分类并非我独创,Popper的第三世界《思考快与慢》里 I、II型系统都有类似的阐述。这个入睡方法是我无意中实践出来的,可能是早年无聊特别喜欢对思辨本身进行思辨,二分模式归类,睡不着是因为大脑老是琢磨事,我无聊得琢磨起琢磨本身(meta-琢磨?),于是逐渐琢磨出一个辅助入睡规律。

今天刚好看到另一个研究:德国蒂宾根大学发现,合理利用睡眠,可以提升一个人的决策能力

研究过程是,招募一群8到11岁的孩子,给他们看一组很吓人的照片,比如凶猛的动物之类。当时,几乎所有的孩子都被吓到了。之后,把这些孩子分成两组,第一组让他们回去睡一觉。第二组,让他们通宵玩游戏。
第二天,再给这些孩子看这些可怕的照片。结果发现,睡觉组的孩子,可以相对平静地观看照片。他们显得没那么害怕了。而通宵游戏组的孩子,被惊吓程度,跟前一天几乎没有区别。
研究人员认为,这就是睡眠的作用。睡眠就像一个过滤器。当我们面对一件事时,大脑的逻辑系统和情感系统,会同时运转。比如,看到一条毒蛇。你既会产生逻辑上的识别,记住这条蛇的基本特征。同时,你也会产生情感层面的反应,也就是害怕。
而睡眠的作用,就在于,过滤掉其中情感的部分,保留逻辑的部分。因此,睡觉的孩子再看到蛇时,他们就不会那么害怕,只会记得,这条蛇昨天见过。而通宵组的孩子,这个害怕的感觉还在,他们再看到蛇时,还会很害怕。
这也是为什么,很多人晚上一上秤,发现自己胖了,发誓第二天早上要起来跑步。但第二天却起不来。这就是因为,你立flag时的激情,被睡眠给过滤掉了。这也是为什么有人说,睡前千万不要立flag。
把这个结论再延伸一步,研究人员认为,假如你做一件事时,在很大程度上受到情绪干扰,那么你最好先去睡一会儿。这能帮助你更好地集中注意力。
比如,要不要创业?不创业,觉得有点虚度青春。创业,又有点患得患失。这就是受情绪干扰太多。按照这个研究,你就应该先去睡一觉。让你的情绪脑先冷却下来,睡醒之后,再用你的逻辑脑仔细想想。
其实,这个研究可以用一句话概括,不就是,一日之计在于晨吗?也就是,把需要动用逻辑思考,需要计算,需要做计划的事情,尽量放在一觉睡醒之后。

所以睡觉就睡觉,别想那么多。自然就能睡得着了。本文也用来回答V站提问《如何在睡觉的时候不胡思乱想》 。 转载请注明来自est的博客 https://blog.est.im/2024/stdout-05 ,如果觉得对你有用,给我留一句评论是再好不过的了 😊

Posted

stdout

1900年居然不是闰年?

看HN讨论 昨天2024-02-29有哪些闰年的bug,有一个吃惊了

>>> datetime.datetime.strptime('Feb 29', '%b %d')
ValueError: day is out of range for month

python的 datetime() 默认 year=1900。但是心里想 1900 不应该也是闰年吗?

查了下还真不是。囧。一年有 365.242374 天:

  • 近似看成 365.25 天就是4年一润;
  • 剩余的精度 365.25-365.242374 == 0.0076 天
  • 而且,0.0076 ✖️ 400 == 3.0504,也就是每400年要少润3天
  • 结论就是每100年不润,但是每400年又要润
  • 比如 1700, 1800, 1900, 2100, 2200, 2300, 2500, 2600 这些年都不是闰年。2000、2400是闰年。

真复杂!

Posted

stdout

穷人的supervisor OOM killer

厂里的docker上跑了个supervisord,用来把挂掉的进程拉起来。最近的问题不是进程挂掉,而是内存泄漏

跑着跑着内存爆了,然后宿主机OOM随机杀掉一个进程,结果占用内存最大的那个还活着,正常的进程反而gg,该漏的继续漏,然后反复OOM。想看下哪里漏了,--cap-add=SYS_PTRACE 也不给加,OOM策略也不给调,思来想去只能自己做防水补漏了。做起来也简单,supervisor再跑一套bash脚本即可:

while true; do
  p=`ps --no-headers  -xo rss,pid --sort=-rss | awk '{
    if($1 > 1000000){ print $2 }
  }'`
  kill $p 2>/dev/null || echo "everything fine";
  sleep 60;
done;

1000000 代表1G 内存。 sleep 60 表示 每60秒检查一次。

Posted

stdout

从 幻兽帕鲁 Palworld 设想一下未来的联机游戏

打开steam刷到一个新闻《Ten years of RimWorld》。啊?都十年了?回想起来最近非常爆火 幻兽帕鲁 Palworld ,被大家称为缝合怪,什么都缝了。细细盘点一下我对该类别游戏大杂烩的一个评价,以及我对未来游戏机制的一个YY。

Minecraft 挖矿争霸

我从这玩意还没中文名字的beta时代就开始玩(盗版),从山脚下一个 1x2 穴居的第一夜,挖到自己的第一铲煤,再到湖底水晶宫,然后修建自己的铁路在广袤的平原和山间品味那让人留连忘返 procedure generated terrain ,那个时候真的能给人一种激动。

后来搞的什么创造模式,各种复杂的机制和道具,没那个简单的快乐了。

Conan Exiles

和 minecraft 的 voxel 建筑风格相比,它的建筑是由正三角形、正方形构造出来的奇特镶嵌 。当然还有特色的 wheel of pain 驯奴系统,简直解放劳动力啊

Rimworld

这玩意好就好在它那套 story-teller 带来的调性,很有那个味。后来的其他作品比如 Stranded: Alien Down 虽然设施设备、机制都堆了,但是总给人一种单薄,后期没劲的感觉。Rimworld 的限制就在于 2D 而且固定尺寸地图,不是 open-world

Grand Theft Auto

这玩意当年从 GTA:SA 接触觉得简直是神作啊!无缝地图在当年看来就是魔法一般。而且迄今为止它也是没有太多科幻元素,完全以「现代」为题材的都市类游戏,国产的喜欢做三国、仙侠,国外的喜欢中世纪、废土和赛博。凸显了 GTA 的独特生态位。当然这玩意玩 online 就是纯纯的 grind,还好我早就通过外挂刷了几个亿,很轻松惬意。(声明:外挂只用来刷钱,并不具备其它不公平功能)。这玩意在我心目中是 open-world 当之无愧的开创者和王者。

Palworld

接触了一下这玩意,其实我对 pokemon 一直有耳闻但是完全不懂,现在看来有点类似收集+配种这样一个机制。想起来玩一些 MMORPG 比如 Black Desert 发现一个叫做 「family」的机制很有趣,你可以创建多个角色并切换,部分物品甚至是通用的。但是这玩意感觉除了对不同角色体验一番,并不能带来 1+1>2 的效果。我当时就非常希望同一个 family 的角色能相互配合,就像「Dragon Age: Origins」里一样,在行走和打架的时候能随时切换,不同的职业甚至能打出来一些配合,那该有多好。

缝合

游戏里最蛋痛的一点,对资源开采的大量需求,应该可以把玩家的角色托付给AI挂机,正如 Black Desert 的 auto-fishing 一样。Palworld 这一点就缝合的相当有趣了,原版的 pokemon 据说只能抓来打架,这里变成了还可以是生产力。比 Conan Exiles 里的奴隶机制更有意思。综合我前面的设想,我觉得以后的联机游戏,player-character 应该不是一个,而是一群。有血缘或者劳动附庸关系的「家族」。玩家应该有自由度选择控制其中一个或多个,玩家离线的时候这个「家族」应该也会按照设定进行生产和劳动。。。。甚至防御!

如果考虑死亡掉落机制,那么可以设计一套更有趣的系统:

  1. 捏脸不能全部自定义,而是类似 Red Dead Redemption 那样的双亲继承+微调
  2. 角色的职业,天赋点,也应该通过血缘获得,而不是生硬的数值
  3. 玩家控制角色可以重伤,甚至死亡。死了就真死透了。但是你可以多生育子女,选一个比如95%相似度的,继续玩。
  4. 玩家随身物品,如果掉落,需要家族里其他人去捞,或者发悬赏换回
  5. 子女太多可以交给社会化抚养,变成NPC。
  6. 付费玩家可以全价自定义投胎,免费玩家可以从弃养的NPC里挑选。
  7. 家族里其他角色可以随时控制,或者分配生产、战斗任务
  8. 有俘虏、雇佣,随从等「条约」系统支持跨血缘配合
  9. 都说文明周边会刷出来蛮族。蛮族哪里来的?超出承载能力的人口流亡出去的。一个「family」生育太多,养不起,可以放出去「自谋生路」,投靠别的部落。NPC杂兵不应该是无脑复制,而是每个角色都有个背景板。类似 Rimworld 里 colonist 被海盗抓走俘虏,颠破流离。

这样的体系下,说不定可以加入家族政治勾心斗角,最后演变成类似 十字军之王 一类的策略游戏?23333

在家族这样的设定下,还需要解决最后一个拼图:玩家终归是要时不时AFK的。那么玩家一定会需要据点。这类 base-building 游戏也烂大街了。但是最大的问题就是,一个服务器地图是有限的,好的建家点位就那么几个,目前联机游戏一般的做法是搞一套巨复杂的防御机制,高难度陷阱防止别的玩家抄家。实际上抄家和被抄家都挺难受的。

我的想法是,要不干脆把 base-building 改成关卡编辑器。把你基地当成Tower of Defense 的地牢来设计,家族成员通过占位和分工设计成闯关NPC,最后把你家族里战斗力最强的配置上最好的装备和武器设计成boss。然后拿一部分家产作为其他玩家闯关成功的奖励。别的玩家来闯关,是闯你家的副本,这样不完全破坏你的家,也可以让玩家发挥自己聪明才智,让别的玩家体会会被阴、被虐的乐趣。

有了「家族」和「关卡」两套机制,游戏的玩家也成了游戏机制进化的一部分,可以说病毒式传播,生生不息了。反正我又不做游戏,所以随便YY,不用关心可行性和成本,谁有兴趣请自行挪用,最好做出来能邀请我内测 :)

Posted

stdout

FastAPI/Starlette 长连接感知断开

之前写过如何 在服务器重启的时候感知长连接,最近发现折腾复杂了。

https://github.com/encode/starlette/discussions/1776

测试代码:

import asyncio

async def async_streamer():
    try:
        while True:
            yield b"--boundary\r\nContent-Type: text/plain\r\nContent-Length: 1\r\n\r\n1\r\n"
            await asyncio.sleep(0)
    except asyncio.CancelledError:
        print("caught cancelled error")

app = Starlette(routes=[
    Route('/async', async_endpoint),
])

跑起来: uvicorn stream:app

这段代码粗一看没啥大不了的,但是神奇的地方在于,如果去掉 try ... except asyncio.CancelledError ,代码也能正常跑


async def async_streamer():
    while True:
        yield b"--boundary\r\nContent-Type: text/plain\r\nContent-Length: 1\r\n\r\n1\r\n"
        await asyncio.sleep(0)

而且不会报错!最蛋痛的是,这玩意因为是个 while True,如果你里面有打开的数据库连接,是不会中断的,也不会回收的。因为这个 coroutine 没有继续 async for 来消费,就一直挂在进程里当僵尸了!

加上 try ... except asyncio.CancelledError,能捕获出错,也能处理善后了。

真是神奇啊。不知道是 uvicorn 的特性,还是 ASGI 都这样。

Posted

stdout

不能忘却的记忆

由于小爱同学不能导出就挨个手打出来。纪念这个日子。

一年前这几天,孩子和爱人相继病倒,我本来以为苟住了能以幸免结果在家人好转的时候一测体温发现偏高。于是赶紧洗了个澡,把小卧室腾出来,弄好电暖气、电吹风,装满开水壶,把mbp和其它个人物品搬进去,因为我知道接下来的日子将会无比煎熬。

date
2022-12-18 16:41 37.3
2022-12-18 16:43 37.3
2022-12-18 17.31 38.1
2022-12-18 17:46 38.3
2022-12-18 18:33 39.3
2022-12-18 18.41 38.7
2022-12-18 19:07 38.3
2022-12-18 19:15 38.7
2022-12-18 20:01 38.6
2022-12-18 21:11 39.5
2022-12-18 21:20 38.9
2022-12-18 21:26 39.1
2022-12-18 21:41 39.2
2022-12-18 23:30 38.4
2022-12-19 02:38 39.3
2022-12-19 02:48 38.3
2022-12-19 10:05 38.5
2022-12-19 10:12 37.9
2022-12-19 10:36 37.4
2022-12-19 11:19 37.7
2022-12-19 14:03 37.7
2022-12-19 14:29 37.0
2022-12-19 15:18 37.7
2022-12-19 16:33 37.6
2022-12-19 18:05 38.1
2022-12-19 18:44 37.8
2022-12-19 19:12 37.2
2022-12-19 19:47 37.8
2022-12-19 20:00 37.6
2022-12-19 21:09 37.2
2022-12-19 21:44 37.2
2022-12-19 22:57 36.9
2022-12-20 00:19 37.2
2022-12-20 06:55 37.1
2022-12-20 09:03 37.0
2022-12-20 09:34 37.0
2022-12-20 10:49 36.8
2022-12-20 11:41 38.0
2022-12-20 11:59 38.4
2022-12-20 14:47 38.6
2022-12-20 15:51 38.1
2022-12-20 16:32 38.0
2022-12-20 18:22 38.3
2022-12-20 22:31 38.4
2022-12-21 09:12 38.1
2022-12-21 09:59 38.2*
2022-12-21 11:57 38.2
2022-12-21 13:46 37.5
2022-12-21 15:10 37.5
2022-12-21 16.44 37.2
2022-12-21 17:07 37.0
2022-12-21 20:37 37.2
2022-12-21 20:54 37.7
2022-12-21 22:54 38.3
2022-12-21 23:05 38.4
2022-12-22 09:23 36.7
2022-12-22 09:43 37.3
2022-12-22 12:44 37.6

*: 布洛芬已用完

可能大家只是看到一串冷冰冰的数字,但是我打字都是颤抖的,每一刻时间和体温都是刻骨铭心的记忆。

NEVER FORGET!

Posted

stdout

京加高铁

今天了解到这么一件事,白令海峡原来也就 82.5km 宽,水深55米,对于现代工程技术来说修桥和隧道都不是问题。

如果中间那个岛做个中转站,那么两侧都各自30km左右。所以理论上从北京修一条到西雅图/加州的高铁完全没有问题。。

Posted

stdout

query git notes with Github GraphQL

git notes is an interesting feature, you can use it like

git notes add -m "hello test git notes"
git push origin 'refs/notes/*'

Github supported them back in 2010 then gave up LMAO.

If you need to retrive them with Github API, try GraphQL like this

  {
    repository(owner: "est", name: "snippets") {
      refs(refPrefix:"refs/notes/",first:1) {
        nodes{
          ... on Ref{
            target {
              oid
              ... on Commit{
                changedFilesIfAvailable
                message
                tree{
                  oid
                  entries{
                    oid
                    path
                    size
                    lineCount
                    mode
                    object{
                      ... on Blob{
                        byteSize
                        text
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  }

It will return something like:

  {
    "data": {
      "repository": {
        "refs": {
          "nodes": [
            {
              "target": {
                "oid": "bfdfda121f3d8a2d6aa399e3e8f0d58b3db0a543",
                "changedFilesIfAvailable": 1,
                "message": "Notes added by 'git notes add'",
                "tree": {
                  "oid": "2ab3e6a8b0dff6596fa60ecfb3c61bf91e5b4e1f",
                  "entries": [
                    {
                      "oid": "404d353a254ffbc97b2e16d17b8c100461aef586",
                      "path": "4fb1272f1e235580f87e3ec071984658de050dc9",
                      "size": 21,
                      "lineCount": 1,
                      "mode": 33188,
                      "object": {
                        "byteSize": 21,
                        "text": "hello test git notes\n"
                      }
                    }
                  ]
                }
              }
            }
          ]
        }
      }
    }
  }

Here "path": "4fb1272f1e235580f87e3ec071984658de050dc9" is the commit SHA.

If you add more filter parameters in refs() you can fetch all the notes.

Posted

stdout

VPS推荐:Hosteon

继续上次 Racknerd 之后发现这家还行。写了个爬虫扒了一下各色配置,独立服除外,有需要的自取

pid spec Disk Bandwidth Annual $
1 1C512M 5GB 100Mbps $27.00
7 6C8G 100GB 100Mbps $270.00
77 1C1G 10GB 1Tbps $21.00
78 2C1.5G 20GB 2Tbps $36.00
79 2C2G 25GB 3Tbps $45.00
80 2C2.5G 30GB 4Tbps $54.00
81 3C3G 40GB 5Tbps $63.00
83 3C5G 50GB 8Tbps $90.00
84 6C10G 120GB 12Tbps $180.00
85 10C12G 150GB 15Tbps $270.00
86 14C20G 200GB 20Tbps $450.00
87 20C30G 300GB 30Tbps $675.00
110 1C1G 15GB 2Tbps $21.00
111 2C2G 20GB 4Tbps $39.99
112 3C3G 30GB 6Tbps $49.50
113 4C4G 40GB 8Tbps $59.99
119 1C512M 10GB 500Gbps $24.00
120 1C1G 15GB 1Tbps $36.00
121 2C2G 30GB 2Tbps $72.00
122 2C3G 50GB 3Tbps $108.00
123 2C4G 70GB 4Tbps $144.00
124 4C5G 90GB 5Tbps $180.00
126 6C6G 100GB 6Tbps $216.00
127 6C7G 120GB 7Tbps $252.00
128 8C8G 140GB 8Tbps $288.00
129 8C9G 150GB 9Tbps $324.00
130 10C10G 170GB 10Tbps $360.00
131 10C11G 180GB 11Tbps $396.00
132 12C12G 200GB 12Tbps $432.00
154 1C1G 20GB 4Tbps $33.00
155 2C2G 30GB 6Tbps $40.00
156 3C3G 40GB 8Tbps $60.00
157 4C4G 50GB 10Tbps $84.00
158 5C5G 60GB 12Tbps $105.00
159 6C6G 70GB 14Tbps $130.00
160 7C7G 80GB 16Tbps $145.00
161 8C8G 90GB 18Tbps $170.00
162 9C9G 100GB 20Tbps $190.00
163 10C10G 110GB 22Tbps $210.00
164 11C11G 120GB 24Tbps $240.00
165 12C12G 130GB 26Tbps $260.00
166 8C8G 50GB 30Tbps $98.99
167 4C4G 25GB 15Tbps $56.99
168 4C4G 25GB 15Tbps $79.00
169 8C8G 50GB 30Tbps $158.00
170 12C12G 75GB 30Tbps $235.00
171 16C16G 100GB 30Tbps $318.00
172 20C20G 125GB 30Tbps $399.00
173 24C24G 150GB 30Tbps $480.00
174 28C28G 175GB 30Tbps $575.00
175 32C32G 200GB 30Tbps $660.00
176 36C36G 225GB 30Tbps $730.00
177 40C40G 250GB 30Tbps $810.00
178 1C1G 20GB 2Tbps $21.00
179 32C32G 1TB 1Gbps $540.00
180 64C64G 1TB 1Gbps $725.00
181 128C128G 2x 1Gbps $1850.00

Posted

stdout

太阳系天体的 unicode 符号

发现Astronomical symbolsPlanet symbols
Alchemical symbol

IAU 七金 符号 name 星期 七曜 备注
☉︎ 太阳 Sun 星期天 日曜
月球 Moon 星期一 月曜 也可以画成 ☽︎
或者月相 🌑︎🌒︎🌓︎🌔︎🌕︎🌖︎🌗︎🌘︎,🌚︎🌛︎︎🌝︎︎🌜︎︎
H 水星 Mercury 星期三 水曜 赫耳墨斯(Hermes),罗马人称 Mercury
现代人称「爱马仕」的双盘蛇带翼权杖 Caduceus
V 金星 Venus 星期五 金曜 维纳斯
E 🜨 地球 Earch Bible 里说的四条河把地球分成四洲
另外的符号是 ♁ 表示十字架钉个球 globus cruciger
M 火星 Mars 星期二 火曜 符号是战神盾牌和长矛
J 木星 Jupiter 星期四 木曜 字母 Zeta(大写Ζ小写ζ)加一杠,代表宙斯/朱庇特
S 土星 Saturn 星期六 土曜 字母 kappa-rho ,表示 Kronos 加一杠
泰坦 克罗诺斯,宙斯他爹
U 天王星 Uranus
N 海王星 Neptune

几点:

  • 其中 金星/火星 符号被拿来代表 女/男 是1750年代Carl Linnaeus干的。
  • 冥王星 Pluto 发现的晚,占星术还没来得及改版就惨遭除名成矮行星。
  • 星期和对应的天体意义,全世界惊人的一致

其它各种卫星,小行星的名字就懒得贴了。

有了这些符号,我们可以画一个比如托勒密的简化版太阳系模型:

本文源自《哥白尼、伽利略、开普勒都是脱离证据拍脑袋撕逼》一文。HN

Posted

stdout

Getting started blogging on Win10

I am planning to setup by blogging environment on my Windows PC.

  • OS: Windows 10
  • Shell: Bash on WSL1
  • Program: pelican on Miniconda3 with Python 3.11

My objective today is get rid of the static/js/core.js from aether-pelican theme and replace it with a simple CSS, and it turns out quite challenging.

WSL1 and file system

As it turns out, the installed Ubuntu root filesystem on WSL1 is located here:

%localappdata%\Packages\CanonicalGroupLimited.UbuntuonWindows_79rhkp1fndgsc\LocalState\rootfs

I plan to move my home dir (at least) to another SSD on my PC, as the C:\ drive is an old SanDisk Plus240, which is unforgivablly slow these days. I tried to mv ~ /mnt/d/home, ok but the permission gets weird.

So I tried to copy the folder on Windows to D:\home, and mklink back. I tried both mklink /D and mklink /J ( the /D switch works on a remote drive whereas the /J junction works on local NTFS disks only) and they both failed be recognized by WSL1. The bash yields Broken stdout/stdin upon launch

So OK, default home dir then. Bad luck for me.

Ubuntu 22

I just found out my cat /etc/lsb-release shows Ubuntu 18, which is the default distro installed on WSL1 I assume. I tried to install Ubuntu 22 from the Microsoft Store, after several wasted minutes (curse the slow Internet in Mainland!) it turns out for WSL2 only. Luckily the Windows Subsystem for Linux was successfully upgraded to latest version:

C:\> wsl --version
WSL version: 1.2.5.0
Kernel version: 5.15.90.1
WSLg version: 1.0.51
MSRDC version: 1.2.3770
Direct3D version: 1.608.2-61064218
DXCore version: 10.0.25131.1002-220531-1700.rs-onecore-base2-hyp
Windows version: 10.0.19045.3570

the wsl --list --online commands stuck for a total of 10 minutes and returns nothing (curse the AS4134 163NET!)

Getting Python, Miniconda and Pelican

So linux distro setup was back to where started, I only have Python to mess with. Miniconda3 gets insalled easily, now that's a some progress. The static site generator I am using pip install pelican[markdown] finishes ok, the pypi was not blocked? Keke.

Then I typed make. It was not part of WSL1? Blaspheme!

Manually running python -m pelican.server, and voila! The Windows asks for firewall listening permission, granted, open up browser, BAM! My blog setup process is done and now I can write and tweak it on Windows.

Oh, and there's git config core.fileMode false needs to be checked!

Posted

stdout

火星上怎么结算工资

看到个段子

突然让我想起个事,如果去火星打工,薪资该怎么计算?摘录 wikipedia

火星的恒星时长 24小时37分钟22.663秒,而太阳日则长 24小时39分钟35.24409 秒。而地球的恒星时和太阳日则分别是 23小时56分钟4.0916秒24小时。在比较之下,一火星太阳日等于1.027491地球太阳日,即比地球太阳日长2.7%。

怎么有两个天?继续搜

太阳日(英语:solar day)是依据太阳运动,所定义的时间,可以分为视觉太阳日和平均太阳日。一太阳日传统称为一“日”、一“天”或一“昼夜”。

英语里把火星的一天叫做一个 Sol。一个火星年在英文的 wikipedia 有讲

sidereal year ... is about 686.98 Earth solar days (≈ 1.88 Earth years), or 668.5991 sols 。也就是说火星一年有 668.5991 火星天。。。

火星因为有25.19°的自转倾角,跟地球相近,所以也可以划分四季。但问题是,火星的轨道长这样:

所以如果按照四个季节划分,最长的季节有 194 个 Martian Sols,最短的 142 Martian sols

那么问题来了。在火星上怎么算工资呢?我觉得得安天结了。还有,假如亲爱的读者小伙伴将来到火星务工,应聘的时候一定确认好是按地球年结算还是火星年哦。谨防火星历诈骗!

Posted

stdout

FastAPI/Starlette graceful shutdown server-sent events

缘起

假如你用 FastAPI/Starlette框架,写了一段 Server-sent Events

@app.get('/api/my_stream')
async def api_stream():
  async def gen():
    while 1:
      yield "data: {}\n\n"
      await asyncio.sleep(1.0)

  return StreamingResponse(gen, media_type="text/event-stream", headers={
    'X-Accel-Buffering': 'no',
    'Cache-Control': "no-cache",
    'Connection': "keep-alive",
  })

FastAPI 事件

然后你重启了服务器,比如 gunicorn+uvicorn,你会发现这个连接不会断开,一直输出结果。直到 worker 进程超过配置里的 timeout 参数(默认60秒),被 master 强行杀死然后重启。

一开始尝试了 FastAPI 官方的 shutdown 事件,再在代码的 while 1 加一个 if 判断。发现不管用

@app.on_event('shutdown')
def on_server_shutdown():
    app.state.running = False

发现这段代码执行是执行了,但是是在杀死worker的时候生效的。得更加提前。用ASGI推荐的lifespanhttp.disconnect 太复杂,放弃。

Linux signal

尝试暴力监听 worker signal

import signal
signal.signal(signal.SIGINT, on_server_shutdown)  # ctrl+c
signal.signal(signal.SIGUSR2, on_server_shutdown)
signal.signal(signal.SIGTERM, on_server_shutdown)
signal.signal(signal.SIGWINCH, on_server_shutdown)
signal.signal(signal.SIGHUP, on_server_shutdown)
signal.signal(signal.SIGQUIT, on_server_shutdown)

发现也不管用。研究了半天,似乎是 UvicornWorker.init_signals 的时候signal.SIG_DFL。无语ing

uvicorn shutdown 流程

看gunicorn日志:

[2023-11-02 09:50:38 +0800] [25918] [INFO] Shutting down
[2023-11-02 09:50:38 +0800] [25918] [INFO] Waiting for application shutdown.
[2023-11-02 09:50:38 +0800] [25918] [INFO] Waiting for connections to close. (CTRL+C to force quit)
....
[2023-11-02 09:51:18 +0800] [25918] [INFO] Application shutdown complete.
[2023-11-02 09:51:18 +0800] [25918] [INFO] Finished server process [25918]
[2023-11-02 09:51:18 +0800] [25918] [INFO] Worker exiting (pid: 25918)

观察到 Waiting for connections to close 之后卡住。

属于 uvicorn 的 Server.shutdown() 方法

这个方法调用链:

  1. Server.install_signal_handlers 里注册 signal
  2. Server.handle_exit 里设置 Server.should_exit = True
  3. 每秒一次的 Server.on_tick 就会打断 Server.main_loop() 死循环
  4. 调用 Server.shutdown

如果能拿到 Server.should_exit 自行判断就好了,但是 ASGI 是容器无感的,只继续研究

断开http连接

上面日志有一个比较关键:

# Request shutdown on all existing connections.
for connection in list(self.server_state.connections):
    connection.shutdown()
    await asyncio.sleep(0.1)

其http连接关闭方法实现为:

def shutdown(self):
    """
    Called by the server to commence a graceful shutdown.
    """
    if self.cycle is None or self.cycle.response_complete:
        self.transport.close()
    else:
        self.cycle.keep_alive = False

这里应该走的是 else 分支。所以解决方法就是去代码里拿到 cycle.keep_alive 这个属性。

ASGI 接口

跟了一会儿,发现ASGI 在 starlette 里如下流程:

  1. Route() 初始化 self.app = request_response(endpoint)
  2. Route().handle() 的时候会调用 self.app(scope, receive, send)
  3. unicornrun_asgiresult = await app(self.scope, self.receive, self.send) 。其中 app 就是 starlette 的 Route() 实例

所以解决方案逐渐明朗了

黎明的前夜

通过 FastAPI/Starlette 请求的 .receive 属性的 __self__ 拿到 unicorn 的 cycle 实例,然后定时判断上面 shutdown 赋值的 self.cycle.keep_alive = False

@app.get('/api/my_stream')
async def api_stream(req: Request):
  async def gen():
    while 1:
      yield "data: {}\n\n"
      await asyncio.sleep(1.0)
      if getattr(req.receive.__self__, 'keep_alive', None) is False:
        break

  return StreamingResponse(...

这里用了个 getattr() 是保证这个 hack 在非 unicorn 下代码也能正常跑

git commit . -m "haha" 上机联调,发现坑了。。。我特么。。。。这个 cycle.keep_alive 默认就是 False

扭转 keep_alive

这玩意一直为 False 的原因是,unicorn 的 HttpToolsProtocol.on_headers_complete 赋值过程:

self.cycle = RequestResponseCycle(..., keep_alive=http_version != "1.0")

这里 http_version 可以通过 req.scope['http_version'] 得到,打印了一下,你猜怎么着,还真tm是。。。原因就是厂里反代 nginx的proxy_http_version没配置。这里是个常见的坑,非常影响性能,因为每个请求会生成一个新的 http 连接。

但是也等不急SA去改配置了。于是直接在代码 async def gen(): 前面写死:

req.receive.__self__.keep_alive = True 。提交,再试。。。咦,怎么 keep_alive 依然为 False????明明刚刚赋值了?

继续读代码,发现第二个坑,发现 unicorn 的 RequestResponseCycle.send() 方法里,在构造返回的时候,有一句

for name, value in headers:
    elif name == b"connection" and value.lower() == b"close":
        self.keep_alive = False

好吧。。。那么解决方法就是,把 keep_alive = True 挪到返回内部。先完成返回构造,再强行改值。

最后的代码

@app.get('/api/my_stream')
async def api_stream(req: Request):
  # req.receive.__self__.keep_alive = True  # doesn't work here
  async def gen():
    req.receive.__self__.keep_alive = True  # works here
    while 1:
      yield "data: {}\n\n"
      await asyncio.sleep(1.0)
      if req.receive.__self__.keep_alive is False:
        break

  return StreamingResponse(gen, media_type="text/event-stream", headers={
    'X-Accel-Buffering': 'no',
    'Cache-Control': "no-cache",
    'Connection': "keep-alive",
  })

测试重启 worker,该连接在1秒后断开,worker平稳重启。完美。

苦逼撸业务的一天又开始了。

Posted

stdout

浏览器拉起钉钉客户端并跳转OA工单

又是 corporate software engineering 吃屎的一天。用户在内部系统走流程,厂里用的是钉钉自带的OA工单审批。已经接入了API创建、完成工单

问题在于发起工单之后,用户并没有感知,不知道工单去哪里了,流程就断了,向钉钉官方售后发起咨询,对方丢了一句:不支持。无语

只能自己鼓捣。已知工单链接,在钉钉聊天窗口内部的最短的网址为:

  https://aflow.dingtalk.com/dingtalk/mobile/homepage.htm?corpid=...&procInstId=...#approval

对方丢了一个 dingtalk:// 跳转协议

反复尝试,发现一个很有用:

桌面端打开URL
dingtalk://dingtalkclient/page/link
侧边栏: pc_slide=true
大容器(类似工作台容器)ddtab=true

于是构造一个试试:

  dingtalk://dingtalkclient/page/link?url=https%3A%2F%2Faflow.dingtalk.com%2Fdingtalk%2Fpc%2Fquery%2Fpchomepage.htm%3Fcorpid%3D...%26procInstId%3D...%23%2Fapproval

然后果然ok了。

钉钉给人的感觉就是各个部门 hack 强行揉合起来一个怪物。各种不完善、不一致的问题。有空写一下吐槽。

Posted

stdout

SVG放在 img 中不能加载图片/字体

最近想在 markdown 里搞图文混排,遇到一个大图把版面占完了,想缩小一些,由于 .md 渲染器安全限制,没法直接指定宽高

只能从图片本身想办法,首先最直接的手段就是服务器再缩放一次,但是蛋痛的问题是缩放之后文件怎么存,怎么给静态文件路由,怎么保障图片可用性,怎么兼容各种尺寸,想想都头大。

想到一个 hack,要不直接放一个 .svg 进去,因为 svg 支持加载位图资源,所以把 svg 位图缩小一点,让浏览器渲染 svg 不就行了。

没想到就被这个 hack 坑了。原因是 svg 如果被 <img> 标签渲染,那么禁止加载任何外部资源,脚本也禁止执行。

为了证明这个限制,我写了个 demo 放在 https://lab.est.im/shit_svg/

这个问题太隐蔽了。stackoverflow 上只有 Robert LongsonThangLeQuocTheHippo
的答案提到隐约线索:

后来搜了到 skychx 把这个问题研究透了。罪魁祸首 SVG Security

If an SVG file is fetched as image, then certain requirements apply to this document:

  • The SVG document is not allowed to fetch any resources. This also applies to scripts, stylesheets or images.
  • Fonts shouldn't be loaded as well. The situation in UAs seems to still be unclear though.
    Scripts must not be executed.
  • Event listeners must be disabled at all times.

浪费我好多时间。SVG2里把这个说得很明白了 https://www.w3.org/TR/SVG2/conform.html#processing-modes

When external references are disabled in an SVG document, any attempt to fetch a document through an external reference must instead be treated as if a network error occurred and no data was received.

不过查阅资料的时候发现个好玩的,SVG 1.1 支持 URL fragments

比如:

Posted

stdout

市以上行政区划去掉后缀

今天才知道,中国一共333个市一级行政单位。数据来源是 民政部首页 ➡ 民政数据

import re

def short_name(name):
    m = re.search(r'''
        (\S+?)
        (?:
            壮族|回族|维吾尔|省|自治区|特别|  # 省
            (?:侗|傈僳|傣|哈尼|回|土家|壮|布依|彝|景颇|朝鲜|白|羌|苗|藏)族|
            蒙古|柯尔克孜|哈萨克|
            地区|市|盟|自治州|自治县
        ).*$
    ''', name, re.VERBOSE)
    if m:
        return m.group(1)
    return name

这段代码的作用是把比如 "黔南布依族苗族自治州" 这样的名字,缩短成 "黔南"

有一个小坑是,市一级行政区包含 蒙古|柯尔克孜|哈萨克 这三个名字后面没有 字。

有一些 XX林区 XX矿区 什么的,我觉得保留比较好。

Python的 re.VERBOSE 真是好东西!

Posted

stdout

在py3里同步/异步混合使用 httpx 调用

比如开发需求是请求一个 http API,得到数据,解析一下返回,那么一般的做法是封装一个方法,比如

import httpx


def get_sth(p1, default=MY_VAL):
    # network
    r = httpx.get(API_URL, params={'t1': p1})
    # parsing
    res = r.json().get('my_key') or MY_VAL

但是如果想在 async/await 里用这段代码,就得改成

import httpx


async def get_sth(p1, default=MY_VAL):
    # network
    with http.AsyncClient() as client:
        r = await client.get(API_URL, params={'t1': p1})
    # parsing
    res = r.json().get('my_key') or MY_VAL

注意其中 def get_sth() 也必须改成 async def get_sth 。这就是所谓 async/await 传染性

这个时候,如果你想把这块代码抽象出来,让同步/异步的库都能调用,我在最近重构里找到一个最佳实践:

import httpx


def get_sth(p1, default=MY_VAL):
    r = httpx.Request('GET', API_URL, params={'t1': p1})
    r.parse = lambda x: (x.get('my_key') or MY_VAL)

# 同步调用:
with httpx.Client() as client:
    r = client.send(get_sth())
r.request.parse(r.json())

# 异步调用:
async with httpx.AsyncClient() as client:
    r = await client.send(get_sth())
r.request.parse(r.json())

思路是 逻辑和 transport解耦,也是某种意义上的 Sans-IO 了吧?

Posted

stdout

git切换马甲

如果你有多个 github、bitbucket、gitlab 账号需要来回切换,那么这个方法或许对你有用。

使用git你需要配置一个 ~/.ssh/id_rsa,如果你有多个身份就麻烦了。传统网上搜到的办法是编辑 ~/.ssh/config 然后加入个类似这样的配置:

  Host estgit
  HostName github.com
  Port 22
  User git
  IdentityFile ~/.ssh/est_github
  IdentitiesOnly yes

这样做也不是不行,就是你输入命令的时候得把主机名改了。比如

git clone git@github.com:est/aether-pelican.git

你需要改成

git@estgit:est/aether-pelican.git

当然这还不是最蛋痛的。最蛋痛的是如果你用了 git submodules 那么你切换马甲变得非常麻烦。因为别人用你的项目没法解析 git@estgit 这样的玩意。

我研究出来了一个hack,直接改repo的 .git/config 可以达到同样的效果。比如

  [core]
      sshCommand = /usr/bin/ssh -o IdentitiesOnly=yes -i ~/.ssh/est_github -a
  [user]
      name = est
      email = ...@...

这样你可以随意在不同的repo切换n个 ssh identity 身份。

Posted

stdout

Rimworld 1.4 Biotech Xenogene/Endogene code names

I found out the corresponding gene code by accident, for Yuran Race mod it's located under

~/Library/Application\ Support/Steam/SteamApps/workshop/content/*/2844129100/Bio1.4/Defs/YuranBioTitle.xml

So the official description can be found at (or wiki):

~/Library/Application\ Support/Steam/SteamApps/common/RimWorld/RimWorldMac.app/Data/Biotech/Defs/GeneDefs/

I need to use the dev tool to modify gene of my pawn, so here's the table:

import os
import xml.etree.ElementTree as ET
p = os.path.expanduser('~/Library/Application Support/Steam/SteamApps/common/RimWorld/RimWorldMac.app/Data/Biotech/Defs/GeneDefs/')
fields = 'defName label description biostatCpx biostatMet biostatArc'.split()
print('| %s |' % ' | '.join(fields))
print('|--------' * len(fields) + '|')
for f in os.listdir(p):
    root = ET.fromstring(open(p + f).read())
    for elem in root.findall('GeneDef'):
        vals = [t.text if isinstance(t:=elem.find(x), ET.Element) else '' for x in fields]
        if vals[0]:
            print('| %s |' % ' | '.join(vals))

The result is:

defName label description biostatCpx biostatMet biostatArc
Hair_BaldOnly no hair Carriers of this gene grow no hair on the head.
Hair_ShortOnly short-haired Carriers of this gene can only grow short hair.
Hair_LongOnly long-haired Carriers of this gene grow hair on the head very quickly.
Hair_Grayless grayless hair Carriers of this gene keep their natural hair color as they age.
Beard_BushyOnly only bushy beards Male carriers of this gene experience rapid beard growth and are uncomfortable cutting their beards.
Beard_NoBeardOnly beardless Carriers of this gene grow no facial hair.
Beard_Always unisex beards Carriers of this gene always have thick facial hair, even women.
Skin_InkBlack ink black skin Carriers of this gene produce a pigment that turns their skin a pale black color almost as dark as ink.
Skin_SlateGray slate gray skin Carriers of this gene produce a pigment that turns their skin slate gray.
Skin_LightGray light gray skin Carriers of this produce a light-gray pigment in their skin.
Skin_SheerWhite sheer white skin Carriers of this gene have sheer white skin, unlike natural skin tones, due to a special engineered reflective cell covering.
Skin_Blue blue skin Carriers of this gene produce a pigment that turns their skin a blue color.
Skin_Purple purple skin Carriers of this gene produce a pigment that gives their skin a purple color.
Skin_PaleRed pale red skin Carriers of this gene produce a pigment that turns their skin a moderate red color.
Skin_DeepRed deep red skin Carriers of this gene produce a deep-red pigment that gives their skin an almost bloody appearance.
Skin_PaleYellow pale yellow skin Carriers of this gene produce a pigment that turns their skin a grayish yellow color.
Skin_DeepYellow deep yellow skin Carriers of this gene produce a pigment that gives their skin a deep yellow color.
Skin_Orange orange skin Carriers of this gene produce a pigment that gives their skin an orange color.
Skin_Green green skin Carriers of this gene produce a pigment that gives their skin a green color.
Furskin furskin Carriers of this gene grow thick fur all over their body, which protects them from cold temperatures. 1 -1
Eyes_Red red eyes Carriers of this gene have deeply red-pigmented eyes.
Eyes_Gray gray eyes Carriers of this gene have pale white-gray eyes.
Brow_Heavy heavy brow Carriers of this gene have a prominent brow. 0
Tail_Furry furry tail Carriers of this gene grow a fluffy tail which partially protects them from cold temperatures. 1 -1
Tail_Smooth smooth tail Carriers of this gene grow a slender tail that can act as a dexterous fifth limb. 1 -1
FacialRidges facial ridges Carriers of this gene grow raised ridges of skin on their face. 0
MinTemp_SmallIncrease cold weakness Carriers of this gene are slightly less comfortable in cold temperatures. 1
MinTemp_SmallDecrease cold tolerant Carriers of this gene are slightly more comfortable in cold temperatures. -1
MinTemp_LargeDecrease cold super-tolerant Carriers of this gene are much more comfortable in cold temperatures. -2
MaxTemp_LargeIncrease heat super-tolerant Carriers of this gene are more comfortable in warm temperatures. -2
MaxTemp_SmallIncrease heat tolerant Carriers of this gene are slightly more comfortable in warm temperatures. -1
MaxTemp_SmallDecrease heat weakness Carriers of this gene are slightly less comfortable in warm temperatures. 1
PsychicAbility_Deaf psychically deaf Carriers of this gene are deaf to all psychic energy and influence outside their own minds. They cannot be affected by psychic influence, nor can they ever wield psychic power. 2
PsychicAbility_Dull psychically dull Carriers of this gene are less psychically-sensitive than others. 1
PsychicAbility_Enhanced psy-sensitive Carriers of this gene are more psychically-sensitive than average. -2
PsychicAbility_Extreme super psy-sensitive Carriers of this gene are much more psychically-sensitive than most. 2 -5
MoveSpeed_Slow slow runner Carriers of this gene move more slowly than normal. 3
MoveSpeed_Quick fast runner Carriers of this gene move more quickly than normal. -3
MoveSpeed_VeryQuick very fast runner Carriers of this gene move much more quickly than normal. -5
Beauty_VeryUgly very unattractive Carriers of this gene have misshapen, asymmetrical facial structures and blotchy skin. They're hard to look at. 2
Beauty_Ugly unattractive Carriers of this gene have exaggerated facial features and poor skin that are generally considered ugly. 1
Beauty_Pretty attractive Carriers of this gene have unusually symmetrical, balanced facial features and extra-clear skin which gives them a pleasing appearance. -1
Beauty_Beautiful very attractive Carriers of this gene have remarkably precise and symmetrical faces. Their features are distinctive and strong without being exaggerated, and their skin is nearly perfect. They are generally seen as beautiful. -2
Learning_Slow slow study Carriers of this gene have deficient long-term memories and don't understand new ideas quickly. They are slow at learning new skills and knowledge. 2
Learning_Fast quick study Carriers of this gene have excellent memories and grasp new ideas quickly. They learn faster than others. 2 -3
Mood_Depressive very unhappy Carriers of this gene are highly predisposed to negative emotion. They'll see the bad in every situation and have a much lower mood than others. 5
Mood_Pessimist unhappy Carriers of this gene are predisposed to pessimistic perceptions. They'll tend to interpret things negatively and have lower mood than others. 3
Mood_Optimist happy Carriers of this gene are predisposed to optimistic feelings. They'll have higher mood than others. 2 -1
Mood_Sanguine very happy Carriers of this gene are highly predisposed to optimism and not at all inclined to think negatively. They'll have much higher mood than others. 3 -2
ToxResist_Partial tox resistance Carriers of this gene are resistant to toxic buildup from any source. This includes pollution, toxic fallout, tox gas, and direct attacks with venom or injected poison. They'll gain half the amount of toxic buildup compared to others.\n\nCellular filters in the lung and skin reduce the dose of toxins entering the bloodstream. -2
ToxResist_Total tox immunity Carriers of this gene are totally immune to toxic buildup from all sources including polluted terrain, toxic fallout, tox gas, and direct attacks with venom or injected poison. They are also not bothered by acidic smog.\n\nThe carrier's biochemical pathways are modified to route around interference from nearly all known toxins. Along with enhancements to the kidneys and liver, this keeps carriers comfortable in even the most toxic of environments. 2 -4
Delicate delicate Carriers of this gene take greater injuries than others from the same damage. They have thin, brittle bones and less binding molecules in joints and flesh. 3
Robust robust Carriers of this gene take less injuries than others from the same damage. They have thickened, densified bones, nearly-solid ribcages, and strengthened binding factors in joints and flesh. -2
Pain_Reduced reduced pain Carriers of this gene feel half as much pain compared to a baseliner. Reduced neuron activity in the brain's nociception centers makes pain dull and faint. This can be advantageous sometimes, and dangerous other times. -1
Pain_Extra extra pain Carriers of this gene feel more pain than others given the same injuries. Neuron activity in the brain's nociception center is amplified, so pain feels extra-intense and fiery. This can be protective, but overall it's considered a negative and makes it hard to push through difficult situations. 2
Aggression_DeadCalm dead calm Carriers of this gene feel calm in every situation and have a very placid demeanor. They will never start social fights or have aggressive mental breaks. -1
Aggression_Aggressive aggressive Carriers of this gene are quick to anger. They are twice as likely to start social fights. When they have mental breaks, they are twice as likely to choose an aggressive kind of break. 2
Aggression_HyperAggressive hyper-aggressive Carriers of this gene are hormonally high-strung and very aggressive. They are three times as likely to start social fights. Any mental break they have will be of an aggressive type. 3
VerySleepy very sleepy Carriers of this gene get tired much faster than others. 4
Sleepy sleepy Carriers of this gene get tired somewhat faster than others. 2
LowSleep low sleep Carriers of this gene get tired less quickly than others. 2 -4
Neversleep never sleep Carriers of this gene have a unique metabolic process which allows clusters of neurons to sleep while the rest of the brain stays awake. They never need to sleep. 3 -6
MeleeDamage_Weak weak melee damage Carriers of this gene do less damage in close-quarters combat. Weak fast-twitch muscle fibers make their strikes shaky and weak. 1
MeleeDamage_Strong strong melee damage Carriers of this gene do more damage in close-quarters combat. Extra-strong fast-twitch muscle fibers make their strikes accurate and powerful. -2
UVSensitivity_Mild mild UV sensitivity Carriers of this gene have biological compounds in their skin that react painfully to UV radiation. They are unusually sensitive to sunlight. 3
UVSensitivity_Intense intense UV sensitivity Carriers of this gene have biological compounds in their skin that react dangerously to UV radiation. They are intensely sensitive to sunlight. 2 4
Libido_Low low libido Carriers of this gene are less likely to engage in lovin' with their partner.
Libido_High high libido Carriers of this gene are more likely to engage in lovin' with their partner.
FireSpew fire spew Carriers are able to spew flammable bile generated by a special organ in their neck. The bile sticks to anything in a small area and can ignite people, objects, and the ground. -2
FoamSpray foam spray Carriers grow glands in the neck that generate and store a fire-retardant foam. They can spew this foam over an area to extinguish fires. -2
LongjumpLegs longjump legs Carriers have special hemogen-powered muscle fibers in their legs which allow them to jump great distances. -2
AnimalWarcall animal warcall Carriers of this gene can perform an animal warcall, using a powerful bellow and psychic connection to call an animal to fight for them. 1 -3
Bloodfeeder bloodfeeder Carriers of this gene have small retractable fangs and an organ on the roof of the mouth which can extract hemogen from fresh warm blood. They can bite an unresisting person, suck the blood, and gain hemogen directly. -1
Coagulate coagulate Carriers of this gene have special glands on their hands and wrists, as well as a unique salivary compound that they can use to rapidly tend wounds. -1
XenogermReimplanter gene implanter Carriers of this gene can implant a copy of their xenogerm into another person through a somewhat gross-looking injector organ. Their own genetic material will then regrow very slowly. If they implant while their genes are regrowing, they will die. Germline genes will be unaffected. 3 1
PiercingSpine piercing spine Carriers grow an opening in their upper chest along with a quiver of keratin spines. Using a hemogen-powered chemical reaction, they can fire these spines at high speed at nearby targets with surprising accuracy. -1
AcidSpray acid spray Carriers grow glands in the neck that generate and store a sticky acid substance, along with acid-tolerant tissues in the mouth. They can spew this acid over an area, where it will stick to enemies and burn them over time. -2
Body_Fat fat body Carriers can have fat bodies. A person can have more than one body type gene; one body type will be chosen among those that are allowed.
Body_Thin thin body Carriers can have thin bodies. A person can have more than one body type gene; one body type will be chosen among those that are allowed.
Body_Hulk hulk body Carriers can have large bodies. A person can have more than one body type gene; one body type will be chosen among those that are allowed.
Body_Standard standard body Carriers can have average-shaped bodies. A person can have more than one body type gene; one body type will be chosen among those that are allowed.
Ears_Human human ears Carriers of this gene have regular human ears.
Ears_Pig pig ears Carriers of this gene will grow pointed pig-like ears.
Ears_Floppy floppy ears Carriers of this gene grow long, floppy hound-like ears.
Ears_Cat cat ears Carriers of this gene have cat-like ears.
Ears_Pointed pointed ears Carriers of this gene have pointed ears.
Nose_Human human nose Carriers of this gene have regular human noses.
Nose_Pig pig nose Carriers of this gene have pig-like snouts.
Jaw_Baseline human jaw Carriers of this gene have regularly-shaped jaws.
Jaw_Heavy heavy jaw Carriers of this gene have large jaws.
Head_Gaunt gaunt head Carriers of this gene have a pinched, gaunt appearance in their face and head.
Hands_Human human hands Carriers of this gene have regular human hands. 0
Hands_Pig trotter hands Carriers of this gene have hands that partially resemble pig trotters. This reduces their ability to manipulate objects. 1 1
ElongatedFingers elongated fingers Long, delicate fingers improve the carrier's manipulation capacity. This aids with many tasks, especially crafting and construction. 1 -1
Headbone_Human human headbone Carriers of this gene have regular human skulls.
Headbone_MiniHorns mini-horns Carriers of this gene grow two small horns protruding from the forehead.
Headbone_CenterHorn center-horn Carriers of this gene grow a single horn protruding from the center of the forehead.
Voice_Human human voice Carriers of this gene have regular human vocal chords.
VoicePig pig voice Carriers have a squealing voice like that of a pig.
VoiceRoar roar voice Carriers have an animal-like roaring voice.
Hemogenic hemogenic Carriers of this gene have a reserve of biological strength powered by a resource called hemogen. The resource can be gained and spent in various ways, all of which are unlocked by other genes.\n\nCarriers lose 2 hemogen per day from biological entropy. 1 1
HemogenDrain hemogen drain Carriers lose an additional 8 hemogen per day from biological entropy. 1 6
FireWeakness tinderskin Carriers have dry, thin skin which burns easily from fire, and their immune systems react very poorly to this kind of threat. Damage from fire is multiplied by 4. 2
FireTerror pyrophobia Carriers of this gene have an intense fear of fire. When fires are close, there is a chance they will have a mental breakdown at any moment. 4
PerfectImmunity perfect immunity Carriers of this gene have archite-enhanced immune systems which intelligently destroy invaders. They are totally immune to most normal illnesses. 3 1
DiseaseFree non-senescent Carriers of this gene do not go through senescence in the normal way. They never get chronic age-related diseases like cancer, bad back, cataracts, or dementia. 3 1
TotalHealing scarless Carriers of this gene have a special type of regenerator cell which can heal old wounds and chronic illnesses like bad back. 4 1
Deathrest deathrest Carriers of this gene must periodically regenerate themselves in a special coma called deathrest. Deathrest takes days, but can confer substantial bonuses. Deathrest can be accelerated and its effects enhanced by the use of a variety of special buildings and technologies.\n\nThose who put off deathresting will suffer from deathrest exhaustion. 6
Ageless ageless Carriers of this gene have archites in the bloodstream which continuously reverse the process of aging. Starting at the age of 13, carriers begin to biologically age slower. By 18, the aging process stops completely. 3 1
Deathless deathless Carriers of this gene have archites in the blood which will sustain their life processes no matter what. As long as the brain remains intact, a carrier of this gene will never die. 7 1
ArchiteMetabolism archite metabolism Carriers of this gene have special archites in their cells that facilitate and optimize metabolism. This improves overall genetic and metabolic quality. 6 6 2
WoundHealing_Slow slow wound healing Carriers of this gene heal from wounds half as fast as normal. 2
WoundHealing_Fast fast wound healing Carriers of this gene heal from wounds twice as fast as normal. -2
WoundHealing_SuperFast superfast wound healing Carriers of this gene heal from wounds four times as fast as normal. -3
Immunity_Weak weak immunity Carriers of this gene gain immunity to diseases more slowly than normal. They may die from infections that others would survive. 2
Immunity_Strong strong immunity Carriers of this gene gain immunity to diseases faster than normal. -1
Immunity_SuperStrong super immunity Carriers of this gene gain immunity to diseases considerably faster than normal. 2 -2
ToxicEnvironmentResistance_Partial partial antitoxic lungs Carriers of this gene are resistant to environmental toxins. They get less toxic buildup from tox gas, polluted terrain, and toxic fallout, but are still vulnerable to direct attacks with venom or injected poison. Additionally, they build up rot stink exposure slower. -1
ToxicEnvironmentResistance_Total total antitoxic lungs Carriers of this gene are immune to environmental toxins, but not from direct toxic attacks. They get no toxic buildup from tox gas, polluted terrain, or toxic fallout, and they are not bothered by acidic smog. They are still vulnerable to direct attacks like venom and injected poison. Additionally, they are immune to rot stink exposure. 2 -3
Sterile sterile Carriers of this gene cannot reproduce by natural means. 1
Fertile fertile Carriers of this gene have a higher chance of becoming pregnant or impregnating others.
Superclotting superclotting Carriers of this gene have extra-power coagulating factors in their blood, and will stop bleeding very quickly when wounded. -1
KindInstinct kind instinct Carriers of this gene are high in trait agreeableness and are very conscientious. They rarely insult others and will sometimes offer kind words to brighten the moods of those around them. They also never judge people by their appearance. -1
ViolenceDisabled violence disabled Carriers of this gene are emotionally and mentally incapable of engaging in violence. They are overwhelmingly resistant to and horrified by the idea of hurting another. 3
Nearsighted nearsighted Carriers of this gene have difficulty seeing at a distance. Their shooting accuracy at long ranges is reduced. 2
StrongStomach strong stomach Carriers of this gene have an extra toxin-filtering organ in their stomach and will never suffer from food poisoning even after eating rotten food. -1
DarkVision dark vision Carriers of this gene see well in low light and are unaffected by mood penalties related to darkness. They have a reflective layer behind the retina that amplifies their ability to see in the dark. -1
KillThirst kill thirst Carriers of this gene lust for the feeling of ending another's life. They will become irritated if they go for too long without killing someone in close combat. 4
FireResistant fire resistant Carriers of this gene have special fast-acting sweat glands and heat-resistant skin. They only take 25% of the normal damage from fire. The chance of them catching on fire is also drastically reduced. -2
Inbred inbred This genetic condition affects a person's fertility, immunity, and mental capacity. -2
RobustDigestion robust digestion Carriers of this gene grow a multi-fold stomach, allowing them to digest raw foods more efficiently than baseline humans. In general, they get the same nutrition from raw food as from if it is cooked. They also don't mind the taste of raw food at all. 2 -2
Instability_Mild mild cell instability Carriers of this gene need less metabolic energy to stay alive, at the cost of reduced stability in their cell-replication machinery. 2
Instability_Major major cell instability Carriers of this gene need much less metabolic energy to stay alive, at the cost of greatly-reduced stability in their cell-replication machinery. 4
PsychicBonding psychic bonding Carriers of this gene have a special neural organ that makes them psychically bond with a lover for life. As long as the lovers are together, they will be happy. If they are physically separated, they will be disturbed by the distance. If one dies, the other's mind will be badly disrupted. 1 -1
PollutionRush pollution stimulus Carriers of this gene get a chemical rush from being exposed to pollution. This makes them move faster and helps them think clearer. A similar gene is found in combat-engineered mega-insects. 1 -1
Unstoppable unstoppable Carriers of this gene are not slowed down when taking damage. 1 -2
NakedSpeed naked speed Carriers of this gene move slower while clothed, and faster while naked. 1 2

Posted

stdout

如何在出远门的时候关闭洗碗机定时启动

家里有个「美的」的某型号洗碗机,一般是积累了当天的碗筷餐具,每天23:00低谷电价之后自动开始定时洗碗。这个功能可以在app上设置实现,还可以设置烘干和通风保存,第二天就能拿出来洁净且干燥的碗筷使用,平时日常这样用起来挺不错。

但是这也导致一个问题,如果节假日出门旅行几天,这个洗碗机里即便没有碗筷,也会傻傻的启动浪费水电,所以需要禁用几天,我的操作流程是:

  1. 掏出手机,解锁屏幕,找到「美的美居」 app并打开
  2. 欣赏该厂家运营设置的精美开屏广告并等待
  3. 打开「场景」
  4. 找到自动化 - 已开启 列表里,关于洗碗机的部分
  5. 找到进入每天自动洗碗的选项,编辑,选择关闭
  6. 保存设置,退出 app
  7. 回到家,又需要按照上面的流程操作一番启用该流程

每次想起这个事,真是又繁琐又蛋痛。特别是回来如果忘记启用,第二天早上起来会发现一篮子未洗的碗筷傻眼。

这次五·一假,我压根忘记了禁用定时洗碗的事。但是回到家添加洗碗粉的时候发现,这几天洗碗机并未启动。问了下是我老婆操作的,她的流程是:

  1. 给洗碗机门板开一条缝

洗碗机不会在门没关严的情况下启动,所以。。。。

我问她这样做是故意的还是不小心的,她说故意不小心的。

Posted

stdout

当前版本gpt的根本弱点

下面的东西仅限当前,不是不可以解决的。

  1. gpt系列数学能力不行,是因为它把每一个数字当成一个整体在背加法表、乘法表。没理解到十进制的精髓

上图来自2020年 GPT-3 论文 22页。主要还是因为 BPE 太糙。

  1. gpt-4能通过一些文字材料和描述感知到每个字母大概长什么样,甚至知道一些简单汉字的结构,但是确切的书写结构它是不知道的。
    所以要避免你的想法被 gpt 感知学会,得考虑每个字的偏旁部首作文章,而且你的变形得很少见。估计这个技巧在将来被AI监视的生活里会被用得上。

  2. 还有一些语音漂移它知道概念但是因为没发音器官所以不能直接感知。但这也不是不能解决,只要语料够大也能摸到规律。相比英语等拼音文字而言,汉字这类表意但是又有同音的文字应该是文本类 AI 的普遍弱点。

如果你要秘密表达一些观点,可以通过描述发音、形状或者动作但是不直接写出来让对方领会。

Posted

stdout

Google 和 Facebook 为什么不行了

Google 最先搞出来 word2vec,最先搞出来transformer 和 BERT,最先搞出来被员工发现 sentient 的 LaMDA,被 ChatGPT 骑脸之后匆忙推出个半成品 Bard

这一切为啥呢?我觉得很可能是 Google的愿景

Google's mission is to organize the world's information and make it universally accessible and useful.

全世界的信息,可以说已经被 Google 成功的「组织」起来了。只不过很可惜,它并没有想要全部「理解」和「消化吸收」,把信息变成「通识」。ChatGPT 正在尝试做到这一点。

微软的愿景是什么呢?

  • "Be what's next." (2010-2012)
  • "Empowering us all"

所以它很快收购了 Skype 推出 Teams,收购 Github 然后投资 OpenAI,结合 Codex (code-davinci-002) 推出 Github Copilot,然后顺理成章搞出来 Office 系列的 Copilot。工具性拉满。

然后说下为啥 Facebook 要 All in metaverse。这个在 2015-06-22 有个 leaked memo 说得很清楚

Our vision is that VR / AR will be the next major computing platform after mobile in about 10 years. It can be even more ubiquitous than mobile – especially once we reach AR – since you can always have it on. It’s more natural than mobile since it uses our normal human visual and gestural systems. It can even be more economical, because once you have a good VR / AR system, you no longer need to buy phones or TV’s or many other physical objects – they can just become apps in a digital store. ... We are vulnerable on mobile to Google and Apple because they make major mobile platforms. We would like a stronger strategic position... We can achieve this only by building both a major platform as well as key apps.
我们的愿景是,在大约十年后,VR / AR将成为继移动设备之后的下一个主要计算平台。它甚至可以比移动设备更普及 - 特别是一旦我们达到AR - 因为您可以随时随地使用它。它比移动设备更自然,因为它使用我们正常的人类视觉和手势系统。它甚至可以更经济,因为一旦您拥有了一个好的VR / AR系统,您就不再需要购买手机、电视或许多其他物理物品 - 它们只能成为数字商店中的应用程序。我们在移动设备上对谷歌和苹果公司非常脆弱,因为它们制作了主要的移动平台。我们希望拥有更强大的战略地位... 我们只有通过建立一个主要平台和关键应用程序才能实现这一点。

问题是,VR/AR 并不是手机的下一代。

Posted

stdout

Play Cities:Skylines on Windows/MacOS without Paradox Launcher

Haven't played Cities: Skylines for ages, so I re-installed it on Steam, after some updates there's this new Paradox Launcher which serves no purpose but wasting time. So I decided to remove it, googled some guide and unfortunately it contains too many steps and some error occured while following it. Here's the simpler & correct step of what I did instead.

After the game is downloaded, open the Steam library, find the game, right click, choose Properties...

Next for the Launch Options, type

on Windows

cmd /c start cities.exe %command%

on MacOS

/Users/$USER/Library/Application\ Support/Steam/steamapps/common/Cities_Skylines/Cities.app/Contents/MacOS/Cities %command%

Make sure you replace $USER with your username

Then the game works like good o' times, straight to the menu.

Posted

stdout

《阿凡达2:水之道》是一部辱日片啊

昨天刚看完,整体感觉画面还是美轮美奂的。我没看出来有什么故弄玄虚的情节,也没觉得网上说那些故事性不行到底不行在哪里。不过有很多场面感觉不是怎么适合带娃去看,好多暴力、流血、残忍包括几十秒展示捏碎一个真正的骷髅等镜头。

在其中一个 Tulkun Hunting 大场面,一开始还对为啥找个东亚面孔扮演反派感到奇怪

avatar2 whaling crew

然后就出现了好几秒的「日捕」爆炸捕鲸叉的特写。这是把「日本捕鯨協会」写脑门上了啊。。。

日捕

截图来自tiktok不是很清晰。但是这妥妥辱日了吧。。。说阿凡达2不好看的,是不是拿了日本外务省的钱?233333

看完网上搜了下讨论,有人说这是 日浦 Hiura 的名字,但是这又不是一个常见的日本姓名 ,所以我觉得 James Cameron 这故意用个错别字避免刚正面。

现实中的日本捕鲸叉长这样

japanese whale harpoon

十分写实了。而且捕鲸一开始也是为了鱼油,而且日本捕鲸也是冒充科研用途,和电影里的情节不能说雷同只能说。。。一模一样?

Posted

stdout

文字的毒害

看到个号称不支持 css/js/wasm 和图片的浏览器 Kristall,觉得这玩意太蠢了。这让我
想起柏拉图的 Phaedrus 里讲了苏格拉底的一个段子,苏格拉底作为欧亚大陆的头号智慧导师,博学多才,却没留下传世著作。因为但是他老人家本身是反对文字和书写的,有这么几个原因:

第一条,文字伤害记忆和智慧。Letter is an aid not to memory, but to reminiscence, and you give your disciples not truth, but only the semblance of truth

首先值得注意的是,这里很有可能指的是拼音文字里的「字母」 而不是古埃及神 Theuth 创作的象形文字。我认为发音字母构成的书写体系肯定是对记忆有害的,文字上的词性和变位更是对视力和理解力的低效率的折磨。快速理解都会强调 Subvocalization 的重要性。汉字作为一个 ideogram 真是一个神发明。总体来说,这条道理是对的。文字只是记忆的粗略模拟和辅助,是真理的外表轮廓。

知识其实这玩意可以被大致分为两类:propositional knowledge 和 know-how。命题和过程知识。

什么是命题?比如「一只青蛙四条腿」就是典型的命题。人们可以根据生活经验获得大量命题;

过程知识就比较微妙了。人们可以通过文字获得过程知识,比如你可以通过学习五线谱了解乐谱如何和发音一一对应,观看钢琴视频得知乐器如何演奏,但是让你实际上手呢?依然不会。

文字就容易造成一个错觉,把结论和过程的「描述」记住了,不等于你获得了真实的知识。有的时候甚至你记下来的就是个结论,连它内在逻辑都无法知晓。孔老夫子就说过「学而不思则罔,思而不学则殆」。文字是有欺骗性的。很多时候你写下来了背下来了以为就记住了。实际上你只记住了一个表皮。

第二条,文字是固化的,死气沉沉的,不如口头语言丰富有活力,不如口传心授有感染力。这个理由,也有局限性。因为文字表达能力在苏格拉底的时候不行,但是这玩意可以进化的。几千年演化下来,文字的表达能力一点也不差。

第三条,文字使语言失控。苏格拉底的原话是这么说的:“一件事一旦被写下来,不论什么内容,到处都会流传的,既会传到能看懂的人的手里,同样也会传到无关的人手里。文字本身并不知道如何与正直的人说话,也不知道如何不与邪恶的人说话。因为文字没有自卫或自救的能力。” 这个道理其实非常值得深思。

书写不是真理的后代,而是真理的私生子。人们歪曲理解文字,就如同私生子受到欺负没有亲生父母帮忙一样。苏格拉底还说,智慧的语言是用来雕刻学习者的心智的。需要知道什么说什么话,什么时候闭嘴。知识是活的,有多面性,需要对听众定制诉说,而不是像一副绘画那样只从一个角度说明问题。理智的人说话就像在花园里埋下一颗种子,听者心里才会开花结果。而不是一味挖洞翻土。

道理讲了这么多,其实对我的启发就是传播思想。喊死板口号是绝对没有用的。必须得定制化、个性化传播。如同现在推荐系统一样,做到千人千面。真正厉害的营销和洗脑不是「羊羊羊」那样反复重复洗脑,而是静心巧妙的植入。

想到这里,我觉得,以前 PC 时代发布视频,是由少数精英团队的制作,通过 Flash/MPEG这种录制成视听作品或者节目,扩散式的单向散开传播;3G 4G 带来的高带宽普及,智能机带来的H.264专用处理芯片的普及,让人人拍视频成为了可能,让全球迎来了一个视频传播思想的时代。大家看不起的营销号洗稿行为,在我看来是一个极其合理必然。知识的传播,本来就应该自然发生变异,我们只是缺乏一个有效的优胜劣汰的定向过滤器。把一件事前因后果用文字写清楚的人不多,但是文盲都可以拿起手机录制一段讲话。所以在人人都能发视频的时代,打破了文字的垄断,带来大量的土味和「俗」味视频。因为媒介的改变,不仅是从文字到图片、视频的升级那么简单,而是人和人对话、交流、碰撞的一次彻底颠覆;以前的大道理、小道理是基于文字的浓缩,抽象成一个又一个的符号,比如国家,比如民族,比如儒家、清教徒这种门派。现在抛弃抽象,回归到具象。在文字的时代,搜索引擎为王。因为这种技术有一定门槛,而且当时互联网上全是「超文本」的海洋。高校的理论知识是网页,门户的新闻是大段文字,个体用户参与的 BBS 讨论也是文字。现在不一样了,学术报告是扫描版pdf,门户都是app里的walled-gargen,群众的议论全是长截图。文字沦为配角,作为字幕存在于某一帧配图的下方,或者是一个箭头指向的注解。最具冲击力的,往往是影像,比如唐山打人的监控 footage,再比如导致 BLM 的那张 Floyd 跪脖子的图片。

社会现代化意味着传统、稳定的社区纽带被打散,现代人的生活成为一场漫长的“流浪”,人们不断被抛入陌生的地点、陌生的群体、陌生的关系,“独自承受”一切挑战。这种原子化状态驱动着弗洛姆所说的“逃避自由”心理——人们试图在不断分崩离析的世界中抓住一点什么,哪怕抓住的仅仅是“想象的共同体”。

从这一点上来说,我们回到了千百年前那个知识以口口相传的时代。面对这样的历史巨变,我个人的见解是:

  1. 口头交流比任何一个时代都更加重要。中小学应该压缩文字书写的时间,改为制作 ppt 宣讲技能培训和口头辩论。
  2. 应该把音韵、对联、单押、双押、rap、music、的重要性提高。现在自媒体 BGM 水平太低,而且说服力和韵脚好坏成正比。
  3. ChatGPT 是一把双刃剑,既可以在一段文字里无缝植入商品广告,也可以解决苏格拉底反对 letters 提出的文字的种种缺点,把文字变成个性化、启发式学习一个利器。
  4. 下一代说服力工具,应该是类似 ChatGPT 但是作为虚拟形象对现实生活的再加工和演绎。技术侧需要有对图片和视频「表达的思想」的理解能力。

Posted

stdout

Jira里JQL实现查找上一个「工作日」的任务

厂里终于开始用笨拙的Jira了。总体用下来跟 Teambition 相比各有各的笨处。。。

默认的 board 过滤器有点不好用。本着为了stand-up meeting和周报快速检索上一个工作日和当周干了什么,找到一个 filter 写法:

JQL过滤上一个工作日干了啥:

(updatedDate < endofweek("-8d") AND updatedDate > startofday("-3d")) OR updatedDate > startofday("-1d")

这里不是简单的查昨天干了啥。比如周一需要看周五干了啥。所以有个 -8d-3d 的神奇操作。具体的逻辑自己琢磨

过滤本周干了啥

updatedDate >= startOfWeek(0)

Jira这种资本主义软件是没法适配我们社会主义法定节假日的设定。所以遇到非双休放假还是判断起来比较蛋痛。

其实我比较反感每天早上开站会。我比较推荐每天下班前最后10分钟开会。好处有:

  1. 当天干了什么记得很清楚。明天需要干什么也很显然。
  2. 如果谁拖堂,谁就承担对不起大家不能准时下班的道德压力
  3. 如果都开会了你事情都还没做完,要么你事情安排得太多,要么你完成能力不足,要么你时间安排有问题。

之前在两个公司这样执行过。结果被大老板强行改到早上开会了。无语。

Posted

stdout

Web Animation实现页面逐渐变灰

在2006年我开始网上留下印记的时候,写了一个 dHTML 特效模拟XP关机对话框变灰, 那个时候大行其道的还是IE6,用的还是微软特有的 filter:progid:DXImageTransform.Microsoft.Fade 叠加两个<div>实现。

今天(2022-11-30)恰好长者去逝,学习一下最新的css姿势再实现一次。本来想用 CSS Animation+Filter实现,还是比较麻烦。比如我参考的例子是模仿 Red Dead Redemption 2 照片效果css

  #invert{
    filter: invert(1.0) grayscale(1.0);
  }
  #photo{
    animation: blur 5s infinite alternate;
  }
  @keyframes blur{
    from{
      filter: blur(3px);
      opacity: 0;
    }
    to{
      filter: blur(0px);
      opacity: 1;
    }
  }

这个方式有个问题就是需要单独设置一个 <script> 元素。后来经过 @yuanchuan23 的指点发现可以通过 Web Animation 更简单的实现:

  document.body.animate([
    {filter: 'brightness(none) grayscale(0%)'},
    {filter: 'brightness(10) grayscale(10%)', offset: 0.2},
    {filter: 'brightness(1) grayscale(100%)'}
  ], {duration: 2000, fill: 'forwards'})

这样写比 css 方便多了。加一个类似照相机闪光灯过曝的效果。其中 fill: 'forwards' 可以让变灰之后停下来。

放一个 demo 在这里:https://lab.est.im/grayscale_animation 点一下可以再变一次

Posted

stdout

8行javascript录制屏幕

看到个骚操作,玩了一下,写了个 bookmarklet

navigator.mediaDevices.getDisplayMedia({video:true}).then(function(stream){
  const mr=new MediaRecorder(stream,{mimeType:"video/webm;codecs=h264,opus"});
  mr.ondataavailable=function(ev){
    const a=document.createElement('a');a.href=URL.createObjectURL(ev.data);
    a.download ='screen_capture.webm';a.click();
  }; // will be called when .stop() with ev.data as a Blob
  mr.start();
})

这段代码也是很多WebRTC共享白板的实现原理。看起来也很简单,而且不需要额外的权限确认。

如果不需要另存为一个名字,则可以直接 location.assign() 又节约几个字。不知道还有没有办法继续节约代码量?

有点很好奇为啥chrome不支持 .mp4 封装。偏袒自家的 webm ?

Posted

stdout

JS播放 DTMF

玩 WebRTC 的时候发现它居然内置支持了 DTMF 拨号音

这里就简单科普一下,DTMF 就是手机电话拨号音,数字按键按下去嘟嘟嘟响那个。具体就2个频率调谐:

DTMF 1209Hz 1336Hz 1477Hz 1633Hz
697Hz 1 2 3 A
770Hz 4 5 6 B
852Hz 7 8 9 C
941Hz * 0 # D

于是兴趣来了想做一个 demo 玩玩,写了半天调试不通,报错

VM987:1 Uncaught DOMException: Failed to execute 'insertDTMF' on 'RTCDTMFSender': The 'canInsertDTMF' attribute is false: this sender cannot send DTMF.

仔细研究了下这玩意,原来 WebRTC 不直接传音频,而是发一个控制信号,由接收方播放出音频,所以你需要本地创建一个真实的双工 RTC 并打开麦克风权限,才能播放DTMF音频。

WebRTC doesn't send DTMF codes as audio data. Instead, they're sent out-of-band, as RTP payloads. Note, however, that although it's possible to send DTMF using WebRTC, there is currently no way to detect or receive incoming DTMF. WebRTC currently ignores these payloads;

这样就走远了。于是又手痒用 AudioContext() 糊了一个。一共不到1000字节。

https://lab.est.im/dtmf 源码

嗯。就挺简单的。特别是那个 oscillator有个 .stop(0.2) 参数可以避免 setTimeout 的麻烦。网上很多教程整复杂了。

btw 把一些老脚本挂在 lab 域名下。
btw2 把 RSS 改成全文输出了。

Posted

stdout

Cloudflare worker 返回浏览器ip:端口

看到个贴 返回客户端连接用的 IP 和端口号,想起来也需要熟悉一下 Cloudflare worker,于是拿来练练手

  export default {
    async fetch(request, env) {
      return await handleRequest(request).catch(
        (err) => new Response(err.stack, { status: 500 })
      )
    }
  }

  async function handleRequest(request) {
    const { pathname } = new URL(request.url);

    if (pathname === "/ip") {
      const ip = request.headers.get('CF-Connecting-IP');
      return new Response(ip + '\n');
    }

    if (pathname === "/ip:port") {
      const ip = request.headers.get('CF-Connecting-IP');
      const port = request.headers.get('est-Connecting-Port') || '?'
      return new Response(ip + ':' + port + '\n');
    }

    return new Response('Tools:\n /ip\n/ip:port\n');
  }

遇到一个问题,Cloudflare 官方有提供浏览器客户端IP变量 CF-Connecting-IP 但是缺少端口,查了下可以自己通过 cf.edge.client_port 这个变量获得, 方法是在进入 CF网站管理 面板 → Rules → Transform Rules → HTTP Request Header Modification 里添加一条请求头规则。如图:

但是 header 不能以 CF- 开头,所以这里改成 est-Connecting-Port,就能获得客户端请求的 IP+端口 了

测试地址: t.本网站根域名/ip:port

目前还缺:IPv6 的正确表示。。。。

Posted

stdout

macOS动态定时弹提醒

上班地方有个付费计时,想在倒计时结束前在电脑弹个提醒,手动结束计费,趴下来API了。接下来就是如何弹窗了。

本来想用macOS自带的 UNUserNotificationCenter 通过 PyObjC 撸个弹窗,遇到两个问题:

  1. 系统自带的 PyObjC 太老了。还是Python2.7的。在/System/Library/Frameworks/Python.framework/Versions/2.7/Extras/lib/python/PyObjC
  2. 只能兼容旧款的 NSUserNotification,状态为 macOS 10.8–11.0 Deprecated
    新的API调用需要进程签名。
    codesign -dv --verbose=4 /usr/bin/python2.7

只有官方的python2.7签名了 anaconda/miniconda 的都没签名。算了。还是直接 AppleScript 走起

  osascript  -e 'display notification "还有1分钟到达时辰" with title "准时付费" sound name "Frog"'

想在这个提示里加一个 snooze 1分钟功能,但是好像无法支持。罢了。2333

Posted

stdout