Skip to main content

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    /// # 文档
95    /// [查看API文档](https://github.com/SocialSisterYi/bilibili-API-collect/tree/master/docs/message)
96    pub async fn message_unread_count(&self) -> Result<BpiResponse<UnreadCountData>, BpiError> {
97        self
98            .get("https://api.vc.bilibili.com/x/im/web/msgfeed/unread")
99            .query(
100                &[
101                    ("build", "0"),
102                    ("mobi_app", "web"),
103                ]
104            )
105            .send_bpi("获取未读消息数").await
106    }
107
108    /// 获取"回复我的"信息列表。
109    ///
110    /// # 文档
111    /// [查看API文档](https://github.com/SocialSisterYi/bilibili-API-collect/tree/master/docs/message)
112    ///
113    /// # 参数
114    ///
115    /// | 名称 | 类型 | 说明 |
116    /// | ---- | ---- | ---- |
117    /// | `start_id` | `Option<u64>` | 起始 ID(上次返回的 cursor.id) |
118    /// | `start_time` | `Option<u64>` | 起始时间戳(上次返回的 cursor.time) |
119    pub async fn message_reply_feed(
120        &self,
121        start_id: Option<u64>,
122        start_time: Option<u64>
123    ) -> Result<BpiResponse<ReplyFeedData>, BpiError> {
124        let mut params = HashMap::new();
125        params.insert("build", "0".to_string());
126        params.insert("mobi_app", "web".to_string());
127        params.insert("platform", "web".to_string());
128        params.insert("web_location", "".to_string());
129
130        if let Some(id) = start_id {
131            params.insert("id", id.to_string());
132        }
133        if let Some(time) = start_time {
134            params.insert("reply_time", time.to_string());
135        }
136
137        self
138            .get("https://api.bilibili.com/x/msgfeed/reply")
139            .query(&params)
140            .send_bpi("获取回复我的信息").await
141    }
142}
143
144#[cfg(test)]
145mod tests {
146    use super::*;
147
148    #[tokio::test]
149    async fn test_get_unread_count() -> Result<(), BpiError> {
150        let bpi = BpiClient::new();
151
152        let new_resp = bpi.message_unread_count().await?;
153        let new_data = new_resp.into_data()?;
154        println!("未读消息数 (新接口): {:?}", new_data);
155        Ok(())
156    }
157
158    #[tokio::test]
159    async fn test_get_reply_feed() -> Result<(), BpiError> {
160        let bpi = BpiClient::new();
161
162        let resp = bpi.message_reply_feed(None, None).await?;
163        let data = resp.into_data()?;
164
165        println!("最近回复我的信息:");
166        println!("  上次查看时间: {}", data.last_view_at);
167        println!("  游标信息: {:?}", data.cursor);
168
169        for item in data.items {
170            println!("---");
171            println!("  回复者: {}", item.user.nickname);
172            println!("  回复内容: {}", item.item.source_content);
173            println!("  回复时间: {}", item.reply_time);
174            println!("  关联视频/动态: {}", item.item.title);
175            println!("  根评论: {}", item.item.root_reply_content);
176            println!("  跳转链接: {}", item.item.uri);
177        }
178
179        if !data.cursor.is_end {
180            println!("---");
181            println!(
182                "还有更多数据,下次请求可使用 id: {:?}, time: {:?}",
183                data.cursor.id,
184                data.cursor.time
185            );
186        }
187
188        Ok(())
189    }
190}