small bsky embedder @ boobsky.app - kinda mid but works - mirror of git.fomx.gay/rooot/embedthing
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: implement redirecting users to original

Signed-off-by: rooot <hey@rooot.gay>

+94 -14
+13 -5
src/bsky.rs
··· 1 1 use crate::meow::{ 2 - EmbedAuthor, EmbedMedia, EmbedSource, EmbedThingy, GenericError, MissingElementError, 2 + BotCheck, EmbedAuthor, EmbedMedia, EmbedResponse, EmbedSource, EmbedThingy, GenericError, 3 + MissingElementError, 3 4 }; 4 5 use crate::ManagedBskyAgent; 5 6 use bsky_sdk::api::app::bsky::embed::images::ImageData; ··· 13 14 14 15 #[get("/profile/<name>/post/<post>")] 15 16 pub(crate) async fn profile_post( 17 + check: BotCheck, 16 18 agent: &State<ManagedBskyAgent>, 17 19 name: &str, 18 20 post: &str, 19 - ) -> Result<EmbedThingy, GenericError> { 21 + ) -> Result<EmbedResponse, GenericError> { 22 + let source = EmbedSource::Bsky(name.into(), post.into()); 23 + 24 + if let Some(redirect) = check.redirect(&source) { 25 + return Ok(redirect); 26 + } 27 + 20 28 let atproto_uri = format!("at://{}/app.bsky.feed.post/{}", name, post); 21 29 let post_thread = agent 22 30 .agent ··· 161 169 } 162 170 } 163 171 164 - return Ok(EmbedThingy { 172 + return Ok(EmbedResponse::Embed(EmbedThingy { 165 173 author: Some(author), 166 174 text, 167 175 embeds: embed_embeds, 168 - source: EmbedSource::Bsky(format!("https://bsky.social/profile/{}/post/{}", name, post)), 169 - }); 176 + source, 177 + })); 170 178 } 171 179 } 172 180 }
+81 -9
src/meow.rs
··· 1 + use std::fmt::{Display, Formatter}; 2 + 1 3 use rocket::http::{ContentType, Status}; 2 - use rocket::response::Responder; 4 + use rocket::request::{FromRequest, Outcome}; 5 + use rocket::response::{Redirect, Responder}; 3 6 use rocket::{response, Request, Response}; 4 7 use serde::ser::SerializeMap; 5 8 use serde::Serialize; ··· 23 26 // this contains the original source/url of the embed 24 27 #[derive(Debug)] 25 28 pub enum EmbedSource { 26 - Bsky(String), 29 + Bsky(String, String), 30 + } 31 + 32 + impl Display for EmbedSource { 33 + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 34 + match self { 35 + EmbedSource::Bsky(name, post) => { 36 + write!(f, "https://bsky.app/profile/{}/post/{}", name, post) 37 + } 38 + } 39 + } 27 40 } 28 41 29 42 #[derive(Debug)] ··· 42 55 43 56 // maybe in the future this will be useful IF i add support for other sites 44 57 45 - let source_url; 46 - match self.source { 47 - EmbedSource::Bsky(url) => { 48 - source_url = url; 49 - html.push_str("<meta property=\"theme-color\" content=\"#0085ff\">\n"); 50 - } 51 - } 58 + let source_url = self.source.to_string(); 59 + let color = match self.source { 60 + EmbedSource::Bsky(_, _) => "#0085ff", 61 + }; 62 + 63 + html.push_str(&format!( 64 + "<meta property=\"theme-color\" content=\"{}\">\n", 65 + color 66 + )); 52 67 53 68 // add canonical urls 54 69 html.push_str(&format!(r#"<link rel="canonical" href="{}">"#, source_url)); ··· 233 248 .finalize()) 234 249 } 235 250 } 251 + 252 + pub enum EmbedResponse { 253 + Embed(EmbedThingy), 254 + Redirect(String), 255 + } 256 + 257 + impl<'r, 'o: 'r> Responder<'r, 'o> for EmbedResponse { 258 + fn respond_to(self, req: &'r Request<'_>) -> response::Result<'o> { 259 + match self { 260 + EmbedResponse::Embed(embed) => embed.respond_to(req), 261 + EmbedResponse::Redirect(url) => Redirect::temporary(url).respond_to(req), 262 + } 263 + } 264 + } 265 + 266 + pub struct BotCheck(bool); 267 + 268 + impl BotCheck { 269 + pub fn redirect(&self, src: &EmbedSource) -> Option<EmbedResponse> { 270 + if self.0 { 271 + Some(EmbedResponse::Redirect(src.to_string())) 272 + } else { 273 + None 274 + } 275 + } 276 + } 277 + 278 + fn is_bot(user_agent: &str) -> bool { 279 + // if you know a bot not listed here that embeds content, pls open an issue!! 280 + let agents = [ 281 + "Discordbot", 282 + "https://discordapp.com", 283 + "Bluesky Cardyb", 284 + "https://opengraph.io", 285 + "Twitterbot", 286 + "TwitterBot", 287 + "FacebookBot", 288 + // not a bot but used for debugging :3c 289 + "curl/", 290 + ]; 291 + 292 + agents.iter().any(|agent| user_agent.contains(*agent)) 293 + } 294 + 295 + #[rocket::async_trait] 296 + impl<'r> FromRequest<'r> for BotCheck { 297 + type Error = (); 298 + 299 + async fn from_request(req: &'r Request<'_>) -> Outcome<Self, Self::Error> { 300 + if let Some(user_agent) = req.headers().get_one("user-agent") { 301 + if !is_bot(user_agent) { 302 + return Outcome::Success(Self(true)); 303 + } 304 + } 305 + Outcome::Success(Self(false)) 306 + } 307 + }