Skip to main content

bpi_rs/creativecenter/season/
action.rs

1//! 创建合集 API
2//!
3//! [参考文档](https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/creativecenter/season.md)
4
5use crate::{ BilibiliRequest, BpiClient, BpiError, BpiResponse };
6use serde::{ Deserialize, Serialize };
7use serde_json::json;
8
9/// 合集视频条目(添加用)
10#[derive(Debug, Clone, Serialize, Deserialize, Default)]
11pub struct EpisodeAdd {
12    pub title: String,
13    pub aid: u64,
14    pub cid: u64,
15
16    #[serde(default)]
17    pub charging_pay: i64,
18    #[serde(default)]
19    pub member_first: i64,
20    #[serde(default)]
21    pub limited_free: bool,
22}
23
24impl BpiClient {
25    /// 创建合集
26    ///
27    /// 创建一个新的视频合集,需要提供标题、封面等信息。
28    ///
29    /// # 参数
30    /// | 名称 | 类型 | 说明 |
31    /// | ---- | ---- | ---- |
32    /// | `title` | &str | 合集标题 |
33    /// | `desc` | `Option<&str>` | 合集简介,可选 |
34    /// | `cover` | &str | 封面图 URL(从上传接口获取) |
35    /// | `season_price` | `Option<u32>` | 合集价格,可选,默认 0 |
36    ///
37    /// # 文档
38    /// [创建合集](https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/creativecenter/season/create.md#创建合集)
39    pub async fn season_create(
40        &self,
41        title: &str,
42        desc: Option<&str>,
43        cover: &str,
44        season_price: Option<u32>
45    ) -> Result<BpiResponse<u64>, BpiError> {
46        // 校验 csrf
47        let csrf = self.csrf()?;
48
49        let mut form = vec![
50            ("title", title.to_string()),
51            ("cover", cover.to_string()),
52            ("csrf", csrf)
53        ];
54
55        if let Some(d) = desc {
56            form.push(("desc", d.to_string()));
57        }
58        if let Some(price) = season_price {
59            form.push(("season_price", price.to_string()));
60        }
61
62        self
63            .post("https://member.bilibili.com/x2/creative/web/season/add")
64            .form(&form)
65            .send_bpi("创建合集").await
66    }
67
68    /// 删除合集
69    ///
70    /// 删除指定的合集,需要提供合集 ID。
71    ///
72    /// # 参数
73    /// | 名称 | 类型 | 说明 |
74    /// | ---- | ---- | ---- |
75    /// | `season_id` | u64 | 合集 ID |
76    ///
77    /// # 文档
78    /// [删除合集](https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/creativecenter/season/del.md#删除合集)
79    pub async fn season_delete(
80        &self,
81        season_id: u64
82    ) -> Result<BpiResponse<serde_json::Value>, BpiError> {
83        let csrf = self.csrf()?;
84
85        let form = vec![("id", season_id.to_string()), ("csrf", csrf)];
86
87        self
88            .post("https://member.bilibili.com/x2/creative/web/season/del")
89            .form(&form)
90            .send_bpi("删除合集").await
91    }
92
93    /// 添加视频到合集
94    ///
95    /// 将视频添加到指定的合集小节中。
96    ///
97    /// # 参数
98    /// | 名称 | 类型 | 说明 |
99    /// | ---- | ---- | ---- |
100    /// | `section_id` | u64 | 合集小节 ID |
101    /// | `episodes` | `Vec<EpisodeAdd>` | 视频列表 |
102    ///
103    /// # 文档
104    /// [添加视频到合集](https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/creativecenter/season/push.md#添加视频到合集)
105    pub async fn season_episodes_add(
106        &self,
107        section_id: u64,
108        episodes: Vec<EpisodeAdd>
109    ) -> Result<BpiResponse<serde_json::Value>, BpiError> {
110        // 校验 csrf
111        let csrf = self.csrf()?;
112
113        let payload =
114            json!({
115            "sectionId": section_id,
116            "episodes": episodes
117        });
118
119        self
120            .post("https://member.bilibili.com/x2/creative/web/season/section/episodes/add")
121            .with_bilibili_headers()
122            .query(&[("csrf", csrf)])
123            .json(&payload)
124            .send_bpi("添加视频到合集").await
125    }
126}
127
128#[cfg(test)]
129mod tests {
130    use base64::{ Engine as _, engine::general_purpose };
131    use std::fs;
132
133    const TEST_AID: u64 = 772876546;
134    const TEST_CID: u64 = 829554597;
135
136    const TEST_SECTION_ID: u64 = 7032691;
137
138    use super::*;
139
140    #[tokio::test]
141    async fn test_create_season() -> Result<(), Box<BpiError>> {
142        let bpi = BpiClient::new();
143
144        let img_data = fs::read("./assets/test.jpg").map_err(|_| BpiError::parse("读取图片失败"))?;
145        let img_base64 = general_purpose::STANDARD.encode(&img_data);
146        let img_data_uri = format!("data:image/jpeg;base64,{}", img_base64);
147        let resp = bpi.upload_cover("image/jpeg", &img_data_uri).await?;
148
149        let result = bpi.season_create(
150            "测试合集 - Powered by Rust",
151            Some("这是一个通过 API 创建的测试合集"),
152            resp.data.unwrap().url.as_str(),
153            Some(0)
154        ).await?;
155
156        let season_id = result.data.unwrap();
157
158        if let Some(season_id) = result.data {
159            tracing::info!("创建成功,合集 ID = {}", season_id);
160        } else {
161            panic!("返回数据为空");
162        }
163
164        tokio::time::sleep(tokio::time::Duration::from_secs(3)).await;
165
166        let season = bpi.season_info(season_id).await?;
167
168        let section_id = season.into_data()?.sections.sections.first().unwrap().id;
169
170        tracing::info!("获取成功,section ID = {}", section_id);
171
172        tokio::time::sleep(tokio::time::Duration::from_secs(3)).await;
173        bpi.season_delete(season_id).await?;
174
175        Ok(())
176    }
177    #[tokio::test]
178    async fn test_add_season() -> Result<(), Box<BpiError>> {
179        let bpi = BpiClient::new();
180        let episodes = vec![EpisodeAdd {
181            aid: TEST_AID,
182            cid: TEST_CID,
183
184            title: "测试合集内单集".to_string(),
185            ..Default::default()
186        }];
187
188        let result = bpi.season_episodes_add(TEST_SECTION_ID, episodes).await?;
189        tracing::info!("{:?}", result);
190
191        Ok(())
192    }
193}