quotes 0.1.2

Request tick、stock_list msgs
Documentation
use calamine::{open_workbook, DataType, Reader, Xlsx};
use futures::join;
use futures::AsyncReadExt;
use isahc::prelude::*;
pub use serde::{Deserialize, Serialize};
use std::fs;
use url::form_urlencoded;

use crate::error::Error;

const TMPFILENAME: &str = "tmp.xlsx";
const CODECOLID: u32 = 4;

#[derive(Deserialize, Serialize, Debug)]
#[serde(rename_all = "UPPERCASE")]
struct ShItem {
    security_code_a: String,
}

#[derive(Deserialize, Serialize, Debug)]
struct ShResult {
    result: Vec<ShItem>,
}

pub async fn stock_list() -> Result<Vec<String>, Error> {
    let (list_sh, list_sz) = join!(stock_list_sh(), stock_list_sz());
    Ok([list_sh?, list_sz?].concat())
}

pub async fn stock_list_sh() -> Result<Vec<String>, Error> {
    let (list_type1, list_type8) = join!(stock_list_sh_by_type("1"), stock_list_sh_by_type("8"));
    Ok([list_type1?, list_type8?].concat())
}

pub async fn stock_list_sz() -> Result<Vec<String>, Error> {
    let encoded: String = form_urlencoded::Serializer::new(String::new())
        .append_pair("SHOWTYPE", "xlsx")
        .append_pair("CATALOGID", "1110")
        .append_pair("TABKEY", "tab1")
        .append_pair("random", "0.6935816432433362")
        .finish();
    let url = format!("http://www.szse.cn/api/report/ShowReport?{}", encoded);
    let response = isahc::get_async(url).await?;
    let mut buf = [0; 8192];
    let mut reader = response.into_body();
    let mut file_u8 = vec![];
    loop {
        match reader.read(&mut buf).await? {
            0 => break,
            len => {
                for &byte in &buf[..len] {
                    file_u8.push(byte);
                }
            }
        }
    }
    fs::write(TMPFILENAME, file_u8)?;
    let mut workbook: Xlsx<_> = open_workbook(TMPFILENAME)?;
    let range = workbook
        .worksheet_range_at(0)
        .ok_or(calamine::Error::Msg("Cannot find 'Sheet0'"))??;
    let mut codes = vec![];
    for i in 1..range.height() as u32 {
        let v = range.get_value((i, CODECOLID));
        if let Some(DataType::String(code)) = v {
            codes.push(code.clone());
        }
    }
    Ok(codes)
}

async fn stock_list_sh_by_type(stock_type: &str) -> Result<Vec<String>, Error> {
    let encoded: String = form_urlencoded::Serializer::new(String::new())
        .append_pair("jsonCallBack", "jsonpCallback66942")
        .append_pair("isPagination", "true")
        .append_pair("stockCode", "")
        .append_pair("csrcCode", "")
        .append_pair("areaName", "")
        .append_pair("stockType", stock_type)
        .append_pair("pageHelp.cacheSize", "1")
        .append_pair("pageHelp.beginPage", "1")
        .append_pair("pageHelp.pageSize", "2000")
        .append_pair("pageHelp.pageNo", "1")
        .append_pair("pageHelp.endPage", "11")
        .append_pair("_", "1589881387934")
        .finish();
    let url = format!(
        "http://query.sse.com.cn/security/stock/getStockListData.do?{}",
        encoded
    );
    let client = HttpClient::new()?;
    let request = Request::get(url)
        .header("Host", "query.sse.com.cn")
        .header("Pragma", "no-cache")
        .header("Referer", "http://www.sse.com.cn/assortment/stock/list/share/")
        .header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36")
        .body("")?;
    let mut response = client.send_async(request).await?;
    let res_str = response.text()?;
    let begin_pos = match res_str.find('{') {
        None => return Err(Error::Msg("Responese text error!")),
        Some(p) => p,
    };
    let res_str = match res_str.get(begin_pos..res_str.len() - 1) {
        None => return Err(Error::Msg("Response text error!")),
        Some(s) => s,
    };
    let res: ShResult = serde_json::from_str(res_str)?;
    Ok(res.result.into_iter().map(|x| x.security_code_a).collect())
}