upub_cli/
import.rs

1use apb::{Activity, ActivityMut, Base, BaseMut, Collection, CollectionMut, Document, DocumentMut, Object, ObjectMut};
2use sea_orm::TransactionTrait;
3
4
5pub async fn import(
6	ctx: upub::Context,
7	file: std::path::PathBuf,
8	from: String,
9	to: String,
10	attachment_base: Option<String>,
11) -> Result<(), Box<dyn std::error::Error>> {
12	// TODO worth including tokio/fs to do this async? it's a CLI task anyway
13	let raw_content = std::fs::read_to_string(file)?;
14	let objects : Vec<serde_json::Value> = serde_json::from_str(&raw_content)?;
15
16	let tx = ctx.db().begin().await?;
17
18	for mut obj in objects {
19		if let Some(data) = obj.get_mut("data") {
20			obj = data.take();
21		}
22
23		let Ok(oid) = obj.id() else {
24			tracing::warn!("skipping object without id : {obj}");
25			continue;
26		};
27
28		let attributed_to = match obj.attributed_to().id() {
29			Ok(id) => id,
30			Err(_) => match obj.actor().id() {
31				Ok(id) => id,
32				Err(_) => {
33					tracing::warn!("skipping object without author: {obj}");
34					continue;
35				},
36			},
37		};
38
39		if attributed_to != from {
40			tracing::warn!("skipping object not belonging to requested user: {obj}");
41			continue;
42		}
43
44		let normalized_attachments = match attachment_base {
45			Some(ref attachment_base) => {
46				let mut out = Vec::new();
47				for attachment in obj.attachment().flat() {
48					let Ok(doc) = attachment.inner() else {
49						tracing::warn!("skipping non embedded attachment: {attachment:?}");
50						continue;
51					};
52					out.push(
53						apb::new()
54							.set_document_type(doc.document_type().ok())
55							.set_name(doc.name().ok())
56							.set_media_type(doc.media_type().ok())
57							.set_url(apb::Node::link(
58								format!("{attachment_base}/{}", doc.url().id().unwrap_or_default().split('/').next_back().unwrap_or_default())
59							))
60					);
61				}
62				apb::Node::array(out)
63			},
64			None => obj.attachment()
65		};
66
67		let normalized_summary = obj.summary()
68			.ok()
69			.filter(|x| !x.is_empty());
70
71		let announces_count = match obj.get("announcement_count") {
72			Some(v) => v.as_u64().unwrap_or_default(),
73			None => obj.shares().inner().map_or(0, |x| x.total_items().unwrap_or(0)),
74		};
75
76		let replies_count = match obj.get("repliesCount") {
77			Some(v) => v.as_u64().unwrap_or_default(),
78			None => obj.replies().inner().map_or(0, |x| x.total_items().unwrap_or(0)),
79		};
80
81		let likes_count = match obj.get("like_count") {
82			Some(v) => v.as_u64().unwrap_or_default(),
83			None => obj.likes().inner().map_or(0, |x| x.total_items().unwrap_or(0)),
84		};
85
86		let normalized_object = obj
87			.set_id(Some(ctx.oid(&upub::Context::new_id())))
88			.set_attributed_to(apb::Node::link(to.clone()))
89			.set_summary(normalized_summary)
90			.set_shares(apb::Node::object(
91				apb::new()
92					.set_total_items(Some(announces_count))
93			))
94			.set_likes(apb::Node::object(
95				apb::new()
96					.set_total_items(Some(likes_count))
97			))
98			.set_replies(apb::Node::object(
99				apb::new()
100						.set_total_items(Some(replies_count))
101			))
102			.set_attachment(normalized_attachments);
103
104		let activity = apb::new()
105			.set_id(Some(ctx.aid(&upub::Context::new_id())))
106			.set_activity_type(Some(apb::ActivityType::Create))
107			.set_actor(apb::Node::link(to.clone()))
108			.set_published(normalized_object.published().ok())
109			.set_object(apb::Node::object(normalized_object));
110
111		if let Err(e) = upub::traits::process::process_create(&ctx, activity, &tx).await {
112			tracing::error!("could not insert object {oid}: {e} ({e:?})");
113		}
114	}
115
116	tx.commit().await?;
117
118	Ok(())
119}