nagisa_types/entity.rs
1//! 统一实体层:好友、用户档案、群、群成员、文件/文件夹、公告、精华、荣誉、运行状态/版本,
2//! 以及若干厂商扩展结果(OCR、点赞、AI 音色、rkey 等)。每个实体取各协议字段的并集,本协议
3//! 没有的字段为 `Option`(或空 `Vec`),并保留 `raw` 原始 JSON 以防协议追加字段。适配器把各自
4//! wire 实体映射进来;同一字段在不同实现端的来源差异写在各结构体/字段的 `///` 上。
5use crate::id::{MessageId, Uin};
6use crate::message::Message;
7use serde_json::Value;
8
9#[derive(Clone, Copy, PartialEq, Eq, Debug)]
10pub enum Sex {
11 Male,
12 Female,
13 /// 未知或协议未提供——adapter 对未知值降级到此,绝不 panic。
14 Unknown,
15}
16
17#[derive(Clone, Copy, PartialEq, Eq, Debug)]
18pub enum Role {
19 Owner,
20 Admin,
21 Member,
22}
23
24#[derive(Clone, Debug)]
25pub struct FriendCategory {
26 pub id: i32,
27 pub name: String,
28}
29
30/// 好友分组(Lagrange `get_friend_list` 元素的 `group` 子对象)。
31/// 与 `FriendCategory` 区别:`category` 来自 NapCat/LLOneBot 的 `categoryId/categroyName`
32/// 扁平字段或 Milky 的嵌套 entity;`group` 是 Lagrange 私有的 `{group_id, group_name}`。
33/// OFFICIAL: Lagrange.OneBot get_friend_list(friend.group → FriendGroup)。
34#[derive(Clone, Debug)]
35pub struct FriendGroup {
36 pub group_id: i32,
37 pub group_name: String,
38}
39
40#[derive(Clone, Debug)]
41pub struct FriendInfo {
42 pub user: Uin,
43 pub nickname: String,
44 pub sex: Sex,
45 pub remark: String,
46 pub qid: Option<String>,
47 pub category: Option<FriendCategory>,
48 /// Lagrange 好友分组 `{group_id, group_name}`(其余实现 → None)。
49 pub group: Option<FriendGroup>,
50 /// NapCat/LLOneBot 扩展:生日(YYYY-MM-DD,由 `birthday_year/month/day` 拼装;
51 /// 任一为 0/缺 → None)。
52 pub birthday: Option<String>,
53 /// NapCat/LLOneBot 扩展:电话(wire `phoneNum`,空串/`-` → None)。
54 pub phone: Option<String>,
55 /// NapCat/LLOneBot 扩展:邮箱(wire `email`,空串 → None)。
56 pub email: Option<String>,
57 /// NapCat/LLOneBot 扩展:登录天数(wire `login_days`)。
58 pub login_days: Option<i32>,
59 /// NapCat/LLOneBot 扩展:个性签名长文(wire `longNick`/`long_nick`,空串 → None)。
60 pub long_nick: Option<String>,
61 pub raw: Value,
62}
63
64impl FriendInfo {
65 /// 好友显示名:优先备注(remark),其次昵称(皆按 trim 后非空取);两者皆空 → 空串。
66 pub fn display_name(&self) -> &str {
67 let remark = self.remark.trim();
68 if !remark.is_empty() {
69 remark
70 } else {
71 self.nickname.trim()
72 }
73 }
74}
75
76/// 用户在线/VIP 状态(Lagrange `get_stranger_info.status`)。
77/// `status`/`ext_status` 为协议原始枚举值(不解释,原样透出);
78/// `battery_status` 为电量百分比。缺字段 → None(绝不 panic)。
79/// OFFICIAL: Lagrange.OneBot get_stranger_info(stranger.status → FriendStatus)。
80#[derive(Clone, Debug)]
81pub struct FriendStatus {
82 pub status: Option<i32>,
83 pub ext_status: Option<i32>,
84 pub battery_status: Option<i32>,
85}
86
87/// VIP / 业务徽章条目(Lagrange `get_stranger_info.Business[]`)。
88/// `kind` 为业务类型(wire `type`),`level` 等级,`icon` 徽章图标 URL,
89/// `is_pro`/`is_year` 为大会员/年费标识。缺字段 → None(绝不 panic)。
90/// OFFICIAL: Lagrange.OneBot get_stranger_info(stranger.Business[] → Business)。
91#[derive(Clone, Debug)]
92pub struct Business {
93 pub kind: Option<i32>,
94 pub name: Option<String>,
95 pub level: Option<i32>,
96 pub icon: Option<String>,
97 pub is_pro: Option<bool>,
98 pub is_year: Option<bool>,
99}
100
101/// 任意用户档案(含陌生人)。OneBot `get_stranger_info` /
102/// Milky `get_user_profile` 字段并集,缺的为 `Option`。
103#[derive(Clone, Debug)]
104pub struct UserInfo {
105 pub user: Uin,
106 pub nickname: String,
107 pub sex: Sex,
108 pub age: Option<i32>,
109 pub level: Option<i32>,
110 pub qid: Option<String>,
111 /// 个性签名 / bio。
112 pub bio: Option<String>,
113 pub country: Option<String>,
114 pub city: Option<String>,
115 pub school: Option<String>,
116 pub remark: Option<String>,
117 /// 在线/VIP 状态(Lagrange `status`;其余实现 → None)。
118 pub status: Option<FriendStatus>,
119 /// VIP / 业务徽章列表(Lagrange `Business[]`;其余实现 → 空 Vec)。
120 pub business: Vec<Business>,
121 /// 注册时间戳(Lagrange `RegisterTime`,秒;缺 → None)。
122 pub register_time: Option<i64>,
123 /// 头像 URL(Lagrange `avatar`;其余实现 → None)。
124 pub avatar: Option<String>,
125 pub raw: Value,
126}
127
128#[derive(Clone, Debug)]
129pub struct GroupInfo {
130 pub group: Uin,
131 pub name: String,
132 pub member_count: i32,
133 pub max_member_count: i32,
134 pub remark: Option<String>,
135 pub created_time: Option<i64>,
136 pub description: Option<String>,
137 pub announcement: Option<String>,
138 /// 加群验证问题(Milky 1.2)。
139 pub question: Option<String>,
140 /// 群是否被冻结(LLOneBot `is_freeze`;其余实现无 → None)。
141 pub is_freeze: Option<bool>,
142 /// 活跃成员数(LLOneBot `active_member_count`;其余实现无 → None)。
143 pub active_member_count: Option<i32>,
144 /// 群是否置顶(LLOneBot `is_top`;其余实现无 → None)。
145 pub is_top: Option<bool>,
146 /// 群主 QQ 号(LLOneBot `owner_id`;其余实现无 → None)。
147 pub owner_id: Option<Uin>,
148 /// 全员禁言截止时间戳(LLOneBot `shut_up_all_timestamp`;其余实现无 → None)。
149 pub shut_up_all_time: Option<i64>,
150 /// 自身被禁言截止时间戳(LLOneBot `shut_up_me_timestamp`;其余实现无 → None)。
151 pub shut_up_me_time: Option<i64>,
152 pub raw: Value,
153}
154
155#[derive(Clone, Debug)]
156pub struct MemberInfo {
157 pub user: Uin,
158 pub group: Uin,
159 pub nickname: String,
160 pub card: String,
161 pub title: String,
162 pub level: i32,
163 pub role: Role,
164 pub sex: Sex,
165 /// 群成员年龄(OneBot `sender.age` / `get_group_member_info.age`;Milky 无 → None)。
166 pub age: Option<i32>,
167 pub join_time: i64,
168 pub last_sent_time: Option<i64>,
169 pub mute_end_time: Option<i64>,
170 /// 群成员地区(OneBot `area`;Milky 无 → None)。
171 pub area: Option<String>,
172 /// 是否不良记录成员(OneBot `unfriendly`;Milky 无 → None)。
173 pub unfriendly: Option<bool>,
174 /// 专属头衔过期时间戳(OneBot `title_expire_time`;Milky 无 → None)。
175 pub title_expire_time: Option<i64>,
176 /// 是否允许修改群名片(OneBot `card_changeable`;Milky 无 → None)。
177 pub card_changeable: Option<bool>,
178 /// QQ 等级(LLOneBot `qq_level`;其余实现无 → None)。
179 /// 注意:与 `level`(群等级)不同,这是账号 QQ 等级。
180 pub qq_level: Option<i32>,
181 /// 是否为机器人(LLOneBot `is_robot`;其余实现无 → None)。
182 pub is_robot: Option<bool>,
183 /// QQ 注册年龄/Q 龄(LLOneBot `qage`;其余实现无 → None)。
184 pub qage: Option<i32>,
185 pub raw: Value,
186}
187
188impl MemberInfo {
189 /// 群成员显示名:优先群名片(card),其次昵称(皆按 trim 后非空取);两者皆空 → 空串。
190 pub fn display_name(&self) -> &str {
191 let card = self.card.trim();
192 if !card.is_empty() {
193 card
194 } else {
195 self.nickname.trim()
196 }
197 }
198}
199
200#[derive(Clone, Debug)]
201pub struct FileMeta {
202 pub id: String,
203 pub name: String,
204 pub size: u64,
205 pub hash: Option<String>,
206 /// 群文件业务 ID(OneBot `group_upload.file.busid` / `busid`;Milky 无 → None)。
207 pub busid: Option<i64>,
208 /// 上传者 QQ 号(OneBot go-cqhttp `uploader` / Milky `uploader_id`;未提供 → None)。
209 pub uploader: Option<Uin>,
210 /// 上传时间戳(OneBot go-cqhttp `upload_time` / Milky `uploaded_time`,秒;未提供 → None)。
211 pub upload_time: Option<i64>,
212 /// 过期/失效时间戳(OneBot go-cqhttp `dead_time`,永久文件为 0 / Milky `expire_time`,
213 /// 秒;未提供 → None)。
214 pub dead_time: Option<i64>,
215 /// 下载次数(OneBot go-cqhttp `download_times` / Milky `downloaded_times`;未提供 → None)。
216 pub download_times: Option<i32>,
217 /// 父文件夹 ID(Milky `parent_folder_id`;OneBot 群文件元素无 → None)。
218 pub parent_folder_id: Option<String>,
219}
220
221/// 群文件夹。
222/// OFFICIAL: https://github.com/SaltifyDev/milky/blob/main/protocol/src/ir/api/file.ts (get_group_files)
223#[derive(Clone, Debug)]
224pub struct GroupFolder {
225 pub id: String,
226 pub name: String,
227 pub file_count: Option<u32>,
228 pub create_time: Option<i64>,
229 /// 父文件夹 ID(Milky `parent_folder_id`;OneBot 无 → None)。
230 pub parent_folder_id: Option<String>,
231 /// 最后修改时间戳(Milky `last_modified_time`;OneBot 无 → None)。
232 pub last_modified_time: Option<i64>,
233 /// 创建者 QQ 号(Milky `creator_id`;OneBot 无 → None)。
234 pub creator_id: Option<Uin>,
235 pub raw: Value,
236}
237
238/// 群文件列表(某文件夹下的文件 + 子文件夹)。
239#[derive(Clone, Debug)]
240pub struct GroupFileList {
241 pub files: Vec<FileMeta>,
242 pub folders: Vec<GroupFolder>,
243}
244
245/// 群公告。
246/// OFFICIAL: https://github.com/SaltifyDev/milky/blob/main/protocol/src/ir/api/group.ts (get_group_announcements)
247/// ENDPOINT(OneBot): go-cqhttp `_get_group_notice`
248#[derive(Clone, Debug)]
249pub struct Announcement {
250 pub id: String,
251 pub group: Uin,
252 pub sender: Uin,
253 pub content: String,
254 pub time: i64,
255 pub image_url: Option<String>,
256 pub raw: Value,
257}
258
259/// 精华消息。
260/// OFFICIAL: https://github.com/SaltifyDev/milky/blob/main/protocol/src/ir/api/group.ts (get_group_essence_messages)
261/// ENDPOINT(OneBot): go-cqhttp `get_essence_msg_list`
262#[derive(Clone, Debug)]
263pub struct EssenceMessage {
264 pub group: Uin,
265 pub message_id: MessageId,
266 pub sender: Uin,
267 pub sender_nick: String,
268 pub operator: Uin,
269 pub operator_time: i64,
270 pub content: Message,
271 pub raw: Value,
272}
273
274/// 收发统计(`get_status.stat`,LLOneBot/Lagrange/go-cqhttp 共有)。
275/// `message_received`/`message_sent` 为累计收/发消息数;缺字段 → None(绝不 panic)。
276/// OFFICIAL: https://github.com/botuniverse/onebot-11/blob/master/api/public.md
277/// (go-cqhttp / LLOneBot 扩展 `stat` 块)。
278#[derive(Clone, Debug)]
279pub struct ImplStat {
280 /// 累计收到消息数(`message_received`;缺 → None)。
281 pub message_received: Option<i64>,
282 /// 累计发送消息数(`message_sent`;缺 → None)。
283 pub message_sent: Option<i64>,
284}
285
286/// 协议端运行状态(OneBot `get_status`)。
287/// OFFICIAL: https://github.com/botuniverse/onebot-11/blob/master/api/public.md (get_status)
288#[derive(Clone, Debug)]
289pub struct ImplStatus {
290 pub online: bool,
291 pub good: bool,
292 /// 收发统计(LLOneBot/Lagrange `stat{message_received,message_sent,...}`;
293 /// 无该块的实现 → None,其余统计字段仍保留于 `raw`)。
294 pub stat: Option<ImplStat>,
295 pub raw: Value,
296}
297
298/// 协议端版本信息(OneBot `get_version_info`)。
299/// OFFICIAL: https://github.com/botuniverse/onebot-11/blob/master/api/public.md (get_version_info)
300#[derive(Clone, Debug)]
301pub struct VersionInfo {
302 pub app_name: String,
303 pub app_version: String,
304 pub protocol_version: String,
305 pub raw: Value,
306}
307
308/// 合并转发发送结果(`send_forward_msg` / `send_group_forward_msg` /
309/// `send_private_forward_msg`)。除落地消息 `message_id` 外,Lagrange 等端还会返回
310/// 合并转发的 `forward_id`(即 resId),下游可凭此**二次引用**该合并转发(构造
311/// `Forward::Ref` 再发)。OneBot v11 标准只规定返回 `message_id`,故 `forward_id`
312/// 为 `Option`——端未回传 resId 时为 `None`(非错误)。
313/// ENDPOINT: NapCat action/go-cqhttp/SendForwardMsg.ts;Lagrange.OneBot
314/// Core/Operation/Message/SendForwardOperation.cs(resp 旁带 `forward_id`/`res_id`)。
315#[derive(Clone, Debug)]
316pub struct ForwardSendResult {
317 /// 落地消息 ID(合并转发卡片本身)。
318 pub message_id: MessageId,
319 /// 合并转发引用 ID(Lagrange resId)。端未回传时为 `None`。
320 pub forward_id: Option<String>,
321}
322
323/// OCR 识别出的一段文本(OneBot/NapCat `ocr_image`)。
324/// ENDPOINT: NapCat packages/napcat-onebot/action/extends/OCRImage.ts (ocr_image)
325/// (https://github.com/NapNeko/NapCatQQ)
326#[derive(Clone, Debug)]
327pub struct OcrText {
328 pub text: String,
329 /// 完整结果项(坐标/置信度等实现相关字段)。
330 pub raw: Value,
331}
332
333/// 对某条消息做出表情回应的人(NapCat `fetch_emoji_like`)。
334/// ENDPOINT: NapCat packages/napcat-onebot/action/extends/FetchEmojiLike.ts
335/// (https://github.com/NapNeko/NapCatQQ)
336#[derive(Clone, Debug)]
337pub struct EmojiLiker {
338 pub tiny_id: String,
339 pub nickname: String,
340 pub head_url: String,
341}
342
343/// 富媒体下载鉴权密钥(Lagrange/NapCat/LLOneBot `get_rkey`)。
344/// ENDPOINT: LagrangeDev/Lagrange.Core Lagrange.OneBot/Core/Operation/Generic/GetRkey.cs
345/// Wire response: `{"rkeys":[{"type":"private"|"group","rkey":"...","created_at":u32,"ttl":u64}]}`
346#[derive(Clone, Debug)]
347pub struct Rkey {
348 /// "private" / "group"。
349 pub kind: String,
350 pub rkey: String,
351 /// 创建时间戳(`created_at`)。
352 pub create_time: Option<i64>,
353 /// 有效期秒数(`ttl`)。
354 pub ttl: Option<i64>,
355 pub raw: Value,
356}
357
358/// `get_file` 取回的富媒体文件(NapCat / LLOneBot 共有)。
359/// ENDPOINT: NapNeko/NapCatQQ packages/napcat-onebot/action/file/GetFile.ts (get_file);
360/// 亦见 LLOneBot/LLOneBot src/onebot11/action/types.ts `get_file`。
361/// Wire 响应:`{file?(本地路径), url?, file_size?(字符串), file_name?, base64?}`。
362#[derive(Clone, Debug)]
363pub struct FileFetch {
364 /// 下载 URL(`url`)。
365 pub url: Option<String>,
366 /// 本地路径(`file`)。
367 pub path: Option<String>,
368 /// 文件名(`file_name`)。
369 pub name: String,
370 /// 文件大小字节数(`file_size`,wire 为字符串,解析为整数;无则 0)。
371 pub size: u64,
372 /// Base64 内容(`base64`,仅在服务端开启 local-file-to-url 时返回)。
373 pub base64: Option<String>,
374 pub raw: Value,
375}
376
377/// AI 语音音色(`get_ai_characters`)。
378/// ENDPOINT: LagrangeDev/Lagrange.Core Lagrange.OneBot/Core/Operation/Generic/GetAiCharacters.cs
379/// (https://github.com/LagrangeDev/Lagrange.Core); 亦见 NapCat/LLOneBot `get_ai_characters`。
380/// Wire 元素:`{character_id, character_name, preview_url?}`。
381#[derive(Clone, Debug)]
382pub struct AiCharacter {
383 pub id: String,
384 pub name: String,
385 pub preview_url: Option<String>,
386 pub raw: Value,
387}
388
389/// 一组 AI 音色(按类型分组;`get_ai_characters` 的顶层数组元素)。
390/// Wire 元素:`{type, characters:[AiCharacter]}`。
391#[derive(Clone, Debug)]
392pub struct AiCharacterGroup {
393 pub kind: String,
394 pub characters: Vec<AiCharacter>,
395 pub raw: Value,
396}
397
398/// 对我点赞的用户(`get_profile_like`,NapCat/LLOneBot 共有)。
399/// ENDPOINT: NapCat packages/napcat-onebot/action/user/GetProfileLike.ts (get_profile_like)
400/// (https://github.com/NapNeko/NapCatQQ). Also: LLOneBot src/onebot11/action/types.ts.
401#[derive(Clone, Debug)]
402pub struct ProfileLiker {
403 pub user: Uin,
404 pub nickname: String,
405 /// 点赞次数。
406 pub times: i32,
407 pub raw: Value,
408}
409
410/// 带分类的好友列表(`get_friends_with_category`,NapCat/LLOneBot 共有)。
411/// ENDPOINT: NapCat packages/napcat-onebot/action/user/GetFriendsWithCategory.ts
412/// (get_friends_with_category) (https://github.com/NapNeko/NapCatQQ).
413/// Wire: `[{categoryId, categoryName, buddyList:[FriendInfo]}]`。
414#[derive(Clone, Debug)]
415pub struct FriendCategoryList {
416 pub category_id: i32,
417 pub category_name: String,
418 pub friends: Vec<FriendInfo>,
419}
420
421/// 群荣誉成员条目(current_talkative / *_list 元素)。
422/// OFFICIAL: https://github.com/botuniverse/onebot-11/blob/master/api/public.md (get_group_honor_info)
423#[derive(Clone, Debug)]
424pub struct HonorMember {
425 pub user: Uin,
426 pub nickname: String,
427 pub avatar: Option<String>,
428 pub description: Option<String>,
429 pub day_count: Option<i32>,
430 pub raw: Value,
431}
432
433/// 群荣誉信息(`get_group_honor_info`)。
434/// OFFICIAL: https://github.com/botuniverse/onebot-11/blob/master/api/public.md (get_group_honor_info)
435#[derive(Clone, Debug, Default)]
436pub struct HonorList {
437 pub group: Uin,
438 pub current_talkative: Option<HonorMember>,
439 pub talkative_list: Vec<HonorMember>,
440 pub performer_list: Vec<HonorMember>,
441 pub legend_list: Vec<HonorMember>,
442 pub strong_newbie_list: Vec<HonorMember>,
443 pub emotion_list: Vec<HonorMember>,
444 pub raw: Value,
445}
446
447impl MemberInfo {
448 /// 是否为群主或管理员。
449 pub fn is_operator(&self) -> bool {
450 matches!(self.role, Role::Owner | Role::Admin)
451 }
452}