bpi_rs/message/
msg.rs

1use crate::{BilibiliRequest, BpiClient, BpiError, BpiResponse};
2use serde::{Deserialize, Serialize};
3use std::collections::HashMap;
4
5// --- API 结构体 ---
6
7/// 未读消息数
8#[derive(Debug, Clone, Deserialize, Serialize)]
9pub struct UnreadCountData {
10    pub coin: u32,       // 未读投币数
11    pub danmu: u32,      // 未读弹幕数
12    pub favorite: u32,   // 未读收藏数
13    pub recv_like: u32,  // 未读收到喜欢数
14    pub recv_reply: u32, // 未读回复
15    pub sys_msg: u32,    // 未读系统通知数
16    pub up: u32,         // 未读UP主助手信息数
17}
18
19/// "回复我的"信息
20#[derive(Debug, Clone, Deserialize, Serialize)]
21pub struct ReplyFeedData {
22    pub cursor: ReplyCursor,
23    pub items: Vec<ReplyItem>,
24    pub last_view_at: u64,
25}
26
27/// 分页游标
28#[derive(Debug, Clone, Deserialize, Serialize)]
29pub struct ReplyCursor {
30    pub is_end: bool,
31    pub id: Option<u64>,
32    pub time: Option<u64>,
33}
34
35/// 单条回复通知
36#[derive(Debug, Clone, Deserialize, Serialize)]
37pub struct ReplyItem {
38    pub id: u64,
39    pub user: ReplyUser,
40    pub item: ReplyDetail,
41    pub counts: u32,
42    pub is_multi: u32,
43    pub reply_time: u64,
44}
45
46/// 回复者用户信息
47#[derive(Debug, Clone, Deserialize, Serialize)]
48pub struct ReplyUser {
49    pub mid: u64,
50    pub nickname: String,
51    pub avatar: String,
52    pub follow: bool,
53    // 以下字段文档表示固定或不返回,但为了完整性保留
54    pub fans: Option<u32>,
55    pub mid_link: Option<String>,
56}
57
58/// 回复通知详情
59#[derive(Debug, Clone, Deserialize, Serialize)]
60pub struct ReplyDetail {
61    pub subject_id: u64,
62    pub root_id: u64,
63    pub source_id: u64,
64    pub target_id: u64,
65    #[serde(rename = "type")]
66    pub reply_type: String,
67    pub business_id: u32,
68    pub business: String,
69    pub title: String,
70    pub desc: String,
71    pub uri: String,
72    pub native_uri: String,
73    pub root_reply_content: String,
74    pub source_content: String,
75    pub target_reply_content: String,
76    pub at_details: Vec<AtUserDetail>,
77    pub hide_reply_button: bool,
78    pub hide_like_button: bool,
79    pub like_state: u32,
80}
81
82/// @的用户详情
83#[derive(Debug, Clone, Deserialize, Serialize)]
84pub struct AtUserDetail {
85    pub mid: u64,
86    pub nickname: String,
87    pub avatar: String,
88    pub follow: bool,
89}
90
91impl BpiClient {
92    /// 获取未读消息数。
93    ///
94    /// 文档: https://github.com/SocialSisterYi/bilibili-API-collect/tree/master/docs/message
95    pub async fn message_unread_count(&self) -> Result<BpiResponse<UnreadCountData>, BpiError> {
96        self.get("https://api.vc.bilibili.com/x/im/web/msgfeed/unread")
97            .query(&[("build", "0"), ("mobi_app", "web")])
98            .send_bpi("获取未读消息数")
99            .await
100    }
101
102    /// 获取"回复我的"信息列表。
103    ///
104    /// 文档: https://github.com/SocialSisterYi/bilibili-API-collect/tree/master/docs/message
105    ///
106    /// 参数
107    ///
108    /// | 名称 | 类型 | 说明 |
109    /// | ---- | ---- | ---- |
110    /// | `start_id` | Option<u64> | 起始 ID(上次返回的 cursor.id) |
111    /// | `start_time` | Option<u64> | 起始时间戳(上次返回的 cursor.time) |
112    pub async fn message_reply_feed(
113        &self,
114        start_id: Option<u64>,
115        start_time: Option<u64>,
116    ) -> Result<BpiResponse<ReplyFeedData>, BpiError> {
117        let mut params = HashMap::new();
118        params.insert("build", "0".to_string());
119        params.insert("mobi_app", "web".to_string());
120        params.insert("platform", "web".to_string());
121        params.insert("web_location", "".to_string());
122
123        if let Some(id) = start_id {
124            params.insert("id", id.to_string());
125        }
126        if let Some(time) = start_time {
127            params.insert("reply_time", time.to_string());
128        }
129
130        self.get("https://api.bilibili.com/x/msgfeed/reply")
131            .query(&params)
132            .send_bpi("获取回复我的信息")
133            .await
134    }
135}
136
137#[cfg(test)]
138mod tests {
139    use super::*;
140
141    #[tokio::test]
142
143    async fn test_get_unread_count() -> Result<(), BpiError> {
144        let bpi = BpiClient::new();
145
146        let new_resp = bpi.message_unread_count().await?;
147        let new_data = new_resp.into_data()?;
148        println!("未读消息数 (新接口): {:?}", new_data);
149        Ok(())
150    }
151
152    #[tokio::test]
153
154    async fn test_get_reply_feed() -> Result<(), BpiError> {
155        let bpi = BpiClient::new();
156
157        let resp = bpi.message_reply_feed(None, None).await?;
158        let data = resp.into_data()?;
159
160        println!("最近回复我的信息:");
161        println!("  上次查看时间: {}", data.last_view_at);
162        println!("  游标信息: {:?}", data.cursor);
163
164        for item in data.items {
165            println!("---");
166            println!("  回复者: {}", item.user.nickname);
167            println!("  回复内容: {}", item.item.source_content);
168            println!("  回复时间: {}", item.reply_time);
169            println!("  关联视频/动态: {}", item.item.title);
170            println!("  根评论: {}", item.item.root_reply_content);
171            println!("  跳转链接: {}", item.item.uri);
172        }
173
174        if !data.cursor.is_end {
175            println!("---");
176            println!(
177                "还有更多数据,下次请求可使用 id: {:?}, time: {:?}",
178                data.cursor.id, data.cursor.time
179            );
180        }
181
182        Ok(())
183    }
184}