tangled.org trending bluesky account

clean up

Changed files
+37 -366
bot
logic
src
-172
Cargo.lock
··· 558 "async-trait", 559 "atproto_api", 560 "atrium-api", 561 - "bsky-sdk", 562 "chromiumoxide", 563 "dotenv", 564 "env_logger", 565 - "futures-util", 566 - "handlebars", 567 "log", 568 "logic", 569 "reqwest", 570 "rocketman", 571 - "rust-embed", 572 "serde", 573 "serde_json", 574 "slingshot", ··· 594 "thiserror 1.0.69", 595 "trait-variant", 596 "unicode-segmentation", 597 - ] 598 - 599 - [[package]] 600 - name = "bstr" 601 - version = "1.12.0" 602 - source = "registry+https://github.com/rust-lang/crates.io-index" 603 - checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4" 604 - dependencies = [ 605 - "memchr", 606 - "serde", 607 ] 608 609 [[package]] ··· 1457 checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" 1458 1459 [[package]] 1460 - name = "globset" 1461 - version = "0.4.16" 1462 - source = "registry+https://github.com/rust-lang/crates.io-index" 1463 - checksum = "54a1028dfc5f5df5da8a56a73e6c153c9a9708ec57232470703592a3f18e49f5" 1464 - dependencies = [ 1465 - "aho-corasick", 1466 - "bstr", 1467 - "log", 1468 - "regex-automata 0.4.9", 1469 - "regex-syntax 0.8.5", 1470 - ] 1471 - 1472 - [[package]] 1473 name = "gloo-timers" 1474 version = "0.3.0" 1475 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1525 ] 1526 1527 [[package]] 1528 - name = "handlebars" 1529 - version = "6.3.2" 1530 - source = "registry+https://github.com/rust-lang/crates.io-index" 1531 - checksum = "759e2d5aea3287cb1190c8ec394f42866cb5bf74fcbf213f354e3c856ea26098" 1532 - dependencies = [ 1533 - "derive_builder", 1534 - "log", 1535 - "num-order", 1536 - "pest", 1537 - "pest_derive", 1538 - "rust-embed", 1539 - "serde", 1540 - "serde_json", 1541 - "thiserror 2.0.12", 1542 - ] 1543 - 1544 - [[package]] 1545 name = "hashbrown" 1546 version = "0.14.5" 1547 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2327 ] 2328 2329 [[package]] 2330 - name = "num-modular" 2331 - version = "0.6.1" 2332 - source = "registry+https://github.com/rust-lang/crates.io-index" 2333 - checksum = "17bb261bf36fa7d83f4c294f834e91256769097b3cb505d44831e0a179ac647f" 2334 - 2335 - [[package]] 2336 - name = "num-order" 2337 - version = "1.2.0" 2338 - source = "registry+https://github.com/rust-lang/crates.io-index" 2339 - checksum = "537b596b97c40fcf8056d153049eb22f481c17ebce72a513ec9286e4986d1bb6" 2340 - dependencies = [ 2341 - "num-modular", 2342 - ] 2343 - 2344 - [[package]] 2345 name = "num-traits" 2346 version = "0.2.19" 2347 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2473 checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 2474 2475 [[package]] 2476 - name = "pest" 2477 - version = "2.8.2" 2478 - source = "registry+https://github.com/rust-lang/crates.io-index" 2479 - checksum = "21e0a3a33733faeaf8651dfee72dd0f388f0c8e5ad496a3478fa5a922f49cfa8" 2480 - dependencies = [ 2481 - "memchr", 2482 - "thiserror 2.0.12", 2483 - "ucd-trie", 2484 - ] 2485 - 2486 - [[package]] 2487 - name = "pest_derive" 2488 - version = "2.8.2" 2489 - source = "registry+https://github.com/rust-lang/crates.io-index" 2490 - checksum = "bc58706f770acb1dbd0973e6530a3cff4746fb721207feb3a8a6064cd0b6c663" 2491 - dependencies = [ 2492 - "pest", 2493 - "pest_generator", 2494 - ] 2495 - 2496 - [[package]] 2497 - name = "pest_generator" 2498 - version = "2.8.2" 2499 - source = "registry+https://github.com/rust-lang/crates.io-index" 2500 - checksum = "6d4f36811dfe07f7b8573462465d5cb8965fffc2e71ae377a33aecf14c2c9a2f" 2501 - dependencies = [ 2502 - "pest", 2503 - "pest_meta", 2504 - "proc-macro2", 2505 - "quote", 2506 - "syn 2.0.101", 2507 - ] 2508 - 2509 - [[package]] 2510 - name = "pest_meta" 2511 - version = "2.8.2" 2512 - source = "registry+https://github.com/rust-lang/crates.io-index" 2513 - checksum = "42919b05089acbd0a5dcd5405fb304d17d1053847b81163d09c4ad18ce8e8420" 2514 - dependencies = [ 2515 - "pest", 2516 - "sha2", 2517 - ] 2518 - 2519 - [[package]] 2520 name = "pin-project-lite" 2521 version = "0.2.16" 2522 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2958 ] 2959 2960 [[package]] 2961 - name = "rust-embed" 2962 - version = "8.7.2" 2963 - source = "registry+https://github.com/rust-lang/crates.io-index" 2964 - checksum = "025908b8682a26ba8d12f6f2d66b987584a4a87bc024abc5bbc12553a8cd178a" 2965 - dependencies = [ 2966 - "rust-embed-impl", 2967 - "rust-embed-utils", 2968 - "walkdir", 2969 - ] 2970 - 2971 - [[package]] 2972 - name = "rust-embed-impl" 2973 - version = "8.7.2" 2974 - source = "registry+https://github.com/rust-lang/crates.io-index" 2975 - checksum = "6065f1a4392b71819ec1ea1df1120673418bf386f50de1d6f54204d836d4349c" 2976 - dependencies = [ 2977 - "proc-macro2", 2978 - "quote", 2979 - "rust-embed-utils", 2980 - "syn 2.0.101", 2981 - "walkdir", 2982 - ] 2983 - 2984 - [[package]] 2985 - name = "rust-embed-utils" 2986 - version = "8.7.2" 2987 - source = "registry+https://github.com/rust-lang/crates.io-index" 2988 - checksum = "f6cc0c81648b20b70c491ff8cce00c1c3b223bb8ed2b5d41f0e54c6c4c0a3594" 2989 - dependencies = [ 2990 - "globset", 2991 - "sha2", 2992 - "walkdir", 2993 - ] 2994 - 2995 - [[package]] 2996 name = "rustc-demangle" 2997 version = "0.1.24" 2998 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 3137 version = "1.0.20" 3138 source = "registry+https://github.com/rust-lang/crates.io-index" 3139 checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" 3140 - 3141 - [[package]] 3142 - name = "same-file" 3143 - version = "1.0.6" 3144 - source = "registry+https://github.com/rust-lang/crates.io-index" 3145 - checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 3146 - dependencies = [ 3147 - "winapi-util", 3148 - ] 3149 3150 [[package]] 3151 name = "schannel" ··· 4040 checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" 4041 4042 [[package]] 4043 - name = "ucd-trie" 4044 - version = "0.1.7" 4045 - source = "registry+https://github.com/rust-lang/crates.io-index" 4046 - checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" 4047 - 4048 - [[package]] 4049 name = "unicode-bidi" 4050 version = "0.3.18" 4051 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 4157 version = "0.9.5" 4158 source = "registry+https://github.com/rust-lang/crates.io-index" 4159 checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 4160 - 4161 - [[package]] 4162 - name = "walkdir" 4163 - version = "2.5.0" 4164 - source = "registry+https://github.com/rust-lang/crates.io-index" 4165 - checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" 4166 - dependencies = [ 4167 - "same-file", 4168 - "winapi-util", 4169 - ] 4170 4171 [[package]] 4172 name = "want" ··· 4351 version = "0.4.0" 4352 source = "registry+https://github.com/rust-lang/crates.io-index" 4353 checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 4354 - 4355 - [[package]] 4356 - name = "winapi-util" 4357 - version = "0.1.11" 4358 - source = "registry+https://github.com/rust-lang/crates.io-index" 4359 - checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" 4360 - dependencies = [ 4361 - "windows-sys 0.61.0", 4362 - ] 4363 4364 [[package]] 4365 name = "winapi-x86_64-pc-windows-gnu"
··· 558 "async-trait", 559 "atproto_api", 560 "atrium-api", 561 "chromiumoxide", 562 "dotenv", 563 "env_logger", 564 "log", 565 "logic", 566 "reqwest", 567 "rocketman", 568 "serde", 569 "serde_json", 570 "slingshot", ··· 590 "thiserror 1.0.69", 591 "trait-variant", 592 "unicode-segmentation", 593 ] 594 595 [[package]] ··· 1443 checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" 1444 1445 [[package]] 1446 name = "gloo-timers" 1447 version = "0.3.0" 1448 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1498 ] 1499 1500 [[package]] 1501 name = "hashbrown" 1502 version = "0.14.5" 1503 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2283 ] 2284 2285 [[package]] 2286 name = "num-traits" 2287 version = "0.2.19" 2288 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2414 checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 2415 2416 [[package]] 2417 name = "pin-project-lite" 2418 version = "0.2.16" 2419 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2855 ] 2856 2857 [[package]] 2858 name = "rustc-demangle" 2859 version = "0.1.24" 2860 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2999 version = "1.0.20" 3000 source = "registry+https://github.com/rust-lang/crates.io-index" 3001 checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" 3002 3003 [[package]] 3004 name = "schannel" ··· 3893 checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" 3894 3895 [[package]] 3896 name = "unicode-bidi" 3897 version = "0.3.18" 3898 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 4004 version = "0.9.5" 4005 source = "registry+https://github.com/rust-lang/crates.io-index" 4006 checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 4007 4008 [[package]] 4009 name = "want" ··· 4188 version = "0.4.0" 4189 source = "registry+https://github.com/rust-lang/crates.io-index" 4190 checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 4191 4192 [[package]] 4193 name = "winapi-x86_64-pc-windows-gnu"
-4
bot/Cargo.toml
··· 6 [dependencies] 7 anyhow = "1.0.98" 8 atproto_api.workspace = true 9 - bsky-sdk.workspace = true 10 chromiumoxide = { version = "0.7", features = ["tokio-runtime"] } 11 - handlebars = { version = "6.3.2", features = ["rust-embed"] } 12 - rust-embed = { version = "8.7.2", features = ["include-exclude"] } 13 rocketman.workspace = true 14 reqwest.workspace = true 15 atrium-api.workspace = true ··· 24 logic.workspace = true 25 slingshot.workspace = true 26 urlencoding = "2.1.3" 27 - futures-util = "0.3.31"
··· 6 [dependencies] 7 anyhow = "1.0.98" 8 atproto_api.workspace = true 9 chromiumoxide = { version = "0.7", features = ["tokio-runtime"] } 10 rocketman.workspace = true 11 reqwest.workspace = true 12 atrium-api.workspace = true ··· 21 logic.workspace = true 22 slingshot.workspace = true 23 urlencoding = "2.1.3"
+35 -111
bot/src/main.rs
··· 1 - mod constellation; 2 - 3 extern crate dotenv; 4 - 5 use crate::constellation::fetch_constellation_count; 6 - use atrium_api::app::bsky::embed::defs::AspectRatioData; 7 use atrium_api::app::bsky::feed::post::RecordEmbedRefs; 8 use atrium_api::app::bsky::richtext::facet::{ByteSliceData, LinkData, MainFeaturesItem}; 9 use atrium_api::types::string::Language; 10 use atrium_api::types::{Collection, Union}; 11 - use bsky_sdk::rich_text::RichText; 12 - use chromiumoxide::browser::{Browser, BrowserConfig, HeadlessMode}; 13 - use chromiumoxide::cdp::browser_protocol::page::{ 14 - CaptureScreenshotFormat, CaptureScreenshotParams, Viewport, 15 - }; 16 use dotenv::dotenv; 17 - use futures_util::stream::StreamExt; 18 - use handlebars::Handlebars; 19 - use log::error; 20 use logic::BotApi; 21 use rocketman::{ 22 connection::JetstreamConnection, handler, ingestion::LexiconIngestor, 23 options::JetstreamOptions, types::event::Operation, 24 }; 25 - use rust_embed::Embed; 26 use serde::Serialize; 27 use slingshot::Slingshot; 28 use sqlx::sqlite::{SqliteConnectOptions, SqlitePool}; ··· 31 use std::sync::{Arc, Mutex}; 32 use std::time::Duration; 33 34 - #[derive(Embed)] 35 - #[folder = "templates/"] 36 - #[include = "*.hbs"] 37 - pub struct Templates; 38 39 #[derive(Debug)] 40 - struct Record { 41 did: String, 42 collection: String, 43 rkey: String, 44 } 45 46 - fn parse_uri(uri: &str) -> anyhow::Result<Record> { 47 let parts: Vec<&str> = uri.trim_start_matches("at://").split('/').collect(); 48 if parts.len() != 3 { 49 return Err(anyhow::anyhow!("Invalid URI format")); 50 } 51 - Ok(Record { 52 did: parts[0].to_string(), 53 collection: parts[1].to_string(), 54 rkey: parts[2].to_string(), ··· 107 let bot_api = BotApi::new_logged_in(bot_username, bot_password, bot_pds_url).await?; 108 let sling_shot = Arc::new(Slingshot::new("https://slingshot.microcosm.blue")?); 109 110 - // setup handlebars 111 - let mut hbs = Handlebars::new(); 112 - let _ = hbs.register_embed_templates::<Templates>(); 113 - 114 - // Initialize a long-running Chromium browser 115 - let cfg = BrowserConfig::builder() 116 - .headless_mode(HeadlessMode::New) 117 - .no_sandbox() 118 - .build() 119 - .map_err(|e| anyhow::anyhow!(e)) 120 - .expect("build browser config"); 121 - 122 - let (browser, mut browser_handler) = Browser::launch(cfg).await.expect("launch browser"); 123 - // Drive the browser handler for the lifetime of the program 124 - tokio::spawn(async move { while let Some(_evt) = browser_handler.next().await {} }); 125 - let browser = Arc::new(browser); 126 - 127 // Ingestor for the star collection 128 let mut ingestors: HashMap<String, Box<dyn LexiconIngestor + Send + Sync>> = HashMap::new(); 129 ingestors.insert( ··· 132 pool: pool.clone(), 133 bot: Arc::new(bot_api), 134 sling_shot: sling_shot.clone(), 135 - browser: browser.clone(), 136 - hbs: Arc::new(hbs), 137 timeframe_hours, 138 star_threshold, 139 post_window_hours, ··· 167 .map_err(|e| anyhow::anyhow!(e.to_string())) 168 } 169 170 - struct StarIngestor { 171 - pool: SqlitePool, 172 - bot: Arc<BotApi>, 173 - sling_shot: Arc<Slingshot>, 174 - browser: Arc<Browser>, 175 - hbs: Arc<Handlebars<'static>>, 176 - timeframe_hours: i64, 177 - star_threshold: i64, 178 - post_window_hours: i64, 179 - } 180 - 181 #[async_trait::async_trait] 182 impl LexiconIngestor for StarIngestor { 183 /// Asynchronously processes an incoming event message related to "stars" and performs database operations and other actions based on the event type. ··· 230 231 if count_in_window >= self.star_threshold { 232 log::info!( 233 - "Star threshold met: {} stars in the last {} hour(s) (threshold: {})", 234 count_in_window, 235 self.timeframe_hours, 236 self.star_threshold ··· 279 280 let handle_and_repo = format!("{handle}/{repo_name}"); 281 282 - let ctx = RepoPreview { 283 repo: handle_and_repo.clone(), 284 stars: stars as u64, 285 description: description.clone(), 286 }; 287 288 - let html = &self.hbs.render("repo_header.hbs", &ctx)?; 289 - 290 - let bytes = render_with_chromiumoxide( 291 - self.browser.as_ref(), 292 - &html, 293 - 336, 294 - 114, 295 - ) 296 - .await?; 297 298 let blob_upload = &self 299 .bot ··· 318 ), 319 aspect_ratio: Some( 320 atrium_api::app::bsky::embed::defs::AspectRatioData { 321 - height: NonZeroU64::try_from(114_u64)?, 322 - width: NonZeroU64::try_from(336_u64)?, 323 } 324 .into(), 325 ), ··· 364 tags: None, 365 text: post_text, 366 }; 367 - 368 match self.bot.agent.create_record(post).await { 369 - Ok(err) => { 370 log::info!("NEW POST MADE") 371 } 372 Err(err) => { 373 log::error!("{err}") 374 } 375 } 376 - log::info!( 377 - "Threshold met and allowed to be posted, stars: {stars}" 378 - ); 379 } 380 } 381 } ··· 394 Ok(()) 395 } 396 } 397 - 398 - #[derive(Serialize)] 399 - struct RepoPreview { 400 - repo: String, 401 - stars: u64, 402 - description: String, 403 - } 404 - 405 - async fn render_with_chromiumoxide( 406 - browser: &Browser, 407 - html: &str, 408 - width: u32, 409 - height: u32, 410 - ) -> Result<Vec<u8>, anyhow::Error> { 411 - // Use the long-running browser to render a page and capture a screenshot 412 - let page = browser.new_page("about:blank").await?; 413 - 414 - // Load the provided HTML as a data URL to avoid external fetches 415 - let data_url = format!("data:text/html;charset=utf-8,{}", urlencoding::encode(html)); 416 - page.goto(data_url).await?; 417 - 418 - // Wait for navigation to settle 419 - page.wait_for_navigation().await.ok(); 420 - 421 - // Screenshot: clip to desired viewport size regardless of page layout 422 - let params = CaptureScreenshotParams::builder() 423 - .format(CaptureScreenshotFormat::Png) 424 - .clip(Viewport { 425 - x: 0.0, 426 - y: 0.0, 427 - width: width as f64, 428 - height: height as f64, 429 - scale: 1.0, 430 - }) 431 - .from_surface(true) 432 - .build(); 433 - let png_bytes = page.screenshot(params).await?; 434 - 435 - // Close just the page; keep the browser alive 436 - page.close().await.ok(); 437 - 438 - Ok(png_bytes) 439 - }
··· 1 extern crate dotenv; 2 use crate::constellation::fetch_constellation_count; 3 use atrium_api::app::bsky::feed::post::RecordEmbedRefs; 4 use atrium_api::app::bsky::richtext::facet::{ByteSliceData, LinkData, MainFeaturesItem}; 5 use atrium_api::types::string::Language; 6 use atrium_api::types::{Collection, Union}; 7 use dotenv::dotenv; 8 use logic::BotApi; 9 use rocketman::{ 10 connection::JetstreamConnection, handler, ingestion::LexiconIngestor, 11 options::JetstreamOptions, types::event::Operation, 12 }; 13 use serde::Serialize; 14 use slingshot::Slingshot; 15 use sqlx::sqlite::{SqliteConnectOptions, SqlitePool}; ··· 18 use std::sync::{Arc, Mutex}; 19 use std::time::Duration; 20 21 + mod constellation; 22 23 #[derive(Debug)] 24 + struct ParsedRecord { 25 did: String, 26 collection: String, 27 rkey: String, 28 } 29 30 + struct StarIngestor { 31 + pool: SqlitePool, 32 + bot: Arc<BotApi>, 33 + sling_shot: Arc<Slingshot>, 34 + timeframe_hours: i64, 35 + star_threshold: i64, 36 + post_window_hours: i64, 37 + } 38 + 39 + #[derive(Serialize)] 40 + struct RepoPreview { 41 + repo: String, 42 + stars: u64, 43 + description: String, 44 + } 45 + 46 + fn parse_uri(uri: &str) -> anyhow::Result<ParsedRecord> { 47 let parts: Vec<&str> = uri.trim_start_matches("at://").split('/').collect(); 48 if parts.len() != 3 { 49 return Err(anyhow::anyhow!("Invalid URI format")); 50 } 51 + Ok(ParsedRecord { 52 did: parts[0].to_string(), 53 collection: parts[1].to_string(), 54 rkey: parts[2].to_string(), ··· 107 let bot_api = BotApi::new_logged_in(bot_username, bot_password, bot_pds_url).await?; 108 let sling_shot = Arc::new(Slingshot::new("https://slingshot.microcosm.blue")?); 109 110 // Ingestor for the star collection 111 let mut ingestors: HashMap<String, Box<dyn LexiconIngestor + Send + Sync>> = HashMap::new(); 112 ingestors.insert( ··· 115 pool: pool.clone(), 116 bot: Arc::new(bot_api), 117 sling_shot: sling_shot.clone(), 118 timeframe_hours, 119 star_threshold, 120 post_window_hours, ··· 148 .map_err(|e| anyhow::anyhow!(e.to_string())) 149 } 150 151 #[async_trait::async_trait] 152 impl LexiconIngestor for StarIngestor { 153 /// Asynchronously processes an incoming event message related to "stars" and performs database operations and other actions based on the event type. ··· 200 201 if count_in_window >= self.star_threshold { 202 log::info!( 203 + "Star threshold met: {} stars in the last {} hour(s) (threshold: {}), checking to see if it should be posted", 204 count_in_window, 205 self.timeframe_hours, 206 self.star_threshold ··· 249 250 let handle_and_repo = format!("{handle}/{repo_name}"); 251 252 + let _ctx = RepoPreview { 253 repo: handle_and_repo.clone(), 254 stars: stars as u64, 255 description: description.clone(), 256 }; 257 258 + // Fetch the pre-rendered image from the external service instead of rendering via Chromium 259 + let encoded_subject = urlencoding::encode(&repo_subject); 260 + let url = format!( 261 + "https://tangled-search.bigmoves.deno.net/repo-image/{}", 262 + encoded_subject 263 + ); 264 + let bytes = reqwest::get(url).await?.bytes().await?.to_vec(); 265 266 let blob_upload = &self 267 .bot ··· 286 ), 287 aspect_ratio: Some( 288 atrium_api::app::bsky::embed::defs::AspectRatioData { 289 + height: NonZeroU64::try_from(400_u64)?, 290 + width: NonZeroU64::try_from(800_u64)?, 291 } 292 .into(), 293 ), ··· 332 tags: None, 333 text: post_text, 334 }; 335 + log::info!( 336 + "Threshold met and allowed to be posted, total stars: {stars}" 337 + ); 338 match self.bot.agent.create_record(post).await { 339 + Ok(_) => { 340 log::info!("NEW POST MADE") 341 } 342 Err(err) => { 343 log::error!("{err}") 344 } 345 } 346 } 347 } 348 } ··· 361 Ok(()) 362 } 363 }
-45
bot/templates/repo_header.hbs
··· 1 - <html lang="en" class="dark:bg-gray-900"> 2 - <head> 3 - <link rel="preload" href="https://tangled.sh/static/fonts/InterVariable.woff2" as="font" type="font/woff2" 4 - crossorigin/> 5 - <link rel="stylesheet" href="https://tangled.sh/static/tw.css?6a194d26" type="text/css"/> 6 - <title>timeline · tangled</title> 7 - </head> 8 - <body> 9 - 10 - <div class="flex-none h-full border border-gray-200 dark:border-gray-700 rounded-sm w-96"> 11 - <div class="py-4 px-6 gap-1 flex flex-col drop-shadow-sm rounded bg-white dark:bg-gray-800 min-h-32"> 12 - <div class="font-medium dark:text-white flex items-center"> 13 - <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" 14 - stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" 15 - class="w-4 h-4 mr-1.5 shrink-0"> 16 - <path d="M10 2v8l3-3 3 3V2"></path> 17 - <path d="M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H19a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1H6.5a1 1 0 0 1 0-5H20"></path> 18 - </svg> 19 - <a href="/{{repo}}" class="truncate">{{repo}}</a></div> 20 - 21 - <div class="text-gray-600 dark:text-gray-300 text-sm line-clamp-2"> 22 - {{description}} 23 - 24 - </div> 25 - 26 - 27 - <div class="text-gray-400 text-sm font-mono inline-flex gap-4 mt-auto"> 28 - <div class="flex gap-1 items-center text-sm"> 29 - <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" 30 - stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" 31 - class="w-3 h-3 fill-current"> 32 - <path d="M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.123 2.123 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.123 2.123 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.122 2.122 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.122 2.122 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.122 2.122 0 0 0 1.597-1.16z"></path> 33 - </svg> 34 - 35 - <span>{{stars}}</span> 36 - </div> 37 - 38 - </div> 39 - 40 - </div> 41 - 42 - </div> 43 - 44 - </body> 45 - </html>
···
+2 -34
logic/src/lib.rs
··· 1 mod resolver; 2 use crate::resolver::ApiDNSTxtResolver; 3 - use atrium_api::agent::atp_agent::store::MemorySessionStore; 4 - use atrium_api::agent::atp_agent::{AtpSession, CredentialSession}; 5 - use atrium_api::agent::{Agent, Configure}; 6 - use atrium_api::did_doc::DidDocument; 7 - use atrium_api::types::string::{Datetime, Did, Handle}; 8 - use atrium_api::types::{Collection, LimitedNonZeroU8}; 9 use atrium_common::resolver::Resolver; 10 - use atrium_common::store::memory::MemoryStore; 11 use atrium_identity::did::{CommonDidResolver, CommonDidResolverConfig, DEFAULT_PLC_DIRECTORY_URL}; 12 use atrium_identity::handle::{AtprotoHandleResolver, AtprotoHandleResolverConfig}; 13 use atrium_oauth::DefaultHttpClient; 14 - use atrium_xrpc_client::reqwest::ReqwestClient; 15 use bsky_sdk::BskyAgent; 16 - use serde::{Deserialize, Serialize}; 17 use std::sync::Arc; 18 use thiserror::Error; 19 ··· 40 pub agent: Arc<BskyAgent>, 41 handle_resolver: Arc<AtprotoHandleResolver<ApiDNSTxtResolver, DefaultHttpClient>>, 42 did_resolver: Arc<CommonDidResolver<DefaultHttpClient>>, 43 - authenticated: bool, 44 - } 45 - 46 - fn get_new_session( 47 - pds_url: String, 48 - ) -> CredentialSession<MemoryStore<(), AtpSession>, ReqwestClient> { 49 - CredentialSession::new( 50 - ReqwestClient::new(pds_url.as_str()), 51 - MemorySessionStore::default(), 52 - ) 53 - } 54 - 55 - /// Parses the PDS url from the DidDocument 56 - fn get_pds_from_doc<'a>(doc: DidDocument) -> Result<String, Error> { 57 - Ok(doc 58 - .service 59 - .as_ref() 60 - .and_then(|services| { 61 - services 62 - .iter() 63 - .find(|service| service.r#type == "AtprotoPersonalDataServer") 64 - .map(|service| service.service_endpoint.clone()) 65 - }) 66 - .ok_or_else(|| Error::ParseError("No valid PDS URL found for this DID".to_string()))?) 67 } 68 69 impl BotApi { ··· 101 agent: Arc::new(agent), 102 handle_resolver: Arc::new(handle_resolver), 103 did_resolver: Arc::new(did_resolver), 104 - authenticated: true, 105 }) 106 } 107
··· 1 mod resolver; 2 use crate::resolver::ApiDNSTxtResolver; 3 + use atrium_api::agent::Configure; 4 + use atrium_api::types::string::{Did, Handle}; 5 use atrium_common::resolver::Resolver; 6 use atrium_identity::did::{CommonDidResolver, CommonDidResolverConfig, DEFAULT_PLC_DIRECTORY_URL}; 7 use atrium_identity::handle::{AtprotoHandleResolver, AtprotoHandleResolverConfig}; 8 use atrium_oauth::DefaultHttpClient; 9 use bsky_sdk::BskyAgent; 10 use std::sync::Arc; 11 use thiserror::Error; 12 ··· 33 pub agent: Arc<BskyAgent>, 34 handle_resolver: Arc<AtprotoHandleResolver<ApiDNSTxtResolver, DefaultHttpClient>>, 35 did_resolver: Arc<CommonDidResolver<DefaultHttpClient>>, 36 } 37 38 impl BotApi { ··· 70 agent: Arc::new(agent), 71 handle_resolver: Arc::new(handle_resolver), 72 did_resolver: Arc::new(did_resolver), 73 }) 74 } 75