bilibili_msg_history/
lib.rs

1use chrono::Local;
2use models::message::{ImageMessage, Message, ShareMessage, TextMessage, ViewerKind};
3use models::response::{BilibiliResponse, ResponseData};
4use reqwest::blocking::Client;
5use reqwest::header::HeaderMap;
6use std::fs;
7use std::io::Write;
8use std::path::PathBuf;
9use std::time::Duration;
10
11pub mod api;
12pub mod models;
13
14// format_message
15//
16// 消息格式化
17// 并不处理消息的存储
18pub fn format_message(message: &Message) -> Result<String, String> {
19    match message.msg_type {
20        // 文本消息
21        1 => {
22            let deserialized = serde_json::from_str::<TextMessage>(&message.content)
23                .map_err(|err| format!("反序列化 [文本消息 (TextMessage)] 时发生错误: {}", err))?;
24
25            Ok(deserialized.into())
26        }
27        // 图片消息
28        2 => {
29            let deserialized = serde_json::from_str::<ImageMessage>(&message.content)
30                .map_err(|err| format!("反序列化 [文本消息 (TextMessage)] 时发生错误: {}", err))?;
31
32            Ok(deserialized.into())
33        }
34        // 分享消息
35        7 => {
36            let deserialized = serde_json::from_str::<ShareMessage>(&message.content)
37                .map_err(|err| format!("反序列化 [分享消息 (ShareMessage)] 时发生错误: {}", err))?;
38
39            Ok(deserialized.into())
40        }
41        // 其他消息
42        _ => Ok("[其他消息]".to_owned()),
43    }
44}
45
46pub fn person_nickname<'a>(
47    viewer: ViewerKind,
48    message: &'a Message,
49    nickname: &'a str,
50    talker_uid: u64,
51) -> &'a str {
52    match viewer {
53        ViewerKind::FirstPerson => {
54            if message.sender_uid == talker_uid {
55                return nickname;
56            }
57
58            return "我";
59        }
60        ViewerKind::ThirdPerson => {
61            if message.receiver_id == talker_uid {
62                return nickname;
63            }
64
65            return "我";
66        }
67    }
68}
69
70#[deprecated(since = "0.0.2", note = "下个版本不在使用,将会被 Args 结构体代替")]
71#[derive(Debug, Default)]
72pub struct Config {
73    pub cookie: String,
74    pub talker_uid: u64,
75    pub size: u32,
76    pub viewer_kind: ViewerKind,
77    pub talker_nickname: String,
78    pub save_path: String,
79}
80
81pub fn run(config: Option<Config>) -> Result<(), String> {
82    let config = config.ok_or("缺省配置")?;
83
84    let size = config.size;
85    let talker_uid = config.talker_uid;
86    // let talker_uid: u64 = 319521269;
87    let cookie = config.cookie;
88    let viewer = config.viewer_kind;
89    let talker_nickname = config.talker_nickname;
90
91    let mut headers = HeaderMap::new();
92    headers.insert(
93        "Cookie",
94        cookie
95            .parse()
96            .map_err(|err| format!("解析 Cookie 时发生错误: {}", err))?,
97    );
98
99    // 获取最新的一条消息的元数据
100    let bilibili_response = api::get_latest_msg_api(headers.clone(), talker_uid)
101        .map_err(|err| format!("反序列化时发生错误: {}", err))?;
102    // 获取最新的 end_seqno
103    let mut mutable_end_seqno = bilibili_response.data.max_seqno;
104    // 获取最新一条消息的 timestamp
105    #[allow(unused)]
106    let timestamp = bilibili_response
107        .data
108        .messages
109        .map(|messages| {
110            messages
111                .iter()
112                .next()
113                .map_or(0, |message| message.timestamp)
114        })
115        .unwrap_or_default();
116
117    // 创建目录和文件
118    let datetime = Local::now().format("%Y-%m-%d").to_string();
119    let filepath = format!("{}/{}.txt", config.save_path, datetime);
120    let path_buf = PathBuf::from(filepath);
121
122    if fs::exists(&path_buf).map_err(|err| format!("检查文件是否存在时发生错误: {}", err))?
123    {
124        fs::remove_file(&path_buf).map_err(|err| format!("删除文件时发生错误: {}", err))?;
125    }
126
127    let mut file =
128        fs::File::create(&path_buf).map_err(|err| format!("创建文件时发生错误: {}", err))?;
129
130    println!("Start getting the message data");
131
132    loop {
133        let bilibili_response =
134            api::get_message_collect_api(headers.clone(), talker_uid, size, mutable_end_seqno)
135                .map_err(|err| format!("反序列化时发生错误: {}", err))?;
136
137        if bilibili_response.code != 0 {
138            break;
139        }
140
141        // 如果 has_more 为 0 说明聊天记录已经到底了
142        // 但是当前数据的 messages 仍可能会有聊天数据
143        if bilibili_response.data.has_more == 0 {
144            if let Some(messages) = bilibili_response.data.messages {
145                messages.iter().for_each(|message| {
146                    // 其他逻辑
147                    let nickname = person_nickname(viewer, message, &talker_nickname, talker_uid);
148                    let formatted_message = format_message(&message).unwrap();
149                    let message_text = format!(
150                        "[{}]{}: {}\n",
151                        message.datetime(),
152                        nickname,
153                        formatted_message,
154                    );
155
156                    if let Err(err) = file.write(message_text.as_bytes()) {
157                        eprintln!("消息写入时发生错误: {}", err);
158                    }
159                });
160            }
161
162            break;
163        }
164
165        if let Some(messages) = bilibili_response.data.messages {
166            messages.iter().for_each(|message| {
167                // 其他逻辑
168                let nickname = person_nickname(viewer, message, &talker_nickname, talker_uid);
169                let message_text = format!(
170                    "[{}]{}: {}\n",
171                    message.datetime(),
172                    nickname,
173                    format_message(&message).unwrap()
174                );
175
176                if let Err(err) = file.write(message_text.as_bytes()) {
177                    eprintln!("消息写入时发生错误: {}", err);
178                }
179            });
180        }
181
182        mutable_end_seqno = bilibili_response.data.min_seqno - 1;
183
184        std::thread::sleep(Duration::from_millis(500));
185    }
186
187    println!("Done.");
188
189    Ok(())
190}