1use crate::{ BilibiliRequest, BpiClient, BpiError, BpiResponse };
6use serde::{ Deserialize, Serialize };
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct ActivityListData {
11 pub list: Vec<ActivityItem>,
13 pub num: i32,
15 pub size: i32,
17 pub total: i32,
19}
20
21#[derive(Debug, Clone, Serialize, Deserialize)]
23pub struct ActivityItem {
24 pub id: i32,
26 pub state: i32,
28 pub stime: i64,
30 pub etime: i64,
32 pub ctime: i64,
34 pub mtime: i64,
36 pub name: String,
38 pub h5_url: String,
40 pub h5_cover: String,
42 pub page_name: String,
44 pub plat: i32,
46 pub desc: String,
48}
49
50#[derive(Debug, Clone, Serialize)]
51pub struct ActivityListParams {
52 #[serde(skip_serializing_if = "Option::is_none")]
54 pub plat: Option<String>,
55
56 #[serde(skip_serializing_if = "Option::is_none")]
58 pub mold: Option<i32>,
59
60 #[serde(skip_serializing_if = "Option::is_none")]
62 pub http: Option<i32>,
63
64 #[serde(skip_serializing_if = "Option::is_none")]
66 pub pn: Option<i32>,
67
68 #[serde(skip_serializing_if = "Option::is_none")]
70 pub ps: Option<i32>,
71}
72
73impl Default for ActivityListParams {
74 fn default() -> Self {
75 Self {
76 plat: Some("1,3".to_string()),
77 mold: Some(0),
78 http: Some(3),
79 pn: Some(1),
80 ps: Some(15),
81 }
82 }
83}
84
85impl BpiClient {
86 pub async fn activity_list(
101 &self,
102 plat: Option<&str>,
103 mold: Option<i32>,
104 http: Option<i32>,
105 pn: Option<i32>,
106 ps: Option<i32>
107 ) -> Result<BpiResponse<ActivityListData>, BpiError> {
108 let params = ActivityListParams {
109 plat: plat.map(|s| s.to_string()).or_else(|| Some("1,3".to_string())),
110 mold: mold.or(Some(0)),
111 http: http.or(Some(3)),
112 pn: pn.or(Some(1)),
113 ps: ps.or(Some(15)),
114 };
115
116 let result = self
117 .get("https://api.bilibili.com/x/activity/page/list")
118 .query(¶ms)
119 .send_bpi("获取活动列表").await?;
120
121 Ok(result)
122 }
123
124 pub async fn activity_list_default(&self) -> Result<BpiResponse<ActivityListData>, BpiError> {
129 self.activity_list(Some("1,3"), None, None, Some(1), Some(15)).await
130 }
131}
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136
137 #[tokio::test]
138 async fn test_get_activity_list() -> Result<(), Box<BpiError>> {
139 let bpi = BpiClient::new();
140
141 let result = bpi.activity_list(Some("1,3"), None, None, Some(1), Some(4)).await?;
143 let data = result.into_data()?;
144 tracing::info!("{:#?}", data);
145
146 assert!(!data.list.is_empty());
147 assert_eq!(data.num, 1);
148 assert_eq!(data.size, 4);
149 assert!(data.total > 0);
150
151 Ok(())
152 }
153
154 #[tokio::test]
155 async fn test_get_activity_list_simple() -> Result<(), Box<BpiError>> {
156 let bpi = BpiClient::new();
157
158 let result = bpi.activity_list_default().await?;
160 let data = result.into_data()?;
161 tracing::info!("{:#?}", data);
162
163 assert!(!data.list.is_empty());
164 assert_eq!(data.num, 1);
165 assert_eq!(data.size, 15);
166
167 Ok(())
168 }
169
170 #[tokio::test]
171 async fn test_activity_item_fields() -> Result<(), Box<BpiError>> {
172 let bpi = BpiClient::new();
173
174 let result = bpi.activity_list(Some("1,3"), None, None, Some(1), Some(1)).await?;
175 let data = result.into_data()?;
176 tracing::info!("{:#?}", data);
177
178 if let Some(activity) = data.list.first() {
179 assert!(activity.id > 0);
180 assert_eq!(activity.state, 1);
181 assert!(!activity.name.is_empty());
182 assert!(!activity.page_name.is_empty());
183 }
184
185 Ok(())
186 }
187}