Skip to main content

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}