Write out individual files for each post
This commit is contained in:
parent
d66f330fd1
commit
f339cd76f2
4 changed files with 88 additions and 23 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -1,3 +1,3 @@
|
||||||
/target
|
/target
|
||||||
/archive
|
/archive
|
||||||
/archive.html
|
/public
|
||||||
|
|
105
src/main.rs
105
src/main.rs
|
@ -1,11 +1,11 @@
|
||||||
use jiff::tz::TimeZone;
|
use jiff::tz::TimeZone;
|
||||||
use jiff::Timestamp;
|
use jiff::Timestamp;
|
||||||
use pleroma::Activities;
|
use pleroma::Activities;
|
||||||
use serde::{Deserialize, Serialize};
|
use std::ffi::OsStr;
|
||||||
use std::sync::OnceLock;
|
use std::sync::OnceLock;
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
env, fmt,
|
env, fs,
|
||||||
fs::File,
|
fs::File,
|
||||||
io::{self, BufReader, BufWriter},
|
io::{self, BufReader, BufWriter},
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
|
@ -18,19 +18,25 @@ mod pleroma;
|
||||||
type BoxError = Box<dyn std::error::Error>;
|
type BoxError = Box<dyn std::error::Error>;
|
||||||
type Mappings = HashMap<String, Option<Url>>;
|
type Mappings = HashMap<String, Option<Url>>;
|
||||||
|
|
||||||
|
const STYLE: &str = include_str!("../style.css");
|
||||||
|
|
||||||
static TZ: OnceLock<TimeZone> = OnceLock::new();
|
static TZ: OnceLock<TimeZone> = OnceLock::new();
|
||||||
static MAPPINGS: OnceLock<Mappings> = OnceLock::new();
|
static MAPPINGS: OnceLock<Mappings> = OnceLock::new();
|
||||||
|
|
||||||
fn main() -> ExitCode {
|
fn main() -> ExitCode {
|
||||||
let Some(path) = env::args_os().skip(1).next().map(PathBuf::from) else {
|
let args = env::args_os().skip(1).collect::<Vec<_>>();
|
||||||
eprintln!("Usage: pleroma-archive path/to/pleroma-archive");
|
let (archive_path, output_path) = match args.as_slice() {
|
||||||
return ExitCode::FAILURE;
|
[archive, output] => (Path::new(archive), Path::new(output)),
|
||||||
|
_ => {
|
||||||
|
eprintln!("Usage: pleroma-archive path/to/pleroma-archive output/path");
|
||||||
|
return ExitCode::FAILURE;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let timezone = TimeZone::system();
|
let timezone = TimeZone::system();
|
||||||
TZ.set(timezone).unwrap();
|
TZ.set(timezone).unwrap();
|
||||||
|
|
||||||
match try_main(&path) {
|
match try_main(archive_path, output_path) {
|
||||||
Ok(()) => ExitCode::SUCCESS,
|
Ok(()) => ExitCode::SUCCESS,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
eprintln!("Error: {err}");
|
eprintln!("Error: {err}");
|
||||||
|
@ -39,7 +45,7 @@ fn main() -> ExitCode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn try_main(path: &Path) -> Result<(), BoxError> {
|
fn try_main(path: &Path, output_path: &Path) -> Result<(), BoxError> {
|
||||||
let actor_path = path.join("actor.json");
|
let actor_path = path.join("actor.json");
|
||||||
let outbox_path = path.join("outbox.json");
|
let outbox_path = path.join("outbox.json");
|
||||||
let mappings_path = path.join("mappings.json");
|
let mappings_path = path.join("mappings.json");
|
||||||
|
@ -57,8 +63,14 @@ fn try_main(path: &Path) -> Result<(), BoxError> {
|
||||||
Err(err) => return Err(err.into()),
|
Err(err) => return Err(err.into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Ensure output path exists, and write out stylesheet
|
||||||
|
fs::create_dir_all(output_path)?;
|
||||||
|
let style_path = output_path.join("style.css");
|
||||||
|
fs::write(&style_path, STYLE)?;
|
||||||
|
|
||||||
let agent = ureq::AgentBuilder::new().redirects(0).build();
|
let agent = ureq::AgentBuilder::new().redirects(0).build();
|
||||||
|
|
||||||
|
// Process posts
|
||||||
let mut posts = Vec::with_capacity(activities.ordered_items.len());
|
let mut posts = Vec::with_capacity(activities.ordered_items.len());
|
||||||
for item in &activities.ordered_items {
|
for item in &activities.ordered_items {
|
||||||
if item.direct_message {
|
if item.direct_message {
|
||||||
|
@ -109,42 +121,91 @@ fn try_main(path: &Path) -> Result<(), BoxError> {
|
||||||
|
|
||||||
MAPPINGS.set(mappings).unwrap();
|
MAPPINGS.set(mappings).unwrap();
|
||||||
|
|
||||||
// dbg!(&posts);
|
// Generate index.html
|
||||||
|
let index_html = Layout {
|
||||||
println!(
|
title: "Pleroma Archive",
|
||||||
"{}",
|
body: Index {
|
||||||
Page {
|
|
||||||
title: "Pleroma Archive",
|
|
||||||
actor: &actor,
|
actor: &actor,
|
||||||
activities: &posts,
|
activities: &posts,
|
||||||
|
},
|
||||||
|
actor: &actor,
|
||||||
|
}
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
let index_path = output_path.join("index.html");
|
||||||
|
println!("Writing {}", index_path.display());
|
||||||
|
fs::write(&index_path, index_html.as_bytes())?;
|
||||||
|
|
||||||
|
// Generate individual post pages
|
||||||
|
for post in posts {
|
||||||
|
let Some(Some(url)) = MAPPINGS.get().unwrap().get(&post.id) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let mut post_path = output_path
|
||||||
|
.iter()
|
||||||
|
.chain(
|
||||||
|
url.path_segments()
|
||||||
|
.ok_or_else(|| BoxError::from("unable to get path segments of {url}"))?
|
||||||
|
.map(OsStr::new),
|
||||||
|
)
|
||||||
|
.collect::<PathBuf>();
|
||||||
|
post_path.set_extension("html");
|
||||||
|
|
||||||
|
let post_html = Layout {
|
||||||
|
title: &format!(
|
||||||
|
"Post from {} on {}",
|
||||||
|
actor.preferred_username,
|
||||||
|
post.human_published()
|
||||||
|
),
|
||||||
|
body: Show {
|
||||||
|
actor: &actor,
|
||||||
|
activity: post,
|
||||||
|
},
|
||||||
|
actor: &actor,
|
||||||
}
|
}
|
||||||
);
|
.to_string();
|
||||||
|
|
||||||
|
println!("Writing {}", post_path.display());
|
||||||
|
fs::create_dir_all(&post_path.parent().expect("post has parent dir"))?;
|
||||||
|
fs::write(&post_path, post_html.as_bytes())?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
markup::define! {
|
markup::define! {
|
||||||
Page<'a>(title: &'a str, actor: &'a pleroma::Actor, activities: &'a [&'a pleroma::Activity]) {
|
Layout<'a, Body: markup::Render>(title: &'a str, body: Body, actor: &'a pleroma::Actor) {
|
||||||
@markup::doctype()
|
@markup::doctype()
|
||||||
html {
|
html {
|
||||||
head {
|
head {
|
||||||
meta[charset="utf-8"];
|
meta[charset="utf-8"];
|
||||||
meta[name="viewport", content="width=device-width, initial-scale=1"];
|
meta[name="viewport", content="width=device-width, initial-scale=1"];
|
||||||
title { @title }
|
title { @title }
|
||||||
link[rel="stylesheet", type="text/css", href="style.css"];
|
link[rel="stylesheet", type="text/css", href="/style.css"];
|
||||||
}
|
}
|
||||||
body {
|
body {
|
||||||
@Header { title, actor }
|
@Header { title, actor }
|
||||||
main {
|
@body
|
||||||
@for activity in activities.iter().rev() {
|
|
||||||
@Activity { actor, activity }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@Footer { }
|
@Footer { }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Index<'a>(actor: &'a pleroma::Actor, activities: &'a [&'a pleroma::Activity]) {
|
||||||
|
main {
|
||||||
|
p { @activities.len() " posts:" }
|
||||||
|
@for activity in activities.iter().rev() {
|
||||||
|
@Activity { actor, activity }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Show<'a>(actor: &'a pleroma::Actor, activity: &'a pleroma::Activity) {
|
||||||
|
a[href="/"] { "☜ Back to home page" }
|
||||||
|
|
||||||
|
@Activity { actor, activity }
|
||||||
|
}
|
||||||
|
|
||||||
Header<'a>(title: &'a str, actor: &'a pleroma::Actor) {
|
Header<'a>(title: &'a str, actor: &'a pleroma::Actor) {
|
||||||
header {
|
header {
|
||||||
h1 { @title }
|
h1 { @title }
|
||||||
|
@ -165,7 +226,7 @@ markup::define! {
|
||||||
@Actor { actor }
|
@Actor { actor }
|
||||||
|
|
||||||
div[class="activity-content"] {
|
div[class="activity-content"] {
|
||||||
a[href=MAPPINGS.get().unwrap().get(&activity.id).and_then(|url| url.as_ref().map(|url| url.as_str()))] {
|
a[href=MAPPINGS.get().unwrap().get(&activity.id).and_then(|url| url.as_ref().map(|url| url.path()))] {
|
||||||
time[datetime=&activity.published] { @activity.human_published() }
|
time[datetime=&activity.published] { @activity.human_published() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
#![allow(unused)]
|
||||||
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
#![allow(unused)]
|
||||||
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
|
|
Loading…
Reference in a new issue