1 #![recursion_limit = "512"]
3 extern crate strum_macros;
5 extern crate lazy_static;
7 extern crate actix_web;
14 extern crate jsonwebtoken;
20 extern crate serde_json;
26 pub mod code_migrations;
33 request::{retry, RecvError},
34 websocket::chat_server::ChatServer,
38 use background_jobs::QueueHandle;
40 use lemmy_utils::{apub::get_apub_protocol_string, settings::Settings, LemmyError};
42 use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC};
44 use serde::Deserialize;
45 use std::process::Command;
47 pub struct LemmyContext {
49 pub chat_server: Addr<ChatServer>,
51 pub activity_queue: QueueHandle,
57 chat_server: Addr<ChatServer>,
59 activity_queue: QueueHandle,
68 pub fn pool(&self) -> &DbPool {
71 pub fn chat_server(&self) -> &Addr<ChatServer> {
74 pub fn client(&self) -> &Client {
77 pub fn activity_queue(&self) -> &QueueHandle {
82 impl Clone for LemmyContext {
83 fn clone(&self) -> Self {
85 pool: self.pool.clone(),
86 chat_server: self.chat_server.clone(),
87 client: self.client.clone(),
88 activity_queue: self.activity_queue.clone(),
93 #[derive(Deserialize, Debug)]
94 pub struct IframelyResponse {
95 title: Option<String>,
96 description: Option<String>,
97 thumbnail_url: Option<String>,
101 pub async fn fetch_iframely(client: &Client, url: &str) -> Result<IframelyResponse, LemmyError> {
102 let fetch_url = format!("http://iframely/oembed?url={}", url);
104 let response = retry(|| client.get(&fetch_url).send()).await?;
106 let res: IframelyResponse = response
109 .map_err(|e| RecvError(e.to_string()))?;
113 #[derive(Deserialize, Debug, Clone)]
114 pub struct PictrsResponse {
115 files: Vec<PictrsFile>,
119 #[derive(Deserialize, Debug, Clone)]
120 pub struct PictrsFile {
122 delete_token: String,
125 pub async fn fetch_pictrs(client: &Client, image_url: &str) -> Result<PictrsResponse, LemmyError> {
126 is_image_content_type(client, image_url).await?;
128 let fetch_url = format!(
129 "http://pictrs:8080/image/download?url={}",
130 utf8_percent_encode(image_url, NON_ALPHANUMERIC) // TODO this might not be needed
133 let response = retry(|| client.get(&fetch_url).send()).await?;
135 let response: PictrsResponse = response
138 .map_err(|e| RecvError(e.to_string()))?;
140 if response.msg == "ok" {
143 Err(anyhow!("{}", &response.msg).into())
147 async fn fetch_iframely_and_pictrs_data(
158 // Fetch iframely data
159 let (iframely_title, iframely_description, iframely_thumbnail_url, iframely_html) =
160 match fetch_iframely(client, url).await {
161 Ok(res) => (res.title, res.description, res.thumbnail_url, res.html),
163 error!("iframely err: {}", e);
164 (None, None, None, None)
168 // Fetch pictrs thumbnail
169 let pictrs_hash = match iframely_thumbnail_url {
170 Some(iframely_thumbnail_url) => match fetch_pictrs(client, &iframely_thumbnail_url).await {
171 Ok(res) => Some(res.files[0].file.to_owned()),
173 error!("pictrs err: {}", e);
177 // Try to generate a small thumbnail if iframely is not supported
178 None => match fetch_pictrs(client, &url).await {
179 Ok(res) => Some(res.files[0].file.to_owned()),
181 error!("pictrs err: {}", e);
187 // The full urls are necessary for federation
188 let pictrs_thumbnail = if let Some(pictrs_hash) = pictrs_hash {
190 "{}://{}/pictrs/image/{}",
191 get_apub_protocol_string(),
192 Settings::get().hostname,
201 iframely_description,
206 None => (None, None, None, None),
210 pub async fn is_image_content_type(client: &Client, test: &str) -> Result<(), LemmyError> {
211 let response = retry(|| client.get(test).send()).await?;
216 .ok_or_else(|| anyhow!("No Content-Type header"))?
218 .starts_with("image/")
222 Err(anyhow!("Not an image type.").into())
226 pub fn captcha_espeak_wav_base64(captcha: &str) -> Result<String, LemmyError> {
227 let mut built_text = String::new();
229 // Building proper speech text for espeak
230 for mut c in captcha.chars() {
231 let new_str = if c.is_alphabetic() {
232 if c.is_lowercase() {
233 c.make_ascii_uppercase();
234 format!("lower case {} ... ", c)
236 c.make_ascii_uppercase();
237 format!("capital {} ... ", c)
243 built_text.push_str(&new_str);
246 espeak_wav_base64(&built_text)
249 pub fn espeak_wav_base64(text: &str) -> Result<String, LemmyError> {
250 // Make a temp file path
251 let uuid = uuid::Uuid::new_v4().to_string();
252 let file_path = format!("/tmp/lemmy_espeak_{}.wav", &uuid);
254 // Write the wav file
255 Command::new("espeak")
261 // Read the wav file bytes
262 let bytes = std::fs::read(&file_path)?;
265 std::fs::remove_file(file_path)?;
268 let base64 = base64::encode(bytes);
275 use crate::{captcha_espeak_wav_base64, is_image_content_type};
279 actix_rt::System::new("tset_image").block_on(async move {
280 let client = reqwest::Client::default();
281 assert!(is_image_content_type(&client, "https://1734811051.rsc.cdn77.org/data/images/full/365645/as-virus-kills-navajos-in-their-homes-tribal-women-provide-lifeline.jpg?w=600?w=650").await.is_ok());
282 assert!(is_image_content_type(&client,
283 "https://twitter.com/BenjaminNorton/status/1259922424272957440?s=20"
292 assert!(captcha_espeak_wav_base64("WxRt2l").is_ok())
295 // These helped with testing
297 // fn test_iframely() {
298 // let res = fetch_iframely(client, "https://www.redspark.nu/?p=15341").await;
299 // assert!(res.is_ok());
303 // fn test_pictshare() {
304 // let res = fetch_pictshare("https://upload.wikimedia.org/wikipedia/en/2/27/The_Mandalorian_logo.jpg");
305 // assert!(res.is_ok());
306 // let res_other = fetch_pictshare("https://upload.wikimedia.org/wikipedia/en/2/27/The_Mandalorian_logo.jpgaoeu");
307 // assert!(res_other.is_err());