From 7f132c1517835c00ceb61fdd06b689583e09a5bc Mon Sep 17 00:00:00 2001 From: Ashley Date: Sun, 10 Dec 2023 10:27:07 +0000 Subject: [PATCH] inital source for january --- january/src/routes/embed.rs | 98 +++++++++++++++++++++++++++++++++++++ january/src/routes/info.rs | 14 ++++++ january/src/routes/mod.rs | 3 ++ january/src/routes/proxy.rs | 43 ++++++++++++++++ 4 files changed, 158 insertions(+) create mode 100644 january/src/routes/embed.rs create mode 100644 january/src/routes/info.rs create mode 100644 january/src/routes/mod.rs create mode 100644 january/src/routes/proxy.rs diff --git a/january/src/routes/embed.rs b/january/src/routes/embed.rs new file mode 100644 index 00000000..911ff83b --- /dev/null +++ b/january/src/routes/embed.rs @@ -0,0 +1,98 @@ +use std::time::Duration; + +use actix_web::{ + web::{self, Query}, + Responder, +}; +use regex::Regex; +use serde::Deserialize; + +use crate::structs::metadata::Metadata; +use crate::structs::{embed::Embed, media::Video}; +use crate::util::request::fetch; +use crate::{ + structs::media::{Image, ImageSize}, + util::{request::consume_size, result::Error}, +}; + +lazy_static! { + static ref CACHE: moka::future::Cache> = + moka::future::Cache::builder() + .max_capacity(1_000) + .time_to_live(Duration::from_secs(60)) + .build(); +} + +#[derive(Deserialize)] +pub struct Parameters { + url: String, +} + +async fn embed(mut url: String) -> Result { + // Twitter is a piece of shit and does not + // provide metadata in an easily consumable format. + // + // So... we just redirect everything to Nitter. + // + // Fun bonus: Twitter denied our developer application + // which would've been the only way to pull properly + // formatted Tweet data out and what's worse is that this + // also prevents us adding those "connections" that other + // platforms have. + // + // In any case, because Twitter, they + // do not provide OpenGraph data. + lazy_static! { + static ref RE_TWITTER: Regex = + Regex::new("^(?:https?://)?(?:www\\.)?twitter\\.com").unwrap(); + } + + if RE_TWITTER.is_match(&url) { + url = RE_TWITTER.replace(&url, "https://nitter.net").into(); + } + + // Fetch URL + let (resp, mime) = fetch(&url).await?; + + // Match appropriate MIME type to process + match (mime.type_(), mime.subtype()) { + (_, mime::HTML) => { + let mut metadata = Metadata::from(resp, url).await?; + metadata.resolve_external().await; + + if metadata.is_none() { + return Ok(Embed::None); + } + + Ok(Embed::Website(metadata)) + } + (mime::IMAGE, _) => { + if let Ok((width, height)) = consume_size(resp, mime).await { + Ok(Embed::Image(Image { + url, + width, + height, + size: ImageSize::Large, + })) + } else { + Ok(Embed::None) + } + } + (mime::VIDEO, _) => { + if let Ok((width, height)) = consume_size(resp, mime).await { + Ok(Embed::Video(Video { url, width, height })) + } else { + Ok(Embed::None) + } + } + _ => Ok(Embed::None), + } +} + +pub async fn get(Query(info): Query) -> Result { + let url = info.url; + let result = CACHE + .get_with(url.clone(), async { embed(url).await }) + .await; + result.map(web::Json) +} diff --git a/january/src/routes/info.rs b/january/src/routes/info.rs new file mode 100644 index 00000000..e76d9e91 --- /dev/null +++ b/january/src/routes/info.rs @@ -0,0 +1,14 @@ +use actix_web::web; +use actix_web::Responder; +use serde::Serialize; + +#[derive(Debug, Serialize)] +pub struct Info { + january: &'static str, +} + +pub async fn get() -> impl Responder { + web::Json(Info { + january: env!("CARGO_PKG_VERSION"), + }) +} diff --git a/january/src/routes/mod.rs b/january/src/routes/mod.rs new file mode 100644 index 00000000..c4aa207d --- /dev/null +++ b/january/src/routes/mod.rs @@ -0,0 +1,3 @@ +pub mod embed; +pub mod info; +pub mod proxy; diff --git a/january/src/routes/proxy.rs b/january/src/routes/proxy.rs new file mode 100644 index 00000000..f53e1634 --- /dev/null +++ b/january/src/routes/proxy.rs @@ -0,0 +1,43 @@ +use std::time::Duration; + +use actix_web::web::Bytes; +use actix_web::{web::Query, HttpResponse, Responder}; +use serde::Deserialize; + +use crate::util::request::{fetch, get_bytes}; +use crate::util::result::Error; + +lazy_static! { + static ref CACHE: moka::future::Cache> = + moka::future::Cache::builder() + .weigher(|_key, value: &Result| { + value.as_ref().map(|bytes| bytes.len() as u32).unwrap_or(1) + }) + .max_capacity(1024 * 1024 * 1024) + .time_to_live(Duration::from_secs(60)) + .build(); +} + +#[derive(Deserialize)] +pub struct Parameters { + url: String, +} + +async fn proxy(url: String) -> Result { + let (mut resp, mime) = fetch(&url).await?; + + if matches!(mime.type_(), mime::IMAGE | mime::VIDEO) { + let bytes = get_bytes(&mut resp).await?; + Ok(bytes) + } else { + Err(Error::NotAllowedToProxy) + } +} + +pub async fn get(Query(info): Query) -> Result { + let url = info.url; + let result = CACHE + .get_with(url.clone(), async { proxy(url).await }) + .await; + result.map(|b| HttpResponse::Ok().body(b)) +}