217 řádky
6.4 KiB
Rust
217 řádky
6.4 KiB
Rust
|
use std::collections::HashMap;
|
||
|
|
||
|
use fancy_regex::{Captures, Regex};
|
||
|
use lazy_static::lazy_static;
|
||
|
use sqlx::query_as;
|
||
|
|
||
|
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();
|
||
|
pub static ref GREENTEXT_REGEX: Regex =
|
||
|
Regex::new(r"(?m)^>((?!>\d+|>>/\w+(/\d*)?|>>#/).*)")
|
||
|
.unwrap();
|
||
|
pub static ref ORANGETEXT_REGEX: Regex = Regex::new(r"(?m)^<(.+)").unwrap();
|
||
|
pub static ref REDTEXT_REGEX: Regex = Regex::new(r"(?m)==(.+?)==").unwrap();
|
||
|
pub static ref BLUETEXT_REGEX: Regex = Regex::new(r"(?m)--(.+?)--").unwrap();
|
||
|
pub static ref GLOWTEXT_REGEX: Regex = Regex::new(r"(?m)\%\%(.+?)\%\%").unwrap();
|
||
|
pub static ref UH_OH_TEXT_REGEX: Regex = Regex::new(r"(?m)\(\(\((.+?)\)\)\)").unwrap();
|
||
|
pub static ref SPOILER_REGEX: Regex = Regex::new(r"(?m)\|\|([\s\S]+?)\|\|").unwrap();
|
||
|
pub static ref URL_REGEX: Regex =
|
||
|
Regex::new(r"https?\://[^\s<>\[\]{}|\\^]+").unwrap();
|
||
|
}
|
||
|
|
||
|
pub fn parse_name(
|
||
|
ctx: &Ctx,
|
||
|
perms: &PermissionWrapper,
|
||
|
anon_name: &str,
|
||
|
name: &str,
|
||
|
) -> Result<(String, Option<String>, Option<String>), NekrochanError> {
|
||
|
let captures = match NAME_REGEX.captures(name)? {
|
||
|
Some(captures) => captures,
|
||
|
None => return Ok((anon_name.to_owned(), None, None)),
|
||
|
};
|
||
|
|
||
|
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));
|
||
|
}
|
||
|
|
||
|
fn capcode_fallback(owner: bool) -> Option<String> {
|
||
|
if owner {
|
||
|
Some("Admin".into())
|
||
|
} else {
|
||
|
Some("Uklízeč".into())
|
||
|
}
|
||
|
}
|
||
|
|
||
|
let capcode = match captures.get(5) {
|
||
|
Some(_) => match captures.get(6) {
|
||
|
Some(capcode) => {
|
||
|
let capcode: String = capcode.as_str().trim().into();
|
||
|
|
||
|
if capcode.is_empty() {
|
||
|
capcode_fallback(perms.owner())
|
||
|
} else {
|
||
|
if capcode.len() > 32 {
|
||
|
return Err(NekrochanError::CapcodeFormatError);
|
||
|
}
|
||
|
|
||
|
Some(capcode)
|
||
|
}
|
||
|
}
|
||
|
None => capcode_fallback(perms.owner()),
|
||
|
},
|
||
|
None => None,
|
||
|
};
|
||
|
|
||
|
Ok((name, tripcode, capcode))
|
||
|
}
|
||
|
|
||
|
pub async fn markup(
|
||
|
ctx: &Ctx,
|
||
|
board: &String,
|
||
|
op: Option<i32>,
|
||
|
text: &str,
|
||
|
) -> Result<String, NekrochanError> {
|
||
|
let text = escape_html(text);
|
||
|
let quoted_posts = get_quoted_posts(ctx, board, &text).await?;
|
||
|
|
||
|
let text = QUOTE_REGEX.replace_all(&text, |captures: &Captures| {
|
||
|
let id_raw = &captures[1];
|
||
|
let id = match id_raw.parse() {
|
||
|
Ok(id) => id,
|
||
|
Err(_) => return format!("<span class=\"dead-quote\">>>{id_raw}</span>"),
|
||
|
};
|
||
|
|
||
|
let post = quoted_posts.get(&id);
|
||
|
|
||
|
match post {
|
||
|
Some(post) => format!(
|
||
|
"<a class=\"quote\" href=\"{}\">>>{}</a>{}",
|
||
|
post.post_url(),
|
||
|
post.id,
|
||
|
if op == Some(post.id) {
|
||
|
" <span class=\"small\">(OP)</span>"
|
||
|
} else {
|
||
|
""
|
||
|
}
|
||
|
),
|
||
|
None => format!("<span class=\"dead-quote\">>>{id}</span>"),
|
||
|
}
|
||
|
});
|
||
|
|
||
|
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>")
|
||
|
});
|
||
|
|
||
|
Ok(text.to_string())
|
||
|
}
|
||
|
|
||
|
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,
|
||
|
) -> Result<HashMap<i32, Post>, NekrochanError> {
|
||
|
let mut quoted_ids: Vec<i32> = Vec::new();
|
||
|
|
||
|
for quote in QUOTE_REGEX.captures_iter(text) {
|
||
|
let id_raw = "e.unwrap()[1];
|
||
|
let id = match id_raw.parse() {
|
||
|
Ok(id) => id,
|
||
|
Err(_) => continue,
|
||
|
};
|
||
|
|
||
|
quoted_ids.push(id);
|
||
|
}
|
||
|
|
||
|
if quoted_ids.is_empty() {
|
||
|
return Ok(HashMap::new());
|
||
|
}
|
||
|
|
||
|
let in_list = quoted_ids
|
||
|
.iter()
|
||
|
.map(|id| id.to_string())
|
||
|
.collect::<Vec<_>>()
|
||
|
.join(",");
|
||
|
|
||
|
let quoted_posts = query_as(&format!(
|
||
|
"SELECT * FROM posts_{} WHERE id IN ({})",
|
||
|
board, in_list
|
||
|
))
|
||
|
.fetch_all(ctx.db())
|
||
|
.await?
|
||
|
.into_iter()
|
||
|
.map(|post: Post| (post.id, post))
|
||
|
.collect::<HashMap<_, _>>();
|
||
|
|
||
|
Ok(quoted_posts)
|
||
|
}
|