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 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}