bpi_rs/creativecenter/season/
action.rs1use crate::{ BilibiliRequest, BpiClient, BpiError, BpiResponse };
6use serde::{ Deserialize, Serialize };
7use serde_json::json;
8
9#[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 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 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 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 pub async fn season_episodes_add(
106 &self,
107 section_id: u64,
108 episodes: Vec<EpisodeAdd>
109 ) -> Result<BpiResponse<serde_json::Value>, BpiError> {
110 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}