普通视图

发现新文章,点击刷新页面。
昨天以前Reorx’s Forge

Rabbit R1 - The Upgraded Replacement for Smart Phones

2024年1月10日 11:22

It all started with The Light Phone.

Living in a world full of technology and digital devices today, I constantly feel distracted, unfocused, and lost. The smartphone has become a part of our bodies, and we have already evolved into a new species - cyborgs. We no longer solely rely on our biological brains for looking and thinking; much of our important information is stored in our phones, our new “organ.” However, it is dumb in comparison because the only efficient way to interact with it is to use our fingers, which is much slower than other natural organs connected and controlled by neural networks. I want to get rid of it, but deep down I know I cannot live without it.

After discovering The Light Phone, I realized that we may not rely on apps and information as much as we think. However, despite initially being intrigued, I eventually found myself unable to resist returning to my iPhone 7. This begs the question: why did I make this decision?

The Light Phone

It’s a downgrade of a person’s capabilities. I can no longer take photos wherever I find something interesting, listen to music whenever I feel like it, or call a taxi wherever I want to go.

It shouldn’t be this way when it comes to enhancing our lifestyle. From a young age, I have never held a high opinion of those individuals in history who chose to live in the wilderness as a means to find mental tranquility. I greatly admire those who can actively engage in society while maintaining a clear mind, living in a manner that brings them personal satisfaction.

Years passed, and it suddenly came to the era of LLMs. One day, I found AI Pin, a wearable device made by Humane with the power of OpenAI. My eyes lit up; I sensed a similar aesthetic and philosophy to the Light Phone. However, that was not enough. The AI Pin has no screen, meaning that I cannot interact with it through vision and touch, a significant loss of HCI technology of the past 60 years.

AI Pin

That being said, AI Pin is heading in the right direction, because what wastes our time and distracts us when using smartphones are the tedious and complex operations with apps. LLM excels at converting natural language into a sequence of machine-understandable commands, which is clearly the way to reduce the friction of using a digital device. I feel that the future is near, only a few steps away, but I didn’t expect it to arrive so quickly.

Today, Rabbit R1 has been released, and I view it as a milestone in the evolution of our digital organ.

Rabbit R1

R1 is definitely an upgraded replacement for smartphones. It’s versatile and fulfills all everyday requirements, with an interaction style akin to talking to a human. Other devices, such as the Light Phone and AI Pin, though minimalist and distraction-free, fall short of enabling us to accomplish our daily tasks. The disadvantages outweigh the benefits in the ultimate goal of improving our lifestyle.

So, why is R1 great? In my opinion, it’s primarily due to the two new technologies it employs: Agent and LAM.

The concept of an Agent is simple: you instruct the AI what to do, and it will analyze your words to formulate a clear goal. Subsequently, it breaks this goal down into various tasks, orchestrates the completion of these tasks, and finally assembles the results to fulfill the goal. The Agent resolves the issue of LLM’s inefficiency at task completion. AutoGPT is the first proof-of-concept that has popularized the Agent. From a certain perspective, R1 can be perceived as a physical embodiment of an Agent.

The following is LAM, which I believe is the most impressive and exciting feature in R1’s presentation. LAM, or Large-Action-Model, possesses the capability to comprehend any user interfaces and act accordingly. Utilizing LAM, Rabbit has developed a framework. This endows R1 with its most potent tool - the ability to learn.

With the power of teaching mode, R1 can acquire a new skill simply through screen recording and voice instructions. In the presentation, Jesse showed how to instruct R1 to use Midjourney for creating images from user voice commands. This proves that R1 is not limited to what the developers built in; users can tweak it to tailor it to their own needs. There’s no compromise of losing capacities while maintaining a minimalist shape and intuitive use.

Someone once stated, the primary distinction between humans and robots is self-learning. While R1 cannot learn independently, it certainly serves as a commendable human companion.

Generally, I believe R1 has the potential to change the world. This is a thought that seldom comes to my mind, as I have seen numerous new technologies and inventions. However, R1 is different; it’s not just another device to please a certain niche. It’s meticulously designed to serve one significant goal for all people: to improve lifestyle in the digital world.

Debounce and Throttle

2023年12月21日 20:23

概念

Debounce 和 Throttle 是两种相似的频率限制手段。Debounce 顾名思义,去掉弹跳/抖动,能看出防止误操作的意味;Throttle 的意思是节流阀,更加直接了当。作为两种常见的设计模式,理解他们的工作原理和细微区别能够帮助我们写出更健壮的应用程序。

虽然是两个通用的概念,但它们的确主要在 JavaScript 中被提及和使用,究其原因,JavaScript 中常常出现连续发生的大量事件,如果不对调用频率做出限制,会产生严重的性能问题。且这些事件是可以舍弃的,一段时间内只需要产生一次有效调用即可。而在后端则很少出现这种情况,所有的事件都必须要处理,性能问题通常通过异步和分布式调用来解决。

下面是我对这两种设计模式的理解。

  • Debounce: 将间隔不超过设定时间的多次连续调用变成一次。

    从工作原理上来讲,Debounce 会使目标函数变为延迟生效,当对其进行连续多次调用时,若前后两次调用的时间间隔不超过设定值,则前一次调用会被取消。直到某次调用后,在设定的时间内没有出现下一次调用,那么这次调用将不会被取消,从而最终被执行。

  • Throttle: 确保一个函数被连续多次调用时,在设定时间内最多只实际执行一次。

放在一起对比的话:

  • 相似之处:Debounce 和 Throttle 都限制了函数执行的最大频率不超过每设定时间一次
  • 不同之处:在快速(间隔小于设定时间)连续调用时,Throttle 确保了函数会规律执行,但 Debounce 只有当连续调用放缓(间隔大于设定时间)时才会执行。

应用场景

设想如下几个场景:

  1. 当用户改变网页窗口的大小后,调用一个函数以调整 UI 布局
  2. 当用户在滚动浏览网页内容时,根据内容所处的位置,持续更新大纲目录中的链接高亮
  3. 在一个输入框的下方,让搜索提示结果随着用户的输入持续不断地更新

我们一起来看看每个场景分别应用 Debounce 和 Throttle 会有什么样的效果,并评判哪个是更加合适的频率控制方式。

场景 1

用户按住鼠标不松一直改变窗口大小,使用 Debounce 的情况下,UI 在用户停顿或者松开鼠标时才会改变;使用 Throttle 的情况下,用户会观察到 UI 在拖拽窗口大小的过程中每隔一会改变一次,容易给人一种反应迟钝或卡住的错觉,因此 Debounce 是更好的选择。

场景 2

用户持续向下滚动鼠标滑轮,使用 Debounce 的情况下,大纲的高亮只有当用户停止滚动时才会更新。所以当用户一次性滚动很长时,只能看到一次高亮的改变,中间仿佛跳过了一般;而使用 Throttle 的情况下,随着用户滚动,高亮会稳定地以设定的时间间隔更新,因此 Throttle 是更好的选择。

场景 3

用户以较快地速度连续输入字符,使用 Debounce 的情况下,只有当用户停止输入时搜索提示才会更新;使用 Throttle 的情况下,搜索提示会稳定地以设定的时间间隔更新,但如果用户输入最后一个字符的时间,正好处于上一次调用后的间隔期,无法触发新的调用,那么用户所看到的提示就不是根据完整的输入内容做出的。Debounce 由于能够保证函数总是在用户停止输入时执行,是比 Throttle 更好的选择。

库的使用

首选 lodash,因为它是一个非常流行且久经考验的库。但如果不想让整个 lodash 混入项目的构建结果,可以安装 lodash.throttlelodash.debounce 两个独立的库。如果你使用的 bundler 支持 tree-shaking,也可以通过 lodash-es 来 import 这两个函数,最终构建结果中只会包含与之相关的代码。

npm 狂魔 sindresorhus 也维护了两个包,debouncethrottleit, 如果你想要更简洁的实现,可以考虑使用。

下面以 lodash.throttle 为例,展示其如何在一个 TypeScript 项目里安装和使用 :

npm i lodash.throttle
# 还需要额外安装 `@types/` 的类型定义
npm i -D @types/lodash.throttle

引入和调用:

import throttle from 'lodash/throttle';

const onScroll = () = {/* 实现细节 */}

// onScroll 执行的最高频率为每 100 毫秒一次
document.addEventListener('scroll', throttle(onScroll, 100));

后续思考

这篇笔记来源于重构 GitHub TOC Sidebar 扩展时对场景 2 的思考,之前用的是自己手写的 Debounce,在滚动过程中经常看不到 ToC 的高亮变化,这次换成了 lodash.throttle,终于达到了预期的效果。其实这三个场景我都在过往的开发经历中遇到过,并且是在不了解这两个概念的情况下独立思考出了(简陋或丑陋的)解决方案,直到最近才重新审视,阅读了相关的文章,学习了更好的实现方式。这也是为什么我在关于状态机的短文中感叹基础知识的重要性,如果能更早地知道这两个概念,就能避免曾经在黑暗中摸索的痛苦。当然,因为自己琢磨过,当看到更系统更高级的实现时,就会有更深刻的理解,这大概是这位推友希望自己是通过 Vanilla JS 学习前端的原因。

Twitter

如果重学前端,我肯定老老实实地用原生 JS 去完整地做几个项目,去 appendChild,去切身体会一下手动管理一切状态,命令式更新 UI,还要时刻让它和状态保持一致的痛苦。然后再去学框架,把这些项目重写一遍。 很遗憾我没有写过 jQuery,太早接触框架的最大问题就是对它们在解决什么问题理解得不够深刻

— Sixian (@noworkforsixian) December 19, 2023

一言以蔽之,开发遇到困难免不了自己琢磨,但在琢磨时多想想能否将问题定义出来,符合一个已有的概念,然后去参考现实世界中系统和标准的解决方案;如果没有也无所谓,未来某一刻这种思考过程会化作某种领悟,不会白费。

参考资料

Window Opener for Chrome

2023年4月4日 11:46

最近又做了一个新的扩展——Window Opener,这篇文章介绍它的动机、开发过程和用法说明。

Window Opener - Chrome Web Store

动机

我平时主屏的窗口布局一般是 Chrome 占 3/5 靠左,VSCode 占 1/2 靠右,交叠的部分一般不会影响两边的浏览。 最近关于 AI 的新闻几乎都从 Twitter 上获取,我很希望它以一个单独的窗口出现在主窗口的右侧,这样我在打开来自 Twitter 的链接时仍然可以继续向下滚动,得到更好的浏览体验。 我在 Moom 1 上添加了让窗口以 1/5 的屏幕大小靠 Chrome 右侧的布局,但仍然觉得很麻烦,因为我在专注工作时会关闭 Twitter,而每次打开时,都要走一遍 [打开新窗口] → [输入 twit 回车] → [快捷键唤出 Moom] → [快捷键应用布局] 的流程。于是我便想,要是能够一键把 Twitter 在当前窗口的侧边以特定大小打开就好了,既然没有这样的工具,何不自己做一个呢?

开发过程

如果你对此不感兴趣,可以直接跳到下一个章节查看插件的功能和用法介绍

说干就干,我从自己的 webpack-chrome-boilerplate 脚手架中复制了 vanilla-ext 到新的项目,为它取了一个简单直接的名字 window-opener. 我的脚手架的 tech stack 为 TypeScript + Webpack,其中内置了一些常用的库比如用于 DOM 操作的 cash-dom 和用于记录日志的 loglevel,不过最重要的一个包是 @reorx/webpack-ext-reloader, 这是我维护的用于自动重载扩展的工具,能够减少开发时每次保存就要手动点击 reload extension 的心智负担。

为了快速实现一个 demo,我首先想到的是让扩展的图标点击就可以打开 Twitter。我在 manifest.json 里添加了下面的配置

  "action": {
    "default_title": "Open a window"
  },

这使得当扩展的图标被点击时可以触发一个事件,从而执行打开新窗口的操作。以下是 background.ts 的代码:

chrome.action.onClicked.addListener(async () => {
  const window = await chrome.windows.getCurrent()
  const context = {
    windowWidth: window.width ?? 0,
    windowHeight: window.height ?? 0,
    screenWidth: 2560,
    screenHeight: 1440,
    xOffset: 58,
  }

  const windowArgs = {
    left: context.windowWidth + context.xOffset,
    top: 0,
    width: context.screenWidth - context.windowWidth - context.xOffset,
    height: context.screenHeight,
  }

  chrome.windows.create({
    url: 'https://twitter.com',
    focused: true,
    ...windowArgs,
  })
})

一个简单的 Proof-of-Concept 便完成了,点击扩展,便会在当前窗口右侧打开高度和屏幕一致、宽度占满剩余空间的 Twitter 窗口。这里用到的最核心的 API 是 chrome.windows 2,实现了当前窗口大小的获取,和新窗口的大小、位置的控制。为了计算出相对于屏幕的空间,我将自己所用屏幕的大小赋值给了 screenWidthscreenHeight, 但这样做不具备通用性,我希望动态获取当前窗口所在屏幕的大小。讽刺的是,Chrome 扩展的 API 竟然无法实现3,经过各种尝试,最终我通过在扩展的设置页获取 window.screen 对象的方式得到了这些数值。(注意这里的 window 并非 chrome.windows.Window, 而是 DOM 的 window。)

核心功能完成后,我又为扩展增加了易于使用的界面。如果是非常简单的扩展 (比如 refgen,未来会写篇单独的文章介绍), 我会直接使用原生的 DOM 接口来实现页面交互,但这次我感觉到编辑界面有一定的复杂度,于是将脚手架换为 webpack-chrome-boilerplate 中的 react-ext,用 React 来增加代码的模块化和可维护性。

一直以来我一直都习惯用 Vanilla JS 来调用 Chrome 扩展接口,但引入 React 后不得不考虑状态管理,于是我找到了 use-chrome-storage,它能够以 hooks 的方式获取和保存扩展数据,使我免于用 useEffect 重新实现。下面是代码示例:

/* define settings store hook */

export interface Settings {
  iconAction: IconAction
  windows: WindowData[]
}

export const INITIAL_SETTINGS: Settings = {
  iconAction: IconAction.defaultWindow,
  windows: [],
}

export const useSettingsStore = createChromeStorageStateHookSync(STORAGE_KEY, INITIAL_SETTINGS);


/* use settings store hook */

const Popup = () => {
  const [settings, setSettings, isPersistent, error, isInitialStateResolved] = useSettingsStore();

  if (!isInitialStateResolved) {
    return (
      <div>loading</div>
    )
  }

  return (
    ...
  )
}

在 Options 页面中,我实现了一个窗口管理器组件 WindowsManager,它会循环渲染所有窗口的编辑界面,而每个窗口都需要用到 chrome.windows.Window 来计算。我不希望每个窗口都调用一次 chrome.windows.getCurrent,便想在整个页面初始化时获取 Window 对象,向下传递给子组件来使用。如果传递的层级很深,React 推荐的方式是使用 useContext4,但我觉得比较麻烦,而且不够灵活,于是引入了 zustand 来做全局状态的同步。下面是代码示例:

/* define app store hook */

export interface AppStore {
  chromeWindow: chrome.windows.Window|null
}

export const useStore = create<AppStore>()((set) => ({
  chromeWindow: null,
}))


/* use app store hook */

// options.tsx: update chromeWindow
chrome.windows.getCurrent().then(window => {
  useStore.setState({
    chromeWindow: window
  })
})

// WindowManager.tsx: get chromeWindow
const WindowItem = ({data, defaultId, onDataChanged, onDelete}: WindowItemProps) => {
  const chromeWindow = useStore(state => state.chromeWindow)
  const context = getContext(data.staticContext, chromeWindow!)
  ...
}

以上是一些开发中的心得和收获,如果你有更多兴趣,可以直接阅读源码。还有一些技巧不再赘述,以下是一个简单的列举:

  • 通过 chrome.action.setPopup 实现切换点击扩展按钮的行为(显示 popup 或触发 action click 事件) → code-0, code-1
  • 通过 chrome.windows.onBoundsChanged 监听窗口大小的改变,并控制事件的发生间隔 → code
  • 使用 expr-eval 进行数学表达式计算,避免使用 Chrome 扩展所不支持的 evalcode
  • 使用 data url 创建一个临时的窗口来显示错误信息 → code
  • 通过 key 属性值的变化使得设置了 defaultValue 的 input 元素在 rerender 时仍可以改变数值 → code

用法说明

Options

在安装了 Window Opener 之后,首次点击扩展按钮,会打开设置页面:

  • Icon action: 点击扩展按钮的行为,有两种模式,Open Default Window 会直接打开默认的窗口,Open Windows List 会打开窗口列表供选择。
  • Windows: 用户自定义的窗口,在这里进行添加、修改和删除
  • Backup and restore: 导出和导入扩展配置。点击 Export 会直接下载 JSON 格式的配置文件。若要导入,请先点击 Choose File 选择文件,再点击 Import 完成导入。

点击 Create 按钮,开始创建第一个窗口。下面的截图是我定义的用于满足最初需求的 Twitter 窗口。

参数说明如下:

  • Name: 窗口名称
  • URL: 窗口链接
  • Type: 窗口类型,normal 是平时使用的正常窗口,popup 仅有边框,没有地址栏和扩展按钮
  • Focused: 是否在打开后聚焦到该窗口
  • Default: 是否为默认窗口。仅能设置一个,需要先取消勾选才能更改为其他窗口。
  • left: 窗口到屏幕左边的像素距离
  • top: 窗口到屏幕顶部的像素距离
  • width: 窗口的宽度
  • height: 窗口的高度
  • Context: 用于参与 left, top, width, height 表达式计算的变量
    • windowWidth: 当前窗口宽度,动态数值
    • windowHeight: 当前窗口高度,动态数值
    • screenWidth: 屏幕宽度。静态数值,与窗口绑定(以下3个变量与此相同)
    • screenHeight: 屏幕高度
    • xOffset: 屏幕X轴的不可用宽度,比如 MacOS 的 Dock 放在屏幕左侧就会使得一部分空间对于窗口来说是不可用的。
    • yOffset: 屏幕Y周的不可用高度,比如 MacOS 的 menubar。

这里我希望 Twitter 在当前窗口的右侧,而我的屏幕将 Dock 放在左侧,因此新窗口距离屏幕左侧的距离 left 应该是 xOffset + windowWidth;与屏幕顶部的距离 top 可以简单设置为 0,Chrome 会考虑 menubar 所占用的空间,自动将窗口下移,也可以像我这样精确设置为 yOffset。宽度 width 要填满右侧可用空间,因此是 screenWidth - (windowWidth + xOffset);高度 height 则可以直接使用 screenHeight,与 top 同理,超出可用长度的部分会被自动处理,也可以填为精确计算的数值 screenHeight - yOffset。

Tips: 要实现一个宽 600px, 高 500px 的居中窗口,请参考以下参数

width=600, height=500, left=(screenWidth - 600) / 2, top=(screenHeight - 500) / 2

Popup

当 Icon action 设置为 Open Windows List 时,就可以打开 popup,界面如下:

蓝色 ★ 表示默认窗口,鼠标点击窗口条目即可打开。

下方 Settings 是设置页的链接。点击 Create from current window 会基于当前窗口的 URL, left, top, width height 创建新的窗口。

目前 Popup 存在 accessibility 上的问题,应该使所有按钮可以通过 tab 键切换 focus,实现仅用键盘导航和打开窗口。

Keyboard shortcut

Window Opener 默认的快捷键是 ⌘ ⌃ T, 也可以在 chrome://extensions/shortcuts 进行自定义。

结语

开发 Chrome 扩展越来越成为我的一大爱好。浏览器是我们在赛博世界赖以生存的基本工具,能让它变得更好用,意味着我可以用更短的时间做更多的事,并享受更好的体验。开一个新的 side project,可以让我短暂离开主线任务和生活中的琐事,专注在具体明确的目标上,不仅是精神上的放松,也是对开发技术的淬炼。

希望你能喜欢 Window Opener :)


  1. Moom 是我使用多年的窗口管理工具 https://manytricks.com/moom/ ↩︎

  2. https://developer.chrome.com/docs/extensions/reference/windows/ ↩︎

  3. chrome.system.display 可以获得所有屏幕的数据,但无法知道当前窗口所在的是哪一个屏幕,而 Window Opener 需要在 background 中运行,此时是无法使用 DOM 的 window.screen 对象的,因此最终将 screenWidth, screenHeight 设计成了绑定在每个用户添加的 window 上的静态数值,但可以在编辑界面动态更新。 ↩︎

  4. Passing Data Deeply with Context ↩︎

用 AI 工具快速撰写分享型推文

2023年3月11日 11:44

前几天刷到一个 YouTube 视频,内容是斯坦福大学一位教授介绍它对 AI 的看法和 “AI Thinking” 思维观,感觉很有收获,于是就一如既往地想把我的所得分享到 Twitter 上。以往我都是自己来写推荐语,或许是受视频主题的影响,我便想到,要不要试试用 AI 来帮助我完成这次内容创作呢?

由于近期 ChatGPT 的火爆,我早已经安装尝试了许多基于 ChatGPT API 的工具,甚至自己开发了一些,因此也没有花功夫去寻找其他的,直接使用已有的工具来完成创作流程。

第一个也是最重要的工具是 Glarity,它是一个 Chrome 扩展,为许多网站提供使用 ChatGPT 提取摘要的功能,比如 Google、YouTube、GitHub 等,效果很好,非常实用。Glarity 支持 OpenAI API 和 ChatGPT Webapp 两种后端,前者需要用户自己提供 API key,后者只需要登录 ChatGPT 网页版即可使用,完全免费。Glarity 的开发者来自中国,近期他们在 Product Hunt 发布了产品,大家可以多多点赞支持。

Glarity 在 YouTube 播放器右侧注入了一个小组件,点击 “Ask ChatGPT to summarize” 即可用当前视频的字幕生成摘要,效果如下:

Use Glarity to summarize a YouTube video.

由于字幕是英文,因此总结内容也是英文,我使用 BobOpenAI Translator 插件将摘要翻译成了中文:

Use Bob to translate Glarity summary into Chinese.

看着效果还不错,读起来也很通顺,但是内容太长了,如果想发表到 Twitter 上,就要受到 280 个字符(140 个汉字)的限制,需要再精简一下。用我自己写的 Drafts 插件 ChatGPT Conversation,通过定义 system message 创建了一个专门用于生成社交媒体内容的笔记,让它基于上一步的翻译结果进行创作:

Use Drafts to generate social media content.

结果为:

斯坦福AIRE项目主任李江指出,未来许多工作将被人工智能代替,因此向下一代传授AI思维尤为重要。AI思维包括理解AI运作方式、与其合作完成任务及区分人类和人工智能的能力。江建议通过教授设计思维培养创造力和创新精神。他鼓励将人工智能视为强大工具,利用它创新并产生新想法。让我们一起学习AI思维,迎接未来挑战吧!

只需稍稍修改便是一个合格的文案了。不过其实我是自己把第二步的翻译结果进行了重编排,推文如下:

Twitter

斯坦福教授李江讨论了人工智能对就业和教育的影响,他强调 AI Thinking 的重要性,这包括了解AI如何工作、区分人类和机器,并与AI合作完成任务。他还建议通过斯坦福 Design Thinking 方法论来提高孩子们的创造力、共情与定义问题的能力,帮助他们在未来竞争中取得优势。https://t.co/n6yy0v1nLg

— Reorx (@novoreorx) March 7, 2023

总结

本文探索了使用 AI 技术将有趣的内容总结成文案后发布到社交网络上的流程。用到了以下工具:

虽然是一个简单的任务,但有 AI 的帮助确实加速了生产力,如果我从头看完视频 + 自己写,至少需要 30 分钟,AI 则可以将这个步骤压缩在 5 分钟内完成。

当然了,这对我来说只是一个实验,我并不想完全以这种方式来创作,一则我并非内容农场,我只创作我认为有价值的内容;二则使用 AI 生成并不能帮助我去思考或深入了解问题。写作是一个创造性的过程,我享受它所带来的成就感,甚至挫败感,它们都能使我得到成长。但我依然非常喜欢 GPT AI,因为它会持续优化我的生产力,帮助我分担非创造性劳作,让我能投入更多时间在创造性工作上。这又让我想起那句话,技术本身是无罪的,只看你怎样使用它。任何结果都是使用者的选择,而不能将原因归咎在完成这件事的工具上。

A Message to GPT-API Product Makers

2023年3月8日 15:37

I regularly check new GPT-APT-based products for my awesome list every day. Recently I found Zeeno.ai and Monica. They both look promising but lack the ability to customize API keys, so I left some comments on Product Hunt asking if this feature could be supported. Luckily, they both responded with a positive attitude.

Additionally, the maker of Monica replied:

I’m a bit curious, is using a personal API because the daily free quota is not enough?

This is an interesting question because I initially asked for this feature solely from a developer’s point of view: utilizing my own key would decrease expenses and provide more autonomy. But for product makers, it’s not worth creating a feature for a small group of people with technical background when the majority of users are normal individuals. That seems to be the case, and the common practice for GPT-API-based products is to build a subscription model around the usage amount—a reseller of the upstream API. So why bother telling the user that you can bring your own key if we want them to think highly of our technology?

Well, there are still reasons to do so. Let me explain:

  1. What the user truly cares about is the usability of your product. Although some may not be aware of OpenAI, the number of competitors in the market continues to grow rapidly on a daily basis. Therefore, providing this option will not have a negative effect on how normal individuals think about your product.
  2. The developer’s favor is a cost-free marketing promotion. As a developer, I appreciate transparent and customizable products. I will spare no effort to promote your product for free through writing articles or posting tweets, as long as I find it powerful and customizable, and I believe other developers are the same as me. YouTubers may also appreciate the opportunity to create tutorials teaching their viewers how to use great products for free by utilizing “certain technologies”. We will spread its popularity.
  3. Those willing to invest in a tool are less concerned about API keys, because they prioritize convenience and time over cost. Consequently, we can assume that conversion rates for subscriptions are constant. The more users a product has, the higher the likelihood of gaining subscribers. It’s straightforward logic, isn’t it?

I am aware that, in many cases, backend logic can be too complex to allow for this feature. However, where possible, I recommend enabling the customization of API keys, which would be a mutually beneficial solution for both of us. So, what is your opinion on this?

谈谈我对 ChatGPT 应用的 prompt 的看法

2023年3月5日 23:08

上一篇更新中我讲到自己基于 ChatGPT API 做了一个校对和润色文字的 Popclip 插件,叫做 Popclip Proofreader。由于 ChatGPT API 本身非常简单,这个插件的核心价值其实是我调教出的可以稳定、准确完成润色这一任务的 prompt。

在见识过越来越多的新产品后,我越发认定,prompt 是一个 ChatGPT 应用的灵魂,甚至未来 prompt 本身就可以成为应用。并且 prompt 关系到用户所输入的信息如何被使用和上传(到 OpenAI),因此我衷心希望所有基于 ChatGPT API 的产品都能向用户公开其所使用的 prompt,保持透明,尊重用户隐私。更何况 prompt 的使用交流无论是对于用户还是开发者都很有价值,任何人都不必敝帚自珍。

我们正身处一场技术发展和下放所产生的时代变革之中,作为一个开发者,应该清醒地认识到,我们开发的应用的价值是由所有参与到生成式 AI 与大型语言模型的学术研究、数据训练、软件开发的学者和公司所赋予的。应带着感激和尊敬,挖掘和普及这项技术的价值,使它能够应用在更多的场景中,为更多的人带来便利。这是我关于开发 ChatGPT API 工具的使命和初心。

Prompts for proofreading

说回正题,我来分享下 Popclip Proofreader 所使用的 prompt:

system: I want you act as a proofreader. I will provide you texts and I would like you to review them for any spelling, grammar, or punctuation errors.

user: Proofread the following content and give me the result without extra delarations or comments:

你可能会好奇,user message 似乎已经包含了足够多的信息,为什么还要有个 system message 呢?我的初衷是通过 system message 传递所有的任务要求,我给了它两个指示,1. 检查文字中的拼写、语法、标点错误并更正;2. 返回的信息不要包含额外的评论和声明。第二点的目的是为了避免输出中包含 “Here’s the corrected sentence:” 这样的无用信息,但结果却不甚理想,这种声明仍然时不时会出现。

于是我查阅了 OpenAI 的文档1,发现这样一段话:

Many conversations begin with a system message to gently instruct the assistant… In general, gpt-3.5-turbo-0301 does not pay strong attention to the system message, and therefore important instructions are often better placed in a user message.

也就是说,ChatGPT 对 system message 的遵从程度没有那么高,一些重要的指示最好还是放在 user message 中。所以我最终将第二个指令放在了 user message 中。但其实这么看来,system 更适合在较长的对话中定下基调,而单条信息的查询只用一条 user 来表达是最好的。

下面我收集了一些同样目的的 prompt 供读者参考和尝试。

可选项 1,来自 ChatGPT Grammar Check PopClip Extension

user: Please correct the grammar and polish the following sentences, do not provide any translation, comments, or notes, and use the same language as input

可选项 2,来自读者 Yu Bai

user: Rewrite the text in authentic English

可选项 3,来自 OpenAI Polisher Bob Plugin,括号部分可以去掉

user: Revise the following sentences to make them more clear, concise, and coherent (Please note that you need to list the changes and briefly explain why)

Other people’s thoughts

其实除了 prompt, ChatGPT API 的其他参数 (parameters) 也影响最终结果的产生,因此在公布 prompt 的同时,也应该将这些参数囊括进来。推友 @mr_easonyang 甚至认为参数应该成为自定义选项,我认为这是很好的提议:

Twitter

OpenAI 的 API 没太多可调的参数,其中比较好玩的应该是 temperature 和 top_p 二选一做微调,说白了就是定制 AI 的严谨(正经)程度。

几番测试下来,我觉得 temperature 0.5~0.8 时比较适合知识型问答、1.2 左右比较适合聊天瞎侃。

希望各位工具开发者们考虑下允许用户对这两个参数做自定义配置。 pic.twitter.com/svfMYgTF6B

— Eason Yang (@mr_easonyang) March 4, 2023

推友 @daydayuuup 也表达了对 prompt 无法控制的担忧:

Twitter

在想一个问题,大家在谈「咒语」(prompt)对于回复质量的重要性,我们在使用CHATGPT的时候是自己主动输入「咒语」,如果使用开发者创建的应用,比如翻译,那么翻译的提示语质量可高可低,我们还控制不了,所以要谨慎选择开发者,选择质量高的应用。否则可能会影响翻译质量。对不? https://t.co/cZHeaCKHsq

— 天天 (@daydayuuup) March 5, 2023

推友 @xxm459259 说 ChatGPT 类产品很容易被下游开发者复刻,我深以为然。一开始大家可能都会想把 prompt 作为壁垒,但对想要破解的人来讲,看一眼可能就猜个七七八八,自己调试下说不定效果还更好了…所以我觉得 prompt 还是公开的好,更快推动 Prompt Engineering 的发展,作为开发者也一定能在更好的环境中获利。

Twitter

其实我这段时间也用 ChatGPT 写了很多小工具,能尝试的 demo 几乎都尝试了一遍,甚至中间一度也想找一个合适的方向搞一个相对成型的产品,而非 Demo。
现在让我踌躇不前最主要的原因其实是壁垒,我觉得下游开发者复刻太容易,即便不知道你的 Prompt 看一眼产品也能猜个七七八八。。

— Micro 小熊猫 (@xxm459259) March 5, 2023

推友 yeaphgel 说到细分行业融入工作流组织化才能形成真正的壁垒,我很认可这个观点,这样的市场环境是我希望看到的。一个有追求的产品应该在工具的深度上做竞争,而不是急功近利地圈地抢人,如果你做的东西不够好,用户最终还是会流失掉。

The “Open Prompt” project

在写这篇文章的时候,我产生了一个想法——发起一个名为 Open Prompt 的开源项目,由社区参与者共同收集和维护各种 AI 产品的 prompt,附带详细的说明,供用户和开发者审阅和参考。与 Awesome ChatGPT Prompts 不同的是,每个产品的 prompt 都会有一个单独的页面,大家可以在这里看到 prompt 版本的变化,提交反馈和参与讨论,使这里成为 AI 爱好者们学习和交流的平台。如果读者们对此项目感兴趣,或有不同的想法,欢迎与我联系和讨论。

The market value of AI prompt

上文中提到的,未来或许 prompt 可以直接成为应用,今天就看到了 PromptBase 这个网站,看来 “Prompt as a Service“ 已经指日可待。这个网站可以搜索和买卖各类 AI 产品的 prompts,除了 ChatGPT 和 GPT-3,还有之前备受关注的 text-to-image 领域的 Stable Diffusion, DALL-E, Midjourney 等等。我随便查看了几个 ChatGPT 的,有教你如何赚钱的 “Eary Money Now”, “The Business Genie”,也有自媒体博主非常需要的 “Never Run Out Of Instagram Ideas”, “Social Media Weekly Content”,价格大都在 $1.99 到 $4.99 之间,卖得好的已经有上千次出售。

看来 prompts 真的有很大的商业价值,或许我关于 prompts 与参数透明化的想法太简单了,但我仍然相信开放才是进步的源动力,就像 OpenAI 完全可以给出更高的定价,完全可以不将 API 开放,继续让更多的人购买 ChatGPT Plus,但他们却把 AI 技术下放,使人人都可低价使用和获利。这种做法的背后即便是为了更大更长远的商业利益,也是我所欣赏和赞扬的,并在事实上推动了文明的进步。

ChatGPT Proofreader extension for Popclip

2023年3月2日 23:36

昨天 OpenAI 在博客中介绍了新的 ChatGPT API 1, 并且已经发布上线。这个消息令所有关注生成式 AI 开发者为之狂热,我也不例外。我有一大堆点子想要通过 OpenAI 的接口实现,而 ChatGPT API 几乎解决了旧 API 存在的所有问题。

但一个好的开发者应该是务实的,这些点子想要实现还要费一番功夫,有什么地方是马上可以用 ChatGPT API 得到改善的呢?这时我看到了 Popclip ChatGPT extension 的推文 2,并立刻发现这是个非常好的点子。Popclip 是个运行在 MacOS 上的软件,它会在选中的文字上方展示一个提示栏,点击其中的按钮就可以对文字进行快捷操作,如复制、粘贴、查询字典,用户也可以安装更多扩展以实现更多的功能。

Popclip ChatGPT extension 实现了将选中的文字作为 prompt 发给 gpt-3.5-turbo 模型并将返回结果粘贴在下方的功能,并能保存上下文。不过我的评价是,这很 cool,但没啥用,不如直接打开 ChatGPT 网页对话。我想到我经常使用 ChatGPT 的需求之一——校对和润色文字,于是我基于这个扩展做了一个新的扩展,取名为 ChatGPT Proofreader.

功能和用法

ChatGPT Proofreader 提供了中英文两种语言的润色功能,用法如下:

  • 选择英文后,在 Popclip 中点击钢笔图标,润色后的英文将被粘贴在选中文字的下方。
  • 选择中文后,在 Popclip 中点击「润」图表,润色后的中文将被粘贴在选中文字的下方。

安装和配置

首先你需要运行 Popclip,然后拖拽鼠标选中下方完整的代码片段,在 Popclip 中点击 “Install Extension ChatGPT Proofreader” 即可完成安装。之后扩展会提示你输入 OpenAI 的 API Key,点击确认便完成了配置。

// #popclip extension for ChatGPT
// name: ChatGPT Proofreader
// icon: iconify:fluent:calligraphy-pen-24-regular
// language: javascript
// module: true
// entitlements: [network]
// options: [{
//   identifier: apikey, label: API Key, type: string,
//   description: 'Obtain API key from https://platform.openai.com/account/api-keys'
// }]

const prefixes = {
    "native": "Paraphrase the following sentences to make it more native:\n",
    "revise": "Revise the following sentences to make them more clear concise and coherent:\n",
    "standard": "Correct this to standard English:\n",
    "polish": "Please correct the grammar and polish the following sentences, do not provide any translation, comments, or notes, and use the same language as input:\n",
    "authentic": "Rewrite the text in authentic English:\n",
    "ielts": "Rewrite the text using IELTS standard:\n",
}
async function chat (input, options, lang, prefixName) {
  const openai = require("axios").create({
    baseURL: "https://api.openai.com/v1",
    headers: { Authorization: `Bearer ${options.apikey}` },
  });

  let messages
  switch (lang) {
    case "en":
      messages = [
        {"role": "system", "content": "I want you act as a proofreader. I will provide you texts and I would like you to review them for any spelling, grammar, or punctuation errors."},
        {"role": "user", "content": `Proofread the following content and give me the result without extra delarations or comments:\n\n${input.text}`},
      ]
      break;
    case "zh":
      messages = [
        {"role": "system", "content": "你是我的写作助手,检查接收到的文字的拼写、语法错误,对其进行润色,向我提供修改后的文字。"},
        {"role": "user", "content": `修改和润色下面的文字,直接输出修改后的结果,不需要额外的声明:\n${input.text}`}
      ]
      break;
  }
  if (prefixName) {
      messages = [{"role": "user", "content": `${prefixes[prefixName]}${input.text}`}]
  }

  const { data } = await openai.post("/chat/completions", {
    model: "gpt-3.5-turbo",
    messages,
  });
  const result = data.choices[0].message;
  return input.text.trimEnd() + "\n\n" + result.content.trim();
};

exports.actions = [
{
  title: "ChatGPT: proofreader en",
  after: "paste-result",
  code: async (input, options) => chat(input, options, "en"),
},
{
  title: "native",
  icon: "text native",
  after: "paste-result",
  code: async (input, options) => chat(input, options, "", "native"),
},
{
  title: "revise",
  icon: "circle revise",
  after: "paste-result",
  code: async (input, options) => chat(input, options, "", "revise"),
},
{
  title: "polish",
  icon: "square filled polish",
  after: "paste-result",
  code: async (input, options) => chat(input, options, "", "polish"),
},
{
  title: "ChatGPT: proofreader zh",
  icon: "square filled 润",
  after: "paste-result",
  code: async (input, options) => chat(input, options, "zh"),
},
];

Alternative Prompts

本扩展所使用的 prompt 如下:

system: I want you act as a proofreader. I will provide you texts and I would like you to review them for any spelling, grammar, or punctuation errors.

user: Proofread the following content and give me the result without extra delarations or comments:

你可以尝试以下 prompt,或使用自己调整的内容。

可选项 1,来自 ChatGPT Grammar Check PopClip Extension

Please correct the grammar and polish the following sentences, do not provide any translation, comments, or notes, and use the same language as input

可选项 2,来自读者 Yu Bai

Rewrite the text in authentic English

可选项 3,来自 OpenAI Polisher Bob Plugin,括号部分可以去掉

Revise the following sentences to make them more clear, concise and coherent (Please note that you need to list the changes and briefly explain why)

其他:

Native

Paraphrase the following sentences to make it more native:

Standard

Correct this to standard English:

ChatGPT API 的优点

ChatGPT API 打开了我们对工具的想象力,但要着手去拿它做些什么,首先需要了解其特性。我在 Twitter 发表了一个 thread 3来总结其优点,希望能帮助你节省一些时间。以下是完整内容:

ChatGPT API 相比 ChatGPT 的一些优点。

  1. 显式定义角色。在新的 ChatGPT API 中,消息增加了 role 属性,表示其所属的角色,其中 system 用于定义接口的行为,比如「你是一个写作助手」, userassistant 则用于区分用户输入和模型输出。这使得创造特定需求的助手变得更加清晰明确。
  2. 选择性地去掉会话中的信息。ChatGPT 最大可以存储 4096 个 token(大约 16384 个英文字母)的上下文,当一个会话的内容超出这个数量,最前面的信息就会被遗忘。而 ChatGPT API 每次都要将完整的上下文传递过去,这意味着我们可以选择保留重要的信息,选择性地去掉一些无用的以避免超出限制。
  3. 返回多个结果供选择。通过传递 n 参数,可以一次性返回多个不同的结果,适合文字润色、短内容生成等场景,避免多次重复问询。
  4. 使用 logit_bias 参数调整特定词汇(token)在结果中出现的可能性,实现特定词汇屏蔽的功能。
  5. 使用 temperature / top_p 参数调整结果的相关性和准确性。当我们需要发散思维、拓宽脑洞时,可适当调低结果的相关性;而当提供的上下文足够多,需要做精确的分析时,则可调高相关性,减少 编造内容的比重。

  1. Introducing ChatGPT and Whisper APIs ↩︎

  2. From twitter.com/PopClipApp/status/1631040246841319427:

    PopClip ChatGPT extension snippet — updated to use the new GPT 3.5 chat API, so you can actually chat with it.https://t.co/xo8xaGABHs pic.twitter.com/gM916fL7io

    — PopClip for Mac (@PopClipApp) March 1, 2023
     ↩︎
  3. From twitter.com/novoreorx/status/1631250035852861440:

    Thread🧵: ChatGPT API 相比 ChatGPT 的一些优点。

    1. 显式定义角色。在新的 ChatGPT API 中,消息增加了 role 的属性,表示其所属的角色,其中 system 用于定义接口的行为,比如「你是一个写作助手」, user 和 assistant 则用于区分用户输入和模型输出。这使得创造特定需求的助手变得更加清晰明确。 pic.twitter.com/35Yzb4sE4Z

    — Reorx (@novoreorx) March 2, 2023
     ↩︎

思考生活与生命在英语中的区别

2023年2月11日 14:41

写笔记时想到一句话:

掌控时间不仅能使我的生活变得更好,同时也是使我的生命更有价值

尝试翻译成英文:

Being in control of time not only makes my life better, but also gives my life more value.

这里「生活」与「生命」都用了 life,如何体现汉语意思中的区别呢?

在中文里:

  • 生活:强调的是一个日常的、持续的状态。当我们谈及生活,大多有着较为具体的感受。
  • 生命:是一种对自身存在意义和价值在精神层面的评判,相较「生活」更加抽象。

我将这个问题发到了 Twitter 上1,并询问了身边的人,陆续收到了一些改进的建议:

  • 推友 @Sunset16094839 提出生活可以翻译为 “my days”:

    Being in control of time not only makes my days better, but also gives my life more value.

  • 表妹(英语老师)的建议:
    • 将生活换成 living;makes 换成 leads to;value 换成 valuable:

      Taking control of time leads to not only a better living but also a more valuable life

    • 不要刻意去区分,直接合并

      Taking control of time leads to a better and more valuable life

  • 女友的建议:
    • 在翻译「生活」时加上一个修饰词,如 daily/working 等。这符合「生活」更加具体的涵义。

      Being in control of time not only makes my daily life better…

    • 根据 “Life” - 生活 vs 生命 vs 人生 | Ci Shifu Vocabulary Mastery Course - YouTube 视频里的讲解,「生活」可以用 to live 来表达:

      Being in control of time not only makes me live better, but also gives my life more value.

  • 推友 @7id 同样认为生活可以用 living 来替代,并且给了很多深入而专业的解读,让我大呼过瘾2:

    关于“掌控时间”的说法,可能你想强调的是时间管理或自律这个概念吧,单说字面翻译 DeepL 更好一些,master 更接近 manipulate 的含义。二是“生活”如果是指物质层面,英语一般习惯用 standard of living 描述,精神层面 quality of life 用得多。

    再就是“生命价值”这个概念,中文里可以说价值多还是少,而英文里 life value 更偏重价值观,对应的是忠诚、正义这样的词,所以这个翻译不是很恰当。然而 value of life 也不行,这个词是形容一条命值多少钱的。我觉得 self actualization 更符合这个语境。

    最后就是两个细节,回到汉语的思维模式,“使我的生活变得更好”显然没有“改善我的生活”更地道,英语也是一样的,像 improve/enhance 这些词自带改变的含义。还有中文的“不仅/还”是隐含的递进关系,强调后面的部分,字面对应的 not only 在关系强调上要若一些。

我自己在写出了「生活」与「生命」的中文意义时,就有了一个想法,那就是这个翻译的原文是可以优化的。汉语思维中我们习惯了说让生活变得更好,但在英文里可以扩展到更具体的意思——到底是生活的哪个方面变得更好了?作为出题人,我很快就想起,当时是因为读到时间管理对工作效率的提升,才感慨其能使生活变得更好。这也非常符合英语写作中不断推导来完善证据链的技巧。

综合各种建议,我最终把翻译修改为:

Being in control of my time not only improves my productivity at work, leading to a better life, but also enables me to reach higher levels of self-actualization.

也许还有一些错误,但翻译的结果其实已经不重要了,由这一句话延伸出的学习过程才是最有价值的。


  1. From twitter.com/novoreorx/status/1623978507327127553:

    英语怎么区分生活和生命?比如我想说「掌控时间不仅能使我的生活变得更好,同时也是使我的生命更有价值」。生活强调的是一个日常的、持续的状态,而生命是一种自身存在意义的评判。图片是 DeepL 的翻译,我也写不出更好的,但感觉并没有表达出我想要的意思。 pic.twitter.com/FQJfPvx29A

    — Reorx (@novoreorx) February 10, 2023
     ↩︎
  2. From twitter.com/7id/status/1623989480062599169:

    生命用 life 比较好,生活可以用 living 来替代。

    对于表达不到位这个事我有个猜测,你用英语的文法习惯在构思,但找不到恰当的词。“构思”说的是你拿“掌控时间”这样一个抽象概念做主语,汉语里习惯是用具象事物做主语。英语里要区分近义词,要么换个词根,要么换个表达方式,生命和生活就是后者。

    — 唐僧 (@7id) February 10, 2023
     ↩︎

Some random thoughts on Generative AI

2023年2月8日 21:40

ChatGPT 带来的震动一次又一次出圈,最近也在不断地看到各类基于 GPT 的新产品。科技圈里,Google 刚刚宣布了 Bard 1的消息没两天,Microsoft 便发布了与 ChatGPT 结合的 Bing 和 Edge 2,两大巨头在生成式 AI 领域的大型科技与商业竞争正在白热化。在这个人人都在谈论 AI 的时刻,我也有几个观点想要分享一下。

What’s next for GPT-based products?

目前我认为基于 GPT 的 Generative AI 最适合做的事情也是最强的功能之一就是 summarization——生成摘要。群聊 tldr (https://chat-simplifier.imzbb.cc/) 是个好点子,但是缺少 integration ,使用上的不便使其节省时间的价值大打折扣。

我设想一个 telegram chat tldr 工具,它会持续同步聊天记录的 archive。当我一觉起来看到一个群 999+未读,我可以向它询问最近12小时的聊天摘要;或者工作了一下午,看到300+条未读,我也可以指定要最近300条的摘要。它会先查询消息再格式化后输入给 OpenAI API 得到结果。

与其他短文本的信息服务整合也同理,如 twitter archive(过去一周我在关注什么?心情如何?吃了什么?);twitter timeline(这几天中英文 tl 分别在讨论什么?);rss news headlines(概述一下过去一个月的热点事件)。

应用到笔记软件,可以把一段时间的 daily notes 生成出 weekly/monthly summary,适合雇员写周报,或者PKM实践者做总结。

现在 GPT 虽然可以给个人用,却都是基于通用的数据集,只能短暂而有限地使用个人数据,比如 ChatGPT 所能保存的上下文最多为3000词。下一步突破,我希望是做到人人都可以用个人的大量数据(浏览器历史记录、所有看过的文字)持续输入,训练出专属的 AI,届时我们便能得到“上周我看过一个关于xx的网页或者推文,帮我找找是哪个”这种问题的答案了,或可诞生 GPT-powered personal search engine 这样的产品。

新技术的应用时机

OpenAI 和 ChatGPT 的成功,以及仅仅月余便百花齐放的 GPT-based products 给我的另一个启发是:当一个新技术成就了革命性的产品,技术便会下放,此时才是它被广泛应用的最好时机

收藏工具 mymind 发布时,距离 ChatGPT 的出现还有一两年时间。mymind 一开始就给自己贴上了 AI 的标签,标榜为新一代的智能收藏工具,做了 AI 分析、摘要、搜索等功能,但整体的市场反响却平平。为何 mymind 没有得到足够多的关注呢?我认为主要是因为在 ChatGPT 之前,AI 被喊了太多年,就像「狼来了」的故事,人们早已失去了开始的好奇心,见怪不怪了。而彼时 AI 技术的确没有突破性的价值,加上过于滥用,最终成了廉价的噱头,在国内科技界尤其如此。

直到 ChatGPT 横空出世,带来了十倍、百倍于之前的 AI 产品的提升,这才重使人们燃起对 AI 的兴趣和热情。而 OpenAI 提供的 API 使任何产品都可以方便地接入,实现接近 ChatGPT 的 AI 功能,大大降低了 AI 技术的应用门槛。此时虽然利好 AI 相关的产品,但 mymind 所宣传的功能不再具有壁垒,未来也没法有很强的竞争力。因此我认为 mymimd 在 AI 上投入是创新的、值得肯定的,但从收益上来看是个失败案例。

《风暴英雄》对我的意义

2023年1月20日 21:52

《风暴英雄》培养了我与人竞争的能力。

从小我就是个胆子比较小的人,遇到与人正面对抗的事情就会犯怵,人多的时候说话都不利索。但接触电脑游戏后,我渐渐察觉到对抗的乐趣。一开始我玩的游戏对手都是 AI,由于它们不是「人」,卸下了我潜意识里的恐惧。我独自玩可以玩得非常好,因为我其实并不怕失败和尝试,每次重来我都能学到新的东西,而电脑程序的逻辑是有穷尽的、不会进化的,最终都会被我攻克。但我的性格仍旧没有改变,一旦我知道对手是人,就会下意识地想要回避、担心失败,发挥不出原本水平的一半。在玩 FPS、RTS、MOBA 游戏时都是如此。

刚开始玩风暴时,由于其出色的美术效果,和暴雪人物所带来的亲切感,我很快就喜欢上这个游戏。但我只敢玩「合作模式」,即匹配 5 个玩家和 5 个 AI 对局。就这样玩了一年多,我对每个英雄的每个天赋都熟悉到倒背如流,所有的组合都已经试过无数遍,合作彻底失去了乐趣,我这才怀着忐忑的心情迈出了和真正的玩家对战的第一步。我先是玩了很久「非排名模式」,这个模式顾名思义,胜负不会计算分数和排名,但和排位赛一样需要通过 Drafting 1 阶段选择英雄。同样没有胜负压力的还有「快速模式」,并且可以直接选择你要玩的英雄,更加自由。我又把这两个模式玩了一年,当时我还使用了 HOTSLogs 这个工具上传我的对局 Replay,根据它的算法,我的 MMR 都已经到大师段位了。这时我已经彻底代谢掉了与人对抗的恐惧,开始享受起来。于是我迈出了最后一步,开始参与排名模式,一个人单排打到了钻石段位,这期间认识了许多好朋友,包括不少百强的宗师玩家,都乐于和我组队,甚至还和职业代打组队,帮他们打「单子」,做黄金联赛替补,留下了很多很多美好的回忆。

现在的我不再惧怕任何形式的与人竞争,并且喜欢上了竞争,肯定竞争的意义和价值。这些都与《风暴英雄》这个游戏有着密不可分的关系。当我写代码累了,或遇到不顺心的事情,总是喜欢在风暴里来一场紧张刺激的对抗,让肾上腺素的分泌激活我的精神。

敢于竞争是一种勇气,这与自己的水平高低无关。但要乐于竞争,还需要足够支撑竞争的能力和自信。这是另一个我从《风暴英雄》中获得的东西。我的反应速度、英雄操作只是中等偏上,但在《风暴英雄》中,我发现自己的观察力和对全局的分析和把握能力能起到更关键的作用,左右对局的胜负。比如在一个局部战斗结束后,无论击杀或被杀,我都能第一时间分析起接下来一分钟甚至到下个「机制」2开启之前要做的正确的事情。这听起来似乎不难,但对大多数玩家而言,一次战斗过后,几秒甚至几十秒大脑可能都在无意识地发呆,有的人甚至从来没有从宏观的角度思考过赢下一场游戏的策略。但对我来说,这种思考方式在刚开始和人对战后不久就察觉到了,仿佛是一种与生俱来的能力。依仗这种能力,加上经常打完后看录像复盘、经常看职业比赛视频和顶级选手的直播,我才能够单排打到较高的段位,得到最顶尖的一波玩家的肯定。

《风暴英雄》使我发现,在一个游戏中,我所擅长的事情完全可以转化成为我自己的优势,但前提是对规则足够的了解。现实中的竞争也可以看做游戏,同样有其规则和各种信息,因此只要发挥主观能动性,去思考和分析,就一定能把自己独特的优势发挥出来,取得出人意料的战果。这样的心态使我对任何形式的竞争都能怀着自信步入赛场。

还有 3 天《风暴英雄》连同暴雪全家桶的国服就要关闭了,这篇文章算是与风暴的正式告别。再见,《风暴英雄》,还有我们一起度过的 6 年难忘的时光!


  1. 国内玩家喜欢称之为 BP,即 Ban/Pick 的缩写,双方队伍互相禁止对方可以选择的英雄并确定己方英雄的过程。 ↩︎

  2. 全称是「地图机制」,是每个地图特有的中立道具或事件,在一场比赛中可以开启多次,类似 Dota 中的 Roshan 和 LOL 中的 Nashor,玩家通过争夺机制获得攻打对方防御塔的助力。《风暴英雄》正因为这一特性而具有所有 MOBA 端游中最频繁的团战节奏。 ↩︎

The debut of Substance: A HTML-to-Markdown extractor

2023年1月18日 00:00

In the past week, I’ve been creating a tool to extract the main content of the current web page and convert it to Markdown for archiving purposes. Currently, I’ve finished a Web app as a Proof-of-Concept or a preview version before the final release. Its only feature is to extract and download Wikipedia articles to markdown files. So here’s the link:

substance.reorx.com

To put it simply, the goal of the product is to be an alternative to MarkDownload with more extensibility. MarkDownload has been an excellent help for archiving content from the web, but it does not always work well on every website. Every now and then, I found it gives bad results for some websites such as Wikipedia (that’s why I take it as an example to work on at the very beginning).

After releasing this web app, I’ll focus on developing the extension and writing documents for the product. The code is open-sourced here though it has no README by far, but you can give me your feedback on the issues or reply here if you like.

In the next post, I’ll give a detailed introduction about what Substance really is and how it works.

Until next time, don’t forget to subscribe to my fresh-made newsletter to get the latest update.

「荒木型」与「三浦型」创作者

2023年1月13日 15:18

最近看 JoJo 石之海的时候,看到替身的战斗中竟然化用了莫比乌斯环和广义相对论的概念,又一次被荒木老师丰富的知识储备和想象力所震惊。之后在 B 站刷到一则荒木最近接受访谈的视频1,精神和皮肤都非常好,谈起自己的工作的样子充满热情,整个人都仿佛笼罩在一层光芒中。昨天又看到一则新闻,JoJo 第九部的主角设计已经公布,看来再画个二三十年也不成问题。

而我另一位喜欢的漫画家,《剑风传奇》的作者三浦健太郎,前阵子刚刚因病去世。在惋惜的同时,我也一直关注着后续的进展。这两天看到一篇文章2讲到,三浦对画作有着非常严格的要求,因此一直以来都没有培养自己的助手团队,直到 2019 年身体每况愈下才逐渐让助手介入到创作中,但一直到死前,他都负担着 90% 以上的创作工作。想到荒木的采访,他虽然坦言自己不擅长和陌生人交流,但和工作室团队却有着非常好的分工与配合。

荒木和三浦都是最出色、最具艺术追求的漫画家,但他们的性格和处事风格却截然不同。荒木良好的 work-life balance 使他拥有远低于实际年龄的年轻外观,粉丝们常以「荒木老妖」、「Jo 级生物」调侃;三浦却一直身体欠佳,最终因「主动脉夹层」而英年早逝。在和他人共事方面,荒木不仅有着多年培养的工作室团队协助漫画创作,使得 JoJo 系列问世 30 多年依旧保持稳定地产出,还与动画公司(大卫社)在动画创作中保持紧密地联系,如亲自挑选每一个 ED 的歌曲;而三浦习惯一个人战斗,随着年龄的增长更新速度越来越慢,直到去世也没能将《剑风传奇》完结,成为所有读者的遗憾。

我在他们身上看到两种开发者(创作者)的影子。荒木型开发者同时有着对生活的热爱和专业的追求,他们不仅能够开发出色的软件,在兴趣爱好上也有持续的投入和收获。由于思想的开放,他们也具有一定的管理能力和产品能力,这使他们能够在需要的时候组建和领导团队,把自己的作品带向更好的生产循环。

三浦型开发者如同独狼,或许是多年持续维护一个硬核软件的开源工作者,或许是隐匿在地下室的超级黑客,大家对他们的了解只有固定使用的 id。他们有着非常高超的技术,能够在一个事情上做到极致,但在现实中却缺少归属感,导致生活节奏混乱或行为孤僻。

希望我能成为一个荒木型的开发者。

“Moving away from UUIDs”, Really?

2022年11月23日 16:20

Recently I saw an article called Moving away from UUIDs – Neil Madden on Hacker News. The title immediately got my attention since I use UUIDs a lot in various projects, no matter personal or commercial. Whether I’m using it right or wrong is a vital concern from an engineering perspective, so I read it thoroughly and carefully. This article is my summary and thoughts on it.

Overall, the author gives an opinion that using UUIDs for unguessable random string like session tokens or cookies is a bad practice, the reasons are as follows:

  1. UUID is insecure in cryptography. In some situations, an attacker can take only 35 minutes to brute-force guess a valid result.
  2. UUID is inefficient in storing data. Because of its hexadecimal format and the use of extra dashes, a UUID takes 36 characters to represent 16 bytes of data.

As a replacement, the author suggests to use a 20 bytes random string that is URL-safe base64-encoded. Here’s an example comparing with an UUID string:

20 bytes base64 random: Xl3S2itovd5CDS7cKSNvml4_ODA
UUID                  : 5a097fe7-1720-457c-8363-8d660a65bab2

The advantages over UUIDs are:

  1. A 20 bytes random value is almost impossible to guess in a reasonable time.
  2. The length of the string is just 224 characters, resulting in much less storage space than UUIDs.

Generally speaking, I think although the conclusion of not using UUIDs for tokens is correct, the assumption is totally wrong. UUID (Universally unique identifier) as the name says, is an ID which should not be used for cryptographic purposes in the first place. The proper scenario for UUIDs is using it as primary keys in distributed systems, in which it prevents collisions without relying on a centralized identity generator. In contrast, random string has no way to achieve that.

I did learn something new from this article, but it failed to give me anything useful upon my understanding of how UUIDs should be used.

Do not write clickbait posts, as being neutral and accurate is a virtue for engineers.

离开国产 SaaS

2022年10月29日 17:31

昨天看到 @dingyi 的推文说:

国产软件真的没必要轻易尝试了……

原因是蜘蛛笔记发布了一个公告1,这款诞生不到半年的国产笔记服务因「战略调整」即将停止运营,甚至没有走出测试阶段,非常令人唏嘘,推友对此的猜测大都集中在行业萧条和内容审查上。

虽然不再尝试国产软件说得有些重,但我非常理解这种心情,因为我也认为,现在国内已经不再适合小公司的 SaaS 生存了。 在国内内容审查持续变严的大背景下,个人和小团体开发者将日渐减少,因为它们根本无法负担起内容审查的时间和金钱成本。 不久之前内容销售平台面包多暂时关停,随后创始人 DK 发布了公告2,表示在恢复后平台的敏感度和审查度都会大幅提高,以确保面包多可以在日益严苛的监管条件下存活下去。

站在行业生态的角度,我对国产应用总是抱有着鼓励和期盼,但站在个人用户的立场,我选择从现在开始尽可能不把重要数据放在国内小公司开发的线上服务上。毕竟数据的重要性高于一切。在服务随时可能关停、数据随时可能因审查而消失的情况下,真的没办法安心使用下去。

下面盘点一些我正在使用的包含重要数据的工具服务,并给出关于迁移方式和替代产品的建议:

Category Service Migration Alternatives
网页保存 Cubox Cubox 国际版 Pinboard Upgrade
Mem.ai
Raindrop.io
wallabag
HamsterBase
待办清单 滴答清单 国际版: TickTick Todoist
书影音记录 Douban 豆伴:豆瓣账号备份工具 NeoDB
生词本 Eudic 生词本同时存在于本地和云端,
导出也非常方便3,因此无需担心
目前还没见过其他
有云同步功能的字典软件
笔记 石墨/语雀/wolai等 我在这些服务上的文档很少,手动下载即可 Notion, Obsidian

Defeat VSCode Tab Bar

2022年10月18日 21:39

A while ago, I found my VSCode tab bar (or tool bar) UI was quite unstable. The reason was that extensions may add icons called “action button” on the right side of the tab bar according to the type of the file, so when switching back and forth between different kinds of files, these buttons will either show or hide, making the available spaces for tabs changing constantly. This may not be noticable when you just open a few tabs, but if you have tabs that are enough to take up the spaces of the tab bar, the whole tab bar will be a clown fiesta with the appearace and disappearance of the action buttons. Tabs are flickering due to their width change, the last one could even be pushed to the next line. It’s just so eyes-hurting and annoying.

After realizing who was the culprit, I immediately started to search for a solution. Sadly, VSCode itself did not provide a way to customize the side bar, but I was luck to find an extension called Customize UI, which allows me to inject css into the application, so that hack the UI whatever I want. I installed the two extensions and add the following lines of code in settings.json and boom, the world was quiet again.

{
    "customizeUI.stylesheet": {
        ".editor-actions": "display: none !important;",
    }
}

But this solution is not perfect, because Customize UI relies on the Monkey Patch Extension to achieve UI hacking, which always requires me to restart the VSCode immediately after I open it. That’s fine, I told myself, as long as I can get ride of those tab bar icons.

Today, I updated VSCode and was surprised to find that VSCode finally implemented a feature called Hide actions from tool bars, it says:

You can now hide actions from tool bars. Right-click on any action in a tool bar and select its hide command or any of the toggle commands. Hidden actions are moved to the … More Actions menu and can be invoked from there.

This means I can hide those buttons without th need of using the clumsy extensions. I tried to right click on an action button and select “Hide …”, it disappeared as expected. Then I uninstalled Customize UI and hide all the action buttons one by one. So yeah, this is the story of how I defeat the VSCode tab bar.

真正的好作品只能靠自己去发现

2022年10月10日 23:06

最近闲暇时间在看一本网络小说,名叫《异仙列传》,作者是流浪的蛤蟆。

蛤蟆是一位非常老资历的网文作者,我从 2008 年接触他的小说《蜀山》开始,不定期关注他的更新。蛤蟆的小说总是给人天马行空的感觉,想象力非常独特,情节自然流畅,主角大都洒脱不羁,仙气和痞气并存,有种与众不同的魅力。虽然许多书都没有完本(俗称「太监」),但读起来总是手不释卷,活泼泼让人快乐起来。

但这样一位有才华的作者,却一直没有多少读者认识他,为什么呢?

在网络小说发端的蒙昧时期,创作难,受众小,许多作者虽然创造出了很好的作品,但受限于市场,他们得到关注和回报都非常有限。后来,以唐家三少、我吃西红柿、天蚕土豆、梦入神机为代表的一批年轻作者,依靠简单的文笔、俗套但有爽点的剧情、极高的更新速率,让网络小说真正走进了普罗大众的生活中。网络小说的创作和阅读门槛被大大降低,与通俗文学渐行渐远,逐渐形成了现在无脑、爽文的刻板印象。

网络小说的发展史,就是一个劣币驱逐良币的过程。好的作品不会过于套路化,因为有追求的创作者总是在尝试突破和创新,因此不仅生产效率不会太高,还需要读者有一定的耐心和智力才能渐入佳境。但大众读者所需要的,其实不是文学意义上的好作品,而是一种工业品,即通俗易懂、能在尽可能短的时间里带来刺激和反馈的大量文字,并能持续稳定的获得。这些需求与好作品的创作条件背道而驰,不愿改变的作者自然难以成为主流。

这让我再次感受到,大众认为好的不一定对自己而言是好的。被大众认可,意味着要满足普适性的需求。要通俗易懂——简单无脑;要政治正确——阉割思想;要适合传播——利用人性的弱点,消费用户而不是被用户消费。在文学、影视、音乐、游戏这些由个人品味和主观感受来判断好坏的领域,普适很可能是对自己而言的不适。

所以,不要去看什么豆瓣电影 Top 250、网易云音乐热门推荐、Steam 畅销周榜了, 真正的好作品只能靠自己去发现。保持好奇心和对美的追求,它们会带你走向精神世界的探索之路。

P.S. 我关注的老资历而鲜有人知的网文作者,还有兰帝魅晨。另外,读者如果想了解早期优秀的网络小说,可以去搜索「网络文学十年盘点」。

我用过的位置追踪应用

2022年10月7日 01:18

国庆接连几天都在外面游玩,拍了许多照片,于是产生了一个想法:

有没有哪个 app 可以导入一堆照片然后把其中的地理位置信息连成一个轨迹?

在 Twitter 和 Telegram 上都被推荐了「一生足迹」这个 app,让我回忆起了之前用过的几款类似的应用,于是把它们一起记录如下。

  • Moves

    接触的第一款位置追踪应用,也是最喜欢的一个。除了有些耗电,各方面都几乎完美。我从 2014 年开始重度使用了一两年的时间,最后因为厌倦了手机发热而放弃。

    2018 年 Moves 被 Facebook 关闭,成为 FAANG 资本化的又一个牺牲品 https://about.fb.com/news/2018/07/hello-tbh-moving-on/

    In 2014, we bought the fitness app Moves. It records your daily activity — including walking, cycling and running. We’re deprecating the Moves app and Moves API on July 31.

  • Gyroscope

    2016 知道这款应用,一开始觉得很漂亮,但很快就审美疲劳了。Gyroscope 并非纯粹的位置记录工具,而是包含了各种 quantified-self 数据,却又做的非常社交化。我对分享自己的隐私数据没有什么兴趣,一年的会员结束后就卸载了。

  • Arc

    Moves 关闭没多久,出于对它的怀念,我找到了 Arc。客观的讲,Arc 也是一个足够优秀的应用,功能简单直接,没有什么多余的东西,但设计上平平无奇,没有了最初 Moves 给人的惊艳感受。

  • 一生足迹

    出自国内开发者之手,最大的特点是把省电这件事研究到了极致,果然优秀的创作者自己才是真正的用户。经过简短的试用,我确定这是目前最好的同类产品。

    不过一生足迹虽然可以导入照片中的位置信息,却没法将其连成轨迹,我只能继续寻觅其他 app,或者想办法自己实现了。

  • Rond

    2023-06-08 发现,官网 rond.azurewebsites.net

    Location-based life records, 不仅记录轨迹,还有丰富的统计功能,可视化查看时间花费的位置分布

浅谈 Chrome Manifest V3 的优缺点

2022年9月29日 23:39

自从 2018 年 Google 发布 Chrome 新的 Extension API Manifest V3 以来,不时会看到反对的声音,认为它限制了扩展的能力,降低了对用户隐私的保护云云。我自己虽然是一个 Chrome 插件开发者,也第一时间使用了 Manifest V3,却没有感受到具体的变化,因此一直对这些观点所描述的问题缺乏实感。

最近阅读了一篇文章,是 AdGuard 开发基于 Manifest V3 新插件的技术性回顾:
AdGuard publishes the world’s first ad blocker built on Manifest V3

这篇文章以 AdGuard 自身作为范例,对比 Manifest V2/V3 的差异,让我终于理解了 V3 API 变化的影响之所在。

简而言之,Manifest V3 对 AdGuard 造成了如下影响:

  • 规则数量限制

    • 每个扩展的静态规则数量被限制在了 30000 个以下,所有扩展的静态规则之和不能超过 330000 个。

      For static rules, Chrome set a minimum guaranteed limit of 30,000 rules per extension and a total limit of 330,000 rules for all extensions installed by a single user (this also takes into account the limit of 1,000 regexp rules per extension).

    • 预设的过滤器最多 50 个,用户自定义过滤器和规则之和不得超过 5000 个。

      blockers must use pre-set filters (no more than 50), and we have to be very selective about which filters will be available to users. Of course, you can still set your own filter manually. But don’t forget the 5000 rule limit on all custom filters and user rules.

  • API 变更: onBeforeRequestdeclarativeNetRequest

    • onBeforeRequest 是 Chrome Extension 中的事件回调函数,广告屏蔽插件的工作原理就是在这个函数中使用自己的一套逻辑来判断是否要中断当前的连接。从 Manifest V3 开始,这个函数就被废除了,取而代之的是 declarativeNetRequest,因此 AdGuard 必须将原来的私有动态规则转换成使用 declarative rules 所描述的新规则。

    • 而受 declarative rules 的语法所限,一些 AdGuard 原有动态规则的功能将无法被实现

    • rulesets(一组 declarative rules)只能在扩展安装和更新时加载,导致 AdGuard 失去了动态更新规则的能力

      declarative rules must be combined into rulesets

      Rulesets are specified in the manifest.json file and are loaded only when the extension is installed or updated.

  • 无法获得统计信息

    • 由于 Chrome 自己实现了一套拦截机制,Manifest V3 不会将请求的统计信息开放给生产环境下的扩展,仅能在开发模式下使用。这意味着 AdGuard 将无法真正确认到动态规则的执行情况,只能通过模拟运行来猜测规则是否可能被应用了,对于规则的维护者来说是灾难性的打击。

      Chrome itself now blocks requests and shares statistics only with extensions unzipped and installed in Developer Mode

      only show which rules may have been applied.

  • Service Workers 不能保证常驻,需要主动唤醒

    • AdGuard 的 cosmetic rules(在网页上屏蔽元素的规则)原本运行在 background.js 中,这个机制在 Manifest V3 中被 Service Worker 取代,由于不能常驻后台,cosmetic rules 有一定无法生效。

      When the browser stops the service worker, the extension goes into a kind of sleep mode: the declarative rules work, but the cosmetic rules that are loaded dynamically do not.

可以看出,AdGuard 这款新的 Manifest V3 插件在各方面都受到了「阉割」,无怪乎文章传达出了一种艰难求存、呼吁支持的态度。

在我看来,其实 Manifest V3 的核心非常明确,就是限制扩展对系统资源的使用。一直以来高资源占用都是 Chrome 为人诟病的痛点。相信每个用户都遇到过 CPU 或内存飙升的情景,许多人甚至说 Mac 没有 16G 以上的内存不要用 Chrome。而扩展由于在后台运行,如果出现问题,更是难以定位和管理,我想这就是 Google 的工程师想要做出改变的原因。

虽然增加了诸多限制,但我确实感受到 Manifest V3 的一些有益之处:

  • Service Worker 使得扩展不再能常驻后台,让扩展所占用的资源可以被回收,降低了浏览器整体的开销
  • 限制规则的数量,相当于控制了单一扩展在规则计算方面的资源使用上限
  • 以无法在运行时更新为代价,declarative rules 实现了更高效的动态规则
  • 不再提供请求底层的统计信息,缩短了请求生命周期中的调用链,提升了处理效率

这些变化可以让 Chrome 变得更加流畅,对于 99% 的用户来说都是好事。

未来的方向,也许 Chrome 可以提供一个 IPC 或者其他更有效率的通信方式,让另一个进程参与到请求的处理流程中,这样 ad blocker 插件的最大开销——规则匹配——就不再是效率和资源的瓶颈了。就像 LSP 的工作模式,把复杂的事情分离出来让独立的组件来做,说不定会诞生一个用 Rust 开发的新一代 rule filter engine 呢。

希望这篇文章能够帮助你理解 Chrome Manifest V3 的变化,辩证地看待它。

后记

补充一些本文完成后,与 Manifest V3 有关的趣闻

2022-09-29

昨天 Chrome 官方博客发布了一个声明 (More details on the transition to Manifest V3),将 Manifest V2 的废除时间从 2023 年 1 月向后推迟了一年:

Starting in June in Chrome 115, Chrome may run experiments to turn off support for Manifest V2 extensions in all channels, including stable channel.

In January 2024, following the expiration of the Manifest V2 enterprise policy, the Chrome Web Store will remove all remaining Manifest V2 items from the store.

再来看看两年前对废除 Manifest V2 的声明:

January 2023: The Chrome browser will no longer run Manifest V2 extensions. Developers may no longer push updates to existing Manifest V2 extensions.

从原本的斩钉截铁,变成现在的含糊和留有余地,看来强如 Google,想要执行一个影响全世界 65% 1 互联网用户的 breaking change,也不是那么容易呀。

2022-10-03

著名单文件网页存档插件 SingleFile 作者依照 Manifest V3 标准重构了原版插件,命名为 SingleFile Lite,并用宣传产品升级的语气描述了新插件的 “features” 和 “benefits”,非常讽刺,可以说是一场代码行为艺术了。

Feel the power of the Manifest V3. The future, right now!

Notable features of SingleFile Lite

  • unreliable auto-updates
  • no auto-save
  • save time limited to 5 minutes max.
  • no “Referrer” header injection
  • limited support for fonts dynamically loaded with the FontFace API
  • and more to come!

Benefits of the Manifest V3

  • none

Hacker news: Feel the power of the Manifest v3 | Hacker News

为什么人们在黄图群喜欢聊哲学

2022年9月29日 01:12

偶然看到一张网图,内容如下:

人类真矛盾,不管进啥群,最后都是搞黄色。反而进了黄色群,最后聊三观、聊实事。

以前也看过类似的说法,都一笑置之了,这次忽然觉得背后可能有着一定的心理学逻辑。

让我们来看看马斯洛需求层次 (Maslow’s hierarchy of needs) 这个广为人知的心理学理论。(中文世界里一般是五种,英文维基百科里有八种,这种差别让我忍不住想起 OSI 模型的四层与七层)

需求 Need 说明
生理 Physiological 级别最低、最急迫的需求,如:食物、水、空气、睡眠、
安全 Safety 对人身安全、生活稳定以及免遭痛苦、威胁或疾病、身体健康以及有自己的财产等与自身安全感有关的事情。
爱和归属 Love and social belonging 较高层的需求,常称为“社交需求”,如对友谊、爱情以及隶属关系的需求
尊重 Esteem 成就、名声、地位和晋升机会等。尊严需求既包括对成就或自我价值的个人感觉,也包括他人对自己的认可与尊重。
自我实现 Self-actualization 最高层的需求,包括针对于真善美至高人生境界获得的需求

以马斯洛需求层次来分析,许多人长期生活在性的需求得不到满足的状态下,「生理」这项需求占据了思想的主导,导致无心探讨群聊原本的话题,最终有意无意地开始性有关的交流。这就是为什么会产生「一切群最终都会变成黄图群」的情况。当然,这只是一种玩笑和夸张的说法,不能代表全部。

而黄图群则不同,由于摆明车马就是来搞黄色的,成员中一定有精通此道的行家,他们在性这一方面有能力得到满足,需求层次就会上升到社交和认知相关的区域,在群里聊起哲学话题就不让人意外了。

说到这里,我忽然想起朋友对技术群的吐槽。有些技术群虽然不搞黄色,却会有很多争论——哪个语言更好,哪个框架不好,为什么用A而不是用B——并且总是会上升到划分阵营、形成鄙视链的程度。用马斯洛的理论来看,或许是为了「尊重」这一需求吧:站定一个立场,维护自身的正确性,来获得他人的认可。不过我觉得要警惕这一行为,因为通过「做」而不是「说」来获得尊重和认同更有价值一些不是吗?

并不乐观的全球化

2022年9月27日 13:44

看到了 @waylybaye 的推文:

我一个人,一台电脑,一个银行账户,不需要任何资质证明,不需要任何政府部门审批,不需要和市场所在地的政府打交道。就可以把作品卖到全世界,收全世界的货币,在全世界交税。全球化真的是一个奇迹。

总觉得哪里怪怪的。它的逻辑是:因为我仅使用任何人可以拥有的设备和条件,就能够将作品卖到全世界,所以我赞美全球化。可事实真的是这样吗?

首先,把产品卖到全世界的是 Apple Inc. 而不是开发者个人。开发者付出 15% 的收入分成为代价,使用苹果所建立的全球化合规和销售渠道。开发者不是自由的,甚至无法以苹果以外的方式为收款渠道。从生意的角度讲,这并不是什么坏事,各取所需互惠互利罢了。但是,一个人把 App 放在 Apple 商店向全世界销售这件事,不应该用于评价「全球化」是如何的。

所以这是全球化的奇迹吗?这只是苹果强大的市场能力的体现。实现全球化的是 Apple 而不是我们个人,Apple 与开发者之间只是一种商业关系。如果有一天,Apple 对来自中国的开发者采用更严格的限制政策,一切都可能成空。

真正的全球化,是个人触及世界各地的渠道和能力。在 Apple 这个渠道之外,个人开发者想要将自己的产品卖向全世界,仍是步履维艰。

实际上全球化正在变得越来越差,我自己在和美国的商业、移民政策打交道时深有感触。即便没有这些经历,国与国之间变得越来越封闭也是任何有基本常识的人,所能感受到的显而易见的事实。


如今社交媒体上充斥着为个人利益而扭曲思想的言论。只说了「不需要什么」,却没说「需要什么」;只展示「就可以」,却忽略「不可以」。「假话全不说,真话不全说」,这样的表达方式是对信息的误导,但有利于传播。因为公众喜欢好听的言论,现在动荡的环境下,赞美全球化能给人带来「世界仍然是美好」的慰藉。

可这正是我所厌恶的,宁愿了解真相而痛苦,不愿欺骗自己而宽心。

童年的 Disco

2022年9月26日 15:40

朋友给我发了一个 B 站视频 别问我什么是迪斯科,是音乐家张蔷 2013 年的歌曲,却有着浓浓的怀旧风情,唤起了五光十色的童年回忆。

于是我翻了翻自己的收藏,给他回发了另一个视频: 当年传唱度过亿的“Dj大舞曲”,DNA储存卡真的被读取了!

溜冰场、点播台、路边特价小卖场,都是这些 Disco 音乐在童年出没的地点。记得上小学时,同学们给我开生日会,女生们还排练了一个舞蹈,曲子是 Smile - Butterfly

这是音乐的一种魅力,与音乐的类型无关,不论是古典的还是现代的、粗糙的或精致的、柔和的或激烈的,只要它触动了你,此刻的感受就会被保存在这段旋律中,许多年后也不会改变。

在都市的夜晚,如果一人独处,不妨逛逛 水晶DJ网,或许会找到一首让你忘却烦恼的 《凤舞九天》

Kevin Kelly 对创作者的指导

2022年9月26日 12:59

最近读到 Kevin Kelly (KK) 的一段话,深有感触:

Separate the processes of creation from improving. You can’t write and edit, or sculpt and polish, or make and analyze at the same time. If you do, the editor stops the creator. While you invent, don’t select. While you sketch, don’t inspect. While you write the first draft, don’t reflect. At the start, the creator mind must be unleashed from judgement.

68 Bits of Unsolicited Advice

放在作为软件工程师的自己身上,creation 就是 building applications,improving 就是 refactoring and algorithms。我们常常讲不要过度优化,那是在宏观的设计层面。而在具体的实现过程中,也不该太在意代码的优化,否则会妨碍自己的创作者思维,拖累从 0 到 1 这个最重要的里程碑的进度。

这并不是说要刻意写出丑陋和低效的代码,而是顺应自身当前的代码素养,不额外付出抛光打磨的精力。维持创造者思维的惯性,专注在将脑中的想法转变为可工作的最小实现的过程中。完成实现后,我们再切换到评审者思维,去重构和优化代码,提升工程能力。这样两件事情都可以做得足够专注、高效。


KK 的个人网站有个名为 LIFESTREAM 的页面,上面是他日常想法的记录和分享。发现这个页面后,我很受震撼。在我的刻板印象中,像 KK 这样的当代著名作家,书籍是一般人了解他思想的唯一途径,他应该生活在自己封闭的圈子里,所见所闻都与我们有着巨大的差异。

如今我发现,他也有自己的互联栖息地,并且持续地保持着与开放世界的信息交换。LIFESTREAM 有时一天两更,有时三四天一更,这位 70 岁的老人有着超过年轻人的旺盛好奇心和创造力。

我非常钦佩 KK,不仅是因为他的思想,更因为他在身体力行的告诉我,什么是一个纯粹的创作者,和一个取悦自己的灵魂。

不换房了,继续向前

2022年9月24日 01:03

前阵子在 如何寻找一个理想的租房 中,讲了最近寻找租房的经历。这篇文章来同步一下后续的进展:我们选择了保持现状,继续居住现在的房子。

为什么呢?我们的目标是降本增效——降低在住房上的开支,提高生活质量和工作效率。我意识到在这两点之间,增效不能为降本妥协。也就是说,如果一个地方租金便宜,但会影响我们的工作效率,是绝对不能接受的。我们在可选的房源中,没有找到符合这一要求的房子,于是便有了这个决定。

事情结束后,我们各自都有一些收获,下面是女友的笔记摘录。

人都是从「他知推动自行」一步步到「自行自知」再到「自知自行」,多次螺旋循环后最终实现「知行合一」。如果前一个过程没有成功完结,就很难推动下一个过程开始。

当你思想中存在诸多矛盾和纠结,不如尝试去行动,让行动检验思想认知。

这里说的是她一开始有了换房的想法,但并不确定,及时行动起来之后,得到新的信息,随后调整目标,知和行得以相互验证和上升。

考察调研的「行」:因为不想为交通便利买单,削减总租房成本,在贝壳上筛选和小红书上做功课后联系中介,花了两天时间考察目标价位的房源。

调研后得出的「知」:杭城的房产市场化程度高。交通、小区环境、周边配套生活设施、房型、室内观景与采光通透度、室内精装修程度这 6 项要素共同决定租房市场的定价。尽管可以在交通方面做一下妥协,但对其他 5 项要素要求高的话也很难找到一套合适的小房子。满足 4 项要素价格相对低的房源在市场上就很抢手,一房难求。目前的房东并不是漫天要价,在 6 项要素都具备的前提下价格高是理所当然的。

认知升级:在无法以相对较低预算交易一套性价比高的房子的情况下,搬家本身消耗精力且需要较长适应期容易打乱目前生活节奏的不可估成本。采取另一种思路——在不削减租房成本的前提下,努力提高单日工作学习效率,增加未来的收益来以降低租房成本的影响。

在几天的「行」之下,最终决定维持现状没有搬家,看似并没有改变,但实际取得建设性成果:对整个租房市场有了进一步的认识,对当下居住环境的重新审视和珍惜,提高租房信息获取能力,认识值得合作的中介朋友,获取一手房源流动性信息和对未来租房对象进行及时调整的能力。

其实生活本身就是我们所要追求的一切,而金钱是生活的燃料,带着喜悦的心情把它投入到这隆隆运转的引擎之中吧。

State of Play September 2022

2022年9月17日 15:08

看完 State of Play September 2022,种草了几个新游戏:

  • God of War Ragnarok

    之前 Trailer 的时候一度以为这是个 DLC,这次终于展示了一些重磅画面,如 Tyr 和 Kratos 双手交握、合作前行;Atreus 对着月蚀射箭,划破天幕,巨狼芬里尔仰天长啸;Kratos 与 Thor 的对决,利维坦之斧与雷神之锤相撞。一切似乎都在向着神话中宿命的方向发展,却在 Kratos 一句 “We will make our own destiny” 中结束。这将是我 2023 年最期待的游戏。

  • Tekken 8

    如果能做到宣传画面的一半我就满足了,打击感和真实感会有巨大提升。作为老格斗游戏玩家,对街霸6过于明亮艳丽的风格不是很满意,很期待铁拳带来不一样的感觉。

  • Hogwarts Legacy

    之前放出的 Trailer 已经足够让我喜欢上这款游戏,这次反而没什么有意思的更新。基于魔杖的战斗系统是非常大的亮点,兼有魔幻感、流畅的动作和优雅的姿态,是游戏史上全新的创造。

  • Synduality

    二次元味道的 No Man’s Sky,冲淡了宇宙探索的孤独感,动作和道具设计看上去也更有趣,爱了。

  • Stellar Blade

    韩国版 Nier Automata,无论是漂亮的人物模型还是独特的韩语配音都让人耳目一新。

  • Yakuza: Ishin

    没玩过如龙,但对坂本龙马是有印象的,和土方岁三一个时期的传奇武士。打斗看起来一般,当做一个江户时代的 GTA 玩应该还是很有趣的。

  • Rise of the Ronin

    和 Yakuza: Ishin 类似,但有着更加开放的世界观,以及更好的动作设计。相对而言我更愿意玩 Rise of the Ronin 一些,感觉两个游戏有点冲突了。

不得不说 3A 画面还是得看 Sony,今年 Nintendo 也不知道犯了什么魔怔,10 个游戏里 5 个在种田:

Japan must be having a serious issue with their farming economy, so Nintendo is taking it upon themselves to push young people into that career path.

—a comment from Nintendo Direct vs Sony Direct


补充下 State of Play June 2022,最期待的当属 Final Fantasy XVI

如何寻找一个理想的租房

2022年9月17日 01:06

正在租的房子临近到期,这两天在四处奔波寻找一个新的居所。

我把整个过程大体分为三个阶段:理清需求扫荡区域对比筛选

理清自己的住房需求是第一位的,否则后面的一切决策都会因没有清晰的标准而难以评判。预算、交通、居住环境、基础设施、商业环境,我会在这几个因素间进行考量,排列出优先级的高低顺序。

我的分析过程是这样的:我平时主要在家工作,因此不需要离地铁很近,但需要周围的环境安静舒适;自己做饭居多,因此需要有购买生鲜的条件,比如盒马、山姆能够送达,小区有配套超市是最好的;周围不需要有很多饭店,但至少要有可以送到家的麦当劳,以备不时之需。

最终优先级为:居住环境 > 基础设施 > 预算 > 商业环境 > 交通

基于这样的需求,就可以很快圈定出合适的区域并扫荡房源了。我所选的几个区域都是远离 CBD、写字楼、商贸,主打环境舒适度的楼盘,但也能通过地铁方便地通勤到市中心。

先用贝壳进行初步扫荡,VR 看房只能代表 60% 的真实情况,因此要降低预期,多收藏一些。在对每个区域的小区和房型有了大致的了解后,用小红书搜索,看有没有转租的信息,转租没有中介费,是最经济的成交方式。也有一些个体中介,但相对没有那么稳定。下一步则是实地看房,先让贝壳的中介带路看一波,再找小区物业问是否有出租的房源。也可以看看公司经营的公寓,总之要尽量扩大房源的渠道,毕竟贝壳的中介费是最高的。总的来说,房源的优先级为:转租 > 个体中介 > 物业 > 公寓 > 贝壳。

最后就是对比筛选了,我认为最好的方法是将每个备选的房子都录入一个表格中,按照自己关心的方面为其打分,然后与伴侣坐下来仔细商量。最优解一定是通过理性的分析得出,一时冲动可能会带来一整年的后悔。

先写到这里,明天还要继续扫荡。希望每个人都能找到自己理想的租房,晚安。

停不下来的创业者——得知 Figma 被 Adobe 收购有感

2022年9月16日 00:33

晚上回家时看到了 Figma 被 Adobe 以 $20 billion 收购的消息。

这令我非常忧心,我已经将 Figma 当成必不可少的创作工具,甚至打算写一篇博客介绍作为非设计师的我如何使用它。但 Adobe 的收购,意味着 Figma 可能不再允许免费使用,更糟糕的结果是成为臃肿而缓慢的 Adobe CC 全家桶的一部分。

一位朋友抱怨,Figma 做产品做烦了是吗,非得把自己卖出去。

我感觉,还真是这样。

诚然,$20 billion 在任何人面前都是难以拒绝的价位,但我认为这不是 Figma founders 决定接受这次收购的唯一原因。

创业者其实不喜欢长期维护产品,他们更享受解决一个前所未有的问题的过程。当产品逐渐稳定,他们内心会躁动起来,不安分地追寻下一个创造的挑战。而且越是被冠以「天才」之名的创业者越是如此。

让他们勉为其难地变成经营者也不好,自古就有「创业守成孰难」的辩题,历史告诉我们,创业者多数都不会在守成上有很高的建树。

另一个例子是 Super 创始人 @traf,下面是他的离职声明的摘录,可以从中窥见创业者内心世界的一角:

Super was the first idea of many, and since it started working right away, it demanded all my attention (which I happily gave it).

Because of that, none of my other ideas saw the light of day, and over time, the itch to keep building things kept growing, until the urge got big enough for me to do something about it.

The success from Super and other products gave me the privilege to step back, reset back to zero, and think through what I want to build next.

大公司为何不愿意做好用的产品?

2022年9月16日 00:07

Twitter 上刷到 酱紫表 对百度云的咆哮:

为什么像百度这么大的公司,百度云这么多用户的产品,高分屏的适配完全没做?完全在给用户喂💩,每次打开都恶心。

因为国内大公司并不需要在意用户体验,他们的核心竞争力都是通过某种方式让你不得不用罢了。

从投入产出比的角度来解释,多投入几个程序员/设计师优化界面几个月,可能还不如运营发布一个活动几天增长的营收多,甚至会倒贴人力成本。但这反映的也是市场的问题,国内用户对产品体验的在意程度还远远不够。

说到这儿,想起之前在 Dji Pocket 论坛看到的一个帖子,是关于 Pocket 在文件传输的便利性上的问题,我自己也有深刻的感受,确实很不好用。这个帖子有很高的热度,回复的用户也多是非常硬核的 Vlog 和 NAS 玩家,言辞恳请地提出了很多合理的优化建议。

但这个有着两年多历史的帖子并没有任何 Dji 员工回应。我想这是因为 Dji 知道,投入人力优化这种非核心功能,帮助很小一撮用户提升生产力,几乎不会对销量产生任何影响。Pocket 真正的核心竞争力,真正的主流用户,不在这里。

「这么大公司,这么多用户」也好,功能有缺陷也好,这些都不足以构成做好用的产品的必要条件,that’s not how capital markets work.

我的 10 月新番表

2022年9月14日 22:00

2022 年 10 月将近,新番列表已经公布,一些如 Netflix 出品的甚至已经可以观看。正好昨天我关注的博主 YuC 更新了 2022年10月新番表,于是我也整理了一份自己的待看列表,按 S, A, B 三个级别表示关注程度。

Rank S

  • 灵能百分百 3

    Bones 近几年最好的动画,前两季我都有追。真的很喜欢它的人物刻画,虽然都是 One 老师的作品,但对我来远比一拳超人要好看。

    其实在我心中,这部番就是一位有着最好三观的小男孩,在一位有着最好三观的社畜老师的带领下,学会如何接纳自己的特质,走出成长中的迷惘的故事。

    虽然超能力满天飞,但我最喜欢看的一直都是师傅灵幻如何用自己的社畜必杀技服务客户的桥段。

  • JOJO 石之海 Part 2

    我发觉 Netflix 这种一次性放出所有剧集的播法不大适合长篇动画。年初看 Part 1 时,半天刷完 12 集真的很爽,可是爽过头了,等待的过程犹如戒断反应般难受。看到 Part 2 放出的时候,理性告诉我这是值得高兴的消息,感性却不为所动——已经忘了前面在讲什么。

  • pop子与pipi美的日常 2

    被誉为「人类神经」的 neta 之王,只要出了就没有不看的道理。由于中毒太深,这两天我随时都会突然对女友进行「怒った」攻击。

  • 新 福星小子

    童年遗憾,小时候看过一点,长大了想补番却一直没记得,这次出重置版简直是最佳的入坑时机。

  • 电锯人

    虽然没看过漫画,但以我的嗅觉,能预感到这是一部不逊于《东京喰种》的现象级霸权番。

Rank A

  • 黄金神威 4

    7月刷完了 2、3 两季,正好无缝衔接,从剧情发展来看,这一部应该会结尾。

  • 赛博朋克 边缘行者

    今石洋之导演的作品对我来说是必看的,但赛博朋克的主题和基于游戏的世界观,这些容易受外部干扰的因素让我降低了预期。

  • 剑风传奇 黄金时代篇

    黄金时代篇的剧场版 10 年前上大学时就看过了,这部番是剧场版重编而成,追加了新曲和新镜头。剑风是我最喜欢的漫画,我的 Twitter banner 。

Rank B

  • 名侦探柯南 犯人犯泽先生

    很有趣的设定,从反派「小黑」的视角看柯南周围发生的事情。这个题材让我想起《赌博默示录》的利根川和班长篇,甚至比正传还要好看。但我柯南看的不多,因此这部番的优先级不高。

  • 高达 水星的魔女

    似乎是第一部以女性为主角的高达作品,PV 里女主站在机甲前的画面让我这个高达路人粉也有了追番的冲动。

  • 间谍过家家 2

    上一季中规中矩,有好玩的梗也有烂制作的剧集,本季也还有耐心继续看,用来下饭应该还是够的。

使用 Railway 和 Supabase 零成本搭建 n8n 自动化平台

2022年9月14日 00:00

在前文 使用自动化工作流聚合信息摄入和输出 中,我介绍了如何在 NAS 提供的 Docker 环境安装 n8n,以及 n8n workflow 的使用方式。经过 3 个月的使用,我有了一些新的体会和尝试,重新设计了 n8n 的部署方案。本文将对这套新的方案进行说明,并分享数据迁移和第三方服务接入的实践。

系统架构

系统架构图

我们所要搭建的这套服务有着如图所示的系统关系。

  • Cloudflare: CDN 和 Proxy,用于加速网站访问,可选组件
  • Railway: PaaS 平台,提供 Docker 环境运行 n8n 的后端进程
  • Supabase: 作为数据库供 n8n 使用

完整部署流程

Supabase: 创建数据库

首先我们要在 Supabase 上注册一个账号,登入之后,点击 New project 创建新的数据库,命名为 n8n,注意要记住创建流程中输入的密码。

定位到 Project settings / Database / Connection info,将图中的信息记录下来即可进入下一步。

Supabase Connection info

GitHub: 创建 Repo

在 Railway 上部署服务最便捷的方式就是关联一个 GitHub repo,你可以 fork 我准备好的: reorx/n8n-on-railway

这个 repo 的内容非常简单,其中最核心的就是 Dockerfile,只有一行代码:

FROM reorx/n8n-custom

它的作用是基于 reorx/n8n-custom 定义新的镜像。Railway 会使用这个 Dockerfile 来自动构建和部署服务。

reorx/n8n-custom 是我个人维护的镜像,基于版本 0.193.5 构建 1,目的是提前使用 PR 的代码,以解决无法接入 Twitter 的问题。如果你对安全性有所担忧,也可以使用官方镜像 n8nio/n8n

Repo 中还有 example.envexport_workflow.sh 两个文件,稍后我们会用到。

Railway: 创建 Project

注册或登入 Railway,点击 New Project,选择 Deploy from GitHub repo,找到上一步创建的 repo 名字(n8n-on-railway)并选择,在下一个界面点击 Deploy now 开始部署。

修改域名

不需要等待部署完成,我们直接进入 Project,点击代表 service 的卡片,打开 Settings。

Railway Settings Domains

可以看到 Railway 已经为我们的服务分配了一个 .up.railway.app 的四级域名,这将是你访问 n8n Web UI 的入口。如果你对域名没有要求,可以保持不变,将这个域名记录下来;如果你希望使用自己的域名,请参考 Exposing Your App 文档中的 Custom Domains 章节进行设置。

下图是我的域名 n8n.reorx.com 在 Cloudflare 的配置信息。由于 n8n 是一个重前端交互的网站,需要加载很多庞大的 JavaScript 文件,直接通过 Railway 访问效率很低。使用 Cloudflare 的 Proxy 功能,可以让这些静态资源走 Cloudflare CDN,加速网站访问。因此我非常推荐用 Cloudflare 来托管 n8n 自定义域名。

Cloudflare DNS config for n8n.reorx.com

设置环境变量

确认域名后,我们要切换到 Variables 页面对环境变量进行设置。

Railway Variables Raw Editor

点击 Raw Editor 按钮,在弹出的输入框中粘贴 GitHub repo 中的 example.env 文件的内容,并调整各变量的值。其中:

  • DB_ 开头的部分要根据 Supabase 的 Connection info 对号入座
  • VUE_APP_URL_BASE_APIWEBHOOK_URL 填写上一步中所使用的域名。
  • N8N_ENCRYPTION_KEY 是 n8n 加密 credentials 所使用的 key,请务必使用自己生成的随机字符串。如果没有这个变量,n8n 会随机生成一个并保存到文件系统中,在 Railway 的运行环境下重启后就会丢失,导致已保存的 credentials 无法解密 2
  • EXECUTIONS_DATA_PRUNE: 打开 n8n 的自动化数据清理功能,使 n8n 定期清理在 EXECUTIONS_DATA_MAX_AGE 小时以前的执行记录,确保数据库体积不会上升过快 3。(Supabase 的免费额度只有 500M)

以上这些变量均可在官方文档 Configuration 中找到详细说明。

编辑完成后,点击 Update Variables,Railway 会开始新的部署任务。至此,我们就完成了 n8n 在 Railway 上使用 Supabase 数据库的部署流程。

初始化和数据迁移

如果一切正常,等 Railway 的部署任务完成后,即可通过 修改域名 环节所确定的域名打开 n8n 网站。第一次访问时,n8n 会引导用户创建管理员账号,安全起见,请尽快完成这一步骤。

Workflow 导入

如果你有已经备份好的 workflow,此时就可以进行导入了。先创建一个空的 workflow,然后在左侧菜单点击 “Import from File”,选择已有 workflow 的 json 文件即可完成导入。导入后,原 workflow 所使用的 credentials 会失效,需要手动选择或创建新的 credentials 才可以正常使用。

reorx/n8n-workflows 是我自己使用的一些 workflow,供读者参考。

Workflow 导出

出于备份或分享的目的,我们可以导出 n8n 的 workflows。下面讲解如何将运行在 Railway 中的 n8n 的 workflows 进行导出。

  1. 准备一个 Docker 环境,包含一个 Docker host 以及命令行工具 docker

  2. 安装 Railway 的命令行工具 railway 4

  3. 进入 GitHub Repo 所在的目录,执行 railway link,选择 n8n 对应的 project

  4. 执行如下命令

    EXPORT_ROOT=/path/to/volume railway run bash export_workflows.sh
    

    其中 EXPORT_ROOT 是文件输出路径,将作为 volume 挂载到所要执行的 Docker 容器中。

    railway run 会将线上 project 的环境变量注入到当前 shell 中,使最终执行的脚本 export_workflows.sh 使用与线上一致的环境。

    执行完毕后,即可在 EXPORT_ROOT 下找到当前日期命名的目录,其中的 json 文件即为导出的 workflows。这些文件是以 workflow 的 id 命名,你还可以使用我的 get_workflows.py 脚本修改文件名为实际的可读名称,并清理其中的无用数据:

    python get_workflows.py $EXPORT_ROOT/n8n-$(date +%Y%m%d) workflows/
    

这个导出方法同样适用于 NAS Docker 中部署的 n8n,只需要去掉 Railway 相关的步骤即可。

接入第三方服务

n8n 的强大在于它内置了很多线上服务的 Integrations,仅需简单的配置即可完成接入。虽然官方有文档说明,但仍然有一些不大不小的坑,这里记录下我的一些配置技巧,希望能帮助你节省一些时间。

Google

Google 的接入相对比较复杂,请跟随文档 Integrations: Google 的详细说明进行操作。这里只说下文档中没有提到的一个注意事项。

在创建 OAuth consent screen 后,要将 Publishing status 设为 Testing,否则 Google 的 OAuth 页面会显示应用未通过审核的警告。还要将想要接入的 Google 账号邮箱加入 Test users 列表,否则无法再 Testing 模式下通过 OAuth 验证。

以下内容为 2022-09-24 更新

在创建 OAuth consent screen 后,要将 Publishing status 设为 Production,否则一周后 OAuth token 就会过期 5。虽然这种方式会导致 OAuth 认证页面显示应用未通过审核的警告(点击左下角 “Go to …” 可以绕过),但总好过每周重新连接一次的麻烦。

以下内容为 2022-12-01 更新

只有当服务的域名为自定义域名且为 authorized domain 时才可以将 Publishing status 设为 Production,否则一段时间后 OAuth 将不可用,Verification Status 异常。关于如何将自己的域名在 Google 进行验证,请参考 Verify your domain (host-specific steps) - Google Workspace Admin Help.

OAuth consent screen: Publishing status

OAuth warning for Production app without verification

Twitter

Twitter 由于这两年来 Developer Portal 的大幅改造,实际操作中可能与文档 Integrations: Twitter 有许多不一致,但只要确保以下几点,应该可以避免大部分问题。

  1. 确保创建的 App 在 “Standalone app” 这个分类下
  2. 确保 “User authentication settings” 按下图所示配置

    User authentication settings

  3. 确保向 n8n 填入的 Consumer Key 和 Consumer Secret 来自下图中红框所在的位置

    Consumer Keys

Pinboard

n8n 没有内置 Pinboard 接入,不过 Pinboard API 设计非常简洁,我们可以手动实现接入。

创建 credential 时选择 “Query Auth”,向 “Name” 填入 auth_token,向 “Value” 填入从 Pinboard password 页面得到的 API Token。完成后,即可在 HTTP Request node 中使用。

HTTP Request node with pinboard Query Auth

GitHub

n8n 虽然有内置的 GitHub 接入,但并非所有 API 都被支持,因此我建议使用 HTTP Request 手动配置验证。

GitHub 提供 PAT (Personal Access Token),与 Pinboard 的 API Token 类似,相比 OAuth 更容易配置。

GitHub API 支持在 HTTP Header 中通过 Authorization 字段进行验证,其值为 Bearer + PAT 6

首先打开 https://github.com/settings/tokens, 点击 Generate new token,勾选所需权限。具体根据所要请求的 API 来决定,一般来说至少要勾上 repouser。创建完成后复制结果即为 $PAT

然后在 n8n 中创建 Header Auth,“Name” 填写 Authorization,Value 填写 Bearer $PAT(将 $PAT 替换为上一步的结果)。完成后,即可在 HTTP Request node 中使用。

HTTP Request node with GitHub Header Auth

总结

随着 PaaS 和 Serverless 平台的兴起,在很多场景下它们都能够代替 NAS 成为自托管服务的最佳选择。个人使用免费额度一般都绰绰有余,无论对于想要快速试验新产品的开发者,还是喜欢体验 SaaS 服务的业余爱好者,现在都是一个非常好的时代。

之前将 n8n 部署在 NAS 上平均每天会有几十条错误报警,一部分是 SQLite 在机械硬盘上频繁读写触发事务锁竞争,一部分则是代理不稳定造成网络访问失败。在迁移到 Railway + Supabase 的方案后,两个问题都得到了解决。PostgreSQL 有着更好的连接和并发性能;而 Railway 的运行环境本身就处于外网,自然也不会遇到代理失效的问题。

Railway 也可以提供包含 PostgreSQL 的全托管方案,但独立运行 PostgreSQL 不仅资源消耗大,显著占用免费额度,而且不如 Supabase 这种专业的 DaaS 稳定和安全。Cloudflare 的免费 CDN 服务,补充了 Railway 分发能力的不足,也降低了出口带宽的成本。几个平台相互搭配,取长补短,使新的方案可以用最低的成本实现最佳的性能和体验。

分体式键盘

2022年9月13日 00:00

我从 2018 年开始使用分体式键盘,如今它已成为我不可缺少的设备之一。

如果你看过我的 2022 年 7 月和 8 月总结,就会知道我的手腕一直都不是非常健康。在接触分体式键盘之前,我就有意识地不用笔记本电脑的自带键盘,并尝试了一些机械键盘,但提升不大,手腕还是会经常感到疲劳。

直到某天我在 Kickstarter 闲逛,发现了 Ergodox EZ(下简称 Ergodox)这款产品,这才了解到分体式键盘这一概念。本着健康至上的消费原则,我第一时间就下了单,就像 2015 年购买 Herman Miller 时一样果决。

Twitter

今天终于拔草了 ErgoDox 的键盘,其实几个月前就想买,当时以为这是个美国发货的产品,觉得只转运一个不划算,想等 kickstarter 上买的东西到货一起运。今天终于到货了,立刻去下单键盘,才发现他们是从台湾发货的😂,而且送达至少要三周…默默写上了香港的地址…

— Reorx (@novoreorx) June 14, 2018

Ergodox 对我的影响是巨大的,它大幅提高了我的打字效率和电脑操作效率,并且最终为我解决了长时间打字手腕疼痛的问题。

对于第一次使用分体式键盘的人,肌肉记忆是最大的挑战。我花了两个月的时间才逐渐适应 Ergodox 的键位,并学会了如何为自己设计键盘布局。这个过程还纠正了我多年的手指键位错误,比如我原本是用右食指敲 B,但键盘分体后就必须用左手了;之前我总是随机用任意手指按数字键,分体式键盘让 1-5 归左手、6-10 归右手,使我盲打数字的精准度产生了飞跃。

分体式键盘的另一个也是最重要的优点,是可以自由调整间距、倾斜度、旋转角度,这使手腕总是可以处于最舒适的位置。换句话说,是键盘来适应你的手腕,而不是反过来。没有用过的人,无法想象这种打字如呼吸般自如的畅快感。甚至在胳膊出现伤痛时,它也能继续使用而不造成伤害,简直可以归于医疗复健设备的范畴了。上个月我因颈椎和肌肉损伤右臂无法活动,也是因为有 Ergodox 我才能尽早恢复打字(实际上,病痛产生的很大一部分原因就是半年没有使用它)。

Ergodox 的布局属于 Columnar stagger,中文里有人把它叫做「直排」,即键帽竖直对齐。常见的键盘是 Symmetric stagger——水平对齐,分体式键盘中也有这样的布局。选择哪种在我看来并不重要,因为分体式键盘自由摆放的特性会最大程度上消除布局的差异。我在使用 Ergodox 前并没有接触过直排键盘,但习惯后并没有觉得有什么问题。

购买建议

我将分体式键盘推荐给任何使用键盘进行工作的人。

如果预算不是问题,我建议购买 Ergodox 家(现在改名叫 ZSA)的新产品 Moonlander,它是 Ergodox EZ 的升级版,更加简洁优雅。一体化的设计便于携带,拇指区域的键位也做出了优化,虽然少了两个键,但反正也用不了那么多,位置合理才是更重要的。

Twitter

ZSA 的 Moonlander 分体式键盘竟然以「猫猫喜欢待在键盘中间」为宣传点,还专门做了个页面放猫片,这谁顶得住啊https://t.co/cfQ5byakUw pic.twitter.com/vJ6r0DuhWI

— Reorx (@novoreorx) September 12, 2022

其实上手一个新的产品,$365 的价格还是挺高的,海淘转运也断绝了试错机会。所以如果不是特别肯定,可以试试在闲鱼/淘宝上找客制化键盘的卖家,几百块买一个尝试一下。搜索的关键词就是「分体键盘」。

如果你喜欢 DIY,也可以找一个开源的分体键盘项目,自己动手做一套,不仅有趣,而且省钱,我相信会是一次非常有成就感的体验。我的 Pinboard 上收藏过一些相关链接,其中我比较喜欢的是 rtitmuss/tornkata0510/Lily58

Torn

Lily58

关于 Essays 的说明

2022年9月12日 00:00

Essays 板块已经正式加入我的个人网站——也就是你现在正在浏览的这个网站,正好用第二篇来说明下创建它的动机和它的设计。

开始写博客至今已有几个月了,但我每次写文章时仍然会有这几个问题:

  1. 动笔难。哪怕有了想法,甚至有了笔记,也总是会因为惧怕组织文章结构而难以下笔。
  2. 写字不够专注。一开始是经常被其他事情打断,久了竟然导致无法长时间专注写字,写一会就不由自主地去刷 Twitter / Telegram / RSS。
  3. 博客文章变得过「重」了,潜意识里觉得是一件需要很多时间来去做的事情,而且由于是静态生成器架构,必须要在有 Markdown 编辑器和 Git 全套工具的电脑上进行。

我逐渐发现若只写长文,博客的更新频率必然不高,不利于持续输出,因此最好的办法就是为自己的写作设定一个新的分类,这就是 Essays 的由来。

Essays 专门用于发表片段化的想法,对文章结构和长度不做要求,并且我希望每篇都尽量在 20 分钟内写完,践行 Clear Communication。

有了 Essays,原来的文章则属于 Blog 板块。Blog 的定位是内容丰富的长文,Essays 则适合记录在产品和技术创作之外,我对这个世界的感受和思考。

我对网站结构也进行了一些改进,之后会出一篇专门的文章详细说明。这里着重说下 RSS 的变化,首页的 RSS 会包含全站所有文章,而 Essays / Blog 页面下的 RSS 则仅包含其所在板块的文章。如果读者有需求,可以考虑订阅不同的链接。

我还为 Essays 增加了一个 n8n workflow 1,自动将最新的文章分享到 Twitter。如果你现在是在 Twitter 的图片中阅读本文,那么这条推文以及图片都是由 n8n 生成并发送的。

我希望能越来越多地分享我的想法、知识、体验,Essays 就是这样一个尝试。下一个目标,是能够随时随地写作并分享,我已经有了一个初步的想法,看看我能创造些什么吧。

要不要回互联网公司上班?

2022年9月12日 00:00

现在的工作状态虽然仍有我不满意的地方,但有时候问自己「要不要回互联网公司上班」这个问题,得到的答案仍然是否定的。

回到刚毕业的时候,为何当时的互联网行业那么吸引我呢?

  • 我尝试过创造产品,却没有足够的能力,这件事情使我感到敬畏。
  • 技术氛围好。遇到的大部分程序员都有自己的技术偏好和追求。
  • 有趣的人多。无论是产品经理、设计师、运营,只要有着好奇心,都能聊到一块去。是兴趣使天南海北不同性格不同特质的人聚集在一起,而不是单纯为了获得收入。

那现在呢,为何感觉互联网行业大多无趣?

  • 大众流量成为一种公式,它的本质是利用人性弱点,因此(做)产品不再是以用户为导向,而是变成为资本服务的收割机。
  • 程序员群体扩大,沦为单纯的工具人。在招聘 junior developer 时,我发现自己面对的简历越来越千篇一律,人们仿佛是用同一个模板、在同一个机器里生产出来的;和他们交谈时,也感觉不到他们对获得这份 offer 以外的任何事情有兴趣,没有个人观点,只有来自面经和培训机构的话术。
  • 自我意识觉醒。我清楚了自己事业的目标,对「有价值的事情」的认知变得纯粹、决绝,不会向所谓的生活的负担妥协。说到底,我已经有了拒绝无意义的事情的精神力量和物质基础。

只要确定自己是走在正确的路上,即便荆棘傍身,也不用害怕和迷惘。

2022 年 9 月苹果发布会观后感

2022年9月9日 00:00

刚过完 2022 年的科技春晚——苹果9月发布会,我也照例观看(指阅读 ifanr 的汇总文章)了一遍,大致总结如下:

产品 买点 售价
iPhone 14 Pro 💊 灵动岛 ¥7999
Apple Watch Ultra 🔋 双倍续航 ¥6299
Airpods Pro 2 📡 如 AirTag 般方便寻找 ¥1899

总价是¥16197,好贵!相当于一台中配 MacBook Pro。但这么一想,反而不觉得贵了,同样的价格可以得到三个不同产品的体验,简直太划算了不是吗 😏

我对苹果产品的购买通常从三点进行考量,一是产品设计是否有颠覆式的变革,或者足够创新有趣,iPhone 14 Pro 的灵动岛显然满足这点,并且是业界独一无二的。许多人(如罗胖)说这是硬件落后倒逼软件妥协,有道理,但我不会因此否认它的人机交互设计价值。

第二点则是目前的设备是否拖累了我的生产力,新的设备能否做出有效的提升。我手里的 iPhone 11 Pro 还挺顺畅,iPhone 14 Pro 也没有 life-saving 级的变化,就看库克啥时候按下降频按钮了。

最后一点, 是手机的大小和重量,而且不容妥协。我非常爱惜自己的右手和手腕,手机太大握持不舒服,太重则手腕容易疲劳。在 iPhone X 发布时就对我产生了巨大的考验,好在 6.1 英寸我还能勉强接受,后续也都维持了这个大小。因此每次看新 iPhone 的 spec,我总是先关注重量。下面是我整理的 iPhone 从 X 到今年的 14,重量变化的表格(X 算作初代的 Pro):

iPhone Pro1 iPhone2
X 174g -
11 188g 194g
12 189g 164g
13 204g 174g
14 206g 172g

画成图表看更加直观:

你一定想不到,当时使我决定购买 iPhone 11 Pro 而不是 iPhone 11 的原因,是前者比后者轻了 6 克!可惜的是,Pro 系列的重量在之后逐年递增,不再是我的首选项了。希望明年不带 Pro 的 iPhone 也能拥有灵动岛,这样我就会乖乖掏钱啦(库克!)。

再看看另外两个产品。Watch Ultra 的情况和 iPhone 14 Pro 差不多,不予考虑。Airpods Pro 2 则决定购买,已经在今晚抢到。现在我还在用着几年前买的 Airpods 2,有些旧了,正好换成最新产品。何况又有了可以像 AirTag 那样去寻找的便利性呢。

关于苹果,从 iPhone 11 到 13 我一直都不觉得有什么值得称道的创新,今年终于让我有些刮目相看。但我不觉得这是什么值得吹捧的事情,作为地球上最有钱的科技公司之一,本就应以引领行业发展为己任,不然赚那么多钱是干什么吃的。有人会说,资本家本就是以最低成本创造最大收益为目标呀。但这个世界还是需要有理想的人存在,哪怕只有些许的人、些许的理想,这样人类文明才不会太过无趣不是吗。

2022 年 7 月和 8 月总结

2022年9月3日 00:00

似乎每年我都会生一场大病,今年也不例外。整个7月加上8月上旬基本在生病和修养中度过。由于这段时间过得太过荒废,使我首次产生写一篇月度总结的念头,想要梳理自己的所见所思所得,也希望能平复内心的压抑和焦虑,重拾生活节奏。

健康

回顾一下过去十年我所经历的病痛:

  • 2013: 严重的中耳炎,耳膜破裂,听觉受损
  • 2014: 肩膀和手臂拉伤,一个月不能使用右臂;右腿拉伤
  • 2015: 右手腱鞘炎导致右手肿大压迫神经,住院一个月
  • 2016: 过敏性支气管炎,咳嗽一个月
  • 2018: 过敏性鼻炎,咳嗽两个月
  • 2020: 支气管炎和轻度肺炎,在家治疗一个月
  • 2021: 过敏性支气管炎,咳嗽一个半月

除了 2017 和 2019 年比较幸运,其余年份都有跨度至少一个月的异常时期,简直让人对生病产生仪式感了。

虽然生病本身并不是什么愉快的经历,但我也因此磨练出了比较坚韧的心态,无论去医院遇到什么麻烦和挫折,都能用平常心面对;不管是出门看病,还是自主治疗,总是能做好周全的准备;家人和朋友的悉心照顾,也让我体会到亲情和友情的珍贵。

生命本身就是在等价交换,有得有失,真的没什么可抱怨的了。所以每次病后,也只当是一次年度提醒,告诉自己要继续坚持锻炼身体,才能持续探索这个无限精彩的世界。

现在来大致回顾下今年的赛况。

上半场是鼻炎和支气管炎。7月3日从老家回到杭州,立刻就开始感冒,随后是持续的鼻炎和咳嗽。期间我没有去医院就诊,依照自己丰富的过往经验,先是在嗓子发炎时服用了针对细菌感染的阿莫西林。进入鼻炎阶段后,没有吃内服药物,而是用布地奈德鼻喷雾剂进行(这是因为我清楚自己是过敏性鼻炎,抑制鼻部症状,等待身体免疫恢复即可)。鼻炎减轻后,按照惯例引发支气管炎和咳嗽,于是我又购买了针对自身情况的孟鲁司特钠片,吃了几天后咳嗽减轻,随后慢慢恢复适度的有氧锻炼,大约在月中彻底结束。

下半场比较意外,是右臂肌肉劳损。前面鼻炎时为了转移注意力,游戏时间增加了很多,由于没掌握好手柄的使用姿势,胳膊一直隐隐作痛,但被我忽视了。等到呼吸系统恢复,我又迫不急待地连续写了两天代码,终于使三角肌不堪重负,剧烈疼痛,整个右臂都肿了起来。这让我一下子回到 2015 年的夏天,好在我对此也有经验,我清楚这种程度的肌肉劳损不是一时半刻能够恢复,便用绷带固定右手,开始练习使用左手打字、用筷子、洗澡、擦屁屁。头两天我买了冰袋冷敷,等到触碰肌肉不再那么疼时,我没事就用左手慢慢按摩三角肌。最后临近恢复时,又购置了握力器来复健因长期不活动而难以握拳的右手。折腾到 7 月 30 日前后,才重新正常使用右臂。

时间来到 8 月,又一个意外出现,进入加时赛阶段——颈椎疼痛,脖子转不动了。这一方面是受右肩的影响和左臂的过渡使用,脖子本身有些僵硬了;另一方面,我把睡了一年的宜家弹簧床垫换成了棕榈床垫,一时间睡不习惯,导致了落枕。第二天发现脖子没法往左右转动,稍微使劲就很疼。但是不慌,我想起 5 年前有过同样的症状,火速在 JD 下单了「颈椎牵引器」,顶着极端高温天气戴了一个星期,终于圆满完赛。

这次持续生病的起因,主要在于 6 月回家乡度假时,作息被打乱,不仅中断了日常锻炼,还几乎没有睡好觉,导致身体素质短时间大幅下降,病也就趁虚而入了。我的体质比较特别,如果持续锻炼,即使量比较少,也能表现出很好的运动机能。但只要停止锻炼,很快就会低于常人,容易困倦和生病。以后要更加关注自己的身体状况,尤其是对于睡眠质量。睡眠关乎身体的自我调节和平衡能力,如果连续产生低质量睡眠,一定要以最高优先级应对。

生病期间,在 Twitter 上知道了 默沙东诊疗手册腾讯医典 两个医学参考手册 1,帮助我重新梳理了对一些医学概念的认知。这个意外收获让我越发觉得,人要在任何情况下保持交流,不断更新自己。

游戏

多亏了游戏,让我在对工作力不从心的时候保持了活跃的精神状态。但游戏玩多了确实有些收不住,会感觉自己该做的事情拖欠了太多,产生空虚感和负罪感。但这不是游戏的问题,而是我需要继续提高控制力、合理安排时间(哪怕是在生病的特殊时期)。

下面按平台列举我这两个月玩过的游戏。

Steam

  • Vampire Survivors (MacOS)

    好玩又上头,Vampire Survivors 属于仅靠优秀的机制就能支撑整个游戏体验的作品,画面几乎就是贴图平移,但并不影响其出色的游戏性(梦回 Dream Quest)。每局 20 分钟,围绕武器系统构建 Build,发现不同组合的趣味,挑战更高的收割效率。它开创了一个新的游戏模式,也带来了很多模仿者 2。不过没关系,这么好玩的东西请多来点。

    操作上,基本只需要上下左右四个按键,对于只能使用左手的我来说特别友好。

    如果你有兴趣,可以尝试下目前已知的几款相似游戏:

  • Stacklands (MacOS)

    Sokpop 7 月的作品(每月出一个新游戏),SLG + DBG 组合,同样有着优秀耐玩的机制。朋友来看望我时,用 Stacklands 度过了一个愉快的下午。

  • V Rising

    来自表妹的礼物 3,一开始我以为它是类似 Diablo 的 ARPG,打开才发现是 Don’t Starve 类型的沙盒经营游戏。总的来说画面不错,但是一个人玩进度太慢,推荐和朋友们一起开黑。

  • Rogue Legacy 2

    白嫖朋友的游戏库。Rogue Legacy 一代很多年前就玩过,非常喜爱 4,二代的感受更像是一代的高清重置 + 新职业和地图 DLC,有时间是可以慢慢通关的。可惜的是,开发团队没有资源将游戏移植到 Switch,对我来说更希望能在 Switch 上玩这种横版的 platformer 游戏。

  • Stray

    没有买,看 streamer 的录播视频云通关。之所以特别记录一下,是因为 Stray 和我 2020 年关注的一款游戏 Little Kitty, Big City (后称 LKBC) 太像了。LKBC 的开发者只有一个人,进展缓慢,但光是预告片就深深吸引了我,我能感觉到作者在其中倾注的对猫咪的爱,相比而言 Stray 就有些缺少温度了。5

    Stray 虽然也很优秀,但作为一款短流程的风格化游戏,本应该充满彩蛋和细节,让人可以不停地把玩,但现实却是缺少打磨、bug 众多。Stray 大火让我对 LKBC 的未来感到担忧:等到发售时,人们会不会将其视作模仿者而不予以足够的重视呢?

  • Disco Elysium

    极乐迪斯科!无需多言,这是一个早该玩但拖到现在的艺术品。由于还没有通关,对游戏剧情就不做评价了。值得一提的是游戏有一键瞬间切换语言的功能,这让一贯坚持用英语完成所有游戏的我非常开心,毕竟叙事型游戏,文案难度还是很高的,中英对照会容易很多。 6

Switch

  • CrossCode

    我很喜欢《远星物语》这个译名,这个游戏我在 Steam 上关注了很久,却在 Switch 上等来了打折 7。游戏无论音乐、画面、UI 都很出色,就是走路太费劲了,2D 表现 3D 的地形实在是有些费眼,更别说制作者将其当做 feature 深度耕耘,竟使我回忆起玩仙剑奇侠传走迷宫时的恐惧。最终我只好遗憾地放弃了。

  • Scott Pilgrim vs. the World: The Game

    同样是打折购买,一款非常好玩的 Beat ’em up 游戏。就像是 Streets of Rage 4 的小人儿版。鼻炎的时候在玩,还差一关打通,右臂损伤之后就没敢玩了。

  • 忍者龙剑传大师合集

    忍龙是大学时的记忆,工作后我还拥有过一台 PS Vita,在上面把 Sigma 2 玩了一半流程,这次是冲着回味和补完去买的。总的来说,一代虽然经典,但是太难了 8;二代是初恋,而且难度和乐趣也都刚刚好;三代是狗屎,10 分钟我就退出了。

  • Super Mario Odyssey

    一个我不好意思说我现在才玩的游戏,不多评价了,问就是「任天堂TM就是世界的主宰」。目前有 300 🌙 的收集进度。

  • 异度神剑 3

    XB3 发售的时候,我的右臂刚摆脱绷带,不敢轻易使用,于是全程用左手操作手柄,好在这个游戏不怎么需要走位之类的复杂操作,好感 +1。连续玩了几天,进度大概有一大半。

    XB2 之前我曾有心去补,但对我来说还是有点太大了🥵。XB3 则恰到好处,几乎没有我不喜欢的角色。剧情方面,XB3 虽然更有深度,但 XB2 的王道少年漫套路或许更适合游戏整体的画风一些。音乐方面,XB3 是好听的,但没有到让人回味的程度;我单独听过 XB2 的 OST,觉得更胜一筹。

  • 旷野之息

    6月购入 Switch 的时候玩的第一个游戏就是旷野之息,当时打到城堡主殿就停下来了。最近有天打开游戏想要随便逛逛,不小心走进主殿触发了 BOSS 战,通关了…于是顺手买了 DLC 开启大师模式,经过 2 小时的练习,成功拿初始装备攻克初始台地人马,成就感满满。开完四神兽后,又尝试了剑之试炼,没想到难度过高,打得我自闭了两天,决定放弃,转头去玩英杰之诗。

    旷野之息对我而言,既有如《风之旅人》般自由写意的精神享受,又有比肩《巫师3》的战斗乐趣。之后我会慢慢把所有的神庙和呀哈哈都刷完,这是对我心目中最好的游戏所能表示的最大敬意。

  • Splatoon 3

    由于发售日将近,Splatoon 3 的宣传视频最近也多了起来,看完之后我就种草了。其实我还挺喜欢多人竞技的游戏,在我看来 Splatoon 就是更好玩的 Overwatch,只恨当年不知道,不然我早就去玩 Splatoon 2 了。但最近身体好了之后也要恢复工作,虽然买了预购,一时半会也不敢轻易去玩了。

Itch

  • 奶奶的菜谱

    非常有趣的互动式小说 (Interactive Fiction) 游戏,是两位留学生的毕设作品。玩家扮演一个孤独的社畜用手机向千里之外的奶奶学习做饭。让我想起了刚毕业那两年的自己。 9

  • Die in the Dungeon

    筛子、DBG、Roguelite、爬塔,全都是我喜欢的元素,放在一起自然是加倍好玩,而且还是全平台免费,快来和🐸一起🎲吧。 10

  • Shogun Showdown

    同样具有 Roguelite 和爬塔元素,玩家扮演一名武士,只身对抗左右不断涌现的敌人。游戏将武士的动作和武器融入到卡牌构筑中,策略非常多样,鼓励进攻,玩起来真的有种狭路相逢勇者胜的刺激感。适合摸鱼或编译的时候随手来两把。 11

  • MOBS, INC

    你是一个给魔王打工的怪物,被派去干掉闯入地城的人类冒险者,如果被打败,地狱的老板会让你不要偷懒继续工作。这种反串阵营的设定在 SkulLegend of Keepers 上见到过,非常有趣。

  • Luckittown

    掷骰子的 SLG 塔防游戏。设计亮点是有多种骰子,一次投掷多个,可以保留一部分用在下个回合。地图很开放,刚开始玩有点不知道怎么下手,但也很值得挖掘不同玩法。这个游戏也是 Sokpop 出的,在 Itch 购买后会提供 Steam key,可以说是买一送一了。

其他

  • 炉石

    女友一直很喜欢看我玩炉石,恰好炉石节奏比较休闲,也很适合左手操作,就在右臂恢复期间久违地玩了一阵子。或许是心态进步了,明显感觉比之前打的好了很多,5000 分后越打越顺手,动不动就连续第一名。最后在到达 6000 分时,趁着还没被吊打,带着已获得的乐趣急流勇退了。一些截图记录在了这串推文里。

  • 风暴英雄

    有半年多没玩 HOTS 了,这次其实也没玩,只是听到了一个消息,风暴开发组宣布停止更新游戏内容,于是再次哀悼了我最喜欢的 MOBA 游戏。还看了职业选手 Grubby 的视频,一开始他强做平静,说着说着痛哭出声,作为5年游戏玩家的我也产生了感同身受的悲伤 12

    好在机核的这篇文章——有的游戏三岁就死了,直到七岁才埋:写在《风暴英雄》发布新公告后——冲淡了难过的情绪,里面还有一个让人忍俊不禁的比喻:

    大致想象一下,你有一位异地恋的女友。四年前,她打电话来告诉你说:“咱们就继续维持这种异地关系吧”。

    在随后的几年里,你们保持着电话联系,偶尔她会和你分享一下最近的趣事,包括她现在在干嘛,是不是毕业了,是不是工作了,是不是升职了?反正有一搭无一搭的还会说几句话。

    大约一年半以前你们通了最后一次电话,从此之后大概一两个月会发一发短信,无非也就是在干嘛,没什么,睡了,再见,有时候刷到几条你还可见的朋友圈,也能去点个赞。

    今天她突然告诉你说:咱们散了吧。作为笔者自己的话,我可能的回复也许是:合着还没分呐?

影视

老番

  • 平稳世代的韦驮天们

    原本只是想补下去年的番,没想到成为我今年最喜欢的动画。反套路、快节奏的剧情,张力十足的战斗画面,MAPPA 在这部番中淋漓尽致地释放了他们业界顶尖的制作水准。唯一的遗憾是故事还未结束便戛然而止,也暂时看不到第二季制作的可能性。

  • Devilman: Crybaby

    在看《平稳世代的韦驮天们》时联想到了恶魔人,便重看了这部汤浅政明 2018 年拍的老番。震撼依旧,但相比第一次观看注意到更多细节,有了更深刻的理解。许是内心的承受力这些年来有所提升,太郎吃妈妈那段情节我终于能面不改色地看下去,这才发现诺埃尔(太郎的爸爸)几次举枪又放下的镜头,对人性的刻画有多么深刻。真有你的汤浅,不愧是我最欣赏的动画导演之一。

  • Burn the Witch

    很精彩的小品番,只有3集,令人意犹未尽。原作漫画的作者是久保带人,代表作是《死神》。在这个新人只会画异世界的时代,只有老一辈艺术家还在继续着纯粹的、不受约束的幻想。以及我再次感受到日本人「边境心态」的优点,不需要对英国的魔法和童话体系有多么深刻的理解,不用怕 Harry Potter 珠玉在前,想画什么就画什么完事了。这并不是对文化的源流缺少敬畏,恰恰相反,作者的心中其实并没有「我」的位置,「我」是一个边境人,没有政治立场或民族情绪。故能对所有的文化一视同仁,以纯粹的热爱,将「我」的认知糅合想象力,创造出新的世界。《来自深渊》和《宝石之国》在我看来也都是在这样的心态下才能创造出的,瑰丽奇幻的世界。(这两部的作者都是女性漫画家,究竟是巧合还是必然呢?)

  • 黄金神威

    日俄战争历史背景,阿伊努人民族风俗,这两大元素让黄金神威在近年的番剧中显得如此特立独行。2018 年我在 B 站看完了第一季,后来因为 B 站下架一直没追。最近发现第四季都出了,遂下载,一口气补完三季。朋友说漫画版不容错过,得空一定要慢慢品鉴。

新番

  • Lycoris Recoil

    挑不出毛病的 JK 战斗番,不挑观众,就是纯粹的好看,无论是老二次元还是普通观众,无论有什么口味偏好,都能得到极好的观看体验。角色设定上有个很有意思的地方,和《吊带袜天使》一样,都是一个黑人大叔带着两个会打枪的妹子维持世界和平,不知是巧合还是致敬 13

    关于这部番还有一则逸闻,知名游戏制作人小岛秀夫每周都会发推文记录自己的看番感受。 14

  • Call of the Night

    吸血鬼与少年的故事,梦回物语系列。第一话台词超棒,让我有找到同类的欣喜 15

    人为什么会熬夜 是因为有想看的节目?有想做的事?还是因为对明天感到不安? 这全都起因于一件事 就是对今天不满足 来熬夜吧,熬到对今天满意为止

    另外这段脱衣服的镜头简直太赞了。

  • 异世界归来的舅舅

    舅舅在异世界穿越了 17 年,回来后用魔法拍视频当 YouTuber,和主角回忆异世界生活的日常。总体来讲是部好看的番,有很多可圈可点的地方。首先是反套路设定,舅舅在异世界过的并不好,因此本番的笑点多数都有黑色幽默的气质。舅舅的人设是一个 SEGA 游戏机的死忠粉,游戏几乎就是他人生的全部,看的时候映照出一些自己的影子,对舅舅的傻里傻气感觉的很亲切。缺点则是女角色没什么意思,而且傲娇 + 直男梗看多了容易疲劳。

  • 夏日重现

    夏日重现其实是4月新番,本季还在继续更新。如果要推荐国产剧观众来观看,我会说这是更加精彩的《开端》——虽然这个说法并不准确。

    在连续两季的更新中,夏日重现整体维持了比较平稳的质量,剧情也是越来越引人入胜。最近快要大结局了,每次我都是攒两集再看,只看一集简直百爪挠心。有次和朋友聊天提到夏日重现动画的真实感很强,朋友说那是你没有看过漫画,随后向我展示了一张男主在漫画中的表情,看到后我毫不犹豫的将其加入到了漫画待看列表中。

纪录片

  • The Social Dilemma

    最早是 2 年前好友 @chiusx 向我推荐的,最近又重温一次 16。这 2 年发生了许多事情,我们的生活越发依赖互联网公司所提供的服务。或许我们最终也无法摆脱作为数据商品被操纵的命运,至少也要尽可能多地了解真相,哪怕它是残酷的,也好过活在无知的快乐中。

  • A Dream Cast

    因为《异世界归来的舅舅》而想起 SEGA 的游戏主机,便找到了这部纪录片。看完颇有感触。Dreamcast 最大的挫折就是在发售时被 PS2 狙击了。更低的售价,更高的附加值(DVD 播放器),索尼终于报了 PS1 和 Saturn 打价格战时的仇。但也怪世嘉自己不争气,在 PS2 发售前两年的时间窗口里没有推出杀手级的游戏单品和足够丰富的游戏阵容。任天堂一直以来就是在这一点做到极致,永远先有游戏,再配合推出硬件,发售的时候就是双拳出击,不给同行任何机会。但不是每个公司都是任天堂,世嘉已经尽力了。

    Dreamcast 承载着社长大川功先生做出极致主机的梦想,在那个时代造就了一系列拥有独立游戏精神的作品,并深深影响了业界的发展。这个梦想投射在了一代制作人和玩家的记忆里,that’s why it’s called a dream cast.

阅读

久居在家,促使我翻出遗忘许久的「泡面盖」重新使用。看的书不多,但是比以往都要仔细。

  • 夜晚的潜水艇

    看到和菜头提及小说家陈春成,就去找了他的第一本书《夜晚的潜水艇》。它应该是十年来最打动我的文字和故事。作者的文笔非常独特,虽然没有华丽的词藻,但却有种将他对世界独一无二的感受传达给人的能力。作者年岁和我相近,把我们那一代人年幼时的懵懂、长大后的迷惘,种种最细微幽深的情结,全部重现在他天马行空的幻想故事中。

  • 山月记

    这是日本作家中岛敦取材中国古代文学作品所编撰的故事集,大部分原著我都没有看过,因此读起来颇有趣味。最喜欢其中《悟净出世》和《悟净叹异》两篇,让我对沙僧甚至整个西游记都有了很多新的理解。

见闻

  • 看到名为「人间观察」的频道主 Hayami 关于「男的」的言论,感到过于好笑,就发推抨击了一下。

  • 某天晚上在小区人行道上发现一只踽踽爬行的小蝉,把它放到了路边的草丛,免得被路人踩到。回忆起童年时光,夏夜可以在树上找到它们,动作笨笨的很听话,捉回家放到花盆里,等它们第二天表演金蝉脱壳。 17

  • 因为感受到买药的麻烦,我发出了「你最好不要生病」的感叹 18,谁知没过几天就应验了:女友用美团外卖购买了午时茶(一种消食冲剂),我们谁也没意识到这个药会和疫情风控有关,第二天她就因买药后 24 小时内没做核酸而被赋为黄码,不得不去附近的医院按照规定流程重新检测。

  • 在天桥上散步时,偶遇在杭州见过最美的黄昏。

  • 看了《回村三天,二舅治好了我的精神内耗》这个视频,鉴定为新时代精神毒品。当时写了点评价,之后整理下单独发一篇短文吧。

好奇心

  • Retro Game Console & Android Box

    在 YouTube 上看到两个关于 Retro Game Console 的视频 19,勾起了我对掌机运行 FC/GB/NES 模拟器玩老游戏的兴趣。以前购买过一个 GameShell,价格接近 Switch 了不说,手感还非常差,对比这次发现的新产品,个个既好看又实用,感觉真是亏爆。

    我便想,不如直接在电视盒子上安装 RetroArch,连接手柄就可以把电视变成复古游戏主机了。但在 Oppo 电视上直接安装没有成功,这时朋友向我推荐了 HK1,于是我又去研究 Android TV Box,找到一篇对比多个盒子的博文 安卓外贸电视盒子体验。在翻看作者的介绍时,发现了他的 GitHub 项目 jdhao/nvim-config,为我升级 nvim 配置提供了重要的参考,不禁感叹 “down the rabbit hole” 之奇妙。

  • The Apple Store Time Machine

    在 Hacker News 发现这个网站,作者用 Unity 写了一个名叫 Shop Different 的「游戏」,以第一人称视角游览不同时期的 Apple Store。我在这座数字博物馆中流连忘返了一个晚上,还发现了之前未曾听过的 iBook 笔记本电脑 20

  • VLC & LibVLC

    过去一年我的主要观影工具是 Plex,时常会遇到一些视频无法播放的问题,开始我怀疑是 NAS 硬件的问题,经过仔细检查发现,大多数情况都是在串流 1080p 码率的原始视频文件时,客户端解码出错。于是我给电视的安卓系统安装了 VLC,测试发现同样的文件都可以播放,这才意识到是 Plex for Android TV 内置播放器的问题。

    但 VLC 毕竟不是完整的媒体服务器,使用体验还是差一些。为了解决这两个月大量的观影需求,我给 NAS 部署了 Jellyfin 并安装了其客户端,在客户端设置里发现了「首选播放器」选项,选择 LibVLC 后,大部分 Plex 不能播放的视频就都可以正常播放了,只剩少数顽固份子还是只能用 VLC 播放。经过这番折腾,我得出了解码能力 VLC > LibVLC > Plex 的结论。 21

  • 模块化机械键盘

    看了B站稚晖君的视频 【自制】我做了一把模块化机械键盘 !【软核】,好生羡慕他的动手能力,而且设计非常硬核,比某何同学不知高到哪里去了。这个键盘最让我感兴趣的是它的可拆卸的智能交互模块,它使用 无刷电机 实现了一个功能极其丰富的旋钮,不仅可以模拟不同程度的阻尼感,作为一个输入设备使用,还可以由软件控制其主动旋转,搭配侧边的屏幕作为仪表盘显示数据。

    说到无刷电机,就想到步进电机,之前在 B 站看过一位 up 主通过步进电机演奏音乐也给我留下了深刻的印象:【电机】来吧,甜蜜的死亡 / come on, sweet death - EVA剧场版OST

    DIY 硬件真的很有趣,希望以后我也能像稚晖君或 DIY Perks 那样创造属于自己的电子设备。

  • Nota & iBar

    由于我主要使用 Obsidian 记录笔记,写博客自然也还是用它。写下十来篇后,逐渐感觉 Obsidian 写作体验并不是那么好。写作需要 distraction free 的环境,但 Obsidian 是个折腾插件的生产力工具,气氛不对;Obsidian 的编辑器中,图片会使段落高度经常发生变化,影响思考状态。最后,我发现笔记系统与博客写作是相互冲突的,笔记系统打开关闭文件的频率很高,而一篇文章的草稿可能要持续打开三五天之久;笔记的目的是将当下的想法快速记录下来,不像博客写作有文笔规范的要求。笔记最终会成为博客的素材,将它们分开在两个软件中似乎是更好的选择。

    于是我找到了 Nota,一个新的 Markdown 编辑器,它的前身是 Caret,作者是两位保加利亚的开发者。Nota 和 Typora 类似,都是所见即得的编辑器,但 Nota 有更好的文件浏览器,以及类似 Obsidian 的文件导航工具;与 Obsidian 相比,又少了许多不那么必要的功能,在简洁和强大之间找到了一个非常恰当的平衡点。总的来说,就是手感对了,我一下子就喜欢上了 Nota,决定开将它作为我的写作专门编辑器。本文是使用 Nota 创作的第一篇博客,截止此处文字,各方面体验都很让人满意。只有一个功能缺失,就是在粘贴图片时将图片存放在当前文件的相对路径上。我追踪了一个 Issue,希望它能尽快得到实现。

    iBar 是 Nota 作者的另一个产品,用来快速搜索浏览器历史记录。原本我只抱着随便试试的心态,使用后却发现相当不错,不仅 fuzzy 的准确性很高,搜索速度也不差。现在它已经是我日常工作流的一部分了,推荐给任何大量浏览网页的人。

学习

虽然身体不适并且只能单手打字,我还是设法学到一些新的知识和技能。

  • PWA

    7月中旬受人之托要实现对一个手机 App 的模拟,我想到最快的方式是写一个符合 PWA 22 标准的网页。经过研究,为网页增加了 Web app manifests,还根据 Apple 的标准添加了一些 Safari 特有的 meta tags,最终效果非常不错,达到了像素级还原 App 的体验,之后也写一篇文章来总结下。

  • Vite

    在实现上文所讲的 PWA 网页时,由于没有复杂的交互,不需要用到 UI 框架,我想回归 vanilla web 的开发方式,即手写 HTML + CSS。手动刷新太麻烦,我还是需要一个能够自动刷新的 dev server。用 Webpack 显得没有必要,而且我也不够熟悉,不能短时间内完成配置。

    最终我发现 Vite 是最佳选择。一直以为 Vite 是 Vue 生态专属,没想到是通用的前端开发和构建工具,无论是开箱即用的流程,还是清晰的文档和 API 设计,都给我带来非常好的使用体验。23

  • Mantine 5.0

    我最喜欢的 React 组件库 Mantine 发布了 5.0,仔细看过 release note 后,发现还是有不少 breaking change 的。等再发几个 minor/patch 版本就对自己使用 Mantine 的项目进行全面更新。

  • IELTS

    考 IELTS 是 6 月回家之后萌生的想法,一是想检测下自己的英语水平,二是可以给未来带来多一些选择。在把好友给我的一套真题完整做了一遍之后,发现自己与裸考通过还是有很大差距的,还是老老实实地背单词 + 做题吧。

    在对英语学习有了更多的重视之后,我整理了一些常用的词典文件 (mdx),分享到了 Telegram 频道里: https://t.me/reorx_share/2040

What’s Next?

  • 首先是重启之前的工作,原本我一直在开发一款 SaaS 产品,最近3个月却是全无进展。为了让工作进行得更有节奏,我会写下每日的 devlog,但可能没有时间润色并发布出来。
  • 维护开源软件:我开发的 Obsidian 插件 Paste image rename 和 Hugo 主题 PaperModX 不知不觉也有了一些用户,这两个月也收到不少 issue。回复正常工作状态后,我会在周末抽时间去维护这些项目。
  • 开始写 Newsletter:最近写的笔记很多,有一部分既符合同样的主题,也有分享的价值。因此我准备尝试写 Newsletter,我将其命名为 Maker’s Daily。这是一个面向创造者和开发者的通讯,1-3 日更新一篇,内容包含我对产品的思考、技术的探索、工具的使用,以及作为一个 Maker 的成长和反思。几乎每天都有与之相关的灵感涌出,所以不愁没素材可写,但我想尽量让文章短小精悍,来培养简洁写作的能力和习惯。
  • 调整博客结构:在产品和技术创作之外,我也有许多关于这个世界的思考,写出来比推文长,又比一篇正式的博客要短。其实若只写长文,博客的更新频率必然不高,不利于持续输出。因此,我准备为博客增加一个新的栏目,名为 Essay,也就是短文,专门用于发表片段化的想法。

这篇文章断断续续写了3天,一共7小时50分钟。如果你能看到这里,真的很感谢你的阅读,希望我的这些琐事能给你带来一些有用的信息和启发。


  1. From twitter.com/novoreorx/status/1544207392002682880:

    查咽喉疼痛相关的资料,发现妙佑医疗 Mayo Clinic 的网站写得相当不错,简单易懂而不乏专业性,非常适合用来了解各种疾病的治疗常识,相比之下中文互联网在医疗领域的内容简直就是粪坑。https://t.co/VehBGL6VVD

    妙佑还有一本关于家庭健康的书评价不错,准备买本读一下:https://t.co/A2r1msbG3m

    — Reorx (@novoreorx) July 5, 2022
     ↩︎
  2. From twitter.com/novoreorx/status/1557237847211798528:

    好耶,又一个类 Vampire Survivors 游戏 https://t.co/g2Ic4jlXYX

    — Reorx (@novoreorx) August 10, 2022
     ↩︎
  3. From twitter.com/novoreorx/status/1542889584388100096:

    收到表妹送的游戏,开心 pic.twitter.com/vA0WcEAYhP

    — Reorx (@novoreorx) July 1, 2022
     ↩︎
  4. From twitter.com/novoreorx/status/885774936161357828:

    收拾桌面的文件,发现上个月通关 Rogue Legacy 的截图,算算这游戏我一共完了有 3、4 年了呢。 pic.twitter.com/NH0lere5Jq

    — Reorx (@novoreorx) July 14, 2017
     ↩︎
  5. From twitter.com/novoreorx/status/1552557062538022913:

    最近 Stray 大火,虽然这是款很优秀的游戏,但我心里却不是滋味,因为它和我2020年初就关注的一款游戏 Little Kitty, Big City 太像了。它的开发者只有一个人,所以进展缓慢,但光是预告片就深深吸引了我,我能感觉到作者在其中倾注的对猫咪的爱,相比而言 Stray 只不过是冷冰冰的猫咪走路模拟器。 https://t.co/y0bYNMaiW2

    — Reorx (@novoreorx) July 28, 2022
     ↩︎
  6. From twitter.com/novoreorx/status/1564296139821555712:

    还有什么游戏比 Disco Elysium 更适合学英语的呢? pic.twitter.com/k0aWX56oyA

    — Reorx (@novoreorx) August 29, 2022
     ↩︎
  7. From twitter.com/novoreorx/status/1538758661241470976:

    没想到在 Steam wishlist 里躺了几年的 CrossCode 竟然让我等到了 Switch 上的好价 pic.twitter.com/VGTQPL0F2i

    — Reorx (@novoreorx) June 20, 2022
     ↩︎
  8. From twitter.com/novoreorx/status/1546430597685727232:

    全程笑死,完全是我第一次玩 Ninja Gaiden Sigma 的感受,以我有限的魂系游戏经验来说,我觉得这个游戏还真是更难一些https://t.co/iLsbHkPOGu

    — Reorx (@novoreorx) July 11, 2022
     ↩︎
  9. From twitter.com/novoreorx/status/1563036228378800129:

    推荐游戏《奶奶的菜谱》,两位留学生的毕设作品。扮演一个孤独的社畜用手机向千里之外的奶奶学习做饭。想起了刚毕业那两年的自己。

    游戏: https://t.co/M5CcSIhQ9S
    文章: https://t.co/KjoS9AXMlM

    — Reorx (@novoreorx) August 26, 2022
     ↩︎
  10. From twitter.com/novoreorx/status/1560940880881917952:

    筛子、DBG,Roguelite、爬塔,这游戏有多好玩就不用我多说了吧,而且还是全平台免费,快来和🐸一起🎲

    https://t.co/d0xkLGJJjP pic.twitter.com/GkoQqrOwow

    — Reorx (@novoreorx) August 20, 2022
     ↩︎
  11. From twitter.com/novoreorx/status/1561736754834866177:

    又找到一个特别好玩的小游戏 Shogun Showdown,同样具有 Roguelite 和爬塔元素,玩家扮演一名武士,只身对抗左右不断涌现的敌人。游戏将武士的动作和武器融入到卡牌构筑中,策略非常多样,鼓励进攻,玩起来真的有种狭路相逢勇者胜的刺激感。适合摸鱼或编译的时候来两把。https://t.co/PXusFm2hws pic.twitter.com/4gLbOyMlbY

    — Reorx (@novoreorx) August 22, 2022
     ↩︎
  12.  ↩︎
  13. From twitter.com/novoreorx/status/1546336180144570369:

    一个黑人大叔带着两个会打枪的妹子维持世界和平,请问这是哪个动画?

    — Reorx (@novoreorx) July 11, 2022
     ↩︎
  14. From twitter.com/Kojima_Hideo/status/1551212854631878661:

    アマプラはあまり慣れておらず、「コーダ あいのうた」を探すのに戸惑った。探索中に「リコリス・ピザ?」風の名のアニメ「リコリス・リコイル」を偶然見つけた。暗殺ライセンスを持つ少女アサシンが喫茶店へ出向?!ありえへん設定と導入!日本アニメらしい?と、気づくと3話まで観た。4話は何処だ! pic.twitter.com/0M46gDJpfV

    — 小島秀夫 (@Kojima_Hideo) July 24, 2022
     ↩︎
  15. From twitter.com/novoreorx/status/1546081640069595136:

    本季新番 Call of the Night——彻夜之歌,台词也太棒了,这是多少人熬夜的人内心的写照:

    人为什么会熬夜
    是因为有想看的节目?有想做的事?还是因为对明天感到不安?
    这全都起因于一件事
    就是对今天不满足
    来熬夜吧,熬到对今天满意为止 pic.twitter.com/9GrHNgBhsL

    — Reorx (@novoreorx) July 10, 2022
     ↩︎
  16. From twitter.com/novoreorx/status/1551980629038092288:

    刚做了两件事情:
    1. 安装了 Minimal Theme for Twitter 扩展
    2. 将 Timeline 改成了 Latest Tweets
    算是重温 The Social Dilemma 之后的一点自我救赎吧。 pic.twitter.com/75E5hgsBJo

    — Reorx (@novoreorx) July 26, 2022
     ↩︎
  17. From twitter.com/novoreorx/status/1543942298815176705:

    路遇一只小蝉,把它放到了路边的草丛。不禁回忆起童年时光,夏夜可以在树上找到它们,动作笨笨的很听话,捉回家放到花盆里,等它们第二天表演金蝉脱壳 pic.twitter.com/Ev9KbwYPyh

    — Reorx (@novoreorx) July 4, 2022
     ↩︎
  18. From twitter.com/novoreorx/status/1544141872364666885:

    不去医院就诊,想买个阿莫西林都很不容易。在这里生活,你最好不要生病。

    — Reorx (@novoreorx) July 5, 2022
     ↩︎
  19. Top 10 Best Retro Handhelds Of 2021Where did these AWESOME Retro Consoles come from??? - Anbernic, Retroid, ODROID ↩︎

  20. From twitter.com/novoreorx/status/1552162963561521152:

    逛了会 The Apple Store Time Machine,才知道苹果曾有 iBook 这款产品存在。

    https://t.co/OfvJnzCQFZ
    https://t.co/NCAvzJ1Ai4 pic.twitter.com/xzVRtAoEnG

    — Reorx (@novoreorx) July 27, 2022
     ↩︎
  21. From twitter.com/novoreorx/status/1553324876739792896:

    在 Android TV 上用 Plex 快一年了,遇到过一些番剧无法直接播放必须转码的情况,但我的 NAS 转码很慢,非常卡顿。后来尝试发现这些视频文件用 VLC 走 SMB 是可以正常播放的,便怀疑问题出在 Plex 客户端上。今天换 Jellyfin 试了下,将播放器换成 LibVLC 之后,果然所有视频都能流畅播放了! pic.twitter.com/CdwjSPVO4Z

    — Reorx (@novoreorx) July 30, 2022
     ↩︎
  22. 参考 Google (web.dev) 的介绍MDN 的介绍 ↩︎

  23. From twitter.com/novoreorx/status/1548366397528764416:

    之前一直以为 Vite 是 Vue 生态专属,没想到是通用的前端开发和构建工具,今天试着用它进行 vanilla HTML + JavaScript 开发,无论是开箱即用的流程,还是清晰的文档和 API 设计,都给人非常好的使用体验。https://t.co/n4oO2RvNfp

    — Reorx (@novoreorx) July 16, 2022
     ↩︎

种草无线便携路由器

2022年6月30日 00:00

时间回到两个月前的某天,和好友 @iwendellsun 一起在咖啡馆工作时,他提出了一个问题:有没有一种路由器,它可以先连接一个已有的 Wi-Fi,再发射一个新的 Wi-Fi 供自己的设备连接?

我立刻理解了这个诉求的来源。咖啡馆、图书馆等场所的公共 Wi-Fi 一般都有身份验证机制,连接超过一个设备就会感觉很不方便,更不用说每个设备都还要独立设置代理;如果多人在一起工作,联机调试很可能会遇到公共 Wi-Fi 的端口限制而无法成功。我们对这个需求讨论了一番,但两人都缺乏对这类产品的认识,便不了了之。

最近在 Twitter 上看到 @酱紫表 分享了一款产品,突然意识到这可能正是我们想要的东西:

Twitter

收到了一款非常有趣的产品— GL-AXT1800,我喜欢这款路由器的地方实在有点多:迷你的体积带出门很方便、高通 IPQ6000 的 CPU 性能足够强大、出厂自带 openwrt 和 uboot 真的方便、type c 电源口、支持 usb 3.0 还有 Wi-Fi 6、还自带 pwm 风扇。 pic.twitter.com/5gAFEdiGkH

— 酱紫表 (@pengchujin) June 29, 2022

虽然没有提及这款名为 GL-AXT1800 的路由器是否可以连接已有 Wi-Fi,但既然设计的这么小巧便携,应该是针对出门在外的应用场景吧?我如此寻思,注意到它在 GL.iNet 的产品矩阵中属于 Travel Router 这一分类,于是顺着这个关键词进行了一番搜索,果然发现这类产品大都有着和我们的需求一致的应用场景。

我随后去阅读了 GL.iNet 的官方使用手册,弄清了 GL-AXT1800 所支持的四种连接互联网的模式:

  • Connect to the Internet via an ethernet cable

    通过网线连接互联网。GL 的路由器都有一个 WAN 口,这种用法与通常的家用路由器一致,连接在光猫或已有路由器的后面。

  • Connect to the Internet via an existing Wi-Fi

    通过已有 Wi-Fi 连接互联网。这就是本文一开始所提到的在咖啡馆中的使用场景,将公共 Wi-Fi 扩展成为相对安全可控的局域网。

    一些公共 Wi-Fi 要求对手机号/身份证等进行验证方可连接互联网,使用无线便携路由器是否会影响验证流程呢?我初步判断是不会,这种验证发生在连接 Wi-Fi 时的密码验证之后,此时无线便携路由器已完成了连接已有 Wi-Fi 并创建新的局域网的工作,假设公共 Wi-Fi 的 IP 是 192.168.1.1,无线便携路由器是 192.168.50.1,我们访问互联网触发的对 192.168.1.1 验证界面的访问是完全没有问题的,类似在家庭双层 NAT 环境下访问光猫的管理界面,DHCP 会处理好不同网段 IP 的访问关系。

  • Connect to the Internet via usb tethering

    通过 USB-A 口连接手机,使用手机的移动网络连接互联网。这种模式需要打开手机的热点功能,使用有线以降低传输损耗。

  • Connect to the Internet via usb modem

    通过 USB 调制解调器连接互联网。这种模式将只能插在笔记本电脑上的传统的无线上网卡变成可供多人使用的 Wi-Fi。

这些模式基本可以定义出 Travel Router——无线便携路由器的主要功能,读者可思考其中是否有符合自己需求的功能,从而对这种产品做出种草或拔草的判断。

同类产品介绍

说完了什么是无线便携路由器,我们再来看看有哪些可供选择的同类产品。

单板机 DIY

弄清楚产品功能之后,我发现家里被用作软路由1NanoPi R2S 是可以成为无线便携路由器的,只不过还需要对系统和配件进行一些 DIY 配置。

一个未经雕琢的 R2S 长这样:

NanoPi R2S original

插上一个外置的无线扩展模块,它从结构上就与 GL-AXT1800 一般无二了:

NanoPi R2S with wireless adapter

Note 1: 不过这样使用也有局限,由于 R2S 只有一个 USB-A 口,当扩展为 Wi-Fi 天线后,就无法实现上文提到的 USB tethering/modem 模式了。

Note 2: 据推友 @q1ngyang 告知 2,R2S 网卡方案有点麻烦,固件大多用不了无线网卡或无法驱动 AC,因此 R2S 不见得是最好的选择,若要使用 SBC 进行 DIY,还请读者在多方考察后慎重行事。

实际上树莓派等其他 SBC 也可以用同样的思路配置实现,不过我更喜欢 NanoPi 的外形和亲民的价格(去年好像涨价了),感兴趣的读者可以在淘宝认准 FriendlyElec 官方店购买。

固件方面,使用 OpenWrt 或其发行版即可,通过 Wi-Fi 连接互联网是 OpenWrt 的基础功能之一,官方文档对此有着非常详细的说明: Wi-Fi extender / repeater / bridge configuration.

纯硬件产品

GL-AXT1800 即属于此类,在硬件层面已经高度集成和完善,但固件、代理等还需用户自行配置。GL.iNet 系产品的优点之一是自带基于 OpenWrt 开发的固件 3,功能足够强大,省去了自己刷的麻烦。

下面列举 GL.iNet 的其他几款 Travel Routers:

  • GL-MT300N-V2

    昵称 Mango 芒果,有着鲜明的颜色和与 R2S 近似的小巧外形。电源输入是 5V/2A,相比 GL-AXT1800 的 5V/4A 对充电头更加友好。

    GL-MT300N-V2, AKA Mango

  • GL-AR300M

    黑色并升级了 CPU 的 GL-MT300N-V2。

    GL-AR300M

  • GL-USB150

    传统 U 盘的大小,可以直接插在充电器 USB-A 口上使用。

    GL-USB150

其他品牌中,TP-Link 有一款 TL-WR902AC 和 GL-MT300N-V2 类似,支持刷入 OpenWrt 4.

TP-Link TL-WR902AC

硬件 + 服务

推友 @BigEyeSmolMouth 向我分享了梅花VPN 5,使我得知有这类产品的存在。它们就像网易的 UU 加速盒,在硬件上内置了自己的网络代理服务,实现成软硬一体化的翻墙 Wi-Fi。

在我看来,这种产品优缺点都很明显。优点是开箱即用,省事不折腾;缺点是可控性差,无法更换代理提供商,且商品的溢价较高,其硬件性能不会有多少剩余空间。从安全的角度考虑,这样一个黑盒设备也无法让人信任。

梅花M3家用版

梅花M3便携版

官网的 spec 中没有关于 CPU、固件、协议等信息的描述。

相似但不同的产品

看过上文对无线便携路由器的介绍后,你是否有种似曾相识的感觉?没错,其实有一些与之类似但却不同的产品存在,平时没有用过也多少见过,这也是我在一开始未能准确把握自己想要什么产品的原因,它们都存在硬件或功能上的共性。下面我将针对两种可以明确定义出功能边界的设备进行阐述。

Wi-Fi 信号放大器

也有人将其称为「无线中继」,比较准确的英文名称应该是 Range Extender。这类产品的核心用途是接收已有 Wi-Fi 的信号,作为 AP 发射出相同的 SSID,以达到延长其信号范围的目的。一般用在家庭、办公室等空间较大、Wi-Fi 信号容易衰减的环境。

它和无线便携路由器在接收 Wi-Fi 信号这件事情上使用了相同的技术 WDS,但一个是扩展已有的 LAN,一个是创建新的 LAN,用途的差异使两者最终呈现出不同的产品形态。可以把 Wi-Fi 信号放大器看做特化的无线便携路由器。

TP-Link 有很多此类产品,它们的外形通常像耳朵长着天线的机器人🤖

TP-Link RE605X

随身 Wi-Fi

也叫「无线上网卡」,比较准确的英文名称应该是 Mobile Wi-Fi Router (or 4G Wi-Fi Router)。这类产品的目的是使用 3G/4G LTE 创建随时随地可供使用的 Wi-Fi。分为内置 eSIM 卡和依赖外部 SIM 卡两种。

国内的随身 Wi-Fi 一般都是内置 eSIM 卡的,外形像 U 盘或充电宝,在设计上并没有提供多少可定制性,但由于其普及程度非常高,广大 DIY 爱好者早已将其研究通透。@酱紫表 分享过一篇 4g 随身 Wi-Fi 刷 openwrt 变成软路由 教程,成品展示在这条推文中。

其实在刷上 OpenWrt 后,随身 Wi-Fi 就等同于上文中 Connect to the Internet via cellular 模式下的 GL.iNet,因此它同样可以看做无线便携路由器的特化版。

Netgear 的 MR1100 则是插卡的,黑色金属质地体现出服务器特有的冷酷和科技感,还有着让我无法抗拒的数据指示屏。

Netgear MR1100

另一款值得一提的产品是 GL.iNet 的 GL-E750,它同时具备无线便携路由器和随身 Wi-Fi 的功能,并且可以通过内置的 7000mAh 供电,相当于 GL-MT300N-V2 + 上网卡 + 充电宝的 all-in-one 组合。在咖啡馆可以连接公共 Wi-Fi 并接线充电,在户外则可以使用移动网络不依赖电源,简直是理想中的产品。但一般来说,越是什么都会,越可能什么都不精,GL-E750 具体表现如何,只有真正入手使用后才能知道。

GL.iNet Mudi (GL-E750)

结语

在移动办公越来越广泛的今天,无论你是时常出差办公的技术工作者,还是四海为家的数字游民,无线便携路由器都是值得了解和尝试的产品。数据和隐私是无价的,当连接到公共 Wi-Fi 时,我们的设备便存在被攻击和窃取数据的风险,而通过无线便携路由器,我们可以在一个可控的局域网中管理设备、增加防护,获得更安全的办公环境和更高效的生产力。

本文仅对一些无线便携路由器做了简单的列举,不涉及传输和加密性能的测评,由于我还没有实际使用过,因此无法对这些产品的好坏做出评价,读者将本文当做科普知识的分享即可。在未来我会补充一篇实际的产品使用体验,如果你使用过这一类产品,欢迎在评论区或 Twitter 分享你的经验。

Revision

  • 2022-06-30: created
  • 2022-07-02: add GL-E750
  • 2022-07-09: update R2S notes
  • 2022-07-10: 添加关于「使用无线便携路由器是否会影响公共 Wi-Fi 手机号验证流程」的描述

更换博客评论系统

2022年5月29日 00:00

告别 Disqus

博客建成也有一段时日了,渐渐也会收到一些读者的评论,在和大家交流中学到了很多新的知识,我很喜欢这种感觉,把评论看做我和读者之间重要的交流渠道。有一天我收到了 @geekdada搭建 umami … 下的留言:

非常感谢推荐。不知道作者有没有想把 Disqus 换掉,又臃肿又收集数据。

于是我开始留意起 Disqus 的使用体验。其实我在 2013 年就注册了 Disqus,用在了自己最早的一版个人网站上,对它也算是有些感情,这也是一开始快速实现时选择它的原因。

我打开 Chrome Dev Tools,刷新文章页,果然看见一大堆请求,一共有 94 次,这可太恶心了!且不论它收集了哪些统计数据和用户隐私,光是数量之多就让有代码洁癖的我无法忍受。

再试探性地关掉 ublock,这下更不得了,显示出有半个屏幕之大的广告区域,充斥着猎奇的图片和 clickbait 标题的链接。去设置页看了下,果然只有付费用户才可以关闭广告,但接连被恶心到,我已经彻底决定抛弃 Disqus。

就像之前从 Google analytics 换到 umami 那样,我踏上了寻找新的评论系统的旅程。

评论系统的选择

经过一番调查,我大致将评论系统分为了如下几类,每类都放上我认为最好的几种可选项,为同样带着自己的需求寻找评论系统的读者提供参考。

  • Full-fledged

    全功能的评论系统,从前端的 UI 组件到后端的 API、数据存储都完整实现。其下又分为 SaaS 和 Self-hosted 两类。

    • SaaS

      Disqus 就是属于这一类产品,优点是配置便捷,不用操心;缺点则是可能会收集隐私数据,缺乏可控性,并且价格都比较贵。

      • Commento: 目前是我最喜欢的一个 SaaS 评论系统,界面简洁清爽,能适应大部分博客的主题样式,无广告和追踪。价格为每月 $10。
      • CommentBox: 我很早收藏过的一个评论系统,和 Commento 一样的 slogan 和价格,就是功能和设计看起来要弱一些。
    • Self-hosted

      与一般的互联网服务不同,评论系统中 Self-hosted 的比例反而比 SaaS 化的大很多,这大概是因为自建博客的市场比较小众,且使用者也多为喜欢造轮子的程序员。Self-hosted 虽然较为麻烦,但优势也非常明显——由开源带来的高可控性、高定制性,并且近年来 PaaS 平台的蓬勃发展大大降低了部署成本,不需要拥有服务器便可一条龙 0 元购搭建起来。因此如果你有一定的技术基础,非常建议尝试自部署一套评论系统。

      • Remark42: 我最喜欢的一个 self-hosted 评论系统,也是本文接下来所要讲解的对象。
      • Isso: 使用 Python 开发,数据库为 SQLite。界面复古而简洁,适合 minimalist。Isso 可以配置评论自动通过,或者先审核再通过。
      • Cusdis: 我很佩服的开发者 Randy 的作品,也是使用 SQLite 作为数据库,就像是 Isso 的 Nodejs 翻版,但界面更加美观。与 Isso 不同的是,Cusdis 要求评论必须通过人工审核才可以显示,提供了 Email 和 Dashboard 两种审核方式。
      • Commento: SaaS Commento 的开源版,使用 Go 开发,支持各种 social auth 登录,甚至可以配置 SSO。但这个项目已经停止更新一年多了,如果你有兴趣,可以考虑使用社区 fork 维护的 Commento++,作者的更新还挺活跃的。
      • Twikoo, Artalk, Valine: 三者都是面向国内用户所开发的评论系统,有很多相似之处,因此放在一起介绍。与上述其他项目不同的是,三者的接入方式并非 iframe,而是直接操作网页的 DOM 元素。
        • Twikoo 主要支持腾讯云 CloudBase,也可以部署到 Vercel。数据库是 MongoDB。更新比较活跃
        • Artalk 使用 Go 开发,无部署环境限制,支持常见的 RDBMS。更新近期较为活跃Artalk 的作者 qwqcode 去年高考步入大学,开发 Artalk 的时候刚上高一。在三年里独立完成 Artalk 从 PHP 向 Go 的架构转变,TypeScript 也写的贼溜,现在的年轻人真是越来越强了
        • Valine 似乎只支持 LeanCloud 部署,已经很久没有更新了。
  • Proxied

    类似 full-fledged,有自己实现的 UI 和 API,但数据存储依托于一个已有的其他服务。我个人并不偏爱此类应用,一者登录方式被限制在其所依托的服务上,再者数据也不好维护。

    • utterances: 应该是最早的基于 GitHub 的评论系统,提供了一个用于接入评论的公开服务。
    • giscus: utterances 的 fork 版,增加了自部署的能力。
  • IM Plugin

    由即时通讯服务提供的插件。这是个顺理成章的思路,对于即时通许服务商来说,可以利用已有的基础设施提供更多的 user engagement,何乐而不为呢。其中最为知名的当属 Facebook,Google 曾经也有,但和 Google Plus 一同被埋葬了。这种评论系统也存在登录方式单一的问题,请谨慎选择。

    • Comments.app: Telegram 提供的网站评论插件,使用简便,界面美观,支持 dark mode 和 accent color。
    • Discussion Widget: 同样是 Telegram 的服务,但与 Comments.app 的不同在于,Discussion Widget 原本的设计目的是将 Channel 消息中的评论嵌入到网页中,但我发现这同样是一种评论系统,并且多出了将评论对应到 Channel 消息的功能。我在探索中已经顺便将 Discussion Widget 实现到了博客主题中,你可以在这个页面中预览和尝试: Commenting System: Telegram Widget
    • Meta Comments Plugin: Meta (Facebook) 的评论插件,我的社交圈里基本没人使用 Facebook,因此不予评价。
  • Static

    静态评论,即评论作为静态网站原始数据的一部分,跟随网站的构建流程生成到页面中。不同于其他评论系统可以灵活嵌入到任何页面,静态评论只能用于静态网站中。评论的数据是否应该属于网站本身呢?这是一个非常两极分化的问题。

    • Welcomments: 静态评论 SaaS 服务,需要用户绑定自己网站的 GitHub 仓库。当有人评论时,Welcomments 会生成一个 commit 包含评论的数据提交到仓库中,触发 GitHub 的自动构建流程,将评论更新到静态网页中。Welcomments 支持在它的网站上进行评论审核,还接入了 Akismet anti-spam 服务,每月 $6,年付减为 $4。
    • Staticman: 更为强大的 Welcomments,并且是 self-hosted。Staticman 支持更多的代码托管平台,并且可以通过 pull-request 的方式审核评论,这才是基于 Git 的工作流应有的样子。但 Staticman 已经有 2 年没更新了,在进行更深入的调研之前,不建议作为第一选择。
    • 手动维护: 任何手动维护评论的方式。有的博主会在网站会留下邮箱、即时通讯号等联系方式,让读者将想要评论的内容发送过去,然后将评论内容手动更新到文章的评论区。这种方式虽然简陋,但也不失为一种最简单的实现手段,毕竟评论的本质就是交流。

我的选择: Remark42

基于如下这些原因,我在众多评论系统中选择了 Remark42。

技术层面

  • 它使用 Go 语言实现,我很难抗拒二进制单文件的诱惑。
  • 它使用基于文件的数据库。我最喜欢的数据库是 SQLite,单文件无任何依赖,Remark42 使用的 boltdb 也是基于文件,对我来说和 SQLite 一样美好。
  • 它有较为现代化的前端技术栈,使用 TypeScript, Preact, Postcss 开发。
  • 它是一个成熟的开源项目,文档全面、开发时间长,基本上我的任何问题都可以在文档和 Issues 中找到解答。
  • 它的 API 非常完善,比如导入导出都可以用一行 curl 来完成。在其他方面作者也显示出很高的技术品味

视觉层面

Remark42 界面简洁美观,虽然比 Isso 和 Cusdis 复杂,但依然保持了足够的适应性和可塑性。

Remark42 简洁的界面

功能层面

我其实不喜欢 pre-moderation,即评论必须先通过审核才可以显示,这会给我带来额外的工作量,并且评论者也没法得到即时的效果反馈。但我也不想开放无审核的匿名评论,给自己带来被评论机器人攻击的风险。

我想到的折中方案是,首先提供尽可能多的 social auth 登录方式,比如 Twitter, Google, GitHub,这些 Remark42 都支持;再提供一种基于邮箱的认证方式,这是 Remark42 最大的亮点——Passwordless Authentication。评论者输入邮箱,点击 login,Remark42 会向其发送一个含有 token 的邮件,评论者使用这个 token 完成登入便可以进行评论。

邮箱认证是我个人最喜欢的登录方式,因为社交账号登录总是会存在风险,服务会拿到可以访问用户数据的 access token,你不知道它会做些什么 1。而邮箱不存在用户数据,唯一的担忧是被 spam 骚扰,但有很多匿名邮箱和邮件转发服务帮助我们解决这个问题。使用邮箱登录进行的评论,在被回复后也会发送邮件提醒评论者,确保了双向沟通的流畅性2

Remark42 还有一个特别的地方,它没有 Admin 界面,或者 Dashboard,这得益于其清晰的设计理念和完善的功能实现。一般对于评论系统来说,Admin 主要用于查看和审核评论,但 Remark42 不需要审核,而对评论和评论者的操作(隐藏、删除、屏蔽)都集成在评论控件中。加上 API 简单易用,去掉 Admin 不仅没有让人感到不便,反而成为一个值得一提的 feature 了。

最后,Remark42 在设计上就支持 multi-site,只需要部署一个实例,便可以为多个不同域名的站点提供评论服务。

隐私层面

Remark42 的标语是「Privacy-focused lightweight commenting engine」,而它的确在隐私这件事情上做了许多事情。无追踪和广告自不必说,尽可能少的申请 OAuth 权限也只是基操,Remark42 还对用户数据做了这些考虑:

Users can request all information Remark42 knows about them and receive the export in the gz file.

用户可将自己在一个 Remark42 服务中的所有评论数据导出,多希望我高中时使用的百度空间也有这个功能。

Supports complete cleanup of all information related to user’s activity by user’s “deleteme” request.

用户可以通过 “deleteme” 请求将所有与自己相关的数据全部清除,类似近年来用户呼吁各大网站去支持的 opt-out 3

小结

Remark42 还有许多功能,比如评论的 RSS 输出、Telegram 通知接入、Webhook 等等,限于本文篇幅,就不做展开了。下一篇博客我会对如何在 Fly.io 部署 Remark42、自定义 UI、接入 CDN 等内容进行详细的讲解,让读者也可以快速搭建自己的 Remark42 实例,敬请期待。


  1. 虽然一个优秀的服务应尽可能少的申请用户权限(减少 scope),但遇到糟糕的服务申请过多权限,我们一个不留神点了 Allow 的风险依然存在。 ↩︎

  2. 经过尝试和搜索,发现 Remark42 的用户提醒是一项单独的功能(Subscribe),与是否用邮箱登录无关。我打算为邮箱登录实现一个「subscribe to notification」的勾选框,实现登录后即可自动订阅邮件提醒,见此 issue 下的评论。 ↩︎

  3. JustDeleteMe 列出了各大网站对删除账户数据的支持情况,并提供直达链接。 ↩︎

使用自动化工作流聚合信息摄入和输出

2022年5月25日 00:00

欢迎关注我的 Telegram 频道: Reorx’s Footprints。这篇文章讲述我为什么创建这个频道,以及如何用 n8n 将 Twitter, YouTube, GitHub, Douban 等服务的动态同步过去,实现个人数字生活的信息聚合。

前言

赛博空间构成了我的精神世界。

我的大脑从苏醒的那一刻开始渴求信息,大量新鲜的信息,它们比饮用水更早地进入我的身体。每天睁开眼睛,我做的第一件事情是拿起手机,逐个查看昨夜的消息提醒。起床之后,我会在马桶上浏览 RSS 阅读器和 Telegram 频道中的新闻,让思维复苏的过程伴随新知识的冲击。我已经习惯了如此,一边加载昨日的进展并思考今日的安排,一边点开链接,使用不同的工具进行阅读、收藏和记录。

一天之中的碎片时间,我也会不断地在 Twitter、RSS、Telegram 之间切换,这对我来说并不是信息焦虑,而是自然而然的生活习惯。在工作上,我也完全依赖于网络,在解决问题的过程中搜索阅读大量的网站,将之转化为代码和学习笔记。

我就像一台不停运行的收集器和过滤器,持续地消费、生产信息。但大脑被设计用于分析和联想,却不擅长高效精确的索引,这些信息散落在各个服务中,我可以想起一些关键词或模糊的上下文,但总是很难快速获取到。我需要一个能将它们聚合在一起的工具,或者工作流,这样既可以方便自己的回溯,也可以将这些经过提纯的信息分享出来,为和我兴趣接近的人提供另一个输入管道。

聚合什么信息?

不是所有在赛博空间产生的信息都需要被聚合。一些被动产生的如搜索记录、地理位置记录、个人健康信息等,他们可以用作 quantified self(这是另一个话题),但并不在我的考虑范畴内。

我要聚合的主要有两类信息,一类是是由我主动创造的,比如Twitter 上发表的看法、GitHub 上为开源项目提交的问题;一类是我主动收藏或标记的,Pinboard 上收藏的书签、YouTube 上点赞过的视频均属此列。它们有的直接流入我的知识库1,有的保存在线上服务中,作为知识的原材料储备,留待日后进行消化吸收。

考虑到分享的目的,这些信息中还要去掉功能性的部分,只保留我确认有价值的那些。比如 Twitter Like 有时会用于社交目的,作为一种赞赏的回应;YouTube, Spotify 中各种 playlist 很多只是为了满足收集癖,不一定具有普适性;豆瓣的想看、想读只是待确认的 enqueue,只有看过、读过才是值得推荐的。

内容来源

基于以上考虑,我将自己主要摄入和产生信息的来源进行了整理,得到如下列表:

  • Telegram

    我在 Telegram 中分享一些一闪而过的想法、菜谱,转发浏览其他频道时看到特别有意思的内容。

    • Fleeting thoughts 💭
    • Recipe 🍳
    • Forward 🔄
  • Twitter 🐦

    我的所有 Tweet 和 Retweet 都经过认真思考,是希望被阅读的内容创作。

    • Tweet
    • Retweet
  • Instapaper 📖

    我这样管理我的待看列表:对于没确认是否有价值的那些,我会加入 TODO 或 Telegram Saved Messages。只有当我认真阅读过并认为值得再次阅读,我才会将其加入 Instapaper 中。

  • Blog 📝

    我的个人博客,通过 RSS 发布更新。

  • Music 🎵

    我主要使用 Spotify 听歌,类似 Instapaper,只有我愿意反复听的歌曲才会加入 Liked Songs,可以看做是我基于个人音乐品味的推荐。

    • Spotify
  • YouTube ▶️

    YouTube 是我探索世界无限可能性的地方,it’s about everything and nothing。每个 Like 过的视频都有着让我会心一笑或拍案叫绝的亮点。

  • Bookmark 🔖

    我从 2013 年开始使用 Pinboard,虽然 Pinboard 的社交属性很弱,但我一直坚持用 private/public 属性来区分我的收藏,所有 public 的书签都具备一定的分享价值。

    • Pinboard
  • GitHub

    由于对 Pinboard 的重度使用,大部分 GitHub 项目都收藏在 Pinboard 中,Star 的是我觉得值得鼓励的项目。每个 Issue 和 Pull-request 我都有认真撰写,他们是我参与开源活动的记录。

    • Star 🌟
    • Issue ⚡️
    • Pull-request ⤴️
  • Douban

    我主要用 Douban 记录看过的电影和书。如上文所说,这里只分享在看、看过的电影和在读、读过的书籍。后续考虑对评分进行过滤。

    • Movie 📺
    • Book 📖

展现形式

我选择 Telegram Channel 作为信息聚合的目的地。一方面我许多信息都来源于 Telegram ,另一方面它也非常易于使用和接入。

Telegram 以下几种功能极大程度地丰富了信息的展现形式2

  • 链接预览

    Telegram 会将消息中附带的链接进行抓取,将摘要和图片显示在消息下方。

  • 富文本格式

    Telegram 消息支持 Markdown 和 HTML 两种富文本格式的解析,可以实现粗体、斜体、下划线、超链接、行内和块级代码等样式。

  • 标签索引

    Telegram 的消息支持 # 为前缀的标签,在频道/群组内点击标签即可对消息进行筛选,这让结构化的信息索引成为可能。

实现自动化同步

我使用 n8n 来实现整套信息收集和聚合的自动化工作流3。下面将对我的操作方法和配置进行讲解,参照这些说明,你也可以快速搭建一套属于自己的自动化信息分享系统。

n8n 概念说明

  • workflow

    用于定义一条自动化工作流,由多个 node 以及 node 之间的指向关系构成。

  • node

    构成 workflow 的基本单位,每个 node 可以完成一个独立的任务,比如定时触发、获取数据、筛选数据、发送信息等。

  • credential

    访问凭据,用于在访问在线服务接口时进行身份验证。workflow 内只存储 credential 的 id,只有在运行时才会获取其中的实际内容,这一机制确保了 workflow 在分享时不会造成凭据泄露。

安装 n8n

n8n 支持使用 Docker 进行自部署,它由 Nodejs 开发,使用 SQLite 作为数据库,没有其他外部依赖,因此理论上可以部署在任何 Docker 或 Nodejs 运行环境。

为了方便调试,我将 n8n 部署在家里的 NAS 上,你也可以尝试将其部署在 Railway 等 PaaS 平台。

正常情况下,参照 n8n 的 Docker Installation 文档即可完成初步运行。但在使用中,我发现了 n8n Twitter 接入的 bug,在对代码进行了修改后,不得不在本地构建 Docker 镜像 (见 n8n - Custom Image)。如果你需要使用 Twitter 接入,在这个 PR 合并之前,建议使用我构建好的镜像 reorx/n8n-custom

附上我的 docker-compose.yml 文件作为参考
version: '3'
services:
  n8n:
    #image: n8nio/n8n
    image: n8n-custom
    ports:
      - 5678:5678
    volumes:
      - /share/CACHEDEV2_DATA/Misc/AppData/n8n:/home/node/.n8n
      - /share/CACHEDEV1_DATA/homes/reorx/Misc_Backup/:/backup
    environment:
      - PUID=1000
      - PGID=1000
      - VUE_APP_URL_BASE_API=http://harrogath-local.com:5678/
      - WEBHOOK_URL=http://harrogath-local.com:5678/
      - GENERIC_TIMEZONE=Asia/Shanghai
      - TZ=Asia/Shanghai
      - N8N_LOG_LEVEL=verbose
    restart: unless-stopped

由于许多服务需要通过 OAuth 进行接入,我们运行的 n8n 需要有一个固定的访问地址,以便接收 OAuth callback url 的访问。我的做法是在局域网内将 harrogath-local.com 这个域名指向 n8n 所在的机器,并为 n8n 设置如下环境变量,使其显示的 OAuth callback 与域名一致。

VUE_APP_URL_BASE_API=http://harrogath-local.com:5678/
WEBHOOK_URL=http://harrogath-local.com:5678/

启动完成后,即可通过 http://harrogath-local.com:5678/ 访问 n8n 的 web 界面了。

创建 Workflows

我的每个 workflow 都分享到了 GitHub reorx/n8n-workflows 仓库中,通过复制 .json 文件的内容、粘贴在 n8n 的 workflow 编辑界面,即可对 workflow 进行复用。也可以使用 n8n 的命令直接将 workflow 导入到 n8n 的数据库中 (见文档 Import workflows and credentials)。注意无论使用何种方式导入,credentials 都需要重新配置。

New tweet to telegram

Link: n8n-workflows/workflows/new tweet to tg.json

这一 workflow 实现了每 5 分钟搜索一次我的 Twitter 时间线,将最新的 tweets 发送到 Telegram Channel。

第一个 node 是 Interval,它是一个 trigger,可以设置重复运行的间隔时长。

随后是 Twitter node,它通过 OAuth 连接了我的账户,使用 from:novoreorx 搜索语法来获取我最近的推文条目。

IF reply to other 是一个 IF node,通过条目中的 in_reply_to_screen_name 字段判断推文是否是对别人的回复,只有否才可以走向下一步。意味着只有单独创建或回复自己的推文才会被转发。

Set Retweeted node 根据条目中的 retweeted_status 字段,识别 retweet 条目,并将原推的 URL 拼凑出来,赋值给新的字段 retweetedretweetedUrl,以便后续使用。

2022-08-12 updated:

Set Retweeted node 在后续的维护中打开了 “Keep Only Set” 选项,只保留其中所定义的字段,实际作用已经与名称不符,称为 “Set properties” 更准确一些。

新增了 proxyUrl 字段,使用 vxTwitter 服务的域名 vxtwitter.com 替代 twitter.com,以在 Telegram 中有更好的预览效果

Function 是我写的一段 JavaScript 代码,它使用了 n8n runtime 内置的 getWorkflowStaticData 函数,记录每次更新的第一个条目 lastItemId,通过与上一次的记录进行对比,确保只有新的条目会被输送到下一个 node 执行。如果没有 lastItemId,则只返回第一个条目,避免冷启动时造成大量信息的无效转发。这段代码基本在每个 workflow 中都有用到,下面将不再赘述。

/* only return new items */
const staticData = getWorkflowStaticData('global');
const lastItemId = staticData.lastItemId;

console.log('lastItemId', lastItemId);
const firstItem = items[0];
let newItems = [];

function getId(item) {
  return item.json.id;
}

if (lastItemId) {
  for (const item of items) {
    if (getId(item) === lastItemId) {
      break;
    }
    newItems.push(item)
  }
} else {
  newItems = [firstItem]
}

staticData.lastItemId = getId(firstItem)
// Reverse the order so that items are sent from old to new
return newItems.reverse()

经过 Function 的处理后,有效条目会被送往 Telegram node,进行 message 组装,最终发送到频道。

n8n 的表达式 (Expression) 有一个可以预览的编辑界面,左侧会展示当前 node 从上一个 node 获取到的输入数据 (Input Data),点击即可将模板变量插入到 Expression 中。模板语法 {{ }} 中可以使用 JavaScript 语法,这里我通过 $json["retweeted"] 来决定 tag 为 #retweet 还是 #tweet。推文 URL 放在了 <a> 标签中,既可以触发 Telegram 的 link preview 功能,也避免展示太长的 URL 影响可读性。

2022-08-12 updated:

Twitter 的 search API 默认会将长推文截断,只有加了 tweet_mode=extended 参数才能确保显示全文,不过推文内容字段会从 tweet 变为 full_text。按下图所示添加参数后,请在 “Set Retweeted” node 中修改字段映射 texttweetfull_texttweet

Blog RSS to telegram

Link: n8n-workflows/workflows/blog rss to tg.json

从第二个 workflow 开始,我将只对大致逻辑进行说明,不再详细讲解每个 node 的实现方式,有兴趣的读者可以在 workflow 编辑器中自行查看。

这一 workflow 实现了每 10 分钟检查一次博客的 RSS,将最新的文章发送到 Telegram Channel。

RSS Feed Read node 填写了博客的 RSS 地址,Function 同样是实现了有状态的更新判断,唯一的不同是将 getId 函数改为从 item.json.guid 获取 item id。

Spotify like to telegram

Link: n8n-workflows/workflows/spotify likes to tg.json

这一 workflow 实现了每 1 小时检查一次 Spotify Liked Tracks,将最新的歌曲发送到 Telegram Channel。

YouTube like to telegram

Link: n8n-workflows/workflows/youtube like to tg.json

这一 workflow 实现了每 30 分钟检查一次 YouTube Liked Playlist,将最新的视频发送到 Telegram Channel。

这里需要注意的是,Liked 是一个内置的 playlist,因此其 ID 与自己创建的不一样,通过访问 YouTube get playlists API,得到其 ID 为 LL,顺利完成了这一 node 的配置。

Pinboard new bookmark to telegram

Link: n8n-workflows/workflows/pinboard to tg.json

这一 workflow 实现了每 5 分钟检查一次 Pinboard 最近的书签,将最新的书签发送到 Telegram Channel。

n8n 没有 Pinboard 支持,但我们可以通过 HTTP Request 直接访问 Pinboard 的接口。古怪的是 Pinboard 没有官方的 API 文档,于是我对一些开源的 Pinboard SDK 代码进行阅读,得到了我所需要的接口 https://api.pinboard.in/v1/posts/recent

Pinboard 接口返回的数据并不直接是一个列表,而是一个形如 {"posts": [...]} 的结构,这里用了 Item Lists 将 posts 字段提取出来作为 items 向后传递。

GitHub activities to telegram

Link: n8n-workflows/workflows/github activities to tg.json

这一 workflow 实现了每 10 分钟检查一次 GitHub 的动态,筛选出 star, pull-request 和 issue 发送到 Telegram Channel。

GitHub 可以通过 https://github.com/$username.atom 获取用户公开的动态信息,因此不需要复杂的 API 接入,直接使用 RSS Feed Read 即可获取到我们所需要的数据。

在 Function filter 中,我用正则为动态进行了分类,并组成最终所需的消息格式,代码如下:

/* filter items */
const newItems = [];

const regexes = {
  'star': /^reorx starred/,
  'pull-request': /^reorx opened a pull request/,
  'issue': /^reorx opened an issue/,
}
const tagSymbols = {
  'star': '🌟',
  'pull-request': '⤴️',
  'issue': '⚡️'
}

for (const item of items) {
  const title = item.json.title
  let tag = ''

  for (const key in regexes) {
    const regex = regexes[key]
    if (regex.test(title)) {
      tag = key
      break
    }
  }
  if (tag !== '') {
    item.json.tag = tag
    item.json.tgTitle = `${tagSymbols[tag]} ${title.replace(/^reorx /, '')} #github #${tag}`
    item.json.guid = item.json.id
    newItems.push(item)
  }
}
return newItems

这个 workflow 还有一个使用 GitHub API 的版本,但 RSS 可以获得所有动态,更加方便,最终胜出。

Link: n8n-workflows/workflows/github issues to tg.json

Douban activities to telegram

Link: n8n-workflows/workflows/douban activities to tg.json

这一 workflow 实现了每 10 分钟检查一次 Douban 的动态,筛选出看过、在看、读过、在读发送到 Telegram Channel。

Douban 也有一个隐藏的 RSS 订阅源 https://www.douban.com/feed/people/$username/interests,包含书影音的所有动态。

Function filter 与上一个 workflow 类似,这里做了一些优化,将正则和符号定义在一个 object 中。

const SYMBOL_MOVIE = '📺'
const SYMBOL_BOOK = '📖'

const tagInfoMap = {
  watched: { re: /看过/, symbol: SYMBOL_MOVIE },
  watching: { re: /在看/, symbol: SYMBOL_MOVIE },
  read: { re: /读过/, symbol: SYMBOL_BOOK },
  reading: { re: /在读/, symbol: SYMBOL_BOOK },
}

异常通知

n8n 作为一个自动化服务,在配置完成后,一般我们不会去主动查看系统的运行情况,因此需要有监控手段得知异常的发生,以便及时维护。

n8n 支持通过 Error Trigger 创建 Error Workflow,用于接收其他 workflow 的错误信息。这一步虽然不是必须,但我强烈建议创建一个全局的 Error Workflow,并在每个 workflow 的 Settings 中设置错误处理指向它。

我在 n8n-workflows/workflows/ERROR TO TG.json 中实现了将错误信息发送到 Telegram Group 的功能,效果如下。

使用 SaaS 自动化服务

对于一些 n8n 尚不支持,或配置比较麻烦的服务,也可以通过 SaaS 服务来实现自动化。IFTTT 是一个不错的选择,它应该是最早被广泛使用的自动化服务,免费用户可以设置 5 个 applet (即 workflow),同步间隔 1 小时。

下图是我在 IFTTT 中配置的 Instapaper 新文章推送到 Telegram 频道的 applet。

Integromat 曾是一个非常好用的 IFTTT alternative,有着现代化的交互界面,和更为灵活的定制功能,但后来 rebrand 成 make.com,变得非常难用,不仅加载速度缓慢,而且连 migration 都无法正常完成4。建议大家远离这个奇怪的公司。

曾经美好的 Integromat

和它出色的交互界面

结语

使用 n8n 的过程总体是非常顺畅的,虽然一开始就在 Twitter 上栽了跟头,好在我能看懂 Nodejs 代码,自己动手修复后一路坦途。

曾经我特别痴迷于自己实现工具、制造轮子,现有工具稍有不顺就要全盘抛弃,自己解决。后来我在 happy xiao 的忘记系统一文中看到 John Voorhees 的一段话,改变了我对使用工具的态度:

If there’s anything I’ve learned about productivity systems, it’s that the best one is the one that works for you. Approaching the problem is tricky, but my advice is to use an app first and adopt a system later. That way, you’ll have a better understanding of what your needs are before you dive in.

实现的结果才是最重要的,工具只是手段,不是目的。

这也让我对自己正在开发的产品有了更多的思考,它是一个用于信息前处理的工具,同样是为了解决信息的分散和封闭而做出的努力,我设想了很多功能要去开发,但哪些是核心需求,哪些有代替品,或者换个思路通过其他方式实现,则必须放在实际场景中去考虑,而不是凭自己作为开发者的喜欢来武断确定。

回到赛博空间这个话题,我一直觉得人类对电子设备的依赖是一个不可逆的过程,人脑与电脑的边界会逐渐模糊。这是当我们有了智慧、抛弃自然为我们提供的进化之路后,自己所选择的进化方式。正如 Ghost In The Shell 开篇所描绘的越来越近的未来:

企業のネットが星を被い、電子や光が駆け巡っても

国家や民族が消えてなくなるほど、情報化されていない近未来

而在这一天到来之前,我在精神上已经完成了自然人向 cyborg 的转变。

Revision

  • 2022-05-26: created
  • 2022-05-27: published
  • 2022-05-28: fixed “Function” node script items order by adding .reverse() to return items
  • 2022-08-12:
    • added notes about using tweet_mode=extended to avoid tweet text truncation
    • added notes about vxTwitter proxy url

  1. 我的知识库中有三个分类:「制品」、「技术」、「事实」。制品 (artifacts) 是人所创造的作品、产品,如一个开源项目、一个软件;技术 (techniques) 是完成一类事情的方法或经验,也可以叫做 know-how,比如做饭的菜谱、编程语言的技巧、健身动作说明;事实 (facts) 是对概念、词汇的客观解释,多数来源于维基百科的词条。这三个分类可以基本涵盖我摄入的各类信息。 ↩︎

  2. 预计微信还需要 100 年才能赶上 ↩︎

  3. self-hosted workflow automation 的另一个选择是 Huginn, 我没有尝试和对比,在看过它的项目页面,感觉 UI 非常简陋就放弃了。 ↩︎

  4. 见我在 Twitter 的吐槽: make.com 大概是我 10 年来用过最糟糕的服务 ↩︎

我关注的独立开发者们

2022年5月22日 00:00

独立开发者,AKA indie makers, solo developers, or quite-my-job makers,是一群特别的人。他们或厌倦了大厂上班做重复性的工作,或不甘于只作为别人设计的实现者,或想要挑战自己的创造力、寻求被动收入甚至实现财务自由,初始的动机虽然各异,但却有一个共同点:不满足于一件事情已有的做法和存在方式,想用自己的手去改变和创造,实现心中那个理想的产品。

An indie maker is an independent creator. They build projects with their own resources, often without funding, taking care of all aspects: development, design, marketing…

by What is an Indie Maker?

独立开发者有着两个特质,勇气和追求。放弃待遇优渥的工作,投身不确定的未来,是勇气;对已有的产品不满,用更高的标准要求打磨,则是追求。

我一直关注着一些独立开发者们,怀着敬意欣赏他们的作品,为他们的成就欢呼喝彩,从他们身上汲取勇气和追求的养分,让踏上同样道路的自己得到志同道合者的陪伴。这篇文章将我所知的独立开发者们列举出来1,让更多的人能看到他们的经历和作品,获得启发。

如果看到本文的你也是一位独立开发者(或者认识某位),欢迎在评论和推文中分享你的经历和作品。

ZH

EN

一些链接

本文不打算整理对如何成为一个独立开发者的建议或工具(以后有机会可以做下分享2),如果你看到这里感到意犹未尽,想了解更多关于独立开发的事情,可以对下面的链接进行探索。

Revision

  • 2022-05-22: created
  • 2022-08-11: added Hawstein in ZH

  1. 本文列举的独立开发者都分享过自己的经历,这是对我来讲特别重要的一点,但并不是只有分享经历的独立开发者才值得关注。每个独立开发者都是人间宝藏。 ↩︎

  2. 但正如我的一个推文所说,专注于具体创作的人在输出经验方面的精力会比较少 https://twitter.com/novoreorx/status/1528309074550747138 ↩︎

❌
❌