Skip to main content

bpi_rs/creativecenter/season/
edit.rs

1//! 编辑合集小节 API
2//!
3//! [参考文档](https://github.com/Yuelioi/bilibili-API-collect/tree/cfc5fddcc8a94b74d91970bb5b4eaeb349addc47/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 SeasonEdit {
12    pub id: u64, // 合集 ID
13    pub title: String, // 合集标题
14    pub cover: String, // 封面图 URL
15    #[serde(default)]
16    pub desc: Option<String>, // 合集简介
17    #[serde(default)]
18    pub season_price: Option<u32>, // 合集价格(默认 0)
19    #[serde(default, rename = "isEnd")]
20    pub is_end: Option<u32>, // 是否完结 0:未完结 1:完结
21}
22
23/// 合集小节信息
24#[derive(Debug, Clone, Default, Serialize, Deserialize)]
25pub struct SeasonSectionEdit {
26    pub id: u64,
27    #[serde(rename = "type")]
28    pub type_field: u64,
29    #[serde(rename = "seasonId")]
30    pub season_id: u64,
31    pub title: String,
32}
33
34/// 合集内视频排序信息
35#[derive(Debug, Clone, Serialize, Deserialize)]
36pub struct SectionSort {
37    pub id: u64, // 合集内视频 ID
38    pub order: u32, // 排序位置
39}
40
41#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
42pub struct EpisodeEdit {
43    pub id: u64,
44    pub title: String,
45    pub aid: u64,
46    pub cid: u64,
47    #[serde(rename = "seasonId")]
48    pub season_id: u64,
49    #[serde(rename = "sectionId")]
50    pub section_id: u64,
51    pub sorts: Vec<EpisodeSort>,
52    pub order: u64,
53}
54
55#[derive(Serialize)]
56struct EpisodeEditPayload {
57    #[serde(flatten)]
58    section: EpisodeEdit,
59    sorts: Vec<EpisodeSort>,
60}
61
62#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
63pub struct EpisodeSort {
64    pub id: u64,
65    pub sort: u64,
66}
67
68/// 合集小节排序信息
69#[derive(Debug, Clone, Serialize, Deserialize)]
70pub struct SeasonSectionSort {
71    pub id: u64, // 小节 ID
72    pub sort: u32, // 排序位置
73}
74
75#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
76pub struct SectionAddEpisodesRequest {
77    #[serde(rename = "sectionId")]
78    pub section_id: u64,
79    pub episodes: Vec<Episode>,
80}
81
82#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
83pub struct Episode {
84    pub title: String,
85    pub aid: u64,
86    pub cid: u64,
87    pub charging_pay: i64,
88    pub member_first: i64,
89    pub limited_free: bool,
90}
91
92impl BpiClient {
93    /// 编辑合集信息
94    ///
95    /// 编辑合集的基本信息,包括标题、封面、简介等。
96    ///
97    /// # 参数
98    /// | 名称 | 类型 | 说明 |
99    /// | ---- | ---- | ---- |
100    /// | `season` | SeasonEdit | 合集信息 |
101    /// | `sorts` | `Vec<SeasonSectionSort>` | 小节排序列表 |
102    ///
103    /// # 文档
104    /// [编辑合集信息](https://github.com/Yuelioi/bilibili-API-collect/tree/cfc5fddcc8a94b74d91970bb5b4eaeb349addc47/docs/creativecenter/season/edit.md#编辑合集信息)
105    pub async fn season_edit(
106        &self,
107        season: SeasonEdit,
108        sorts: Vec<SeasonSectionSort>
109    ) -> Result<BpiResponse<serde_json::Value>, BpiError> {
110        let csrf = self.csrf()?;
111
112        let payload = json!({
113            "season": season,
114            "sorts": sorts
115        });
116
117        self
118            .post("https://member.bilibili.com/x2/creative/web/season/edit")
119            .query(&[("csrf", csrf)])
120            .json(&payload)
121            .send_bpi("编辑合集信息").await
122    }
123    /// 编辑合集小节(需要开启小节功能)
124    ///
125    /// 编辑合集中的小节信息,包括小节标题和视频排序。
126    ///
127    /// # 参数
128    /// | 名称 | 类型 | 说明 |
129    /// | ---- | ---- | ---- |
130    /// | `section` | SeasonSectionEdit | 小节信息 |
131    /// | `sorts` | `Vec<SectionSort>` | 视频排序信息 |
132    ///
133    /// # 文档
134    /// [编辑合集小节](https://github.com/Yuelioi/bilibili-API-collect/tree/cfc5fddcc8a94b74d91970bb5b4eaeb349addc47/docs/creativecenter/season/edit.md#编辑合集小节)
135    pub async fn season_section_edit(
136        &self,
137        section: SeasonSectionEdit,
138        sorts: Vec<SectionSort>
139    ) -> Result<BpiResponse<serde_json::Value>, BpiError> {
140        let csrf = self.csrf()?;
141
142        let payload = json!({
143            "section": section,
144            "sorts": sorts
145        });
146
147        self
148            .post("https://member.bilibili.com/x2/creative/web/season/section/edit")
149            .query(&[("csrf", csrf)])
150            .json(&payload)
151            .send_bpi("编辑合集小节").await
152    }
153
154    /// 编辑小节中的章节
155    ///
156    /// 编辑合集中的小节信息,包括小节标题和视频排序。
157    ///
158    /// # 参数
159    /// | 名称 | 类型 | 说明 |
160    /// | ---- | ---- | ---- |
161    /// | `section` | SeasonSectionEdit | 小节信息 |
162    /// | `sorts` | `Vec<SectionSort>` | 视频排序信息 |
163    ///
164    /// # 文档
165    /// [编辑合集小节](https://github.com/Yuelioi/bilibili-API-collect/tree/cfc5fddcc8a94b74d91970bb5b4eaeb349addc47/docs/creativecenter/season/edit.md#编辑合集小节)
166    pub async fn season_section_episode_edit(
167        &self,
168        section: EpisodeEdit,
169        sorts: Vec<EpisodeSort>
170    ) -> Result<BpiResponse<serde_json::Value>, BpiError> {
171        let csrf = self.csrf()?;
172
173        let payload = EpisodeEditPayload { section, sorts };
174
175        self
176            .post("https://member.bilibili.com/x2/creative/web/season/section/episode/edit")
177            .query(&[("csrf", csrf)])
178            .json(&payload)
179            .send_bpi("编辑合集章节").await
180    }
181
182    /// 切换小节/正常显示
183    ///
184    /// # 参数
185    /// * season_id 合集id
186
187    pub async fn season_enable_section(
188        &self,
189        season_id: u64,
190        enable: bool
191    ) -> Result<BpiResponse<serde_json::Value>, BpiError> {
192        let csrf = self.csrf()?;
193        let params = vec![
194            ("csrf", csrf),
195            ("season_id", season_id.to_string()),
196            ("no_section", (if enable { "0" } else { "1" }).to_string())
197        ];
198
199        self
200            .post("https://member.bilibili.com/x2/creative/web/season/section/switch")
201            .form(&params)
202            .send_bpi("切换 小节/正常 模式").await
203    }
204    /// 添加视频到小节(需要开启小节功能)
205    ///
206    /// 将视频添加到指定的合集小节中。
207    ///
208    /// # 参数
209    /// | 名称 | 类型 | 说明 |
210    /// | ---- | ---- | ---- |
211    /// | `aid` | u64 | 视频 aid |
212    /// | `season_id` | u64 | 合集 ID |
213    /// | `section_id` | u64 | 小节 ID |
214    /// | `title` | &str | 视频标题 |
215    ///
216    /// # 文档
217    /// [编辑投稿视频合集/小节](https://github.com/Yuelioi/bilibili-API-collect/tree/cfc5fddcc8a94b74d91970bb5b4eaeb349addc47/docs/creativecenter/season/edit.md#编辑投稿视频合集小节)
218    pub async fn season_section_add_episodes(
219        &self,
220        section_id: u64,
221        episodes: Vec<Episode>
222    ) -> Result<BpiResponse<serde_json::Value>, BpiError> {
223        let csrf = self.csrf()?;
224
225        let payload = SectionAddEpisodesRequest {
226            section_id,
227            episodes,
228        };
229
230        self
231            .post("https://member.bilibili.com/x2/creative/web/season/section/episodes/add")
232            .json(&payload)
233            .query(&[("csrf", csrf)])
234            .send_bpi("编辑投稿视频合集").await
235    }
236}
237
238#[cfg(test)]
239mod tests {
240    use super::*;
241
242    const TEST_SECTION_ID: u64 = 7032691;
243    const TEST_SEASON_ID: u64 = 6363779;
244    const TEST_PART_ID: u64 = 147443135; // 不知道从哪弄
245
246    const TEST_AID: u64 = 772876546;
247    const TEST_CID: u64 = 829554597;
248
249    #[tokio::test]
250    async fn test_edit_season_info() -> Result<(), Box<BpiError>> {
251        let bpi = BpiClient::new();
252
253        let season = SeasonEdit {
254            id: TEST_SEASON_ID,
255            title: "修改后的合集标题".to_string(),
256            cover: "https://archive.biliimg.com/bfs/archive/fb699e3f5ae17285cf5f6ebd42c156482f829215.jpg".to_string(),
257            desc: Some("修改后的合集简介".to_string()),
258            ..Default::default()
259        };
260
261        let sorts = vec![SeasonSectionSort {
262            id: TEST_SECTION_ID,
263            sort: 1,
264        }];
265
266        bpi.season_edit(season, sorts).await?;
267
268        tracing::info!("编辑合集信息成功");
269        Ok(())
270    }
271
272    #[tokio::test]
273    async fn test_edit_season_section() -> Result<(), Box<BpiError>> {
274        let bpi = BpiClient::new();
275
276        let section = SeasonSectionEdit {
277            id: TEST_SECTION_ID,
278            season_id: TEST_SEASON_ID,
279            title: "测试修改小节标题".to_string(),
280            ..Default::default()
281        };
282
283        let sorts = vec![SectionSort {
284            id: TEST_PART_ID,
285            order: 1,
286        }];
287
288        bpi.season_section_edit(section, sorts).await?;
289
290        tracing::info!("编辑合集小节成功");
291        Ok(())
292    }
293
294    #[tokio::test]
295    async fn test_edit_season_episode() -> Result<(), Box<BpiError>> {
296        let bpi = BpiClient::new();
297
298        let section = EpisodeEdit {
299            id: TEST_PART_ID,
300            season_id: TEST_SEASON_ID,
301            section_id: TEST_SECTION_ID,
302            sorts: vec![],
303            title: "测试修改章节标题".to_string(),
304            aid: TEST_AID,
305            cid: TEST_CID,
306            order: 1,
307        };
308
309        let sorts = vec![EpisodeSort {
310            id: TEST_PART_ID,
311            sort: 1,
312        }];
313
314        bpi.season_section_episode_edit(section, sorts).await?;
315
316        tracing::info!("编辑合集小节成功");
317        Ok(())
318    }
319
320    #[tokio::test]
321    async fn test_add_episodes() -> Result<(), BpiError> {
322        let bpi = BpiClient::new();
323
324        bpi
325            .season_section_add_episodes(
326                TEST_SECTION_ID,
327                vec![Episode {
328                    title: "新增章节标题".to_string(),
329                    aid: TEST_AID,
330                    cid: TEST_CID,
331                    ..Default::default()
332                }]
333            ).await
334            .map(|_| ()) // 忽略 Ok 的内容
335            .or_else(|e| {
336                if e.code() == Some(20080) { Ok(()) } else { Err(e) }
337            })?;
338
339        Ok(())
340    }
341}