use pathbufd::PathBufD;
use serde::{Serialize, Deserialize};
use tetratto_shared::{snow::Snowflake, unix_epoch_timestamp};
use crate::config::Config;
use std::fs::{write, exists, remove_file};
use super::{Error, Result};
#[derive(Serialize, Deserialize, PartialEq, Eq)]
pub enum MediaType {
#[serde(alias = "image/webp")]
Webp,
#[serde(alias = "image/avif")]
Avif,
#[serde(alias = "image/png")]
Png,
#[serde(alias = "image/jpg")]
Jpg,
#[serde(alias = "image/gif")]
Gif,
#[serde(alias = "image/carpgraph")]
Carpgraph,
}
impl MediaType {
pub fn extension(&self) -> &str {
match self {
Self::Webp => "webp",
Self::Avif => "avif",
Self::Png => "png",
Self::Jpg => "jpg",
Self::Gif => "gif",
Self::Carpgraph => "carpgraph",
}
}
pub fn mime(&self) -> String {
format!("image/{}", self.extension())
}
}
#[derive(Serialize, Deserialize)]
pub struct MediaUpload {
pub id: usize,
pub created: usize,
pub owner: usize,
pub what: MediaType,
pub alt: String,
}
impl MediaUpload {
pub fn new(what: MediaType, owner: usize) -> Self {
Self {
id: Snowflake::new().to_string().parse::<usize>().unwrap(),
created: unix_epoch_timestamp(),
owner,
what,
alt: String::new(),
}
}
pub fn path(&self, config: &Config) -> PathBufD {
PathBufD::current()
.extend(&[config.dirs.media.as_str(), "uploads"])
.join(format!("{}.{}", self.id, self.what.extension()))
}
pub fn write(&self, config: &Config, bytes: &[u8]) -> Result<()> {
match write(self.path(config), bytes) {
Ok(_) => Ok(()),
Err(e) => Err(Error::MiscError(e.to_string())),
}
}
pub fn remove(&self, config: &Config) -> Result<()> {
let path = self.path(config);
if !exists(&path).unwrap() {
return Ok(());
}
match remove_file(path) {
Ok(_) => Ok(()),
Err(e) => Err(Error::MiscError(e.to_string())),
}
}
}
#[derive(Serialize, Deserialize)]
pub struct CustomEmoji {
pub id: usize,
pub created: usize,
pub owner: usize,
pub community: usize,
pub upload_id: usize,
pub name: String,
pub is_animated: bool,
}
pub type EmojiParserResult = Vec<(String, usize, String)>;
impl CustomEmoji {
pub fn new(
owner: usize,
community: usize,
upload_id: usize,
name: String,
is_animated: bool,
) -> Self {
Self {
id: Snowflake::new().to_string().parse::<usize>().unwrap(),
created: unix_epoch_timestamp(),
owner,
community,
upload_id,
name,
is_animated,
}
}
pub fn replace(input: &str) -> String {
let res = Self::parse(input);
let mut out = input.to_string();
for emoji in res {
if emoji.1 == 0 {
out = out.replace(
&emoji.0,
match emoji.2.as_str() {
"100" => "💯",
"thumbs_up" => "👍",
"thumbs_down" => "👎",
_ => match emojis::get_by_shortcode(&emoji.2) {
Some(e) => e.as_str(),
None => &emoji.0,
},
},
);
} else {
out = out.replace(
&emoji.0,
&format!(
"<img class=\"emoji\" src=\"/api/v1/communities/{}/emojis/{}\" />",
emoji.1, emoji.2
),
);
}
}
out
}
pub fn parse(input: &str) -> EmojiParserResult {
let mut out = Vec::new();
let mut buffer: String = String::new();
let mut escape: bool = false;
let mut in_emoji: bool = false;
let mut chars = input.chars();
while let Some(char) = chars.next() {
if char == '\\' && !escape {
escape = true;
continue;
} else if char == ':' && !escape {
let mut community_id: String = String::new();
let mut accepting_community_id_chars: bool = true;
let mut emoji_name: String = String::new();
for (char_count, char) in (0_u32..).zip(chars.by_ref()) {
if (char == ':') | (char == ' ') {
in_emoji = false;
break;
}
if char.is_ascii_digit() && accepting_community_id_chars {
community_id.push(char);
} else if char == '.' {
accepting_community_id_chars = false;
} else {
emoji_name.push(char);
}
if char_count >= 4 && community_id.is_empty() {
accepting_community_id_chars = false;
}
}
out.push((
format!(
":{}{emoji_name}:",
if !community_id.is_empty() {
format!("{community_id}.")
} else {
String::new()
}
),
community_id.parse::<usize>().unwrap_or(0),
emoji_name,
));
continue;
} else if in_emoji {
buffer.push(char);
}
escape = false;
}
out
}
}