用rust实现掘金自动签到抽奖脚本

975 阅读4分钟

最近上班闲来无事,每天都在混掘金,看文章学技术。每天签到混点矿石,然后我就突然想到,那我为什么不实现一个自动签到及抽奖的脚本呢?反正闲着也是闲着,还不如干点喜欢的事。

说干就干,最近在研究学习rust语言。那我就用rust实现一个简单的掘金自动签到及抽奖的脚本。

首先,我们需要准备好要用到的rust工具库:

[dependencies]
reqwest = { version = "0.11", features = ["json"] }
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }

简单介绍一下:

  1. tokio,大名鼎鼎的rust异步运行时框架。用来实现async\await异步运行。
  2. serde,序列化与反序列化工具库。
  3. reqwest,http请求库。

准备好工具,我们立马开始正文。

1. 定义全局变量

通过const关键字,定义接口为全局变量,用字符串字面量把请求接口写进程序的硬编码。注意类型为&str

签到抽奖需要的接口:

//基础url
pub const BASE_URL: &str = "https://api.juejin.cn";
//查询今日是否已经签到
pub const ISSIGNINURL: &str = "/growth_api/v1/get_today_status";
//签到
pub const SIGNINURL: &str = "/growth_api/v1/check_in";
//查询今日免费抽奖机会
pub const ISDRAW: &str = "/growth_api/v1/lottery_config/get";
//抽奖
pub const DRAWURL: &str = "/growth_api/v1/lottery/draw";

记录账号需要的cookie信息:

pub const AID: &str = "xxxx";
pub const UUID: &str = "xxxxx";
pub const  SIGNATURE: &str = "xxx";
pub const _COOKIE: &str="xxxxx";

2.初始化reqwest客户端

首先定义好通用的错误类型:

type RespErr = Box<dyn std::error::Error>;

然后初始化reqwest客户端:

use reqwest::header::{self, COOKIE};
use reqwest::Client;

#[derive(Serialize, Deserialize, Debug)]
pub struct Post<'a> {
    pub aid: &'a str,
    pub uuid: &'a str,
    pub _signature: &'a str,
    pub cookie: &'a str,
}

#[tokio::main]
async fn main() -> Result<(), RespErr> {
    let params = Post {
        aid: AID,
        uuid: UUID,
        _signature: SIGNATURE,
        cookie: _COOKIE,
    };
    let client = init(&params).unwrap();
    Ok(())
}

这里定义一个post结构体,把前面写好的账号cookie信息当作请求头和请求参数。注意post的生命周期标识'a,因为前面我们把cookie信息写进了全局变量,所以post结构体是引用了全局变量的值,并不拥有所有权。在结构体中使用引用需要为结构体中的每一个引用标注上生命周期

下面是初始化reqwest客户端函数:

//初始化reqwest客户端
fn init(params: &Post) -> Result<Client, RespErr> {
    let mut headers = header::HeaderMap::new();
    headers.insert(COOKIE, params.cookie.parse().unwrap());
    let client = Client::builder().default_headers(headers).build()?;
    Ok(client)
}

在HTTP请求头加上cookie信息,初始化reqwest客户端之后将其返回。

3. 检验是否已签到

首先是检验是否已签到的函数:

#[derive(Debug, Deserialize)]
pub struct SignResp {
    pub err_no: i32,
    pub err_msg: String,
    pub data: Option<bool>,
}

//是否已签到
async fn is_sign_in(client: Client) -> Result<SignResp, RespErr> {
    let resp = client
        .get(BASE_URL.to_string() + ISSIGNINURL)
        .send()
        .await?
        .json::<SignResp>()
        .await?;
    println!("是否已签到:{:#?}", resp.data.unwrap());
    Ok(resp)
}

把reqwest客户端当作参数,实现HTTP请求。定义SignResp结构体作为返回值。在这里用json::<SignResp>()方法对返回值进行json序列化。 然后在mian函数里面使用is_sign_in函数:

#[tokio::main]
async fn main() -> Result<(), RespErr> {
    let params = Post {
        aid: AID,
        uuid: UUID,
        _signature: SIGNATURE,
        cookie: _COOKIE,
    };
    let client = init(&params).unwrap();
    //加上此内容
     let sign_resp = is_sign_in(client.clone()).await?;
      if let Some(false) = sign_resp.data {
        sign_in(client.clone(), &params).await?;      
    };
    
    Ok(())
}

因为事先用Option<bool>把返回值的data进行了包裹,进行json序列化之后,会成Some(T)或者None枚举(至于为什么需要用Option进行包裹?你猜?)。然后对返回值的data进行模式匹配,匹配到Some(false)表示未签到。

随后运行签到的sign_in方法。

//签到
async fn sign_in(client: Client, new_post: &Post<'_>) -> Result<SignResp, RespErr> {
    let resp = client
        .post(BASE_URL.to_string() + SIGNINURL)
        .json(new_post)
        .send()
        .await?
        .json::<SignResp>()
        .await?;
    println!("签到:{:#?}", resp);
    Ok(resp)
}

这时,返回值的data会变成null,所以SignResp结构体的data类型需要是Option<bool>(bingo,你猜中了没)。经过进行json序列化之后,会成None。

4. 检测是否已抽奖

经过前面的签到,我们会获得一次免费的抽奖机会。

在签到之后,继续进行抽奖进程:

 if let Some(false) = sign_resp.data {
        sign_in(client.clone(), &params).await?;

        let draw_resp = is_draw(client.clone()).await?;
        if draw_resp.data.free_count != 0 {
            draw(client.clone(), &params).await?;
        }
    };
//是否已抽奖
async fn is_draw(client: Client) -> Result<DrawResp, RespErr> {
    let resp = client
        .get(BASE_URL.to_string() + ISDRAW)
        .send()
        .await?
        .json::<DrawResp>()
        .await?;
    println!("未抽奖次数还有{:#?}次", resp.data.free_count);
    Ok(resp)
}

因为返回值的结构,所以需要定义多层嵌套的DrawResp结构体:

#[derive(Debug, Deserialize)]
pub struct DrawResp {
    pub err_no: i8,
    pub err_msg: String,
    pub data: DrawData,
}

#[derive(Debug, Deserialize)]
pub struct DrawData {
    pub lottery: Vec<Lottery>,
    pub free_count: i8,
    pub point_cost: i32,
}

#[derive(Debug, Deserialize)]
pub struct Lottery {
    pub lottery_id: String,
    pub lottery_name: String,
    pub lottery_type: i8,
    pub lottery_image: String,
    pub unlock_count: i8,
}

当返回值的free_count不为0时,表示还剩下免费抽奖次数。

继续进行抽奖:

//抽奖
async fn draw(client: Client, new_post: &Post<'_>) -> Result<String, RespErr> {
    let resp = client
        .post(BASE_URL.to_string() + DRAWURL)
        .json(new_post)
        .send()
        .await?
        .text()
        .await?;
    println!("抽奖:{:#?}", resp);
    Ok(resp)
}

5. 结束

就这样,我用rust实现了一个简简单单的掘金签到+抽奖脚本。

项目在这里:github

如果大佬们有更好的处理方法,可以提pr。

注:本项目仅当学习用途,各位切莫以身犯险。容易被封号!