#![allow(non_snake_case)]
use crate::files::{Downloadable, FileManager};
use crate::utils::auth::AuthConfig;
use bytes::Bytes;
use regex::Regex;
use reqwest::{self, header, Error};
use std::str;
const LIST_HOST: &str = "https://www.open.go.kr/rqestMlrd/rqestDtls/reqstDocSrchList.ajax";
const LOGIN_HOST: &str = "https://www.open.go.kr/com/login/memberLogin.ajax";
const DETAIL_HOST_FOR_NOT_OPENED: &str =
"https://www.open.go.kr/rqestMlrd/rqestDtls/reqstDocDetail.do";
const DETAIL_HOST_FOR_OPENED: &str =
"https://www.open.go.kr/rqestMlrd/rqestDtls/reqstDocDecsnNotie.do";
const DOWNLOAD_HOST: &str = "https://www.open.go.kr/util/FileDownload.do";
#[derive(serde::Deserialize, Debug)]
struct CsrfTokenResponse {
csrfToken: String,
}
#[derive(serde::Deserialize, Debug)]
pub struct AuthResponseModelAndViewModelResultRtnV0 {
pub accesType: String,
pub addr1: String,
pub addr2: String,
pub age: i32,
pub agent: String,
pub agentInfo: String,
pub apoloId: String,
pub birth: String,
pub birthDe: String,
pub bizrNo: String,
pub bizrNo1: String,
pub bizrNo2: String,
pub bizrNo3: String,
pub changePwdYn: String,
pub crt: String,
}
#[derive(serde::Deserialize, Debug)]
pub struct AuthResponseModelAndViewModelResult {
pub error_code: String,
pub error_msg: String,
pub mberSeCd: String,
pub sysdate: String,
pub today: String,
}
#[derive(serde::Deserialize, Debug)]
pub struct AuthResponseModelAndViewModel {
pub result: AuthResponseModelAndViewModelResult,
}
#[derive(serde::Deserialize, Debug)]
pub struct AuthResponseModelAndView {
pub empty: bool,
pub model: AuthResponseModelAndViewModel,
}
#[derive(serde::Deserialize, Debug)]
pub struct AuthResponse {
pub modelAndView: AuthResponseModelAndView,
}
#[derive(serde::Deserialize, serde::Serialize, Debug)]
pub struct ListVo {
pub totalPage: i32, }
#[derive(Clone, serde::Deserialize, serde::Serialize, Debug)]
pub struct DtlVo {
pub deptSn: String,
pub clsdrResnCn: String, pub clsdrResnNm: String,
pub chckerClsfNm: String, pub chckerFnm: String, pub dcrberFnm: String, pub dcrberClsfNm: String, pub dcanerFnm: String, pub dcanerClsfNm: String, pub drafterFnm: String, pub drafterClsfNm: String, pub sanctnDocNo: String,
pub decsnCn: String, pub trnsfInsttNmCn: String,
pub opetrId: String, pub opetrFnm: String, pub opetrDeptCd: String, pub opetrDeptNm: String, pub opetrClsfCd: String, pub opetrClsfNm: String, pub opetrCbleTelno: String,
pub othinstSmtmProcessYn: String,
pub othbcDtApnResnNm: String, pub othbcOprtnDt: String, pub othbcSeNm: String, pub othbcStleSeNm: String, pub othbcPrearngeDt: String,
pub recptMthSeNm: String, pub recptnServerId: String,
pub nticeDt: String,
pub insttAddr: String, pub insttRqestProcStCd: String, pub insttRqestProcStNm: String,
pub mberId: String,
pub prcsInsttCd: String, pub prcsInsttNm: String, pub prcsFullInsttNm: String, pub procCn: String, pub procDt: String, pub procRegstrNo: String, pub procDeptCbleTelno: String, pub procUserEmailAdres: String,
pub rceptDt: String, pub rqestCn: String, pub rqestDt: String, pub rqestFullInsttNm: String, pub rqestInsttCd: String, pub rqestInsttNm: String,
pub rqestProcRegstrNo: String, pub rqestRceptNo: String, pub rqestSj: String, }
#[derive(Clone, serde::Deserialize, serde::Serialize, Debug)]
pub struct DntcFile {
pub atchmnflByteCo: String, pub atchmnflPrsrvNm: String, pub csdCnvrStCd: String, pub fileAbsltCoursNm: String, pub fileSn: String, pub fileUploadNo: String, pub frstRegisterId: String, pub uploadFileOrginlNm: String, }
#[derive(serde::Deserialize, serde::Serialize, Debug)]
pub struct BillWithFiles {
pub atchFileList: Option<Vec<DntcFile>>,
pub dntcFileList: Option<Vec<DntcFile>>,
pub dtlVo: DtlVo,
}
#[derive(serde::Deserialize, serde::Serialize, Debug)]
pub struct RedirectedBillWithFiles {
pub redirectUrl: String,
}
#[derive(Debug)]
pub enum BillReturnType {
BillWithFiles(BillWithFiles),
None,
}
impl Downloadable for BillWithFiles {
fn get_filename(&self, prcs_full_instt_nm: &str, orig_file_name: &str) -> String {
FileManager::make_filename(
&self.dtlVo.rqestProcRegstrNo.trim(),
prcs_full_instt_nm,
orig_file_name.trim(),
)
}
fn get_dirname(&self) -> String {
FileManager::make_dirname(&self.dtlVo.rceptDt.trim(), &self.dtlVo.rqestSj.trim())
}
}
#[derive(serde::Deserialize, serde::Serialize, Debug)]
pub struct Bills {
pub list: Vec<DtlVo>,
pub vo: ListVo,
}
#[derive(Debug)]
pub struct Client {
pub username: String,
client: reqwest::Client,
scui: String,
csrf_token: String,
}
impl Client {
pub async fn new() -> Result<Self, Error> {
let mut headers = header::HeaderMap::new();
headers.insert(
"Accept",
"application/json, text/javascript, */*; q=0.01"
.parse()
.unwrap(),
);
headers.insert(
"Content-Type",
"application/x-www-form-urlencoded; charset=UTF-8"
.parse()
.unwrap(),
);
headers.insert("Host", "www.open.go.kr".parse().unwrap());
headers.insert("Origin", "https://www.open.go.kr".parse().unwrap());
headers.insert("Connection", "keep-alive".parse().unwrap());
headers.insert("Sec-Fetch-Site", "same-origin".parse().unwrap());
headers.insert("X-Requested-With", "XMLHttpRequest".parse().unwrap());
headers.insert(
"User-Agent",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:80.0) Gecko/20100101 Firefox/80.0"
.parse()
.unwrap(),
);
let client = reqwest::ClientBuilder::new()
.default_headers(headers)
.cookie_store(true)
.build()
.unwrap();
let response = client
.get("https://www.open.go.kr/com/login/memberLogin.do")
.send()
.await?;
let text_response = response.text().await?;
let regex = Regex::new(r"var result(\s+)=(\s+)(.+);").unwrap();
let mut stringified_json_result = String::from("");
for cap in regex.captures_iter(&text_response) {
stringified_json_result = String::from(&cap[3]);
}
let csrf_token_response: CsrfTokenResponse =
serde_json::from_str(&stringified_json_result).unwrap();
let scui = "";
let username: &str = "";
Ok(Client {
username: username.to_owned(),
client,
csrf_token: csrf_token_response.csrfToken,
scui: scui.to_owned(),
})
}
pub async fn auth(
&mut self,
username: &str,
password: &str,
) -> Result<(), Box<dyn std::error::Error>> {
self.username = username.to_owned();
let auth: [(&str, &str); 5] = [
("mberId", username),
("pwd", password),
("agent", "PC"),
("_csrf", &self.csrf_token),
("csrf", &self.csrf_token),
];
let response = self.client.post(LOGIN_HOST).form(&auth).send().await?;
match response.json::<AuthResponse>().await {
Ok(response_json) => {
if response_json.modelAndView.model.result.error_msg == "로그인 완료" {
let response_scui = self
.client
.post("https://www.open.go.kr/com/main/mainView.do")
.send()
.await?;
let response_scui_text = response_scui.text().await?;
let regex = Regex::new(r"const scui = '(.+)';").unwrap();
for cap in regex.captures_iter(&response_scui_text) {
self.scui = cap[0].to_owned();
}
return Ok(());
}
if response_json.modelAndView.model.result.error_msg
== "비밀번호를 마지막으로 변경한지 180일이 지났습니다."
{
let set_password: [(&str, &str); 2] = [("hash", "true"), ("scui", &self.scui)];
self.client
.post("https://www.open.go.kr/com/main/mainView.do")
.form(&set_password)
.send()
.await?;
return Ok(());
}
println!("{}", response_json.modelAndView.model.result.error_msg);
panic!("사용자이름과 비밀번호를 확인해주세요.");
}
Err(e) => {
println!("{}", e.to_string());
panic!("사용자이름과 비밀번호를 확인해주세요.");
}
}
}
pub async fn auth_from_storage(
&mut self,
org: Option<&str>,
) -> Result<(), Box<dyn std::error::Error>> {
let config = AuthConfig::load_or_new()?;
match org {
Some(org) => {
let account = config.find_org(org).unwrap();
self.auth(
&account.borrow().username,
&account.borrow().get_decoded_password(),
)
.await?;
Ok(())
}
None => {
let account = config.find_org("default").unwrap();
self.auth(
&account.borrow().username,
&account.borrow().get_decoded_password(),
)
.await?;
Ok(())
}
}
}
pub async fn download_file(&self, file: &DntcFile) -> Result<Bytes, Error> {
let params = &[
("fileUploadNo", &file.fileUploadNo),
("fileSn", &file.fileSn),
];
self.client
.post(DOWNLOAD_HOST)
.form(params)
.send()
.await?
.bytes()
.await
}
pub async fn fetch_a_bill(
&self,
registration_proc_number: &str,
open_status_code: &str,
dept_sn: &str,
) -> Result<BillReturnType, Box<dyn std::error::Error>> {
let host = match open_status_code {
"141" | "143" | "1411" | "1413" | "1415" | "1421" | "163" | "165" | "1861" => {
DETAIL_HOST_FOR_OPENED
}
_ => DETAIL_HOST_FOR_NOT_OPENED,
};
let params: [(&str, &str); 8] = [
("rqestRceptNo", ""),
("rqestProcRegstrNo", registration_proc_number),
("procRegstrNo", registration_proc_number),
("insttRqestProcStCd", open_status_code),
("deptSn", &dept_sn),
("hash", "true"),
("multiDeptProcYn", "N"),
("scui", &self.scui),
];
let response = self.post(host, ¶ms).await?;
let text_response = response.text().await?;
let regex = Regex::new(r"var result(\s+)=(\s+)(.+);").unwrap();
let mut stringified_json_result = String::from("");
for cap in regex.captures_iter(&text_response) {
stringified_json_result = String::from(&cap[3]);
}
if stringified_json_result != "" {
match serde_json::from_str(&stringified_json_result) {
Ok(result) => {
return Ok(BillReturnType::BillWithFiles(result));
}
Err(_) => {
let host = match host {
DETAIL_HOST_FOR_NOT_OPENED => DETAIL_HOST_FOR_OPENED,
_ => DETAIL_HOST_FOR_NOT_OPENED,
};
let params: [(&str, &str); 8] = [
("rqestRceptNo", ""),
("rqestProcRegstrNo", registration_proc_number),
("procRegstrNo", registration_proc_number),
("insttRqestProcStCd", open_status_code),
("deptSn", &dept_sn),
("hash", "true"),
("multiDeptProcYn", "N"),
("scui", &self.scui),
];
let response = self.post(host, ¶ms).await?;
let text_response = response.text().await?;
let regex = Regex::new(r"var result(\s+)=(\s+)(.+);").unwrap();
let mut stringified_json_result = String::from("");
for cap in regex.captures_iter(&text_response) {
stringified_json_result = String::from(&cap[3]);
}
if stringified_json_result != "" {
match serde_json::from_str(&stringified_json_result) {
Ok(result) => {
return Ok(BillReturnType::BillWithFiles(result));
}
Err(_) => {
return Ok(BillReturnType::None);
}
};
} else {
return Ok(BillReturnType::None);
}
}
};
} else {
return Ok(BillReturnType::None);
}
}
pub async fn fetch_bills(
&self,
page: &i32,
from_date: &str,
to_date: &str,
page_count: &i32,
) -> Result<Bills, Error> {
let params: [(&str, &str); 11] = [
("stRceptDt", from_date),
("edRceptDt", to_date),
("viewPage", &page.to_string()),
("totalPage", "0"),
("selRowPage", &page_count.to_string()),
("rowPage", &page_count.to_string()),
("sort", "rqestDtList"),
("searchYn", "Y"),
("moveStatus", "L"),
("chkDate", "nonClass"),
("scui", &self.scui),
];
let response = self.client.post(LIST_HOST).form(¶ms).send().await?;
response.json::<Bills>().await
}
pub async fn post(&self, url: &str, form: &[(&str, &str)]) -> Result<reqwest::Response, Error> {
self.client.post(url).form(form).send().await
}
}