Blog
Top

Fragments 用于存放不适合单独成文的碎片内容。

内容随心所欲,没有方向限制。本页内容独立于归档文章,单独设置 RSS 全文订阅

10:00 · 23 Mar, 2023

前端NuxtJS

2022 年,我快乐地将博客迁移到了 Nuxt

2024 年,我记录了维护这个 Nuxt 博客遇过的事情

2025 年,我要跑路了

众所周知,Nuxt content 官网的文档都是语焉不详的,能参考文档完成的迁移只占很小一部分。剩下的只能在遇到问题时多翻 issue

这次更新最大也是最明显的改动就是查询底层的语法从类 MongoDB 变成了 SQL

因此需要自己写 schema 用于建表,启动时会扫描 /content 目录把符合要求的内容全部写到数据库里面

既然底层改了,那查询的 DSL 也要改,直接大改造

输出 Markdown 的 AST 也改了,那基于 v2 写的 body parser 怎么办,没办法搓了个 Wrapper 用来把 AST 改回原本 v2 时的输出

JAVASCRIPT
export const NuxtContentV3ASTWrapper = (body) => {
    let _type = 'element'

    if (Array.isArray(body[0])) {
        _type = 'root'
    } else if (typeof body === 'string') {
        _type = 'text'
    }

    let tmpElement = {
        type: _type
    }
    if (_type === 'text') {
        tmpElement.value = body
    } else if (_type === 'root') {
        tmpElement.children = body.map((children) => NuxtContentV3ASTWrapper(children))
    } else {
        tmpElement.tag = body[0]
        tmpElement.props = body[1]
        if (_type === 'element') {
            tmpElement.children = body.slice(2).map((children) => NuxtContentV3ASTWrapper(children))
        }
    }

    return tmpElement
}

默认的时间戳变成了 z.date() ,别忘了旧版 Safari 不支持 ISO 8601

JAVASCRIPT
post.date.replaceAll('-', '/').replace('T', ' ')

用于 Server 的 serverQueryContent 直接被删了,要用只能 queryCollection(event, 'collection-name'),然后看着 typescript 检查爆红

在我解决完这些问题以后,我还没意识到问题的严重性,当我随手检查开发者控制台的时候,我发现了 SQLite wasm 的请求……

这不对吧?在我费尽心思想要节省那丁点流量消耗的时候,一个 wasm 下场杀死了比赛

我一开始就看到缓存文件里面的数据库,我以为这个 SQLite 只是用于打包时的优化,用临时介质来优化处理速度是很正常的,我就没多想

但,我一个简简单单的博客,为什么要在前端跑这种重量级的东西?

也许有人会说几百 KB 并不多,现在谁会在乎那点流量。没错,没人在乎多消耗那一丁点,CDN 还会压缩优化。但跟 v2 相比呢?为了这点东西多消耗了多少倍?我做了个粗略的比较:

新旧两版同时打开首页,然后打开第一篇文章

TXT
# blog.nest.moe
58 requests | 488 kB transferred | 1.3 MB resources
# localhost
56 requests | 1.3 MB transferred | 2.7 MB resources

显然,社区里面被这个新特性苦恼的使用者不止我一个,Nuxt 官方的人员也有回复这么干的原因

In Nuxt Content v1, we used LokiJS for the query builder which is now unmaintained for years.

In Nuxt Content v2, we used unstorage + our own query builder which was slower and making the server bundle bigger (was impossible to deploy on workers for instance).

冷静下来我的觉得是这是一个侧重程度的问题,究竟要重服务端,还是要客户端。作为开发者是很难实现完美平衡的

Nuxt content 的定位很尴尬,要作为 CMS 比它成熟的现成的方案多种多样,也没有选它的必要

这个博客一年都出不了几篇文章(那还不赶紧多写点?),自然就没有任何更新上去的理由

等到 v2 彻底被抛弃并且出现漏洞时(?),大概也是我跟 Nuxt 说拜拜的时候了

7:42 · 28 Apr, 2025

Vibe codingAI流水账

关于 vibe coding 的争论已经够多了,我无意加入,只是复盘一次修 bug 的过程

Tweet Listener的爬虫和主站通信使用的是一套运行在 WebRTC 上的内部 RPC,但在一次更新以后,爬虫因为无法从账号池获取到账号而停机

这问题挺好解决的对吧,找到哪里出问题修了就好了,但问题这时就出现了,前期我出于偷懒并没有打 log,导致根本不知道问题出在哪里,只能把 error log 打出来,然后继续跑直到下一次停机

等到第二次停机,我发现根本没有什么 error,log 非常干净:在一次常规的掉线重连以后爬虫就进入了神秘的单向通信的状态——能上报数据,但接收不到任何主站下发的配置和账号。数据进了 webrtc.DataChannel.Send() 以后就消失了,但 Send() 也没返回任何错误,然后就卡在这里好几个星期,期间我为爬虫加了一小段代码,用于检测来自主站的消息,当一定时间内接收不到消息时直接 os.exit(1),让守护程序去保证重启。然后还考虑了组网软件 zerotier 和 tailscale 的影响,停止使用组网的内网 ip,改成直接通过公网 ip 连接

但问题一直都在,并且还是捉摸不透,连稳定复现的办法都没有。不过还是有一点好消息的:停机越来越频繁,一般不超过 30 小时就能出现一次

我精简了整个项目的代码,整理出简单的 WebRTC 服务端和客户端,这两个端的工作就是不断连接,连上以后尝试互相发送消息,接收成功以后就断开重连……不断重复,希望能找到原因,期间还是一无所获,我还很迷惑 pion 没有 debug 级别的 log 吗,翻了 issue 都是直接贴代码,没看到几个会贴 log 的……好神奇的 debug 手法

终于,在一次偶然的机会,我重新查看 pion 仓库的 examples 目录,找到了一段打 log 的示例,修改了代码过了两天以后,最终于有好消息传来:

TXT
customLogger Warn: Failed to start manager: connecting canceled by caller
customLogger Warn: Failed to start SCTP: DTLS not established
customLogger Warn: undeclaredMediaProcessor failed to open SrtpSession: the DTLS transport has not started yet
customLogger Warn: undeclaredMediaProcessor failed to open SrtcpSession: the DTLS transport has not started yet

有了这几行文字,自然就能搞清楚问题所在,那么修复手法也就显而易见了:

DIFF
- Peer.OnICEConnectionStateChange(func(state webrtc.ICEConnectionState) {
-     log.Printf("Peer ICE Connection State Changed: %s [%s]\n", state, rtcContext.Addr)
+ Peer.OnConnectionStateChange(func(state webrtc.PeerConnectionState) {
+     log.Printf("Peer Connection State Changed: %s [%s]\n", state, rtcContext.Addr)
      switch state {
-     case webrtc.ICEConnectionStateConnected:
+     case webrtc.PeerConnectionStateConnected:
          OnConnected()
-     case webrtc.ICEConnectionStateDisconnected, webrtc.ICEConnectionStateClosed, webrtc.ICEConnectionStateFailed:
+     case webrtc.PeerConnectionStateFailed, webrtc.PeerConnectionStateClosed:
          rtcContext.Close()
    }
})

很简单的 bug 对吧,一下子就修好了

现在 bug 修完了,那么 vibe coding 在哪里?接下来就到 chatgpt 的聊天记录复盘了

去年 12 月想到这个需求的时候我对 WebRTC 一无所知,就一路顺着 chatgpt 的话来写——它给什么我用什么,顶多就简单改改,再经过简单测试一下就上线了

OnICEConnectionStateChange 这里我直接拷贝了代码粘贴上去运行,后面就没看了。其实我只需要往下拉大约 200vh 的网页内容,就能看到用 OnConnectionStateChange 的正解

但那时我连个概念都没有,怎么会思考两个事件之间会有什么异同呢。并且复现条件要求 ICE 连上但 DTLS 未连接……那么特殊的条件,webrtc.DataChannel.Send() 默认还不抛出 error,谁都不会第一时间想到这种原因,只能一点一点试。现在复盘可以站在上帝视角知晓一切,而在当时,就算我去提 issue,连稳定复现都做不到,又怎么描述呢,总不能 “各位别着急,可能下一秒复现,也可能要等几个星期。另外复现要求东京和洛杉矶各买一台服务器” 吧?

那么为什么在使用 WebRTC 后,那次更新前又不会停机呢?原因正好就在那次奇怪的更新为了配合特殊用户组自部署的特性,收紧了爬虫的权限:以前爬虫从主站获取账号池的地址和金钥自行连接获取,更新后只能通过主站获取这些配置。账号池跟爬虫位于同一台设备,所以可以非常可靠地直连,而爬虫在设计上只要有稳定的账号供应就能运作(先有爬虫,后来才有主站),所以连接到主站不是必须的;即使连接出现问题上报还是正常的,而启动后第一次连接基本都是正常的,没有任何问题——我连发现监听了不合适的事件的机会都没有

我也是老菜鸟了,也用过很长时间的 LLM 辅助编程,在无脑相信 LLM 的内容编程时还是翻车了——面对不熟悉的地方连有错有漏都不知道,更别说发现问题在哪了。那么那些时间线上各种零基础管杀不管埋的补光灯,是怎么回事就不用多说了……

2025-05-02 补充

半个月前写的,然而现在时间线好像见不到这些怪人了,另外爬虫也正常运行已经一百多个小时了(修了别的导致崩溃的 bug 重启过)

16:00 · 13 Apr, 2025

NuxtGolang

用 Golang 和 Nuxt 重写了一个 PHP 软件,到插件系统时懵了,最后花了点时间折腾出来了,这里记录一下思路

  • Golang 同一个包的文件的命名对打包没太大影响(只要不是隐藏文件或者带不打包标记),但同一个包的所有文件会共用变量/函数名称。所以不能重复,要为每个插件准备自己的 namespace 作为变量的前缀或者后缀
    • 准备一个公共的列表变量,以及一个注册函数
      GO
      // /plugins/hooks.go
      type PluginInfo struct {
          Name string
          // ...other data or func
      }
      
      var PluginList = make(map[string]PluginInfo)
      func RegisterPlugin(name string, plugin PluginInfo) {
          PluginList[name] = plugin
      }
      
    • 插件文件也放置在 /plugins/,通过 init() 调用 RegisterPlugin() 自动将插件信息注入到 PluginList
      GO
      // /plugins/example.go
      func init() {
          RegisterPlugin(ExamplePlugin.Name, ExamplePlugin)
      }
      
      type ExamplePluginType struct {
          PluginInfo
      }
      
      var ExamplePlugin = ExamplePluginType{
          Name: "example1"
          //...
      }
      
    • 为什么还是要通过修改源码再编译打包实现?因为打包成动态库对系统有限制,还有可能遇到还不知道的坑,不如求稳 反正也只有我在写插件,一块分发更方便
  • Nuxt 做前端就没啥好说的,反正最终都会注入到 router ,Nuxt 有个 definePageMeta 可以用来放置页面信息
    VUE
    <!--/pages/some-page.vue-->
    <script setup>
    definePageMeta({
      plugin_name: 'example1'
      // ...
    })
    </script>
    
    <!--/pages/another-page.vue-->
    <script setup>
    definePageMeta({
      plugin_name: 'example2'
      // ...
    })
    
    const router = useRouter()
    console.log(router.currentRoute.value.meta.plugin_name)// example2
    console.log(router.getRoutes().find(route => route.name === 'some-page')?.meta.plugin_name) // example1
    </script>
    

9:16 · 10 Oct, 2024

OnePlus 8t屏幕竖线

万恶起源是有一天发现屏幕冒了条绿线,手机是21年1月买的,上官网找了一下保外物料的价格:¥1150……还是凑合着继续用好了,然后就这么用了差不多一年。

直到昨晚吃过饭又有点受不了十几条绿线,去搜才发现一年前一加就已经针对这事提供以无修无摔无进水为前提的保外免费换屏,但官方从来都没有公开宣传过(印度那边的换屏政策也是通过媒体宣布的,一加官方对于这政策没有留下任何记录),于是今天爬起来做完备份就直接跑去本地的 OPPO 售后换屏,顺便自费换块电池。

一套流程(检测->拆盖->拆机->换屏/电池->组装->清胶打胶封盖->下载 H2OS 刷机->校准指纹传感器)下来大概耗时 2 小时,前 1 小时修机,后 1 小时刷机,我使用的 PixelExperience 没有一加的工程模式,要校准就只能刷回官方的系统,选择 ColorOS 是不可能的,我又不可能让中国售后给我刷 OxygenOS ……那只能折中一下刷大氢了……

下面是工单节选:

序号收费类型物料名称零售价数量优惠金额理赔金额
1维修电池组件¥119.001¥0.00¥0.00
2维修屏幕上盖组件¥1150.001¥1150.00¥0.00

物料原价合计:¥1269.00,单项优惠金额合计:¥1150.00,理赔金额合计:¥0.00

刷机期间有一台 9R 来送修,一看屏幕又是熟悉的绿线……

再后续是刷氧刷 PE 折腾到两三点:自从网页不再发布完整卡刷包以后,一加发了很多 OTA 完整包,我又没有这些包只能每次都下载安装一步一步更新上去,费了很多时间。恢复备份弄到四五点

希望这玩意能再战三年

  • 注1:免费保外换屏服务可能有手机型号限制
  • 注2:维修前打工单需要查看 IMEI,可能还需要检查购买凭证来确定购买日期
  • 注3:要校准指纹就最好用官方系统(ColorOS、H2OS、OxygenOS),不然可能会因为缺少工程模式而需要刷机,刷机不保资料。不过也可以选择不校准,拼完等胶水干了就可以拿走了
  • 注4:维修期间跟小哥聊天,听说 vivo 也有类似的换屏政策

8:29 · 5 Aug, 2024

树莓派Chromium

折腾某个 DePIN 项目(互联网是一个圈,早年的挂机网赚玩法换个皮又出来了)要用到 Chromium 插件挂机,拿我自己的笔记本挂怕是到最后连电费都交不起……最后翻箱倒柜找出当年的树莓派3B+

系统是 Ubuntu 22.04.4 LTS,装上 KDE 、 xrdp(我主要用 Windows 系统自带的 RDP 客户端,如果是其它系统可以用 VNC 代替) 和 Chromium 挂机就完事了

SHELL
apt install kde-standard xrdp chromium-browser

这篇应该会在发币后公开(看看能有多少收益 :))

You’ve Successfully Claimed 55.04 GRASS

也太抠了

啊?怎么就四倍了???

3:00 · 3 Aug, 2024

MediaPipeLanguage Detector

虽然可能没什么用,不过既然花了点时间研究就顺便发出来吧

首先我要说的是这玩意没什么用,因为它并不开源,用户只能通过 npm 拿到混淆后的版本,其次文档也等于没有,参数给了也不一定会用到,不论是 官方demo 还是 用户自己实际体验 都跟 官网demo(注:官网需要登录 Google 帐号) 的体验不一致

官方似乎就没考虑过给 Node.js 等非浏览器运行时使用,硬是塞了一个完全没用上的 canvas API 进代码,最后导致其他运行时用不了

所以要用就得改改文件,由于 npm 包内的文件内容会被压缩到一行,所以要修改的部分基本都是在同一行的,需要单独搜索找到并修改

同样是因为被压缩到一行,所以我懒得放出特别大的 patch 包……尤其是在这个一次会加载全部内容的碎片栏目

DIFF
# node_modules/@mediapipe/tasks-text/wasm/text_wasm_internal.js
## begin
#### note: esm only
+ import fs from "fs";
+ import path from "path";
+ import crypto from "crypto";
+ 
+ import { fileURLToPath } from "url";
+ import { dirname } from "path";
+ const __filename = fileURLToPath(import.meta.url);
+ const __dirname = dirname(__filename);
+ 
+ const nodePath = path;
+ const crypto_module = crypto;

#### ---
#### note: esm only
- var fs=require("fs");var nodePath=require("path");
- var crypto_module=require("crypto");

#### ---
### replace
- if(typeof custom_dbg==="undefined"){function custom_dbg(text){console.warn.apply(console,arguments)}}
+ function custom_dbg(text){console.warn.apply(console,arguments)}
#### ---

## end
### replace
#### note: esm only

- if (typeof exports === 'object' && typeof module === 'object')
-   module.exports = ModuleFactory;
- else if (typeof define === 'function' && define['amd'])
-   define([], () => ModuleFactory);
+ export default ModuleFactory

# node_modules/@mediapipe/tasks-text/text_bundle.mjs
### replace
- void 0!==e?this.h.canvas=e:Ui()?this.h.canvas=new OffscreenCanvas(1,1):(console.warn("OffscreenCanvas not supported and GraphRunner constructor glCanvas parameter is undefined. Creating backup canvas."),this.h.canvas=document.createElement("canvas"))
+ this.h.canvas=null

### replace
- if(e&&await Oi(e),!self.ModuleFactory)throw Error("ModuleFactory not set.");if(n&&(await Oi(n),!self.ModuleFactory))throw Error("ModuleFactory not set.");return self.Module&&r&&((e=self.Module).locateFile=r.locateFile,r.mainScriptUrlOrBlob&&(e.mainScriptUrlOrBlob=r.mainScriptUrlOrBlob)),
#### note: next line is ending with a space " "
+ return

然后就是很不优雅的使用,需要提前将模型下载到本地

JAVASCRIPT
import { LanguageDetector,FilesetResolver } from "@mediapipe/tasks-text";

import ModuleFactory from "./node_modules/@mediapipe/tasks-text/wasm/text_wasm_internal.js";

import { readFileSync } from "fs";
globalThis.ModuleFactory = ModuleFactory;
global.self = globalThis;

const buf = readFileSync(`./language_detector.tflite`);

const wasmFileSet = await FilesetResolver.forTextTasks('./node_modules/@mediapipe/tasks-text/wasm')

const languageDetector = await LanguageDetector.createFromOptions(wasmFileSet, {
  baseOptions: {
    modelAssetBuffer: new Uint8Array(buf),
    maxResults: -1
  },
});
const textData = "hello";
const result = languageDetector.detect(textData);

console.log(result);

// Graph successfully started running.
// INFO: Created TensorFlow Lite XNNPACK delegate for CPU.
// WARNING: Attempting to use a delegate that only supports static-sized tensors with a graph that has dynamic-sized tensors (tensor#15 is a dynamic-sized tensor).
// W0714 11:12:40.749000  678624 inference_feedback_manager.cc:121] Feedback manager requires a model with a single signature inference. Disabling support for feedback tensors.
// {
//   languages: [ { languageCode: 'en', probability: 0.5111953616142273 } ]
// }

我也不知道为什么只能返回一种语言,鉴于 官方demo 其实也只会返回一种语言,我也无话可说了

10:27 · 14 Jul, 2024

随想

我一直在用中文写作的原因其实很简单……外语没学好,与其写出非中文和中文使用者都看不懂的奇怪表达,不如用中文写明白写清楚,写到即使机翻也能轻松转换

不过,我还是进行了新的尝试,前两天我上传了一篇拿 GPT-4o 翻译的库存文章,未来可能还会上传这种机翻润色的文章来帮助到非中文的读者。

现在归档页面是全语言的文章混合的,未来可能会按照语言来拆分不同的栏目,并且做好其他仅中文的组件的翻译工作


The following part is translated using GPT-4o.

The reason I've been writing in Chinese all along is quite simple: I haven't mastered foreign languages. Instead of producing strange expressions that neither Chinese nor non-Chinese speakers can understand, it's better to write clearly and intelligibly in Chinese, so that even machine translation can easily convert it.

However, I've tried something new recently. A few days ago, I uploaded a previously written article translated by GPT-4o. In the future, I might upload more machine-translated and polished articles to help non-Chinese readers.

Currently, the archive page mixes articles in all languages. In the future, I might split it into different sections based on language and ensure proper translation for other Chinese-only components.

16:30 · 10 Jul, 2024

sqlitejson

7:20 · 13 Feb, 2024

护照

年初我就想着去更新我那个早在 2018 年就过期的护照,结果一路拖到年末才去,前后10分钟就搞定了,之前在网上看的一大堆“教程”似乎也没什么用

在移民局小程序预约最大的作用是提早填好一大堆琐碎的东西,不必到现场再填,现场并没有多少人,所以不看预约,直接去也是可以的

拍照可以现场用自助机拍照,至于要穿有衣领的衣服什么的古早规定早就不用在意了……

然后就是经典的提交相片回执和身份证,签名,交钱,然后就没了

邮寄 18CNY 每本

旧护照/通行证也不管,不回收也不剪角,所以可以不带

最后战绩:

  • 护照
  • 港澳通行证
  • 台湾通行证(说是没开,那之前看的推友们是怎么办成的?)

后续补充:

移民局小程序查到待取证状态就可以带身份证直接去自助机取了,那张写着要带的回执并没有用上

另外自助机真的很慢很慢……

11:07 · 14 Dec, 2023

前端Material Design

翻 md3 官网时看到一个 figma 插件,于是就改造了一下,现在支持主题色了,分享一下主题色是 #1a6356

9:46 · 28 Nov, 2023

Twitter

Some twitter api endpoints location

North America

  • Atlanta, GA
  • Portland, OR

* I made a mistake, NOT ALL verified accounts send tweets to Atlanta

South America

  • Sao Paulo, BR

Europe

  • London, UK

Asia

  • Tokyo, JP
  • Singapore, SG

Oceania

  • Sydney, SYD

3:16 · 21 Oct, 2023

爬虫golang

最近学习了一番怎么写高并发的爬虫

首先丢掉的是用了很久的 Node.js,类型转过来倒过去时间就被浪费掉了,想了半天我还记得又便于部署的语言,就剩下 golang 了

然后到了各种被各种“教程”写到烂的优化办法,我就不细讲了

  • 尽量不用 https,用也不验证证书(所以要我自己保证连接安全?)
  • 调大 MaxIdleConnsPerHost 和 MaxIdleConns……具体用处好像是 Transport 的连接池保留多少连接 超时肯定要设置的,不然等着卡死吧
  • goroutine + channel,都用 go 了,这个没什么好说的
  • 什么代理和帐号池?常规操作不值一提
  • json.NewDecoder(..).Decode(...) / json.NewEncoder(..).Encode(...) 替代更慢的 json.Marshal() / json.Unmarshal(),同时关掉 json.NewEncoder(..) 的转义 html 选项
  • 少点输出,输出也占时间,而且还不少,在我羸弱的树莓派上面能用几百毫秒

然而还是有个一个奇奇怪怪的接口,它的反爬手法是限制单个 tcp 连接的总请求数…… stackoverflow 的回答说 http.Client 没有这个方法,得自己手操 tcp 连接池(net.Dial()),GPT 只会跟我说要 response.Body.Close(),手操连接池什么的还是饶了我吧……经过几天的现场学习,我暂且算是想到了一个解决办法,丢在这里算是抛砖引玉吧

GO
package main

import (
    "io"
    "log"
    "net/http"
    "time"
)

func initClient() *http.Client {
    var transport = http.DefaultTransport.(*http.Transport).Clone()
    return &http.Client{
        Timeout:   time.Second,
        Transport: transport,
    }
}

var sequence = 0
var client *http.Client
var maxConnection = 100

func main() {
    client = initClient()
    var requestTicker = time.NewTicker(time.Millisecond * 100)
    for {
        select {
        case <-requestTicker.C:
            if sequence%maxConnection == 0 {
                client = initClient()
            }
            go func() {
                req, _ := http.NewRequest("GET", "http://example.com", nil)
                response, err := client.Do(req)
                if err != nil {
                    log.Fatal(err)
                    return
                }
                defer response.Body.Close()
                str, _ := io.ReadAll(response.Body)
                log.Println(string(str[:]))
            }()
            sequence++
        }
    }
}

大概思路就是次数够了就创建一个新的 http.Client(),再用指针 client 指向它,原来的 http.Client() 会继续执行到结束,然后被 GC 回收 <- 不知道 GPT 有没有坑我

8:15 · 15 Oct, 2023

随想Twitter

最近 Twitter 费尽心思在阻止用户和开发者匿名访问推文内容,尽管我在 nitter 的 issue 提出取得访客帐号并签名请求的方案,但在本篇我也提到过要运行这玩意需要大量的访客帐号,所以单打独斗可能不是一个好的办法。

压力大时我就会胡思乱想,想着能不能构建一个分布式的帐号池,由于 Twitter 客户端的请求统一采用 OAuth 签名,这一套方案甚至能在访客帐号彻底失效后继续由人工录入 userbot 的各种信息的方式延续下去。

架构我就参考 ZeroTier,总共分成三级:

  • Planet,这个是中心节点,会保存属于自己的帐号池,通过录入添加的 userbot 在允许共享前也只会存在这里,下一层的节点会跟此节点共享帐号池;Planet 可以自由地允许或者移除下一级别的节点的连接;基于上一点,Planet 需要公网可访问。
  • Node,Node 可以自由地选择连接到一个或多个 Planet,同一 Planet 下的 Node 会共享帐号池,Node 可以理解为一种基础设施,是所有人都能用的
  • Leaf,叶节点专注于获得访客帐号,只能单向将获取到的访客帐号传输给 Node,一般用于没有公网ip的设备,如果设备能在公网访问,其实可以跟 Node 节点合并。

Planet 可以开放 API 供外部调用,发号顺序应该是 本机的访客帐号 > 同步来的访客帐号 > 本机的 userbot 帐号(如果开放)

这只是一个想法,还有很多不完善的地方,暂时想到的:

  • 如何防止”串号“的情况发生?方案似乎没有解决这个问题。
  • 存储方面我在考虑 SQLite,也许会有更好的方案?
  • 通信用什么方式,长连接?WebSocket?还是别的,我不知道。如果一个 Planet 连接着大量的下一级节点,会不会因为这些连接拖慢整个系统的运行效率?
  • 怎么防止恶意请求?如果每位 Planet 控制者都限制他人的连接这还叫不叫“分布式”?
  • 如果将整个架构改成类似 bt 的架构,Planet 改成类似 Tracker,Node 之间共享访客帐号,这时 Twitter 偷偷也建立一个 Node 节点加进来,将所有的帐号都一网打尽时又该如何应对?
  • 访客帐号有效期只有一个月,一个月后怎么同步删除?

当然啦,这只是我的一点想法,目前我还没有时间和精力去完善那些问题,甚至还没新建文件夹。

也许只是又一个在我的胡思乱想下的无用产物。

19:38 · 21 Aug, 2023

前端滚动emoji

在两个月前的某一天,我就想到了我这个 blog 的标题栏的那个 很适合拿来替换成各种各样的元素,于是这个组件的雏形就出来了:

  • 单字。因为 也是单字,这就意味着中间的元素需要尽量方正,否则切换时会让标题反复横跳,于是 emoji 就成了我的首选
  • 扁平化。当时我看了 Android 和 iOS 自带的 emoji,感觉都不够平,于是选了微软的 fluent-emoji-flat,由于微软一向不自绘各种国旗,所以想要国旗的只能自己另找图标了
  • 动画。直接切换太生硬了,加点动画是很有必要的
  • 可筛选时间。目前我写了一套简单的方法来决定什么时候放什么 emoji,以后独立成组件时应该会用相关轮子来提高可玩性

至于怎么引入 svg,我试了好几种,目前的方案是 @iconify-json + @unocss,这并不是最优解,我还需要自己在 uno.config.tssafelist

动画用了点奇技淫巧,本来我想像轮播图那样滚动,js 控制当前要显示的元素,css 控制动画,于是不同步的计时器就会导致动画跑完了图还没切最后一闪而过的问题,最后选择用 position: absolute + display: none 来让所有元素都堆在同一位置,再把进出的动画拆分,当下一个元素显示时当前元素会淡出,最后不透明度变 0,从视觉上消失

19:45 · 15 Aug, 2023

前端生日气球

前段时间心血来潮写 chāo 了个类似 Twitter 生日气球的组件,因为是 vue 组件所以这边也能玩,点卡片右下角的气球标就会放气球,鼠标移上去就可以戳爆了,气球跑完以后再点再放

跟原版有小改动以适应博客环境,未来会加入好玩的组件大包中。

  • 调教水平有限,直接用原版左右飘关键帧得到的效果过于诡异,所以我去掉了
  • 击破统计挡字的可以点一下以清零隐藏

17:27 · 5 Aug, 2023

GitHub Actions

最近用 GitHub Actions 来更新静态资产,由于涉及到爬虫,需要跟第三方网站交互,我感觉用官方实例来跑有可能会像那些跑签到脚本的仓库那样被封,终究还是自托管一个实例。

吐槽几句:

  • 每个实例只能用于一个仓库,不能重复添加……想加个仓库?那就再新建一个文件夹吧……
  • 没有 yarn,首次使用时需要自己装,另外我这台甲骨文的小鸡跑 npm i 直接跑挂了,不知什么问题
  • 上半年 GitHub 泄露了私钥因此最近更换了公钥,我用的其中一个组件没更新 known_hosts ,最后自己换上去就好了,没有用 KNOWN_HOSTS_FILE 是因为文档也没细讲干脆一改了之
  • 后来翻阅 GitHub 附加产品和功能条款,发现 此外,无论操作是否使用自托管运行器,Actions 都不应用于...,使用自托管也还是有风险,今后可能还是会换成 ci/cd + cron 的方案(主要是简单了解一下市面上的那些 ci/cd 占内存都是以 GB 为单位的,而我的小鸡的内存 1GB 都不到

16:12 · 5 Aug, 2023

SequelizeSQLite吐槽

怎么说呢……体验就是很难受:

  • 由于 SQLite 的库不像 MySQL 的那样支持自动将 BIGINT 转成 TEXT,只能自己想办法,找了半天找到一个 CAST(<AA> AS text) AS <AA>,然后就是很苦逼地挨个去拷贝粘贴
    JAVASCRIPT
      attributes: [
          [Sequelize.options.dialect === 'sqlite' ? Sequelize.literal('CAST(value AS text)') : 'value', 'value'],
      ]
    
  • 没有 MATCH...AGAINSTMATCH 还在研究怎么搞,REGEXP 那个耗时看着就头疼,最后只能回归 LIKE%% 大法
  • 全局大锁直接锁死了,不知为啥 WAL 模式也救不了,写都写不进去;高 I/O 的查询直接歇菜,动不动就是几十秒的耗时

吐槽归吐槽,最后还要继续折腾……

18:50 · 24 Jul, 2023

CloudflareBackblaze B2

一直都有听说 B2 桶,但注册以后用不上只能摆在一边吃灰,最近准备将跑了三年半的 bing 每日图片丢上云,于是这玩意就成了省钱利器。

什么注册账号配置代理这些烂大街的内容就不说了,看 如有乐享的文章 就好。

这里就补充一个,应该是前人写文章时还没出的功能:Redirect Rules。

  • If...
    • All incoming requests
    • Custom filter expression
  • When incoming requests match…
    (http.host eq "b2.example.com" and http.request.uri.query ne "")
  • Then...
    TypeExpressionStatus code
    Dynamicconcat("https://b2.example.com", http.request.uri.path)301
    • Preserve query string

其中那个 b2.example.com 改成自己的 host

这样做主要是防止被有心人无限改查询字符串刷调用次数,毕竟 2500 次动动手指就刷完了。

17:20 · 20 Jul, 2023

AndroidFiddler抓包

签发证书

先把证书导出来得到一个 FiddlerRoot.cer

SHELL
openssl x509 -inform der -in FiddlerRoot.cer -out fiddler.pem
file1=$(openssl x509 -inform PEM -subject_hash_old -in fiddler.pem | head -1)
cat fiddler.pem > "$file1.0"
openssl x509 -inform PEM -text -in fiddler.pem -out /dev/null >>"$file1.0"
file2=$(openssl x509 -inform PEM -subject_hash -in fiddler.pem | head -1)
cat fiddler.pem > "$file2.0"
openssl x509 -inform PEM -text -in fiddler.pem -out /dev/null >>"$file2.0"

然后将生成的这两个文件拷贝到 /system/etc/security/cacerts/ 目录下,重启设备。

SSL Pinning Bypass

LSPosed + JustTrustMe

4:20 · 7 Jul, 2023

尝试Svelte

拿 Svelte 来重构了一下blog,虽然最后没完成,不过还是挺有意思的,处理 Markdown 的生态一般,有种 戴着镣铐跳舞的感觉

留下了一个本来用来替换掉右下角 NuxtJS 标的玩意。

svelte-logo

9:14 · 30 May, 2023


评论区