+57
-71
bot/src/main.rs
+57
-71
bot/src/main.rs
···
111
111
let mut hbs = Handlebars::new();
112
112
let _ = hbs.register_embed_templates::<Templates>();
113
113
114
-
// let cfg = BrowserConfig::builder()
115
-
// .headless_mode(HeadlessMode::New)
116
-
// .no_sandbox()
117
-
// .build()
118
-
// .map_err(|e| anyhow::anyhow!(e))
119
-
// .expect("build browser config");
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");
120
121
121
-
// let handle = tokio::spawn(async move { while let Some(_) = browser_handler.next().await {} });
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);
122
126
123
127
// Ingestor for the star collection
124
128
let mut ingestors: HashMap<String, Box<dyn LexiconIngestor + Send + Sync>> = HashMap::new();
···
128
132
pool: pool.clone(),
129
133
bot: Arc::new(bot_api),
130
134
sling_shot: sling_shot.clone(),
131
-
//Come back to this
132
-
// browser: Arc::new(browser),
135
+
browser: browser.clone(),
133
136
hbs: Arc::new(hbs),
134
137
timeframe_hours,
135
138
star_threshold,
···
168
171
pool: SqlitePool,
169
172
bot: Arc<BotApi>,
170
173
sling_shot: Arc<Slingshot>,
171
-
// browser: Arc<Browser>,
174
+
browser: Arc<Browser>,
172
175
hbs: Arc<Handlebars<'static>>,
173
176
timeframe_hours: i64,
174
177
star_threshold: i64,
···
284
287
285
288
let html = &self.hbs.render("repo_header.hbs", &ctx)?;
286
289
287
-
// let bytes = render_with_chromiumoxide(&html, 336, 114).await?;
290
+
let bytes = render_with_chromiumoxide(
291
+
self.browser.as_ref(),
292
+
&html,
293
+
336,
294
+
114,
295
+
)
296
+
.await?;
288
297
289
-
// let blob_upload = &self
290
-
// .bot
291
-
// .agent
292
-
// .api
293
-
// .com
294
-
// .atproto
295
-
// .repo
296
-
// .upload_blob(bytes)
297
-
// .await?;
298
+
let blob_upload = &self
299
+
.bot
300
+
.agent
301
+
.api
302
+
.com
303
+
.atproto
304
+
.repo
305
+
.upload_blob(bytes)
306
+
.await?;
298
307
299
308
// let rt = RichText::new_with_detect_facets(format!(
300
309
// "{handle_and_repo}{description}\n⭐️ {stars} {tangled_sh_url}"
···
303
312
let post_text =
304
313
format!("{handle_and_repo}{description}\n⭐️ {stars}");
305
314
306
-
// let image = atrium_api::app::bsky::embed::images::ImageData {
307
-
// alt: format!(
308
-
// "An image showing the same text inside of the post. {post_text}"
309
-
// ),
310
-
// aspect_ratio: Some(
311
-
// atrium_api::app::bsky::embed::defs::AspectRatioData {
312
-
// height: NonZeroU64::try_from(114_u64)?,
313
-
// width: NonZeroU64::try_from(336_u64)?,
314
-
// }
315
-
// .into(),
316
-
// ),
317
-
// //Good lord how many clones is that
318
-
// image: blob_upload.clone().blob.clone(),
319
-
// };
320
-
// let embed = Some(atrium_api::types::Union::Refs(
321
-
// RecordEmbedRefs::AppBskyEmbedImagesMain(Box::new(
322
-
// atrium_api::app::bsky::embed::images::MainData {
323
-
// images: vec![image.into()],
324
-
// }
325
-
// .into(),
326
-
// )),
327
-
// ));
315
+
let image = atrium_api::app::bsky::embed::images::ImageData {
316
+
alt: format!(
317
+
"An image showing the same text inside of the post. {post_text}"
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
+
),
326
+
//Good lord how many clones is that
327
+
image: blob_upload.clone().blob.clone(),
328
+
};
329
+
let embed = Some(atrium_api::types::Union::Refs(
330
+
RecordEmbedRefs::AppBskyEmbedImagesMain(Box::new(
331
+
atrium_api::app::bsky::embed::images::MainData {
332
+
images: vec![image.into()],
333
+
}
334
+
.into(),
335
+
)),
336
+
));
328
337
329
338
let post = atrium_api::app::bsky::feed::post::RecordData {
330
339
created_at: atrium_api::types::string::Datetime::now(),
331
-
embed: None,
340
+
embed,
332
341
entities: None,
333
342
facets: Some(vec![
334
343
atrium_api::app::bsky::richtext::facet::MainData {
···
394
403
}
395
404
396
405
async fn render_with_chromiumoxide(
406
+
browser: &Browser,
397
407
html: &str,
398
408
width: u32,
399
409
height: u32,
400
410
) -> Result<Vec<u8>, anyhow::Error> {
401
-
// Configure and launch the browser
402
-
403
-
let cfg = BrowserConfig::builder()
404
-
.headless_mode(HeadlessMode::New)
405
-
.no_sandbox()
406
-
.build()
407
-
.map_err(|e| anyhow::anyhow!(e))
408
-
.expect("build browser config");
409
-
410
-
let (mut browser, mut browser_handler) = Browser::launch(cfg).await.expect("launch browser");
411
-
412
-
let handle = tokio::task::spawn(async move {
413
-
loop {
414
-
match browser_handler.next().await {
415
-
Some(h) => match h {
416
-
Ok(_) => continue,
417
-
Err(_) => break,
418
-
},
419
-
None => break,
420
-
}
421
-
}
422
-
});
423
-
411
+
// Use the long-running browser to render a page and capture a screenshot
424
412
let page = browser.new_page("about:blank").await?;
425
413
426
414
// Load the provided HTML as a data URL to avoid external fetches
427
415
let data_url = format!("data:text/html;charset=utf-8,{}", urlencoding::encode(html));
428
416
page.goto(data_url).await?;
429
417
430
-
// Wait for network idle-ish
418
+
// Wait for navigation to settle
431
419
page.wait_for_navigation().await.ok();
432
420
433
421
// Screenshot: clip to desired viewport size regardless of page layout
···
444
432
.build();
445
433
let png_bytes = page.screenshot(params).await?;
446
434
447
-
// Close
435
+
// Close just the page; keep the browser alive
448
436
page.close().await.ok();
449
-
browser.close().await.ok();
450
-
// stop handler task
451
-
handle.await?;
437
+
452
438
Ok(png_bytes)
453
439
}