use tera::{Result, Function};
use std::collections::HashMap;
use once_cell::sync::Lazy;
static CLIENT: Lazy<reqwest::Client> = Lazy::new(|| reqwest::Client::new());
pub struct Shortcodes {
pub functions: HashMap<String, fn(&HashMap<String, tera::Value>) -> String>,
}
impl Shortcodes {
pub fn new() -> Self {
Shortcodes {
functions: HashMap::new(),
}
}
pub fn register(mut self,
display: &str,
shortcode_fn: fn(&HashMap<String, tera::Value>) -> String,
) -> Self {
self.functions.insert(display.to_owned(), shortcode_fn);
self
}
}
impl Function for Shortcodes {
fn call(&self,
args: &HashMap<String, tera::Value>,
) -> Result<tera::Value> {
let display = match args.get("display") {
Some(value) => value.as_str()
.unwrap()
.trim_matches(|c| c == '"' || c == '\''),
None => return Ok(tera::Value::String("Missing display attribute".to_owned())),
};
let fragment = match self.functions.get(display) {
Some(shortcode_fn) => shortcode_fn(args),
None => {
return Ok(tera::Value::String(format!("Unknown shortcode display name: {}", display)))
},
};
Ok(tera::Value::String(fragment))
}
}
pub fn fetch_shortcode_js(
url: &str,
method: Option<&str>,
json_body: Option<&str>,
alt: Option<&str>,
) -> String {
let method = method.unwrap_or("GET");
let json_body = json_body.unwrap_or("{}");
let fetch_js = match method.to_lowercase().as_str() {
"get" => format!(r#"const response = await fetch("{}");"#, url),
"post" => format!(r#"
const request = new Request("{}", {{
headers: (() => {{
const headers = new Headers();
headers.append("Content-Type", "application/json");
return headers;
}})(),
method: "POST",
body: JSON.stringify({}),
}});
const response = await fetch(request);"#, url, json_body),
_ => return format!(r#"<output style="background-color:#f44336;color:#fff;padding:6px;">
Invalid method {} for url {} (only GET and POST methods available)
</output>"#, method, url),
};
let js_code = format!(r#"<script>
(function () {{
async function fetchShortcodeData() {{
try {{
{}
if (!response.ok) {{
throw new Error(`HTTP error! Status: ${{response.status}}`);
}}
return await response.text();
}} catch (error) {{
console.error("Fetch failed:", error);
return "";
}}
}}
function reScript(helper) {{
for (const node of helper.childNodes) {{
if (node.hasChildNodes()) {{
reScript(node);
}}
if (node.nodeName === 'SCRIPT') {{
const script = document.createElement('script');
script.type = "text/javascript";
script.textContent = node.textContent;
node.replaceWith(script);
}}
}}
}}
(async () => {{
const currentScript = document.currentScript;
const content = await fetchShortcodeData();
// console.log(content);
const helper = document.createElement('div');
helper.id = 'helper';
helper.innerHTML = content;
reScript(helper);
currentScript.after(...helper.childNodes);
currentScript.remove();
}})();
}})();
</script>"#,
fetch_js);
if method.to_lowercase().as_str() == "get" && alt.is_some() {
let alt = alt.unwrap();
js_code.to_string() + &format!(r#"<noscript><a href="{}">{}</a></noscript>"#, url, alt)
} else {
js_code
}
}
pub fn fetch_shortcode(
url: &str,
method: Option<&str>,
json_body: Option<&str>,
) -> String {
let method = method.unwrap_or("GET");
let json_body = json_body.unwrap_or("{}");
let data_to_route = async {
let response = match method.to_lowercase().as_str() {
"get" => CLIENT.get(url)
.send()
.await,
"post" => CLIENT.post(url)
.header("Content-Type", "application/json")
.body(json_body.to_owned())
.send()
.await,
_ => return format!("Invalid method: {}", method),
};
match response {
Ok(res) => {
if res.status().is_success() {
res.text().await.unwrap_or_else(|_| "Failed to read response body".into())
} else {
format!("Request failed with status: {}", res.status())
}
}
Err(e) => format!("Request error: {}", e),
}
};
tokio::task::block_in_place(||
tokio::runtime::Handle::current()
.block_on(data_to_route)
)
}