Repository navigation
Releases
知弦 0.54.0
知弦 0.54.0:群资源带上人数
guild.get、guild.list返回的群资源多带两个扩展字段:member_count(现有人数)、max_member_count(人数上限)。人数原本就在内核的群列表缓存里,只是转成 Satori 形状时丢掉了,这次不多发任何请求;读不出时不带- 配合 acumen 1.77.0 起的群目录:连上时翻一遍群列表,就把每个群的名字和人数都记下了。欢迎词里的
{member_count}原先从知弦这儿拿不到数字、总是空的,现在有了,而且不用为每个新人现问 - 需要重启手机后生效
知弦 0.53.3
知弦 0.53.3:入群事件复查补两处
- 新人的号码一时换不出、要重拉成员表时,原先在共用的定时线程上同步等(最多 4 秒);同时进来好几个人就会把线程占满,连回声过期这类别的定时任务都跟着慢。现在改到后台拉(同一个群同时只拉一份),之后按 0.5、1、2、4、8、12、20、60 秒查缓存,通常一秒内就能补发
- 「A 邀请 B 加入了群聊」这种灰条,原先取第一个人,会把邀请人 A 当成新人去欢迎。现在取「邀请」后面的那个人
知弦 0.53.2
知弦 0.53.2:入群事件少走两步
接着 0.53.1,把入群事件发出之前的每一步再削一遍:
- 入群灰条里本来就带着号码(
uin字段或名片链接里的uin=)时直接用号码,不再先拿 uid 去资料服务换——新人刚进群时那一换常常换不出来,0.53.1 之前就是这一步把整条事件拖到成员列表回调 - 灰条带着新人的昵称时,发事件前不再查成员表与资料服务补名字
- 0.53.1 的挂起补发照旧兜底:号码真要换的时候立刻重拉成员表,新人先开口就在他的消息之前补发
知弦 0.53.1
知弦 0.53.1:新人进群就有入群事件,不用等他说完好几句
问题
入群欢迎偶尔要等新人在群里说了好几句之后才出来。原因在实现端:QQ 推来的入群灰条只带新人的 uid,刚进群的人资料服务里常常还换不出 QQ 号;换不出时这条入群通知被整条丢掉,只能等成员列表变化回调补发——那条回调来得很慢,新人往往已经说了五六句。
客户端这边看到的顺序也就反了:先收到新人的消息,过很久才收到「他进群了」。
改法
- 认不出号码的入群通知不再丢掉,先挂起,带着灰条里的昵称:
- 立刻、1 秒、3 秒、8 秒、20 秒、60 秒各再查一次号码,第一次与第四次顺带向服务器重拉一份成员表(新人通常要重拉后才在表里)
- 这个人先在群里开口的话,消息里本来就有他的号码:在这条消息之前补发入群事件,客户端总是先看到「进群」、再看到他说的话
- 挂着的时候他退群了,这条入群直接作废,不会退群之后才冒出一条「进群」
- 成员列表回调仍然兜底;两路谁先到都只投递一次(沿用 0.49.2 的统一去重)
- 挂起超过 10 分钟还认不出人就放弃,不发
user_id=0的假事件
知弦 0.53.0
知弦 0.53.0:群管理补齐,资料卡点赞回来了
群管理
- 群详情
group_detail:群主、建群时间、人数与上限、群介绍、全员禁言到几时,以及本号在群里的身份、入群时间、接收方式、是否置顶,整理成固定字段 - 群设置
group_settings(群主或管理员):加群方式、入群问题与答案、成员能否邀请、能否被搜到、发言频率、群介绍。只改带了的那几项,其余不动;什么都不带就读当前设置 - 批量踢人 / 批量禁言
group_kick/group_mute:一次处理多人,踢人可带理由、可拒绝再次申请 - 转让群
group_transfer、解散群group_destroy(要再写一遍群号确认) - 群消息接收方式
group_msg_mask:接收并提醒、接收不提醒、收进群助手、屏蔽 - 会话置顶
session_top、拉黑friend_block - 一次改本号在多个群的群名片:
card带guild_ids[]或all: true,每个群单独限频,回执逐群列出
账号资料
- 一次改几项资料
profile_set:昵称、签名、性别、生日 - 在线状态
online_status:在线、离开、隐身、忙碌、Q我吧、请勿打扰 - 资料详情
user_info:昵称、备注、签名、性别、年龄、生日、地区、学校、注册时间、会员与等级,整理成固定字段 - 资料卡点赞
like:0.23 去掉钩子时一起下线的点赞回来了。改由本端编码手机 QQ 的VisitorSvc.ReqFavorite(JCE),经已有的裸 SSO 通道收发,不再需要钩子;失败时带服务端原文(比如对方设置了不许赞)
以上都在 internal/capabilities 的目录里,踢、禁言、转让、解散、群设置归为 manage。
验证
JVM 单元测试 37 项通过(新增 JCE 编解码与能力目录的用例)。新动作的内核调用签名按 QQ 9.3.80 安装包核对,群设置的写法照 QQ 自己的 TroopOperationRepo;要重启手机让新 .so 生效后才能在真机上跑。加群方式的名字与数字的对应、点赞的 WUP 包在真机上还没跑过。
知弦 0.52.0
知弦 0.52.0:转发、语音转文字、群荣誉、WebHook,以及 QQ 升级后的自检
新能力
- 原生转发
internal/message_forward:把已有消息原样转到别的群或私聊(可多个目标),走 QQ「转发」菜单那条内核调用,图片、文件、语音不重新上传 - 语音转文字
internal/voice_to_text:借 QQ 自带的「转文字」听写一条语音,结果写回那条消息,再问直接读缓存 - 待处理申请
internal/request_list:还没处理的好友申请、入群申请与群邀请,形状与推送事件相同,客户端重启后能补上错过的 - 群荣誉榜
group_honor:龙王、群聊之火、群聊炽焰、冒尖小春笋、快乐源泉。群荣誉页已改成前端渲染,改读它背后的qunapp/honor_*接口 - 名片卡
contact_card与消息元素<satori-qq:contact type="group|user" id="…"/>:发「推荐群 / 推荐好友」卡片 - 收藏表情
fav_emoji_list - 以上都在
internal/capabilities的目录里,acumen 这类按目录办事的客户端不用改代码就能用
协议对齐
- 实现了元信息 API 的
meta/webhook.create/webhook.delete:事件原样 POST 到登记的地址,带Satori-Opcode与Authorization: Bearer,与官方服务端同形;登记写盘,QQ 重启后接着推
修正
- 网页接口的 bkn 先按 p_skey 前 10 个字符算(qun.qq.com 前端自己的写法),整串算出来的会被荣誉接口判
csrf error;群精华、群公告同样受益 - 群文件列表:去掉按 UTF-8 硬解出来的二进制
md5/sha;上传时间从内核误放的uploadedSize归位到uploadTime;limit真的按条数截断;走了内核路径时说明 0x6D8_1 为什么没用上(oidb_error)
QQ 升级适配
- 模块记住上次运行的 QQ 版本,升级后第一次自检就在日志里写清「从哪版到哪版、接口面过了多少、缺什么」,
/healthz在升级后 7 天内带qq_upgrade - 反射找不到方法时,错误里直接写着新版同名方法的真实签名,也汇总在
internal/compat的observed.signature_mismatches scripts/qq-upgrade.sh:一条命令存新安装包、与上一版做 dex-diff、强制自检、清旧安装包;scripts/dex-sig.py按类名或方法名查安装包里的签名
验证
JVM 单元测试 36 项与只读协议探针 157 项通过(探针跑的是线上 0.51.0);群荣誉与 bkn 口径用真账号的网页凭据实测过。新动作的内核调用(forwardMsg、translatePtt2Text、名片卡、收藏表情)签名按 9.3.80 安装包核对,要重启手机让新 .so 生效后才能在真机上跑。
知弦 0.51.0
知弦 0.51.0:群文件、群精华、群公告读得出来了
现象
group_file_list对任何群都回空列表(all_file_count: -1),像是群里没有文件group_essence_list、group_bulletin_get一律报pskey unavailable: code=-1 no valid local guest ticket
原因
- p_skey 一直取错了地方:0.41 起退到登录服务的
guestA2ToPskey,它只给访客模式发票,正常登录的账号永远拿不到。依赖它的群精华、群公告(读与发)、发说说全部失效 - 群文件列表的内核请求漏填了
sortType(枚举字段,null 交给了 native),内核不回包;回包又按「第一条目的群号」分发,空文件夹、出错的回包没有条目,永远送不到——调用方等满 15 秒拿回空列表 - 精华读的是一个不存在的网页接口;公告接口的群号参数名写错(
group_code,应为qid),条目字段(feeds/inst)也认错了
改法
- p_skey 依次取:票据管理器缓存
TicketManager.getPskey(uin, domain)→ QQ 网页业务自己用的IPskeyManager.getPskey(String[], cb)(回调是混淆接口,按方法签名取类型、按参数形状认成败)→ 访客票。网页凭据另带TicketManager.getSkey,bkn 先按 skey 算、被拒再按 p_skey 算 - 群精华改走
qun.qq.com/cgi-bin/group_digest/digest_list,按页翻(page、page_limit);群公告改走带qid的announce/get_t_list,置顶的在前 - 两者回包规整成固定字段(谁发的、什么时候、谁加的精、正文、图片),公告正文解开 HTML 转义;失败带
error与原回包raw - 群文件列表先发
OidbSvcTrpcTcp.0x6D8_1(一问一答,不靠监听器),回不来或解出零条再走内核;内核请求补上sortType、sortOrder,回包按请求号收进信箱再取。条目统一成内核GroupFileInfo/GroupFolderInfo的字段名,外加type与回执里的source internal/compat的 pskey 一项改为检查IPskeyManager.getPskey
验证
真机验收要重启手机让新 .so 生效后做:internal/call 依次调 pskey(domains=["qun.qq.com"])、group_essence_list、group_bulletin_get、group_file_list(guild_id 取一个有文件的群)。
知弦 0.50.0
知弦 0.50.0:身份查询不再报旧的、不再冒充「普通成员」
现象
账号已经是群管理员,acumen 的入群验证却判定「不是管理员」、整个跳过。
原因
- 成员表拉下来之后就不再刷新:账号后来被设成管理员,
guild.member.get问自己,答的仍是几小时前那一行里的「普通成员」 - 成员表一时拉不下来(内核 4 秒没回调)时直接报错,调用方只能当自己不是管理员
- 消息事件里,发言者的身份说不准时(成员表还没拉)写成
member,接收方以为是查过的结果 - 内核推来的成员资料变更(
onMemberInfoChange)没有接,身份变了没人知道
改法
- 账号本人的身份改读群资料里的
memberRole:群资料由内核推送维护,被设成管理员时立刻跟着变;guild.member.get问自己、消息事件里自己的发言都以它为准 - 成员表一时拉不下来、问的又是账号自己时,先答身份(名字留空),不再报错
- 成员表拉了超过 10 分钟,
guild.member.get照常用缓存回答,同时后台向服务器要一份新的 - 接上
onMemberInfoChange:变更并进成员表;任何人身份变了(含账号自己),推guild-member-updated,带_type=satori-qq/role、_data.role与member.roles - 发言者身份说不准时消息事件不带
member.roles,不再写成member
知弦 0.49.2
知弦 0.49.2:新人进群就欢迎,不再等慢回调
新版 JSON 入群灰条被丢掉了
QQ 9.3.80 会用 subElementType=17 / busiId=1022(RELATION_GROUP_MEMBER_ADD)通知成员入群。实现端原来只从 JSON 灰条里识别戳一戳,入群则依赖 XML、groupElement 或 onMemberListChange:
- 即时到达的 JSON 入群通知被记为
unparsed grayTip后丢弃 - 只能等较慢的成员列表变化回调补发
guild-member-added - 下游欢迎逻辑本身没有合并等待,因此看到的就是「QQ 已显示新人进群,欢迎却很久才到」
改法
- 识别
1022,同时覆盖同语义的1015 / 1016入群变体,从items[]的uid(必要时从uin或名片链接)定位新成员 - 灰条里的显示昵称直接放进
member_name,不必为了欢迎再补一次资料查询;uid → QQ 号暂时解析失败时不制造user_id=0的假事件,仍由成员列表回调兜底 - 成员事件去重移到统一事件出口:即时灰条与慢成员列表回调无论谁先到,同一次入群只投递一次;相反方向会解除旧状态,退群后重新入群不会被吞
- 不增加轮询,也不在欢迎链路新增等待或网络 API 调用
知弦 0.49.1
知弦 0.49.1:发出去的 @ 是真 @
atNtUid 一直是空的
发消息时 <at id="123456"/> 一直带着 atType=2 与 atUid,但 atNtUid(QQ 的 uid)只从消息缓存里取。缓存里只记着在本进程里发过言的人,于是:
- 新入群的成员还没说过话,缓存里没有他的 uid,欢迎里的 @ 落下去是一段普通文字:客户端里点不开,对方也收不到提醒
- 消息发出去以后,这一段就定型了,不会再被追认成真 @
改法
- 缺 uid 时按「成员表缓存 → 资料服务」补齐,两步都是本地调用,不联网、不增加往返;补到就记进缓存,同一个人后续的 @ 直接命中
- 灰条里的成员变动(入群、退群、禁言、踢人)本来就带着当事人的 uid,转事件时顺手把 uin ↔ uid 记下来。入群欢迎紧接着发出的 @ 因此不必再查一次
- @ 后面的文字取成员的群名片或昵称(元素里带了
name就用它),取不到才退回号码 dumpSent的诊断行带上atType/atUid/atNtUid,能直接看出这一段是不是真 @
配合 0.48.2 的入群事件补全,客户端收到 guild-member-added 就能立刻发出真 @,不必先等 guild.member.get。