Twitter User Info
刷 Feed 时直接看到这个人是谁
刷 X 的时间线时,直接在推文旁边看到这个人是谁——简介、粉丝数、注册时间,以及你自己给他写的备注和标签。
X 原生的悬浮卡只有简介,而且不能加任何属于你的信息。这个扩展补的就是那一块。
它做什么
- 悬停出卡:鼠标停在头像或用户名上弹出完整档案。延迟可调,也可以改成按住 Alt 才出、或者只在点击时出。默认会把 X 自己那张悬浮卡关掉——内容重复,而且会跟这张叠在一起。
- 私人备注:给任何人写一句话(「搞 RAG 的」「面基过」「只看他技术推」),直接显示在 Feed 的用户名旁边。微信式的备注名,在 X 上终于有了。
- 标签:一个人可以打多个。逛自己某个列表的成员页时,成员会自动被打上以列表名命名的标签——把 X 那个半残的列表功能变成能用的标签系统。
- 导出导入:备注和标签是纯本地数据,一键导出 JSON,换设备导回去。
数据全部留在本地,不往任何服务器发东西。
数据是怎么来的
不额外发任何请求。 X 加载时间线时走的是自己的内部 GraphQL 接口,响应 JSON 里每条推文本来就带着作者的完整档案——简介、粉丝数、注册时间全都有。扩展做的只是把这份已经到了你浏览器里的数据截下来。
其他几条路都不通:主动调 X 的内部 API 有速率限制、高频会触发风控;官方 API v2 免费额度极低、收费贵得离谱;从 DOM 上刮的话,Feed 的 DOM 里根本没有简介,刮不到。
不打扰页面上的其他脚本
这是这个项目的硬约束。推特生态里有大量用户脚本——媒体下载、自动翻译、时间线增强——它们的原理跟这个扩展一模一样:hook fetch / XHR 去截 GraphQL 响应。也就是说必然跟它们叠在同一个 prototype 上,而且加载顺序不可控。同时它们还会按 DOM 结构定位推文元素。任何一处不克制的实现,都会让别人的脚本莫名其妙地坏掉,而用户只会觉得是这个扩展的锅。
所以:
- 一个 DOM 节点都不加。备注用
data-tui-note属性加 CSS::after画,伪元素不进 DOM 树。 - 不截事件。全程没有
preventDefault/stopPropagation,悬停触发不需要碰事件流。 - 不污染
<head>。样式走 Constructed Stylesheet(adoptedStyleSheets),不插<style>节点;也不给<html>加 class 或属性,开关靠重写样式表内容实现。 - 不常驻。悬浮卡宿主只在显示时挂载,关掉立刻摘除;
keydown/pointerdown只在卡片打开期间注册。 - 不广播消息。跨 world 通信用带命名空间的
CustomEvent,不用postMessage——监听message的脚本会全部收到。 - 网络钩子透明。
Reflect.apply原样转发,保留name/length/toString(),非 GraphQL 请求零介入,URL 存WeakMap而不是挂在实例上。
最后剩下的可见痕迹只有两处,都是功能必需:X 自己元素上的两个 data-tui-* 属性,以及悬停瞬间存在的那一个 Shadow DOM 宿主。
图标的一条硬约束
全平涂,不许出现渐变和投影。 图标要在 16px 下渲染,渐变会糊成脏色块,投影会变成一层灰边。
白底那圈描边不是装饰而是必要的:卡片本身是纯白,而 Chrome 亮色工具栏是 #F1F3F4,没有描边时卡的边缘会整个消失,只剩中间几个色块浮着。描边是纯色平涂,不违反上面那条。
安装
还没上 Chrome 应用店(需要 5 美元开发者注册费),所以走「加载已解压的扩展程序」。Chrome 同时禁止安装非应用店来源的 .crx,这是唯一的途径。
更新时下载新包覆盖原文件夹再刷新即可,备注不会丢——扩展 ID 是钉死的,跟文件夹路径无关。
技术
WXT + TypeScript,Chrome MV3,Firefox 也能构建。备注、标签、设置走 storage.local(量小、popup 能直接读写、一次拉进内存后渲染 Feed 时可以同步查);档案缓存走 IndexedDB。


- 上线
- 2026
- 我的角色
- 独立开发
- 技术栈
- WXTTypeScriptChrome MV3IndexedDB