Telegram 在线时间统计 发布于 2023/08/14主页

最近糊了一个类似 Github 贡献墙的 Telegram 在线时间统计。目前已经作为公共服务推出了:@Online_Stats_Bot

效果可以直接打开链接查看。

后端

Telegram 会主动推送账号联系人的在线状态更新,接收更新并存储即可。存储后端我选择了 Elasticsearch。

为了计算在线时长,我选择的是计算 online 事件和 offline 事件之间的时间。对于无法闭合的落单事件,则默认这个事件发生的时刻在线了1秒钟。

数据结构

为了把数据塞进 URL 参数(并且避免了后端的需要),数据应被压缩的尽可能短,这样才方便链接的分享且看着好看。

首先来看原始数据:[{"timestamp": 1691971200, "online_seconds": 300}, {"timestamp": 1691974800, "online_seconds": 100}, ...]

有一个很明显的问题就是这里面其实这个 key 是不必要的,key 的长度甚至比数据本身还要长,可以改成元组:((1691971200, 300), (1691974800, 100), ...)

再观察这个数据,还会发现其实时间戳和秒数也包含了大量冗余信息。时间戳只需要精确到小时,而且时间戳是连续的,因此并无必要每一个时间戳都单独表示一次,只需要有一个起始时间时间戳,并且第0个小时、第1个小时这样就可以了。此外,由于展示时只需精确到分钟,数据中的秒数精度显得多余。

稍加优化,并且把 value 的单位改为分钟,我们得到了:(1691971200, (5, 2, ...))

如果我们要把这样的数据传进 URL 参数,实际上也是可以接受的:https://xxx.yyy?offset=1691971200&data=300,100,...

不过,显然还有更好的方法。URL 参数作为字符却只拿来传数字实在是亏麻了。我们可以把数据打包成二进制然后传 base64 编码后的二进制。

由于 value 是一个小时内的分钟,因此其范围肯定在0-60内。最接近这一范围的是 uint6,其范围为 0-63。由于0实际上表示在线了0-30s,所以还需要用 63 来表示这一小时为离线。还有两个数字没有用上,为了进一步节省空间,将62定为这一小时及下两个小时共三个小时均为离线;61为这以小时及下四个小时共五个小时均为离线。

编程语言的标准数据类型并没有 uint6 这种东西,这与 CPU 有关。我们可以把4个 uint6 组合成3个 uint8。按照大端序,则有 [AAAAAABB][BBBBCCCC][CCDDDDDD],其中ABCD代表四个 uint6 所占空间(6 bits),而中括号则代表 uint8 的边界。由于 24%4=0,(在不考虑61-62的情况下)一天的数据总是能不需要 padding 而组合成18个 uint8。而这也是为什么要把62定为代表下两个小时,61定为下四个小时。因为62可以省去2个 uint6,而一个61可以省去4个uint6,这样对齐就不需要 padding。如果有62落单,那么落单的62实际上并没有省空间,因为最后转换的时候还需要 padding 回去。

下面是由 ChatGPT 写的转换代码(以及 ChatGPT 写的代码展开栏):

转换代码(JavaScript)及测试代码