Skip to main content

bpi_rs/article/
view.rs

1//! 专栏内容
2//!
3//! [查看 API 文档](https://github.com/Yuelioi/bilibili-API-collect/tree/cfc5fddcc8a94b74d91970bb5b4eaeb349addc47/docs/article/view.md)
4
5use super::models::{ ArticleAuthor, ArticleCategory, ArticleMedia, ArticleStats };
6use crate::{ BilibiliRequest, BpiClient, BpiError, BpiResponse };
7use serde::{ Deserialize, Serialize };
8
9/// 专栏内容响应类型
10pub type ArticleViewResponse = BpiResponse<ArticleViewData>;
11
12/// 专栏内容数据
13#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct ArticleViewData {
15    /// 操作ID?
16    pub act_id: i64,
17    /// 应用时间?
18    pub apply_time: String,
19    /// 属性位?
20    pub attributes: Option<i32>,
21    /// 授权码?
22    #[serde(rename = "authenMark")]
23    pub authen_mark: Option<serde_json::Value>,
24    /// 作者信息
25    pub author: ArticleAuthor,
26    /// 文章头图URL 空则为无
27    pub banner_url: String,
28    /// 专栏分类信息 首项为主分区, 第二项为子分区
29    pub categories: Vec<ArticleCategory>,
30    /// 专栏分类信息 子分区
31    pub category: ArticleCategory,
32    /// 检查状态?
33    pub check_state: i32,
34    /// 检查时间?
35    pub check_time: String,
36    /// 文章内容 type字段为0为HTML, 3为JSON
37    pub content: String,
38    /// 内容图片列表?
39    pub content_pic_list: Option<serde_json::Value>,
40    /// 封面视频AV号 0为无视频
41    pub cover_avid: i64,
42    /// 创建时间 UNIX秒级时间戳
43    pub ctime: i64,
44    /// 争议信息?
45    pub dispute: Option<serde_json::Value>,
46    /// 动态opus id
47    pub dyn_id_str: String,
48    /// 动态信息? 可能不存在
49    pub dynamic: Option<String>,
50    /// 专栏文章ID
51    pub id: i64,
52    /// 图片URL
53    pub image_urls: Vec<String>,
54    /// 是否喜欢?
55    pub is_like: bool,
56    /// 关键词 以逗号分隔
57    pub keywords: String,
58    /// 文集信息
59    pub list: Option<ArticleList>,
60    /// 媒体信息?
61    pub media: ArticleMedia,
62    /// 修改时间 UNIX秒级时间戳
63    pub mtime: i64,
64    /// opus信息 当type字段为3时存在, 包含了更加详细的富文本信息
65    pub opus: Option<ArticleOpus>,
66    /// 原始图片URL
67    pub origin_image_urls: Vec<String>,
68    /// 原始模板ID?
69    pub origin_template_id: i32,
70    /// 是否原创 0: 非原创 1: 原创
71    pub original: i32,
72    /// 仅自己可见
73    pub private_pub: i32,
74    /// 发布时间 UNIX秒级时间戳
75    pub publish_time: i64,
76    /// 是否允许转载 0: 不允许 1: 允许规范转载
77    pub reprint: i32,
78    /// 专栏状态
79    pub state: i32,
80    /// 统计数据
81    pub stats: ArticleStats,
82    /// 专栏开头部分内容 纯文本
83    pub summary: String,
84    /// 专栏标签
85    pub tags: Vec<ArticleTag>,
86    /// 模板ID?
87    pub template_id: i32,
88    /// 专栏标题
89    pub title: String,
90    /// 封面食品信息?
91    pub top_video_info: Option<serde_json::Value>,
92    /// 作者总文章数
93    pub total_art_num: i64,
94    /// 类型?
95    pub r#type: i32,
96    /// 版本ID?
97    pub version_id: i64,
98    /// 文章总词数
99    pub words: i64,
100}
101
102/// 作者VIP信息
103#[derive(Debug, Clone, Serialize, Deserialize)]
104pub struct AuthorVip {
105    /// VIP类型
106    pub r#type: i32,
107    /// VIP状态
108    pub status: i32,
109    /// 到期时间
110    pub due_date: i64,
111    /// 支付类型
112    pub vip_pay_type: i32,
113    /// 主题类型
114    pub theme_type: i32,
115    /// 标签
116    pub label: Option<serde_json::Value>,
117}
118
119/// 专栏文集信息
120#[derive(Debug, Clone, Serialize, Deserialize)]
121pub struct ArticleList {
122    /// 文集ID
123    pub id: i64,
124    /// 文集名称
125    pub name: String,
126    /// 文集图片
127    pub image_url: String,
128    /// 文集更新时间
129    pub update_time: i64,
130    /// 文集创建时间
131    pub ctime: i64,
132    /// 文集发布时间
133    pub publish_time: i64,
134    /// 文集简介
135    pub summary: String,
136    /// 文集字数
137    pub words: i64,
138    /// 文集阅读量
139    pub read: i64,
140    /// 文集内文章数量
141    pub articles_count: i32,
142    /// 文集状态
143    pub state: i32,
144    /// 文集原因
145    pub reason: String,
146    /// 文集申请时间
147    pub apply_time: String,
148    /// 文集审核时间
149    pub check_time: String,
150}
151
152/// 专栏标签
153#[derive(Debug, Clone, Serialize, Deserialize)]
154pub struct ArticleTag {
155    /// 标签ID
156    pub tid: i32,
157    /// 标签名称
158    pub name: String,
159    // /// 标签类型
160    // pub r#type: i32,
161}
162
163/// 专栏Opus信息(富文本内容)
164#[derive(Debug, Clone, Serialize, Deserialize)]
165pub struct ArticleOpus {
166    /// 以JSON呈现的文本内容
167    pub ops: Vec<OpusOperation>,
168}
169
170/// Opus操作
171#[derive(Debug, Clone, Serialize, Deserialize)]
172pub struct OpusOperation {
173    /// 属性
174    pub attribute: Option<OpusAttribute>,
175    /// 插入内容
176    pub insert: OpusInsert,
177}
178
179/// Opus属性
180#[derive(Debug, Clone, Serialize, Deserialize)]
181pub struct OpusAttribute {
182    /// 文字对齐
183    pub align: Option<String>,
184    /// 块级引用
185    pub blockquote: Option<bool>,
186    /// 加粗
187    pub bold: Option<bool>,
188    /// 类名
189    pub class: Option<String>,
190    /// 颜色
191    pub color: Option<String>,
192    /// 标题级别
193    pub header: Option<i32>,
194    /// 删除线
195    pub strike: Option<bool>,
196    /// 站内链接
197    pub link: Option<String>,
198    /// 斜体
199    pub italic: Option<bool>,
200    /// 列表
201    pub list: Option<String>,
202}
203
204/// Opus插入内容
205#[derive(Debug, Clone, Serialize, Deserialize)]
206#[serde(untagged)]
207pub enum OpusInsert {
208    /// 文本内容
209    Text(String),
210    /// 富文本内容
211    Rich(OpusRichInsert),
212}
213
214/// Opus富文本插入内容
215#[derive(Debug, Clone, Serialize, Deserialize)]
216pub struct OpusRichInsert {
217    /// 原生图片
218    pub native_image: Option<OpusImage>,
219    /// 分割线
220    pub cut_off: Option<OpusCutOff>,
221    /// 视频卡片
222    pub video_card: Option<OpusVideoCard>,
223    /// 专栏卡片
224    pub article_card: Option<OpusArticleCard>,
225    /// 投票卡片
226    pub vote_card: Option<OpusVoteCard>,
227    /// 直播卡片
228    pub live_card: Option<OpusLiveCard>,
229}
230
231/// Opus图片
232#[derive(Debug, Clone, Serialize, Deserialize)]
233pub struct OpusImage {
234    /// 图像的备用文本描述
235    pub alt: String,
236    /// 图像的URL
237    pub url: String,
238    /// 图像的宽度
239    pub width: i32,
240    /// 图像的高度
241    pub height: i32,
242    /// 图像的文件大小
243    pub size: i64,
244    /// 图像状态
245    pub status: String,
246}
247
248/// Opus分割线
249#[derive(Debug, Clone, Serialize, Deserialize)]
250pub struct OpusCutOff {
251    /// 类型
252    pub r#type: String,
253    /// 分割线图片URL
254    pub url: String,
255}
256
257/// Opus视频卡片
258#[derive(Debug, Clone, Serialize, Deserialize)]
259pub struct OpusVideoCard {
260    /// 备用文本
261    pub alt: String,
262    /// 卡片高度
263    pub height: i32,
264    /// 视频ID
265    pub id: String,
266    /// 大小
267    pub size: Option<serde_json::Value>,
268    /// 状态
269    pub status: String,
270    /// 类型ID
271    pub tid: f64,
272    /// 卡片图片URL
273    pub url: String,
274    /// 卡片宽度
275    pub width: i32,
276}
277
278/// Opus专栏卡片
279#[derive(Debug, Clone, Serialize, Deserialize)]
280pub struct OpusArticleCard {
281    /// 备用文本
282    pub alt: String,
283    /// 卡片高度
284    pub height: i32,
285    /// 文章ID
286    pub id: String,
287    /// 大小
288    pub size: Option<serde_json::Value>,
289    /// 状态
290    pub status: String,
291    /// 类型ID
292    pub tid: i32,
293    /// 卡片图片URL
294    pub url: String,
295    /// 卡片宽度
296    pub width: i32,
297}
298
299/// Opus投票卡片
300#[derive(Debug, Clone, Serialize, Deserialize)]
301pub struct OpusVoteCard {
302    /// 备用文本
303    pub alt: String,
304    /// 卡片高度
305    pub height: i32,
306    /// 投票ID
307    pub id: String,
308    /// 大小
309    pub size: Option<serde_json::Value>,
310    /// 状态
311    pub status: String,
312    /// 类型ID
313    pub tid: i32,
314    /// 卡片图片URL
315    pub url: String,
316    /// 卡片宽度
317    pub width: i32,
318}
319
320/// Opus直播卡片
321#[derive(Debug, Clone, Serialize, Deserialize)]
322pub struct OpusLiveCard {
323    /// 备用文本
324    pub alt: String,
325    /// 卡片高度
326    pub height: i32,
327    /// 直播间ID
328    pub id: String,
329    /// 大小
330    pub size: Option<serde_json::Value>,
331    /// 状态
332    pub status: String,
333    /// 类型ID
334    pub tid: i32,
335    /// 卡片图片URL
336    pub url: String,
337    /// 卡片宽度
338    pub width: i32,
339}
340
341impl BpiClient {
342    /// 获取专栏正文内容
343    ///
344    /// # 参数
345    /// * `id` - 专栏文章ID (必要)
346    /// * `gaia_source` - 来源,默认为"main_web" (可选)
347    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(&params)
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}