use std::collections::HashMap;
use crate::{
err::{ErrInfoBuildle, TransError},
DomainType, Language, Translation,
};
use aes::cipher::{block_padding::Pkcs7, BlockEncryptMut, KeyInit};
use base64::{
alphabet,
engine::{self, general_purpose},
Engine as _,
};
use reqwest::header::{REFERER, USER_AGENT};
use serde::Deserialize;
use serde_json::Value;
type Result<T> = std::result::Result<T, TransError>;
type Aes128EcbEnc = ecb::Encryptor<aes::Aes128>;
const TOKEN_URL: &str = "https://dict.cnki.net/fyzs-front-api/getToken";
const TRANSLATION_API: &str = "https://dict.cnki.net/fyzs-front-api/translate/literaltranslation";
const MAX_LEN: usize = 1200;
#[derive(Debug, Deserialize)]
pub struct CnkiRes {
pub msg: String,
pub code: usize,
pub data: Data,
}
#[derive(Debug, Deserialize)]
pub struct Data {
pub words: String,
pub mResult: String,
pub code: usize,
}
pub struct CNKI<'a> {
words: &'a str,
encode_words: String,
max_len: usize,
key: &'a str,
agent: String, tras_type: usize,
}
impl<'a> Translation<'a> for CNKI<'a> {
fn set_words(&mut self, words: &'a str) {
self.words = words;
}
fn get_words(&self) -> &'a str {
self.words
}
fn trans(&mut self) -> Result<String> {
match self.check_len() {
Ok(_) => self.get_res(self.get_token()?),
Err(e) => Err(e),
}
}
fn set_from(&mut self, language: crate::Language) {
match language {
crate::Language::Zh => self.tras_type = 0,
crate::Language::En => self.tras_type = 1,
_ => self.tras_type = 1,
}
}
fn set_to(&mut self, language: crate::Language) {
match language {
crate::Language::Zh => self.tras_type = 1,
crate::Language::En => self.tras_type = 0,
_ => self.tras_type = 1,
}
}
fn set_appid(&mut self, _appid: &'a str) {}
fn set_secret_key(&mut self, secret_key: &'a str) {
self.key = secret_key;
}
fn set_domain(&mut self, _domain: DomainType) {}
fn from(&self) -> Language {
match self.tras_type {
0 => Language::Zh,
1 => Language::En,
_ => Language::En,
}
}
fn to(&self) -> Language {
match self.tras_type {
0 => Language::En,
1 => Language::Zh,
_ => Language::Zh,
}
}
fn appid(&self) -> &'a str {
""
}
fn secret_key(&self) -> &'a str {
self.key
}
fn domain(&self) -> DomainType {
DomainType::General
}
fn build(&self) -> Box<dyn Translation<'a> + 'a> {
Box::new(CNKI::new())
}
fn set_max_length(&mut self, l: usize) {
self.max_len = l;
}
fn max_length(&mut self) -> usize {
self.max_len
}
}
impl<'a> CNKI<'a> {
pub fn new() -> Self {
Self {
words: "",
key: "",
agent: rand_user_agent::UserAgent::random().to_string(),
encode_words: String::default(),
tras_type: 1,
max_len: MAX_LEN,
}
}
pub fn default_new() -> Self {
Self {
words: "",
key: "4e87183cfd3a45fe",
agent: rand_user_agent::UserAgent::random().to_string(),
encode_words: String::default(),
tras_type: 1,
max_len: MAX_LEN,
}
}
fn get_res(&mut self, token: String) -> Result<String> {
self.aes_ecb_pkcs7_words_no_url();
let mut map = HashMap::new();
map.insert("words", self.encode_words.to_string());
map.insert("translateType", self.tras_type.to_string());
let client = reqwest::blocking::Client::new();
let request = client
.post(TRANSLATION_API)
.header("Token", token.to_string())
.header(USER_AGENT, self.agent.as_str())
.header(REFERER, "https://dict.cnki.net/index")
.json(&map);
let response = request.send().map_err(|e| {
TransError::RequestError(
ErrInfoBuildle::new()
.model("CNKI")
.method("get_res(token)")
.original(e.to_string())
.data(format!("{}", token))
.build(),
)
})?;
let status = response.status();
let res = response.json::<CnkiRes>().map_err(|e| {
TransError::CNKIError(
ErrInfoBuildle::new()
.model("CNKI")
.method("get_res(token)")
.original(e.to_string())
.data(format!("status code:{}", status))
.build(),
)
})?;
Ok(res.data.mResult)
}
fn get_token(&self) -> Result<String> {
let response = reqwest::blocking::get(TOKEN_URL).map_err(|e| {
TransError::RequestError(
ErrInfoBuildle::new()
.model("CNKI")
.method("get_token()")
.original(e.to_string())
.build(),
)
})?;
let status = response.status();
let body = response.json::<HashMap<String, Value>>().map_err(|e| {
TransError::CNKIError(
ErrInfoBuildle::new()
.model("CNKI")
.method("get_token())")
.original(e.to_string())
.data(format!("status code:{}", status))
.build(),
)
})?;
match body.get("data") {
Some(d) => Ok(d.to_string()),
None => Err(TransError::CNKIError(
ErrInfoBuildle::new()
.model("CNKI")
.method("get_token())")
.others(map!("data"=>response.text().unwrap()))
.build(),
)),
}
}
fn aes_ecb_pkcs7_words(&mut self) {
let res = Aes128EcbEnc::new(self.key.as_bytes().into())
.encrypt_padded_vec_mut::<Pkcs7>(self.words.as_bytes());
let encoded: String = general_purpose::URL_SAFE_NO_PAD.encode(res);
self.encode_words = encoded
}
fn check_len(&self) -> Result<bool> {
if self.words.len() > self.max_len {
return Err(TransError::CNKIError(
ErrInfoBuildle::new()
.model("CNKI")
.method("check_len()")
.original(format!("字符太长了,Max:{}", self.max_len))
.build(),
));
}
return Ok(true);
}
fn aes_ecb_pkcs7_words_no_url(&mut self) {
let res = Aes128EcbEnc::new(self.key.as_bytes().into())
.encrypt_padded_vec_mut::<Pkcs7>(self.words.as_bytes());
let alphabet = alphabet::Alphabet::new(
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_",
)
.unwrap();
let crazy_config = engine::GeneralPurposeConfig::new();
let crazy_engine = engine::GeneralPurpose::new(&alphabet, crazy_config);
let encoded: String = crazy_engine.encode(res);
self.encode_words = encoded
}
}