2023-12-11 15:18:43 +00:00
|
|
|
use lazy_static::lazy_static;
|
2023-12-18 22:48:05 +00:00
|
|
|
use regex::{Captures, Regex};
|
2023-12-11 15:18:43 +00:00
|
|
|
use sqlx::query_as;
|
2023-12-12 17:54:48 +00:00
|
|
|
use std::collections::HashMap;
|
2023-12-11 15:18:43 +00:00
|
|
|
|
|
|
|
use crate::{
|
|
|
|
ctx::Ctx,
|
|
|
|
db::models::Post,
|
|
|
|
error::NekrochanError,
|
|
|
|
perms::PermissionWrapper,
|
|
|
|
trip::{secure_tripcode, tripcode},
|
|
|
|
};
|
|
|
|
|
|
|
|
lazy_static! {
|
|
|
|
pub static ref NAME_REGEX: Regex =
|
|
|
|
Regex::new(r"^([^#].*?)?(?:(##([^ ].*?)|#([^#].*?)))?(##( .*?)?)?$").unwrap();
|
|
|
|
pub static ref QUOTE_REGEX: Regex = Regex::new(r">>(\d+)").unwrap();
|
2023-12-18 22:48:05 +00:00
|
|
|
pub static ref GREENTEXT_REGEX: Regex = Regex::new(r"(?mR)^>(.*)$").unwrap();
|
|
|
|
pub static ref ORANGETEXT_REGEX: Regex = Regex::new(r"(?mR)^<(.*)$").unwrap();
|
|
|
|
pub static ref REDTEXT_REGEX: Regex = Regex::new(r"==(.+?)==").unwrap();
|
|
|
|
pub static ref BLUETEXT_REGEX: Regex = Regex::new(r"--(.+?)--").unwrap();
|
|
|
|
pub static ref GLOWTEXT_REGEX: Regex = Regex::new(r"\%\%(.+?)\%\%").unwrap();
|
|
|
|
pub static ref UH_OH_TEXT_REGEX: Regex = Regex::new(r"\(\(\((.+?)\)\)\)").unwrap();
|
|
|
|
pub static ref SPOILER_REGEX: Regex = Regex::new(r"\|\|([\s\S]+?)\|\|").unwrap();
|
2023-12-11 15:18:43 +00:00
|
|
|
pub static ref URL_REGEX: Regex =
|
|
|
|
Regex::new(r"https?\://[^\s<>\[\]{}|\\^]+").unwrap();
|
2024-01-20 13:35:20 +00:00
|
|
|
pub static ref JANNYTEXT_REGEX: Regex = Regex::new(r"##(.+?)##").unwrap();
|
2023-12-11 15:18:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn parse_name(
|
|
|
|
ctx: &Ctx,
|
|
|
|
perms: &PermissionWrapper,
|
|
|
|
anon_name: &str,
|
|
|
|
name: &str,
|
|
|
|
) -> Result<(String, Option<String>, Option<String>), NekrochanError> {
|
2023-12-18 22:48:05 +00:00
|
|
|
let Some(captures) = NAME_REGEX.captures(name) else {
|
2023-12-11 15:59:32 +00:00
|
|
|
return Ok((anon_name.to_owned(), None, None));
|
2023-12-11 15:18:43 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
let name = match captures.get(1) {
|
|
|
|
Some(name) => {
|
|
|
|
let name = name.as_str().to_owned();
|
|
|
|
|
|
|
|
if name.len() > 32 {
|
|
|
|
return Err(NekrochanError::PostNameFormatError);
|
|
|
|
}
|
|
|
|
|
|
|
|
name
|
|
|
|
}
|
|
|
|
None => anon_name.to_owned(),
|
|
|
|
};
|
|
|
|
|
|
|
|
let tripcode = match captures.get(2) {
|
|
|
|
Some(_) => {
|
|
|
|
let strip = captures.get(3);
|
|
|
|
let itrip = captures.get(4);
|
|
|
|
|
|
|
|
if let Some(strip) = strip {
|
|
|
|
let trip = secure_tripcode(strip.as_str(), &ctx.cfg.secrets.secure_trip);
|
|
|
|
|
|
|
|
Some(format!("!!{trip}"))
|
|
|
|
} else if let Some(itrip) = itrip {
|
|
|
|
let trip = tripcode(itrip.as_str());
|
|
|
|
|
|
|
|
Some(format!("!{trip}"))
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
None => None,
|
|
|
|
};
|
|
|
|
|
|
|
|
if !(perms.owner() || perms.capcodes()) {
|
|
|
|
return Ok((name, tripcode, None));
|
|
|
|
}
|
|
|
|
|
|
|
|
let capcode = match captures.get(5) {
|
|
|
|
Some(_) => match captures.get(6) {
|
|
|
|
Some(capcode) => {
|
|
|
|
let capcode: String = capcode.as_str().trim().into();
|
|
|
|
|
2024-01-15 15:06:25 +00:00
|
|
|
if capcode.is_empty() || !(perms.owner() || perms.custom_capcodes()) {
|
2023-12-11 15:59:32 +00:00
|
|
|
Some(capcode_fallback(perms.owner()))
|
2023-12-11 15:18:43 +00:00
|
|
|
} else {
|
|
|
|
if capcode.len() > 32 {
|
|
|
|
return Err(NekrochanError::CapcodeFormatError);
|
|
|
|
}
|
|
|
|
|
|
|
|
Some(capcode)
|
|
|
|
}
|
|
|
|
}
|
2023-12-11 15:59:32 +00:00
|
|
|
None => Some(capcode_fallback(perms.owner())),
|
2023-12-11 15:18:43 +00:00
|
|
|
},
|
|
|
|
None => None,
|
|
|
|
};
|
|
|
|
|
|
|
|
Ok((name, tripcode, capcode))
|
|
|
|
}
|
|
|
|
|
2023-12-11 15:59:32 +00:00
|
|
|
fn capcode_fallback(owner: bool) -> String {
|
|
|
|
if owner {
|
|
|
|
"Admin".into()
|
|
|
|
} else {
|
|
|
|
"Uklízeč".into()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-11 15:18:43 +00:00
|
|
|
pub async fn markup(
|
|
|
|
ctx: &Ctx,
|
2024-01-20 13:35:20 +00:00
|
|
|
perms: &PermissionWrapper,
|
2024-01-15 15:06:25 +00:00
|
|
|
board: Option<String>,
|
2024-01-02 11:58:47 +00:00
|
|
|
op: Option<i64>,
|
2023-12-11 15:18:43 +00:00
|
|
|
text: &str,
|
2024-03-02 23:19:48 +00:00
|
|
|
) -> Result<(String, Vec<Post>), NekrochanError> {
|
2024-01-15 15:06:25 +00:00
|
|
|
let text = escape_html(text);
|
2023-12-18 22:48:05 +00:00
|
|
|
|
2024-03-02 23:19:48 +00:00
|
|
|
let (text, quoted_posts) = if let Some(board) = board {
|
2024-01-15 15:06:25 +00:00
|
|
|
let quoted_posts = get_quoted_posts(ctx, &board, &text).await?;
|
2023-12-11 15:18:43 +00:00
|
|
|
|
2024-01-15 15:06:25 +00:00
|
|
|
let text = QUOTE_REGEX.replace_all(&text, |captures: &Captures| {
|
|
|
|
let id_raw = &captures[1];
|
2023-12-11 15:59:32 +00:00
|
|
|
|
2024-01-15 15:06:25 +00:00
|
|
|
let Ok(id) = id_raw.parse() else {
|
2024-01-20 13:35:20 +00:00
|
|
|
return format!("<span class=\"dead-quote\">>>{id_raw}</span>");
|
|
|
|
};
|
2023-12-11 15:18:43 +00:00
|
|
|
|
2024-01-15 15:06:25 +00:00
|
|
|
let post = quoted_posts.get(&id);
|
|
|
|
|
|
|
|
if let Some(post) = post {
|
|
|
|
format!(
|
|
|
|
"<a class=\"quote\" href=\"{}\">>>{}</a>{}",
|
|
|
|
post.post_url(),
|
|
|
|
post.id,
|
|
|
|
if op == Some(post.id) {
|
|
|
|
" <span class=\"small\">(OP)</span>"
|
|
|
|
} else {
|
|
|
|
""
|
|
|
|
}
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
format!("<span class=\"dead-quote\">>>{id}</span>")
|
|
|
|
}
|
|
|
|
});
|
2023-12-11 15:18:43 +00:00
|
|
|
|
2024-03-02 23:19:48 +00:00
|
|
|
let quoted_posts = quoted_posts
|
|
|
|
.into_values()
|
|
|
|
.filter(|post| op == Some(post.thread.unwrap_or(post.id)))
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
(text.to_string(), quoted_posts)
|
2024-01-15 15:06:25 +00:00
|
|
|
} else {
|
2024-03-02 23:19:48 +00:00
|
|
|
(text, Vec::new())
|
2024-01-15 15:06:25 +00:00
|
|
|
};
|
2023-12-11 15:18:43 +00:00
|
|
|
|
|
|
|
let text = GREENTEXT_REGEX.replace_all(&text, "<span class=\"greentext\">>$1</span>");
|
|
|
|
let text = ORANGETEXT_REGEX.replace_all(&text, "<span class=\"orangetext\"><$1</span>");
|
|
|
|
let text = REDTEXT_REGEX.replace_all(&text, "<span class=\"redtext\">$1</span>");
|
|
|
|
let text = BLUETEXT_REGEX.replace_all(&text, "<span class=\"bluetext\">$1</span>");
|
|
|
|
let text = GLOWTEXT_REGEX.replace_all(&text, "<span class=\"glowtext\">$1</span>");
|
|
|
|
let text = SPOILER_REGEX.replace_all(&text, "<span class=\"spoiler\">$1</span>");
|
|
|
|
|
|
|
|
let text = UH_OH_TEXT_REGEX.replace_all(&text, |captures: &Captures| {
|
|
|
|
format!(
|
|
|
|
"<span class=\"uh-oh-text\">((( {} )))</span>",
|
|
|
|
captures[1].trim()
|
|
|
|
)
|
|
|
|
});
|
|
|
|
|
|
|
|
let text = URL_REGEX.replace_all(&text, |captures: &Captures| {
|
|
|
|
let url = &captures[0];
|
|
|
|
|
|
|
|
format!("<a rel=\"nofollow\" href=\"{url}\">{url}</a>")
|
|
|
|
});
|
|
|
|
|
2024-01-20 13:35:20 +00:00
|
|
|
let text = if perms.owner() || perms.jannytext() {
|
|
|
|
JANNYTEXT_REGEX.replace_all(&text, "<span class=\"jannytext\">$1</span>")
|
|
|
|
} else {
|
|
|
|
text
|
|
|
|
};
|
|
|
|
|
2024-03-02 23:19:48 +00:00
|
|
|
Ok((text.to_string(), quoted_posts))
|
2023-12-11 15:18:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn escape_html(text: &str) -> String {
|
|
|
|
text.replace('&', "&")
|
|
|
|
.replace('\'', "'")
|
|
|
|
.replace('/', "/")
|
|
|
|
.replace('`', "`")
|
|
|
|
.replace('=', "=")
|
|
|
|
.replace('<', "<")
|
|
|
|
.replace('>', ">")
|
|
|
|
.replace('"', """)
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn get_quoted_posts(
|
|
|
|
ctx: &Ctx,
|
|
|
|
board: &String,
|
|
|
|
text: &str,
|
2024-01-02 11:58:47 +00:00
|
|
|
) -> Result<HashMap<i64, Post>, NekrochanError> {
|
|
|
|
let mut quoted_ids: Vec<i64> = Vec::new();
|
2023-12-11 15:18:43 +00:00
|
|
|
|
|
|
|
for quote in QUOTE_REGEX.captures_iter(text) {
|
2023-12-18 22:48:05 +00:00
|
|
|
let id_raw = "e[1];
|
2023-12-11 15:59:32 +00:00
|
|
|
let Ok(id) = id_raw.parse() else { continue };
|
2023-12-11 15:18:43 +00:00
|
|
|
|
|
|
|
quoted_ids.push(id);
|
|
|
|
}
|
|
|
|
|
|
|
|
if quoted_ids.is_empty() {
|
|
|
|
return Ok(HashMap::new());
|
|
|
|
}
|
|
|
|
|
|
|
|
let in_list = quoted_ids
|
|
|
|
.iter()
|
2023-12-11 15:59:32 +00:00
|
|
|
.map(std::string::ToString::to_string)
|
2023-12-11 15:18:43 +00:00
|
|
|
.collect::<Vec<_>>()
|
|
|
|
.join(",");
|
|
|
|
|
|
|
|
let quoted_posts = query_as(&format!(
|
2023-12-11 15:59:32 +00:00
|
|
|
"SELECT * FROM posts_{board} WHERE id IN ({in_list})"
|
2023-12-11 15:18:43 +00:00
|
|
|
))
|
|
|
|
.fetch_all(ctx.db())
|
|
|
|
.await?
|
|
|
|
.into_iter()
|
|
|
|
.map(|post: Post| (post.id, post))
|
|
|
|
.collect::<HashMap<_, _>>();
|
|
|
|
|
|
|
|
Ok(quoted_posts)
|
|
|
|
}
|