use crate::{ BilibiliRequest, BpiClient, BpiError, BpiResponse };
use serde::{ Deserialize, Serialize };
use serde_json::{ Value, json };
use chrono::Utc;
use uuid::Uuid;
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct SingleUnreadData {
pub unfollow_unread: u32,
pub follow_unread: u32,
pub unfollow_push_msg: u32,
pub dustbin_push_msg: u32,
pub dustbin_unread: u32,
pub biz_msg_unfollow_unread: u32,
pub biz_msg_follow_unread: u32,
pub custom_unread: u32,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct SendMsgData {
pub msg_key: Option<u64>,
pub e_infos: Option<Vec<EmojiInfo>>,
pub msg_content: Option<String>,
pub key_hit_infos: Option<KeyHitInfos>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct EmojiInfo {
pub text: String,
pub uri: String,
pub size: u32,
pub gif_url: Option<String>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct KeyHitInfos {
pub toast: Option<String>,
pub rule_id: Option<u64>,
pub high_text: Option<Vec<Value>>, }
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Image {
pub url: String,
pub height: u64,
pub width: u64,
#[serde(rename = "imageType")]
pub image_type: Option<String>,
pub original: Option<u64>, pub size: f64,
}
pub enum MessageType {
Text(String),
Image(Image),
}
impl BpiClient {
pub async fn message_single_unread(
&self,
unread_type: Option<u32>,
show_unfollow_list: Option<u32>,
show_dustbin: Option<u32>
) -> Result<BpiResponse<SingleUnreadData>, BpiError> {
let params = [
("build", "0"),
("mobi_app", "web"),
("unread_type", &unread_type.map_or("0".to_string(), |v| v.to_string())),
("show_unfollow_list", if show_unfollow_list == Some(1) { "1" } else { "0" }),
("show_dustbin", if show_dustbin.is_some() { "1" } else { "0" }),
];
self
.get("https://api.vc.bilibili.com/session_svr/v1/session_svr/single_unread")
.query(¶ms)
.send_bpi("获取未读私信数").await
}
pub async fn message_send(
&self,
receiver_id: u64,
receiver_type: u32,
message_type: MessageType
) -> Result<BpiResponse<SendMsgData>, BpiError> {
let csrf = self.csrf()?;
let sender_uid = &self.get_account().ok_or(BpiError::auth("未登录"))?.dede_user_id;
let dev_id = Uuid::new_v4().to_string();
let timestamp = Utc::now().timestamp();
let msg_type = match message_type {
MessageType::Text(_) => 1,
MessageType::Image(_) => 2,
};
let mut form = vec![
("msg[sender_uid]", sender_uid.to_string()),
("msg[receiver_id]", receiver_id.to_string()),
("msg[receiver_type]", receiver_type.to_string()),
("msg[msg_type]", msg_type.to_string()),
("msg[msg_status]", "0".to_string()),
("msg[dev_id]", dev_id.clone()),
("msg[timestamp]", timestamp.to_string()),
("msg[new_face_version]", "1".to_string()),
("csrf", csrf.clone()),
("csrf_token", csrf.clone()),
("build", "0".to_string()),
("mobi_app", "web".to_string())
];
let content = match message_type {
MessageType::Text(text) => json!({ "content": text }).to_string(),
MessageType::Image(image) => serde_json::to_string(&image)?,
};
form.push(("msg[content]", content));
let params = vec![
("w_sender_uid", sender_uid.to_string()),
("w_receiver_id", receiver_id.to_string()),
("w_dev_id", dev_id.clone())
];
let signed_params = self.get_wbi_sign2(params).await?;
self
.post("https://api.vc.bilibili.com/web_im/v1/web_im/send_msg")
.query(&signed_params)
.form(&form)
.send_bpi("发送私信").await
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::Path;
use tracing::info;
#[tokio::test]
async fn test_get_single_unread() -> Result<(), BpiError> {
let bpi = BpiClient::new();
let all_unread_resp = bpi.message_single_unread(None, None, None).await?;
let all_unread_data = all_unread_resp.into_data()?;
println!("所有未读私信数: {:?}", all_unread_data);
assert_eq!(all_unread_data.dustbin_unread, 0);
Ok(())
}
#[tokio::test]
async fn test_send_text_message() -> Result<(), BpiError> {
let bpi = BpiClient::new();
let receiver_id = 107997089;
let test_file = Path::new("./assets/test.jpg");
if !test_file.exists() {
return Err(BpiError::parse("Test file 'test.jpg' not found.".to_string()));
}
let resp = bpi.dynamic_upload_pic(test_file, None).await?;
let data = resp.into_data()?;
info!("上传成功!图片 URL: {}", data.image_url);
tokio::time::sleep(tokio::time::Duration::from_secs(3)).await;
let resp = bpi.message_send(
receiver_id,
1, MessageType::Image(Image {
url: data.image_url.to_string(),
height: data.image_height,
width: data.image_width,
image_type: None,
original: Some(1),
size: data.img_size,
})
).await?;
println!("发送私信响应: {:?}", resp);
assert_eq!(resp.code, 0);
Ok(())
}
}