2023-01-23 02:14:13 +00:00
|
|
|
use std::collections::HashMap;
|
2023-01-19 22:56:12 +00:00
|
|
|
|
2023-01-23 02:31:55 +00:00
|
|
|
use mlua::prelude::*;
|
2023-02-03 19:40:06 +00:00
|
|
|
use reqwest::Method;
|
2023-01-19 22:56:12 +00:00
|
|
|
|
2023-01-23 23:52:31 +00:00
|
|
|
use crate::utils::{net::get_request_user_agent_header, table::TableBuilder};
|
2023-01-19 22:56:12 +00:00
|
|
|
|
2023-01-23 07:38:32 +00:00
|
|
|
pub fn create(lua: &Lua) -> LuaResult<()> {
|
2023-01-22 20:23:56 +00:00
|
|
|
lua.globals().raw_set(
|
|
|
|
"net",
|
2023-01-23 01:18:09 +00:00
|
|
|
TableBuilder::new(lua)?
|
2023-01-22 20:23:56 +00:00
|
|
|
.with_function("jsonEncode", net_json_encode)?
|
|
|
|
.with_function("jsonDecode", net_json_decode)?
|
|
|
|
.with_async_function("request", net_request)?
|
2023-01-23 01:18:09 +00:00
|
|
|
.build_readonly()?,
|
2023-01-22 21:26:45 +00:00
|
|
|
)
|
2023-01-19 22:56:12 +00:00
|
|
|
}
|
|
|
|
|
2023-01-23 02:31:55 +00:00
|
|
|
fn net_json_encode(_: &Lua, (val, pretty): (LuaValue, Option<bool>)) -> LuaResult<String> {
|
2023-01-19 22:56:12 +00:00
|
|
|
if let Some(true) = pretty {
|
2023-01-23 02:31:55 +00:00
|
|
|
serde_json::to_string_pretty(&val).map_err(LuaError::external)
|
2023-01-19 22:56:12 +00:00
|
|
|
} else {
|
2023-01-23 02:31:55 +00:00
|
|
|
serde_json::to_string(&val).map_err(LuaError::external)
|
2023-01-19 22:56:12 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-23 02:31:55 +00:00
|
|
|
fn net_json_decode(lua: &Lua, json: String) -> LuaResult<LuaValue> {
|
|
|
|
let json: serde_json::Value = serde_json::from_str(&json).map_err(LuaError::external)?;
|
2023-01-19 22:56:12 +00:00
|
|
|
lua.to_value(&json)
|
|
|
|
}
|
|
|
|
|
2023-01-23 02:31:55 +00:00
|
|
|
async fn net_request<'lua>(lua: &'lua Lua, config: LuaValue<'lua>) -> LuaResult<LuaTable<'lua>> {
|
2023-01-19 22:56:12 +00:00
|
|
|
// Extract stuff from config and make sure its all valid
|
|
|
|
let (url, method, headers, body) = match config {
|
2023-01-23 02:31:55 +00:00
|
|
|
LuaValue::String(s) => {
|
2023-01-19 22:56:12 +00:00
|
|
|
let url = s.to_string_lossy().to_string();
|
|
|
|
let method = "GET".to_string();
|
2023-01-23 02:21:11 +00:00
|
|
|
Ok((url, method, HashMap::new(), None))
|
2023-01-19 22:56:12 +00:00
|
|
|
}
|
2023-01-23 02:31:55 +00:00
|
|
|
LuaValue::Table(tab) => {
|
2023-01-19 22:56:12 +00:00
|
|
|
// Extract url
|
2023-01-23 02:31:55 +00:00
|
|
|
let url = match tab.raw_get::<_, LuaString>("url") {
|
2023-01-23 02:21:11 +00:00
|
|
|
Ok(config_url) => Ok(config_url.to_string_lossy().to_string()),
|
2023-01-23 02:31:55 +00:00
|
|
|
Err(_) => Err(LuaError::RuntimeError(
|
2023-01-23 02:21:11 +00:00
|
|
|
"Missing 'url' in request config".to_string(),
|
|
|
|
)),
|
|
|
|
}?;
|
2023-01-19 22:56:12 +00:00
|
|
|
// Extract method
|
2023-01-23 02:31:55 +00:00
|
|
|
let method = match tab.raw_get::<_, LuaString>("method") {
|
2023-01-19 22:56:12 +00:00
|
|
|
Ok(config_method) => config_method.to_string_lossy().trim().to_ascii_uppercase(),
|
|
|
|
Err(_) => "GET".to_string(),
|
|
|
|
};
|
|
|
|
// Extract headers
|
2023-01-23 02:31:55 +00:00
|
|
|
let headers = match tab.raw_get::<_, LuaTable>("headers") {
|
2023-01-19 22:56:12 +00:00
|
|
|
Ok(config_headers) => {
|
2023-01-23 02:14:13 +00:00
|
|
|
let mut lua_headers = HashMap::new();
|
2023-01-23 02:31:55 +00:00
|
|
|
for pair in config_headers.pairs::<LuaString, LuaString>() {
|
2023-01-23 02:14:13 +00:00
|
|
|
let (key, value) = pair?.to_owned();
|
|
|
|
lua_headers.insert(key, value);
|
2023-01-19 22:56:12 +00:00
|
|
|
}
|
2023-01-23 02:14:13 +00:00
|
|
|
lua_headers
|
2023-01-19 22:56:12 +00:00
|
|
|
}
|
2023-01-23 02:14:13 +00:00
|
|
|
Err(_) => HashMap::new(),
|
2023-01-19 22:56:12 +00:00
|
|
|
};
|
|
|
|
// Extract body
|
2023-01-23 02:31:55 +00:00
|
|
|
let body = match tab.raw_get::<_, LuaString>("body") {
|
2023-01-19 22:56:12 +00:00
|
|
|
Ok(config_body) => Some(config_body.as_bytes().to_owned()),
|
|
|
|
Err(_) => None,
|
|
|
|
};
|
2023-01-23 02:21:11 +00:00
|
|
|
Ok((url, method, headers, body))
|
2023-01-19 22:56:12 +00:00
|
|
|
}
|
2023-01-23 02:31:55 +00:00
|
|
|
value => Err(LuaError::RuntimeError(format!(
|
2023-01-23 02:21:11 +00:00
|
|
|
"Invalid request config - expected string or table, got {}",
|
|
|
|
value.type_name()
|
|
|
|
))),
|
|
|
|
}?;
|
2023-01-19 22:56:12 +00:00
|
|
|
// Convert method string into proper enum
|
2023-01-23 02:14:13 +00:00
|
|
|
let method = method.trim().to_ascii_uppercase();
|
|
|
|
let method = match method.as_ref() {
|
2023-02-03 19:40:06 +00:00
|
|
|
"GET" => Ok(Method::GET),
|
|
|
|
"POST" => Ok(Method::POST),
|
|
|
|
"PUT" => Ok(Method::PUT),
|
|
|
|
"DELETE" => Ok(Method::DELETE),
|
|
|
|
"HEAD" => Ok(Method::HEAD),
|
|
|
|
"OPTIONS" => Ok(Method::OPTIONS),
|
|
|
|
"PATCH" => Ok(Method::PATCH),
|
2023-01-23 02:31:55 +00:00
|
|
|
_ => Err(LuaError::RuntimeError(format!(
|
2023-01-23 02:21:11 +00:00
|
|
|
"Invalid request config method '{}'",
|
|
|
|
&method
|
|
|
|
))),
|
|
|
|
}?;
|
2023-02-03 19:40:06 +00:00
|
|
|
// TODO: Figure out how to reuse this client
|
|
|
|
let client = reqwest::ClientBuilder::new()
|
|
|
|
.build()
|
|
|
|
.map_err(LuaError::external)?;
|
2023-01-19 22:56:12 +00:00
|
|
|
// Create and send the request
|
2023-02-03 19:40:06 +00:00
|
|
|
let mut request = client.request(method, &url);
|
2023-01-23 02:14:13 +00:00
|
|
|
for (header, value) in headers {
|
2023-02-03 19:40:06 +00:00
|
|
|
request = request.header(header.to_str()?, value.to_str()?);
|
2023-01-23 02:14:13 +00:00
|
|
|
}
|
2023-02-03 19:40:06 +00:00
|
|
|
let res = request
|
|
|
|
.header("User-Agent", &get_request_user_agent_header()) // Always force user agent
|
|
|
|
.body(body.unwrap_or_default())
|
|
|
|
.send()
|
|
|
|
.await
|
|
|
|
.map_err(LuaError::external)?;
|
|
|
|
// Extract status, headers
|
|
|
|
let res_status = res.status().as_u16();
|
|
|
|
let res_status_text = res.status().canonical_reason();
|
|
|
|
let res_headers = res
|
|
|
|
.headers()
|
|
|
|
.iter()
|
|
|
|
.map(|(name, value)| (name.to_string(), value.to_str().unwrap().to_owned()))
|
|
|
|
.collect::<HashMap<String, String>>();
|
|
|
|
// Read response bytes
|
|
|
|
let res_bytes = res.bytes().await.map_err(LuaError::external)?;
|
|
|
|
// Construct and return a readonly lua table with results
|
|
|
|
TableBuilder::new(lua)?
|
|
|
|
.with_value("ok", (200..300).contains(&res_status))?
|
|
|
|
.with_value("statusCode", res_status)?
|
|
|
|
.with_value("statusMessage", res_status_text)?
|
|
|
|
.with_value("headers", res_headers)?
|
|
|
|
.with_value("body", lua.create_string(&res_bytes)?)?
|
|
|
|
.build_readonly()
|
2023-01-19 22:56:12 +00:00
|
|
|
}
|