1use super::models::{ ArticleAuthor, ArticleCategory, ArticleMedia, ArticleStats };
6use crate::{ BilibiliRequest, BpiClient, BpiError, BpiResponse };
7use serde::{ Deserialize, Serialize };
8
9pub type ArticleViewResponse = BpiResponse<ArticleViewData>;
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct ArticleViewData {
15 pub act_id: i64,
17 pub apply_time: String,
19 pub attributes: Option<i32>,
21 #[serde(rename = "authenMark")]
23 pub authen_mark: Option<serde_json::Value>,
24 pub author: ArticleAuthor,
26 pub banner_url: String,
28 pub categories: Vec<ArticleCategory>,
30 pub category: ArticleCategory,
32 pub check_state: i32,
34 pub check_time: String,
36 pub content: String,
38 pub content_pic_list: Option<serde_json::Value>,
40 pub cover_avid: i64,
42 pub ctime: i64,
44 pub dispute: Option<serde_json::Value>,
46 pub dyn_id_str: String,
48 pub dynamic: Option<String>,
50 pub id: i64,
52 pub image_urls: Vec<String>,
54 pub is_like: bool,
56 pub keywords: String,
58 pub list: Option<ArticleList>,
60 pub media: ArticleMedia,
62 pub mtime: i64,
64 pub opus: Option<ArticleOpus>,
66 pub origin_image_urls: Vec<String>,
68 pub origin_template_id: i32,
70 pub original: i32,
72 pub private_pub: i32,
74 pub publish_time: i64,
76 pub reprint: i32,
78 pub state: i32,
80 pub stats: ArticleStats,
82 pub summary: String,
84 pub tags: Vec<ArticleTag>,
86 pub template_id: i32,
88 pub title: String,
90 pub top_video_info: Option<serde_json::Value>,
92 pub total_art_num: i64,
94 pub r#type: i32,
96 pub version_id: i64,
98 pub words: i64,
100}
101
102#[derive(Debug, Clone, Serialize, Deserialize)]
104pub struct AuthorVip {
105 pub r#type: i32,
107 pub status: i32,
109 pub due_date: i64,
111 pub vip_pay_type: i32,
113 pub theme_type: i32,
115 pub label: Option<serde_json::Value>,
117}
118
119#[derive(Debug, Clone, Serialize, Deserialize)]
121pub struct ArticleList {
122 pub id: i64,
124 pub name: String,
126 pub image_url: String,
128 pub update_time: i64,
130 pub ctime: i64,
132 pub publish_time: i64,
134 pub summary: String,
136 pub words: i64,
138 pub read: i64,
140 pub articles_count: i32,
142 pub state: i32,
144 pub reason: String,
146 pub apply_time: String,
148 pub check_time: String,
150}
151
152#[derive(Debug, Clone, Serialize, Deserialize)]
154pub struct ArticleTag {
155 pub tid: i32,
157 pub name: String,
159 }
162
163#[derive(Debug, Clone, Serialize, Deserialize)]
165pub struct ArticleOpus {
166 pub ops: Vec<OpusOperation>,
168}
169
170#[derive(Debug, Clone, Serialize, Deserialize)]
172pub struct OpusOperation {
173 pub attribute: Option<OpusAttribute>,
175 pub insert: OpusInsert,
177}
178
179#[derive(Debug, Clone, Serialize, Deserialize)]
181pub struct OpusAttribute {
182 pub align: Option<String>,
184 pub blockquote: Option<bool>,
186 pub bold: Option<bool>,
188 pub class: Option<String>,
190 pub color: Option<String>,
192 pub header: Option<i32>,
194 pub strike: Option<bool>,
196 pub link: Option<String>,
198 pub italic: Option<bool>,
200 pub list: Option<String>,
202}
203
204#[derive(Debug, Clone, Serialize, Deserialize)]
206#[serde(untagged)]
207pub enum OpusInsert {
208 Text(String),
210 Rich(OpusRichInsert),
212}
213
214#[derive(Debug, Clone, Serialize, Deserialize)]
216pub struct OpusRichInsert {
217 pub native_image: Option<OpusImage>,
219 pub cut_off: Option<OpusCutOff>,
221 pub video_card: Option<OpusVideoCard>,
223 pub article_card: Option<OpusArticleCard>,
225 pub vote_card: Option<OpusVoteCard>,
227 pub live_card: Option<OpusLiveCard>,
229}
230
231#[derive(Debug, Clone, Serialize, Deserialize)]
233pub struct OpusImage {
234 pub alt: String,
236 pub url: String,
238 pub width: i32,
240 pub height: i32,
242 pub size: i64,
244 pub status: String,
246}
247
248#[derive(Debug, Clone, Serialize, Deserialize)]
250pub struct OpusCutOff {
251 pub r#type: String,
253 pub url: String,
255}
256
257#[derive(Debug, Clone, Serialize, Deserialize)]
259pub struct OpusVideoCard {
260 pub alt: String,
262 pub height: i32,
264 pub id: String,
266 pub size: Option<serde_json::Value>,
268 pub status: String,
270 pub tid: f64,
272 pub url: String,
274 pub width: i32,
276}
277
278#[derive(Debug, Clone, Serialize, Deserialize)]
280pub struct OpusArticleCard {
281 pub alt: String,
283 pub height: i32,
285 pub id: String,
287 pub size: Option<serde_json::Value>,
289 pub status: String,
291 pub tid: i32,
293 pub url: String,
295 pub width: i32,
297}
298
299#[derive(Debug, Clone, Serialize, Deserialize)]
301pub struct OpusVoteCard {
302 pub alt: String,
304 pub height: i32,
306 pub id: String,
308 pub size: Option<serde_json::Value>,
310 pub status: String,
312 pub tid: i32,
314 pub url: String,
316 pub width: i32,
318}
319
320#[derive(Debug, Clone, Serialize, Deserialize)]
322pub struct OpusLiveCard {
323 pub alt: String,
325 pub height: i32,
327 pub id: String,
329 pub size: Option<serde_json::Value>,
331 pub status: String,
333 pub tid: i32,
335 pub url: String,
337 pub width: i32,
339}
340
341impl BpiClient {
342 pub async fn article_view(&self, id: i64) -> Result<ArticleViewResponse, BpiError> {
348 let params = vec![("id", id.to_string()), ("gaia_source", "main_web".to_string())];
349 let params = self.get_wbi_sign2(params).await?;
350
351 let result: ArticleViewResponse = self
352 .get("https://api.bilibili.com/x/article/view")
353 .query(¶ms)
354 .send_bpi("获取专栏正文内容").await?;
355
356 Ok(result)
357 }
358}
359
360#[cfg(test)]
361mod tests {
362 use super::*;
363
364 #[tokio::test]
365 async fn test_article_view() -> Result<(), Box<BpiError>> {
366 let bpi = BpiClient::new();
367
368 let cvid = 2;
369
370 let result = bpi.article_view(cvid).await?;
371
372 let data = result.data.unwrap();
373 assert!(!data.title.is_empty());
374 assert!(!data.content.is_empty());
375 assert!(!data.author.name.is_empty());
376
377 Ok(())
378 }
379}