arhivach-downloader

Download arhivach.vc threads
git clone https://git.ea.contact/arhivach-downloader
Log | Files | Refs | README

commit f943d06302bad9359d690b6bc984ac3f777d6345
parent 749dd8a7032b747281c3abb7e6cb07e1262abe08
Author: egor-achkasov <eaachkasov@gmail.com>
Date:   Mon, 23 Feb 2026 16:48:21 +0000

Add top level index.html

Diffstat:
Msrc/export.rs | 35++++++++++++++++++++++++++++++++++-
Msrc/main.rs | 21++++++++++++++++-----
2 files changed, 50 insertions(+), 6 deletions(-)

diff --git a/src/export.rs b/src/export.rs @@ -2,6 +2,8 @@ use crate::{parse_args::Config, post::Post}; use anyhow::{Result, Context}; +const TEMPLATE: &'static str = include_str!("../template.html"); + fn html_escape(s: &str) -> String { s.replace('&', "&amp;") .replace('<', "&lt;") @@ -36,6 +38,38 @@ fn render_text_to_html(text: &str) -> String { lines.join("<br>\n") } +/// Write a top-level index.html with one entry per thread (first post + link to thread folder) +pub fn write_index_html(first_posts: &[Post], config: &Config) -> Result<()> { + if first_posts.is_empty() { + return Ok(()); + } + + let posts_html: String = first_posts + .iter() + .map(|p| { + let mut post_html = render_post(p, config.files, config.thumb); + // render_post references thumbnails and images in the same directory, + // so replace them with links to the thread folder + config.files.then(|| post_html = post_html.replace( + "<a href=\"files/", + &format!("<a href=\"{}/files/", p.id), + )); + config.thumb.then(|| post_html = post_html.replace( + "<img src=\"thumb/", + &format!("<img src=\"{}/thumb/", p.id), + )); + format!("<div><a href=\"{}/index.html\">В тред &rarr;</a></div>{}\n", p.id, post_html) + }) + .collect::<Vec<String>>() + .join("\n"); + + let index_html = TEMPLATE.replace("{{posts}}", &posts_html); + std::fs::write("index.html", index_html) + .context("failed to write index.html")?; + + Ok(()) +} + /// Export the thread to a simple static HTML /// /// Creates a directory as follows: @@ -77,7 +111,6 @@ pub fn export2html(posts: &[Post], config: &Config) -> Result<()> { )?; } - const TEMPLATE: &'static str = include_str!("../template.html"); let index_html = TEMPLATE.replace("{{posts}}", &posts_html); std::fs::write(format!("{}/index.html", dir), index_html)?; diff --git a/src/main.rs b/src/main.rs @@ -7,8 +7,9 @@ use parse_args::{Config, parse_args}; use post::Post; use anyhow::{Context, Ok, Result}; +use std::result::Result::Ok as StdOk; -fn scrape_thread(url: &str, config: &Config) -> Result<()> { +fn scrape_thread(url: &str, config: &Config) -> Result<Post> { use std::io::Write; let t_total = std::time::Instant::now(); @@ -28,11 +29,13 @@ fn scrape_thread(url: &str, config: &Config) -> Result<()> { .context("failed to parse thread HTML")?; println!(" Done ({} ms)", t.elapsed().as_millis()); + let first_post = posts.first().context("thread has no posts")?.clone(); + export::export2html(&posts, &config) .context("failed to export thread")?; println!("Done processing {} ({} ms)", url, t_total.elapsed().as_millis()); - Ok(()) + Ok(first_post) } @@ -43,11 +46,19 @@ fn main() -> Result<()> { std::process::exit(1); }); + let mut first_posts: Vec<Post> = Vec::new(); + let mut i = 1; for url in &config.urls { - println!("Processing {}:", url); - scrape_thread(url, &config) - .unwrap_or_else(|e| eprintln!("Error processing {}: {:#}", url, e)); + println!("Processing {} ({} / {}):", url, i, config.urls.len()); + i += 1; + match scrape_thread(url, &config) { + StdOk(first_post) => first_posts.push(first_post), + Err(e) => eprintln!("Error processing {}: {:#}", url, e), + } } + export::write_index_html(&first_posts, &config) + .context("failed to write main index.html")?; + Ok(()) }