tetratto_core/model/
uploads.rs1use pathbufd::PathBufD;
2use serde::{Serialize, Deserialize};
3use tetratto_shared::{snow::Snowflake, unix_epoch_timestamp};
4use crate::config::Config;
5use std::fs::{write, exists, remove_file};
6use super::{Error, Result};
7
8#[derive(Serialize, Deserialize, PartialEq, Eq)]
9pub enum MediaType {
10 #[serde(alias = "image/webp")]
11 Webp,
12 #[serde(alias = "image/avif")]
13 Avif,
14 #[serde(alias = "image/png")]
15 Png,
16 #[serde(alias = "image/jpg")]
17 Jpg,
18 #[serde(alias = "image/gif")]
19 Gif,
20 #[serde(alias = "image/carpgraph")]
21 Carpgraph,
22}
23
24impl MediaType {
25 pub fn extension(&self) -> &str {
26 match self {
27 Self::Webp => "webp",
28 Self::Avif => "avif",
29 Self::Png => "png",
30 Self::Jpg => "jpg",
31 Self::Gif => "gif",
32 Self::Carpgraph => "carpgraph",
33 }
34 }
35
36 pub fn mime(&self) -> String {
37 format!("image/{}", self.extension())
38 }
39}
40
41#[derive(Serialize, Deserialize)]
42pub struct MediaUpload {
43 pub id: usize,
44 pub created: usize,
45 pub owner: usize,
46 pub what: MediaType,
47 pub alt: String,
48}
49
50impl MediaUpload {
51 pub fn new(what: MediaType, owner: usize) -> Self {
53 Self {
54 id: Snowflake::new().to_string().parse::<usize>().unwrap(),
55 created: unix_epoch_timestamp(),
56 owner,
57 what,
58 alt: String::new(),
59 }
60 }
61
62 pub fn path(&self, config: &Config) -> PathBufD {
64 PathBufD::current()
65 .extend(&[config.dirs.media.as_str(), "uploads"])
66 .join(format!("{}.{}", self.id, self.what.extension()))
67 }
68
69 pub fn write(&self, config: &Config, bytes: &[u8]) -> Result<()> {
71 match write(self.path(config), bytes) {
72 Ok(_) => Ok(()),
73 Err(e) => Err(Error::MiscError(e.to_string())),
74 }
75 }
76
77 pub fn remove(&self, config: &Config) -> Result<()> {
79 let path = self.path(config);
80
81 if !exists(&path).unwrap() {
82 return Ok(());
83 }
84
85 match remove_file(path) {
86 Ok(_) => Ok(()),
87 Err(e) => Err(Error::MiscError(e.to_string())),
88 }
89 }
90}
91
92#[derive(Serialize, Deserialize)]
93pub struct CustomEmoji {
94 pub id: usize,
95 pub created: usize,
96 pub owner: usize,
97 pub community: usize,
98 pub upload_id: usize,
99 pub name: String,
100 pub is_animated: bool,
101}
102
103pub type EmojiParserResult = Vec<(String, usize, String)>;
104
105impl CustomEmoji {
106 pub fn new(
108 owner: usize,
109 community: usize,
110 upload_id: usize,
111 name: String,
112 is_animated: bool,
113 ) -> Self {
114 Self {
115 id: Snowflake::new().to_string().parse::<usize>().unwrap(),
116 created: unix_epoch_timestamp(),
117 owner,
118 community,
119 upload_id,
120 name,
121 is_animated,
122 }
123 }
124
125 pub fn replace(input: &str) -> String {
127 let res = Self::parse(input);
128 let mut out = input.to_string();
129
130 for emoji in res {
131 if emoji.1 == 0 {
132 out = out.replace(
133 &emoji.0,
134 match emoji.2.as_str() {
135 "100" => "💯",
136 "thumbs_up" => "👍",
137 "thumbs_down" => "👎",
138 _ => match emojis::get_by_shortcode(&emoji.2) {
139 Some(e) => e.as_str(),
140 None => &emoji.0,
141 },
142 },
143 );
144 } else {
145 out = out.replace(
146 &emoji.0,
147 &format!(
148 "<img class=\"emoji\" src=\"/api/v1/communities/{}/emojis/{}\" />",
149 emoji.1, emoji.2
150 ),
151 );
152 }
153 }
154
155 out
156 }
157
158 pub fn parse(input: &str) -> EmojiParserResult {
165 let mut out = Vec::new();
166 let mut buffer: String = String::new();
167
168 let mut escape: bool = false;
169 let mut in_emoji: bool = false;
170
171 let mut chars = input.chars();
172 while let Some(char) = chars.next() {
173 if char == '\\' && !escape {
174 escape = true;
175 continue;
176 } else if char == ':' && !escape {
177 let mut community_id: String = String::new();
178 let mut accepting_community_id_chars: bool = true;
179 let mut emoji_name: String = String::new();
180
181 for (char_count, char) in (0_u32..).zip(chars.by_ref()) {
182 if (char == ':') | (char == ' ') {
183 in_emoji = false;
184 break;
185 }
186
187 if char.is_ascii_digit() && accepting_community_id_chars {
188 community_id.push(char);
189 } else if char == '.' {
190 accepting_community_id_chars = false;
192 } else {
193 emoji_name.push(char);
194 }
195
196 if char_count >= 4 && community_id.is_empty() {
197 accepting_community_id_chars = false;
198 }
199 }
200
201 out.push((
202 format!(
203 ":{}{emoji_name}:",
204 if !community_id.is_empty() {
205 format!("{community_id}.")
206 } else {
207 String::new()
208 }
209 ),
210 community_id.parse::<usize>().unwrap_or(0),
211 emoji_name,
212 ));
213
214 continue;
215 } else if in_emoji {
216 buffer.push(char);
217 }
218
219 escape = false;
220 }
221
222 out
223 }
224}