SnowCat 雪猫底层全源码:NapCat 4.18.28 底座 + SnowLuma 能力移植 + V8 通道适配 9.9.37

26 包 monorepo(TS 全源码):
- snowcat-core: NT 内核 API 封装 + listener(含 9.9.37 六回调适配)+ Packet 发包
- snowcat-onebot: OneBot v11 全动作 + MCP Streamable HTTP 适配器 + 稳定线
- snowcat-webui-frontend/backend: 雪猫控制台(React+HeroUI)
- snowcat-shell: 单文件 bundle 出口(vite inlineDynamicImports)
- snowcat-native: napi2native / MoeHoo / ffmpeg / pty 运行时
- 56 项 SnowLuma 独门动作移植(qzone 全家桶/群相册/装扮/闪照/收藏)
- 工具件: 解包器/启动器/便携制作器/doctor

GitHub 关联已全量指向 DandanLLab/SnowCatQQ
This commit is contained in:
天天的鸟蛋蛋 committed 2026-11-12 06:27:36 +08:00
commit b9655a5ab1
1048 files changed
+130851

No files matched your search

@@ -0,0 +1,138 @@
/**
* SnowCat · 第三批动作组(SnowLuma 移植):群相册补/群管补/自定义表情/杂项
*
* 底层取舍(对账定谳 2026-11-11):
* - 群相册 get_group_album_list:走 NapCat 现役 Web 线 getAlbumList(qun_list_album_v2,已实装)
* - 群相册 Web 线媒体列表:getAlbumMediaListByNTQQ(NT AlbumService,已实装)
* - get_msg_emoji_likes:NT getMsgEmojiLikesList(已实装,补 OB11 动作壳)
* - get_friend_dress:NT getUserDetailInfo(已实装,补动作壳)
* - fetch_sys_faces:NapCat 无目录表,返回空集占位(SnowLuma 侧是协议自编目录,待后续独立移植)
* - get_group_todo_list/get_group_tags/set_group_reaction 等 OIDB 线:NapCat PacketApi 只透传裸包
* (send_packet 动作已在),SDK 级 OIDB 编解码层为后续独立批次,此批不重复造。
*/
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
import { ActionName } from '@/snowcat-onebot/action/router';
import { Static, Type } from '@sinclair/typebox';
// ─────────────── 群相册补 ───────────────
const AlbumListPayload = Type.Object({
group_id: Type.Union([Type.Number(), Type.String()], { description: '群号' }),
});
export class GetGroupAlbumList extends OneBotAction<Static<typeof AlbumListPayload>, unknown> {
override actionName = ActionName.GetGroupAlbumList;
override actionSummary = '获取群相册列表';
override actionDescription = '获取群相册列表(qzone Web 线 qun_list_album_v2)';
override actionTags = ['扩展接口'];
override payloadExample = { group_id: 123456789 };
override returnExample = [{ id: '1', title: '相册一' }];
override payloadSchema = AlbumListPayload;
override returnSchema = Type.Any();
async _handle (payload: Static<typeof AlbumListPayload>) {
return this.core.apis.WebApi.getAlbumList(String(payload.group_id));
}
}
const AlbumWebMediaPayload = Type.Object({
group_id: Type.Union([Type.Number(), Type.String()], { description: '群号' }),
album_id: Type.String({ description: '相册id(来自 get_group_album_list)' }),
});
export class GetGroupAlbumMediaListWeb extends OneBotAction<Static<typeof AlbumWebMediaPayload>, unknown> {
override actionName = ActionName.GetGroupAlbumMediaListOnly;
override actionSummary = '获取群相册媒体列表(Web线)';
override actionDescription = '经 NT AlbumService 读取群相册媒体列表(与 get_group_album_media_list 分页线并存)';
override actionTags = ['扩展接口'];
override payloadExample = { group_id: 123456789, album_id: '1' };
override returnExample = [];
override payloadSchema = AlbumWebMediaPayload;
override returnSchema = Type.Any();
async _handle (payload: Static<typeof AlbumWebMediaPayload>) {
return this.core.apis.WebApi.getAlbumMediaListByNTQQ(String(payload.group_id), payload.album_id);
}
}
// ─────────────── 群管补 ───────────────
const MsgEmojiLikesPayload = Type.Object({
group_id: Type.Union([Type.Number(), Type.String()], { description: '群号' }),
message_seq: Type.Union([Type.Number(), Type.String()], { description: '消息 seq' }),
emoji_id: Type.String({ description: '表情id' }),
emoji_type: Type.Optional(Type.String({ description: '表情类型(1-2,省略按 id 长度推断)' })),
cookie: Type.Optional(Type.String({ description: '翻页 cookie,来自上一页返回' })),
count: Type.Optional(Type.Union([Type.Number(), Type.String()], { default: 20, description: '每页数量' })),
});
export class GetMsgEmojiLikes extends OneBotAction<Static<typeof MsgEmojiLikesPayload>, unknown> {
override actionName = ActionName.GetMsgEmojiLikes;
override actionSummary = '查询消息表情回应列表';
override actionDescription = '查询一条群消息的表情回应(谁点了什么表情),可翻页';
override actionTags = ['扩展接口'];
override payloadExample = { group_id: 123456789, message_seq: 12345, emoji_id: '128077' };
override returnExample = { cookie: '', likes: [] };
override payloadSchema = MsgEmojiLikesPayload;
override returnSchema = Type.Any();
async _handle (payload: Static<typeof MsgEmojiLikesPayload>) {
const peer = {
chatType: 1,
peerUin: String(payload.group_id),
peerUid: '',
guildId: '',
};
return this.core.apis.MsgApi.getMsgEmojiLikesList(
peer,
String(payload.message_seq),
payload.emoji_id,
payload.emoji_type ?? (payload.emoji_id.length > 3 ? '2' : '1'),
payload.cookie ?? '',
payload.count !== undefined ? +payload.count : 20,
);
}
}
// ─────────────── 自定义表情/装扮 ───────────────
const FriendDressPayload = Type.Object({
user_id: Type.Optional(Type.String({ description: '目标QQ号,省略=自己' })),
});
export class GetFriendDress extends OneBotAction<Static<typeof FriendDressPayload>, unknown> {
override actionName = ActionName.GetFriendDress;
override actionSummary = '获取用户装扮';
override actionDescription = '获取好友/自己的装扮信息(挂件/气泡等,getUserDetailInfo)';
override actionTags = ['扩展接口'];
override payloadExample = { user_id: '123456789' };
override returnExample = { dress: {} };
override payloadSchema = FriendDressPayload;
override returnSchema = Type.Any();
async _handle (payload: Static<typeof FriendDressPayload>) {
const uin = payload.user_id ?? this.core.selfInfo.uin;
const uid = uin === this.core.selfInfo.uin
? this.core.selfInfo.uid
: await this.core.apis.UserApi.getUidByUinV2(uin);
if (!uid) throw new Error('用户不存在');
const info = await this.core.apis.UserApi.getUserDetailInfo(uid);
return { dress: info as unknown as Record<string, unknown> };
}
}
const FetchSysFacesPayload = Type.Object({
count: Type.Optional(Type.Union([Type.Number(), Type.String()], { default: 50, description: '取回数量' })),
});
export class FetchSysFaces extends OneBotAction<Static<typeof FetchSysFacesPayload>, unknown> {
override actionName = ActionName.FetchSysFaces;
override actionSummary = '获取系统表情目录';
override actionDescription = '获取 QQ 系统表情目录(占位:协议自编目录待后续批次移植,当前返回空集)';
override actionTags = ['扩展接口'];
override payloadExample = { count: 50 };
override returnExample = { items: [], note: 'sys face catalog 移植批次待排' };
override payloadSchema = FetchSysFacesPayload;
override returnSchema = Type.Any();
async _handle (payload: Static<typeof FetchSysFacesPayload>) {
void payload;
return { items: [], note: 'sys face catalog 移植批次待排' };
}
}