1 use crate::{settings::structs::Settings, LemmyError};
4 use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC};
6 use serde::Deserialize;
7 use std::future::Future;
11 #[derive(Clone, Debug, Error)]
12 #[error("Error sending request, {0}")]
13 struct SendError(pub String);
15 #[derive(Clone, Debug, Error)]
16 #[error("Error receiving response, {0}")]
17 pub struct RecvError(pub String);
19 pub async fn retry<F, Fut, T>(f: F) -> Result<T, reqwest::Error>
22 Fut: Future<Output = Result<T, reqwest::Error>>,
24 retry_custom(|| async { Ok((f)().await) }).await
27 async fn retry_custom<F, Fut, T>(f: F) -> Result<T, reqwest::Error>
30 Fut: Future<Output = Result<Result<T, reqwest::Error>, reqwest::Error>>,
32 let mut response: Option<Result<T, reqwest::Error>> = None;
36 Ok(t) => return Ok(t),
39 response = Some(Err(e));
47 response.expect("retry http request")
50 #[derive(Deserialize, Debug)]
51 pub(crate) struct IframelyResponse {
52 title: Option<String>,
53 description: Option<String>,
54 thumbnail_url: Option<Url>,
58 pub(crate) async fn fetch_iframely(
61 ) -> Result<IframelyResponse, LemmyError> {
62 let fetch_url = format!("{}/oembed?url={}", Settings::get().iframely_url(), url);
64 let response = retry(|| client.get(&fetch_url).send()).await?;
66 let res: IframelyResponse = response
69 .map_err(|e| RecvError(e.to_string()))?;
73 #[derive(Deserialize, Debug, Clone)]
74 pub(crate) struct PictrsResponse {
75 files: Vec<PictrsFile>,
79 #[derive(Deserialize, Debug, Clone)]
80 pub(crate) struct PictrsFile {
85 pub(crate) async fn fetch_pictrs(
88 ) -> Result<PictrsResponse, LemmyError> {
89 is_image_content_type(client, image_url).await?;
91 let fetch_url = format!(
92 "{}/image/download?url={}",
93 Settings::get().pictrs_url(),
94 utf8_percent_encode(image_url.as_str(), NON_ALPHANUMERIC) // TODO this might not be needed
97 let response = retry(|| client.get(&fetch_url).send()).await?;
99 let response: PictrsResponse = response
102 .map_err(|e| RecvError(e.to_string()))?;
104 if response.msg == "ok" {
107 Err(anyhow!("{}", &response.msg).into())
111 pub async fn fetch_iframely_and_pictrs_data(
114 ) -> (Option<String>, Option<String>, Option<String>, Option<Url>) {
117 // Fetch iframely data
118 let (iframely_title, iframely_description, iframely_thumbnail_url, iframely_html) =
119 match fetch_iframely(client, url).await {
120 Ok(res) => (res.title, res.description, res.thumbnail_url, res.html),
122 error!("iframely err: {}", e);
123 (None, None, None, None)
127 // Fetch pictrs thumbnail
128 let pictrs_hash = match iframely_thumbnail_url {
129 Some(iframely_thumbnail_url) => match fetch_pictrs(client, &iframely_thumbnail_url).await {
130 Ok(res) => Some(res.files[0].file.to_owned()),
132 error!("pictrs err: {}", e);
136 // Try to generate a small thumbnail if iframely is not supported
137 None => match fetch_pictrs(client, url).await {
138 Ok(res) => Some(res.files[0].file.to_owned()),
140 error!("pictrs err: {}", e);
146 // The full urls are necessary for federation
147 let pictrs_thumbnail = if let Some(pictrs_hash) = pictrs_hash {
148 let url = Url::parse(&format!(
149 "{}/pictrs/image/{}",
150 Settings::get().get_protocol_and_hostname(),
154 Ok(parsed_url) => Some(parsed_url),
156 // This really shouldn't happen unless the settings or hash are malformed
157 error!("Unexpected error constructing pictrs thumbnail URL: {}", e);
167 iframely_description,
172 None => (None, None, None, None),
176 async fn is_image_content_type(client: &Client, test: &Url) -> Result<(), LemmyError> {
177 let response = retry(|| client.get(test.to_owned()).send()).await?;
181 .ok_or_else(|| anyhow!("No Content-Type header"))?
183 .starts_with("image/")
187 Err(anyhow!("Not an image type.").into())
193 // These helped with testing
195 // fn test_iframely() {
196 // let res = fetch_iframely(client, "https://www.redspark.nu/?p=15341").await;
197 // assert!(res.is_ok());
201 // fn test_pictshare() {
202 // let res = fetch_pictshare("https://upload.wikimedia.org/wikipedia/en/2/27/The_Mandalorian_logo.jpg");
203 // assert!(res.is_ok());
204 // let res_other = fetch_pictshare("https://upload.wikimedia.org/wikipedia/en/2/27/The_Mandalorian_logo.jpgaoeu");
205 // assert!(res_other.is_err());