Skip to main content

bpi_rs/login/login_action/
sms.rs

1use reqwest::header::SET_COOKIE;
2
3use serde::{Deserialize, Serialize};
4use tracing::{error, info};
5
6use crate::{BilibiliRequest, BpiClient, BpiError, BpiResponse};
7
8#[derive(Debug, Deserialize, Serialize)]
9pub struct SMSSendData {
10    captcha_key: String, // 短信登录 token
11}
12
13#[derive(Debug, Deserialize, Serialize)]
14struct SMSLoginData {
15    is_new: bool, // 是否为新用户
16    status: i32,  // 0:成功
17    url: String,  // 跳转url
18}
19
20impl BpiClient {
21    /// 发送短信验证码(Web端)
22    ///
23    /// # 参数
24    /// * `cid` - 国际冠字码
25    /// * `tel` - 手机号码
26    /// * `source` - 登录来源 "main_web" 或 "main_mini"
27    /// * `token` - 登录 API token
28    /// * `challenge` - 极验 challenge
29    /// * `validate` - 极验 result
30    /// * `seccode` - 极验 result + "|jordan"
31    pub async fn login_send_sms_code(
32        &self,
33        cid: u32,
34        tel: u32,
35        source: &str,
36        token: &str,
37        challenge: &str,
38        validate: &str,
39        seccode: &str,
40    ) -> Result<BpiResponse<SMSSendData>, BpiError> {
41        // 构建表单
42        let form = vec![
43            ("cid", cid.to_string()),
44            ("tel", tel.to_string()),
45            ("source", source.to_string()),
46            ("token", token.to_string()),
47            ("challenge", challenge.to_string()),
48            ("validate", validate.to_string()),
49            ("seccode", seccode.to_string()),
50        ];
51
52        // 发送请求
53        let result = self
54            .post("https://passport.bilibili.com/x/passport-login/web/sms/send")
55            .form(&form)
56            .send_bpi("发送短信验证码")
57            .await?;
58
59        Ok(result)
60    }
61
62    /// 短信登录
63    ///
64    /// * `cid` - 国际冠字码
65    /// * `tel` - 手机号码
66    /// * `captcha_key` - 短信登录 token(基于login_send_sms_code)
67    /// * `code` - 短信验证码 5min过期
68    pub async fn login_with_sms(
69        &self,
70        cid: u32,
71        tel: u32,
72        captcha_key: &str,
73        code: &str,
74    ) -> Result<(), String> {
75        let form = vec![
76            ("cid", cid.to_string()),
77            ("tel", tel.to_string()),
78            ("code", code.to_string()),
79            ("source", "main_web".to_string()),
80            ("captcha_key", captcha_key.to_string()),
81            ("go_url", "https://www.bilibili.com".to_string()),
82            ("keep", true.to_string()),
83        ];
84
85        let response = BpiClient::new()
86            .post("https://passport.bilibili.com/x/passport-login/web/login/sms")
87            .form(&form)
88            .send()
89            .await
90            .map_err(|e| e.to_string())?;
91
92        if let Some(cookies) = response
93            .headers()
94            .get_all(SET_COOKIE)
95            .iter()
96            .map(|v| v.to_str().unwrap_or(""))
97            .collect::<Vec<_>>()
98            .join("; ")
99            .into()
100        {
101            info!("登录返回的 Cookie: {}", cookies);
102        }
103
104        let resp = response
105            .json::<BpiResponse<SMSLoginData>>()
106            .await
107            .map_err(|e| {
108                error!("解析短信登录响应失败: {:?}", e);
109                e.to_string()
110            })?;
111
112        if resp.code != 0 {
113            error!("短信登录失败: code={}, message={}", resp.code, resp.message);
114            return Err(resp.message);
115        }
116
117        match resp.code {
118            0 => {
119                info!("短信登录成功");
120                Ok(())
121            }
122            code => {
123                error!("验证码发送失败: code={}, message={}", code, resp.message);
124                let msg = match code {
125                    -400 => "请求错误".to_string(),
126                    1006 => "请输入正确的短信验证码".to_string(),
127                    1007 => "短信验证码已过期".to_string(),
128
129                    _ => resp.message,
130                };
131                Err(msg)
132            }
133        }
134    }
135}