From 2ded7772c3d435ce3cf13e23ac2374f474e63aa2 Mon Sep 17 00:00:00 2001 From: Wesley Moore Date: Sun, 24 Nov 2024 13:37:18 +1000 Subject: [PATCH] Initial commit --- .gitignore | 3 + Cargo.lock | 682 ++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 12 + src/main.rs | 202 ++++++++++++ src/pleroma.rs | 5 + src/pleroma/activity.rs | 146 +++++++++ src/pleroma/actor.rs | 85 +++++ style.css | 47 +++ 8 files changed, 1182 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/main.rs create mode 100644 src/pleroma.rs create mode 100644 src/pleroma/activity.rs create mode 100644 src/pleroma/actor.rs create mode 100644 style.css diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a176ca3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/target +/archive +/archive.html diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..2535d94 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,682 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "cc" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd9de9f2205d5ef3fd67e685b0df337994ddd4495e2a28d185500d0e1edfea47" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "itoa" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "540654e97a3f4470a492cd30ff187bc95d89557a903a2bbf112e2fae98104ef2" + +[[package]] +name = "jiff" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d9d414fc817d3e3d62b2598616733f76c4cc74fbac96069674739b881295c8" +dependencies = [ + "jiff-tzdb-platform", + "windows-sys", +] + +[[package]] +name = "jiff-tzdb" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91335e575850c5c4c673b9bd467b0e025f164ca59d0564f69d0c2ee0ffad4653" + +[[package]] +name = "jiff-tzdb-platform" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9835f0060a626fe59f160437bc725491a6af23133ea906500027d1bd2f8f4329" +dependencies = [ + "jiff-tzdb", +] + +[[package]] +name = "libc" +version = "0.2.164" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f" + +[[package]] +name = "litemap" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "markup" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74a887ad620fe1022257343ac77fcdd3720e92888e1b2e66e1b7a4707f453898" +dependencies = [ + "markup-proc-macro", +] + +[[package]] +name = "markup-proc-macro" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ab6ee21fd1855134cacf2f41afdf45f1bc456c7d7f6165d763b4647062dd2be" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pleroma-archive" +version = "0.1.0" +dependencies = [ + "jiff", + "markup", + "serde", + "serde_json", + "ureq", + "url", +] + +[[package]] +name = "proc-macro2" +version = "1.0.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom", + "libc", + "spin", + "untrusted", + "windows-sys", +] + +[[package]] +name = "rustls" +version = "0.23.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9cc1d47e243d655ace55ed38201c19ae02c148ae56412ab8750e8f0166ab7f" +dependencies = [ + "log", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pki-types" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b" + +[[package]] +name = "rustls-webpki" +version = "0.102.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "serde" +version = "1.0.215" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.215" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.133" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "unicode-ident" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "ureq" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b74fc6b57825be3373f7054754755f03ac3a8f5d70015ccad699ba2029956f4a" +dependencies = [ + "base64", + "log", + "once_cell", + "rustls", + "rustls-pki-types", + "url", + "webpki-roots", +] + +[[package]] +name = "url" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "webpki-roots" +version = "0.26.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d642ff16b7e79272ae451b7322067cdc17cadf68c23264be9d94a32319efe7e" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + +[[package]] +name = "yoke" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerofrom" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..3570b44 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "pleroma-archive" +version = "0.1.0" +edition = "2021" + +[dependencies] +jiff = "0.1.14" +markup = "0.15.0" +serde = { version = "1.0.215", features = ["derive"] } +serde_json = "1.0.133" +ureq = { version = "2.10.1", default-features = false, features = ["tls"] } +url = { version = "2.5.4", features = ["serde"] } diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..1ee05f1 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,202 @@ +use jiff::tz::TimeZone; +use jiff::Timestamp; +use pleroma::Activities; +use serde::{Deserialize, Serialize}; +use std::sync::OnceLock; +use std::{ + collections::HashMap, + env, fmt, + fs::File, + io::{self, BufReader, BufWriter}, + path::{Path, PathBuf}, + process::ExitCode, +}; +use url::Url; + +mod pleroma; + +type BoxError = Box; +type Mappings = HashMap>; + +static TZ: OnceLock = OnceLock::new(); +static MAPPINGS: OnceLock = OnceLock::new(); + +fn main() -> ExitCode { + let Some(path) = env::args_os().skip(1).next().map(PathBuf::from) else { + eprintln!("Usage: pleroma-archive path/to/pleroma-archive"); + return ExitCode::FAILURE; + }; + + let timezone = TimeZone::system(); + TZ.set(timezone).unwrap(); + + match try_main(&path) { + Ok(()) => ExitCode::SUCCESS, + Err(err) => { + eprintln!("Error: {err}"); + ExitCode::FAILURE + } + } +} + +fn try_main(path: &Path) -> Result<(), BoxError> { + let actor_path = path.join("actor.json"); + let outbox_path = path.join("outbox.json"); + let mappings_path = path.join("mappings.json"); + + let file = BufReader::new(File::open(&actor_path)?); + let actor: pleroma::Actor = serde_json::from_reader(file)?; + + let file = BufReader::new(File::open(&outbox_path)?); + let activities: Activities = serde_json::from_reader(file)?; + + // Load mappings of ids to public URLs + let mut mappings: Mappings = match File::open(&mappings_path) { + Ok(file) => serde_json::from_reader(BufReader::new(file))?, + Err(err) if err.kind() == io::ErrorKind::NotFound => HashMap::new(), + Err(err) => return Err(err.into()), + }; + + let agent = ureq::AgentBuilder::new().redirects(0).build(); + + let mut posts = Vec::with_capacity(activities.ordered_items.len()); + for item in &activities.ordered_items { + match &item.object { + pleroma::activity::ObjectUnion::ObjectClass(activity) => { + let id: Url = item.id.parse()?; + if !mappings.contains_key(id.as_str()) { + let response = match agent.head(id.as_str()).call() { + Ok(res) => res, + Err(ureq::Error::Status(status, _res)) => { + eprintln!("expected 3xx response, got {} for {}", status, id); + mappings.insert(item.id.clone(), None); + continue; + } + Err(err) => return Err(err.into()), + }; + if !(300..400).contains(&response.status()) { + eprintln!( + "expected 3xx response, got {} for {}", + response.status(), + id + ); + mappings.insert(item.id.clone(), None); + continue; + } + let Some(location) = response.header("location") else { + return Err("expected a Location header, but it's missing".into()); + }; + + let url = id.join(location)?; + mappings.insert(item.id.clone(), Some(url)); + } + + posts.push(activity); + } + pleroma::activity::ObjectUnion::String(s) => { + eprintln!("ObjectUnion::String: {s}"); + // TODO + } + } + } + + let mappings_writer = BufWriter::new(File::create(&mappings_path)?); + serde_json::to_writer_pretty(mappings_writer, &mappings)?; + + MAPPINGS.set(mappings).unwrap(); + + // dbg!(&posts); + + println!( + "{}", + Page { + title: "Pleroma Archive", + actor: &actor, + activities: &posts, + } + ); + + Ok(()) +} + +markup::define! { + Page<'a>(title: &'a str, actor: &'a pleroma::Actor, activities: &'a [&'a pleroma::Activity]) { + @markup::doctype() + html { + head { + meta[charset="utf-8"]; + meta[name="viewport", content="width=device-width, initial-scale=1"]; + title { @title } + link[rel="stylesheet", type="text/css", href="style.css"]; + } + body { + @Header { title } + main { + @for activity in *activities { + @Activity { actor, activity } + } + } + @Footer { } + } + } + } + + Header<'a>(title: &'a str) { + header { + h1 { @title } + } + } + + Footer() { + footer { + "Generated by " + a[href="https://forge.wezm.net/wezm/pleroma-archive"] { "pleroma-archive" } + } + } + + Activity<'a>(actor: &'a pleroma::Actor, activity: &'a pleroma::Activity) { + div[class=activity_class(&activity.object_type)] { + @Actor { actor } + + div[class="activity-content"] { + a[href=MAPPINGS.get().unwrap().get(&activity.id).and_then(|url| url.as_ref().map(|url| url.as_str()))] { + time[datetime=&activity.published] { @activity.human_published() } + } + + @if let Some(in_reply_to) = &activity.in_reply_to { + a[href=in_reply_to, class="activity-reply-to"] { "↩ reply to" } + " " + } + @markup::raw(&activity.content) + } + } + hr; + } + + Actor<'a>(actor: &'a pleroma::Actor) { + @if let Some(icon) = &actor.icon { + @if icon.icon_type == "Image" { + img[src=&icon.url, alt=&actor.preferred_username, class="actor-icon"]; + } + } + } +} + +fn activity_class(object_type: &pleroma::activity::OneOfType) -> &'static str { + match object_type { + pleroma::activity::OneOfType::Note => "activity activity-note", + pleroma::activity::OneOfType::Question => "activity activity-question", + } +} + +impl pleroma::Activity { + fn human_published(&self) -> String { + let published = self + .published + .parse() + .map(|timestamp: Timestamp| timestamp.to_zoned(TZ.get().unwrap().clone())) + .expect("invalid published value"); + + published.strftime("%d %b %Y").to_string() + } +} diff --git a/src/pleroma.rs b/src/pleroma.rs new file mode 100644 index 0000000..e0180cb --- /dev/null +++ b/src/pleroma.rs @@ -0,0 +1,5 @@ +pub mod activity; +pub mod actor; + +pub use activity::{Activities, Activity}; +pub use actor::Actor; diff --git a/src/pleroma/activity.rs b/src/pleroma/activity.rs new file mode 100644 index 0000000..58f5a65 --- /dev/null +++ b/src/pleroma/activity.rs @@ -0,0 +1,146 @@ +use serde::Deserialize; + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Activities { + #[serde(rename = "@context")] + pub(crate) context: String, + pub(crate) id: String, + #[serde(rename = "type")] + pub(crate) activities_type: String, + pub(crate) ordered_items: Vec, + pub(crate) total_items: i64, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct OrderedItem { + pub(crate) actor: String, + pub(crate) cc: Vec, + pub(crate) context: String, + pub(crate) context_id: Option, + #[serde(default, rename = "directMessage")] + pub(crate) direct_message: bool, + pub(crate) id: String, + pub(crate) object: ObjectUnion, + pub(crate) published: String, + pub(crate) to: Vec, + #[serde(rename = "type")] + pub(crate) ordered_item_type: OrderedItemType, +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(untagged)] +pub enum ObjectUnion { + ObjectClass(Activity), + String(String), +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Activity { + pub(crate) actor: String, + pub(crate) attachment: Vec, + pub(crate) attributed_to: String, + pub(crate) cc: Vec, + pub(crate) content: String, + pub(crate) context: String, + pub(crate) conversation: String, + pub(crate) id: String, + pub(crate) in_reply_to: Option, + pub(crate) in_reply_to_status_id: Option, + pub(crate) published: String, + pub(crate) summary: Option, + pub(crate) tag: Vec, + pub(crate) to: Vec, + #[serde(rename = "type")] + pub(crate) object_type: OneOfType, + pub(crate) sensitive: Option, + pub(crate) replies_count: Option, + pub(crate) replies: Option, + pub(crate) closed: Option, + pub(crate) one_of: Option>, + pub(crate) source: Option, +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Attachment { + pub(crate) media_type: String, + pub(crate) name: String, + #[serde(rename = "type")] + pub(crate) attachment_type: AttachmentType, + pub(crate) url: String, +} + +#[derive(Debug, Clone, Deserialize)] +pub enum AttachmentType { + Document, +} + +#[derive(Debug, Clone, Deserialize)] +pub enum OneOfType { + Note, + Question, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct OneOf { + pub(crate) name: String, + pub(crate) replies: OneOfReplies, + #[serde(rename = "type")] + pub(crate) one_of_type: OneOfType, +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct OneOfReplies { + pub(crate) total_items: i64, + #[serde(rename = "type")] + pub(crate) replies_type: RepliesType, +} + +#[derive(Debug, Clone, Deserialize)] +pub enum RepliesType { + Collection, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct ObjectReplies { + pub(crate) items: Vec, + #[serde(rename = "type")] + pub(crate) replies_type: RepliesType, +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(untagged)] +pub enum SourceUnion { + SourceClass(SourceClass), + String(String), +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SourceClass { + pub(crate) content: String, + pub(crate) media_type: String, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct Tag { + pub(crate) href: String, + pub(crate) name: String, + #[serde(rename = "type")] + pub(crate) tag_type: TagType, +} + +#[derive(Debug, Clone, Deserialize)] +pub enum TagType { + Hashtag, + Mention, +} + +#[derive(Debug, Clone, Deserialize)] +pub enum OrderedItemType { + Announce, + Create, +} diff --git a/src/pleroma/actor.rs b/src/pleroma/actor.rs new file mode 100644 index 0000000..855f41f --- /dev/null +++ b/src/pleroma/actor.rs @@ -0,0 +1,85 @@ +use serde::Deserialize; + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Actor { + #[serde(rename = "@context")] + pub(crate) context: Vec, + pub(crate) also_known_as: Vec>, + pub(crate) attachment: Vec, + pub(crate) capabilities: Capabilities, + pub(crate) discoverable: bool, + pub(crate) endpoints: Endpoints, + pub(crate) featured: String, + pub(crate) followers: String, + pub(crate) following: String, + pub(crate) icon: Option, + pub(crate) id: String, + pub(crate) inbox: String, + pub(crate) manually_approves_followers: bool, + pub(crate) name: String, + pub(crate) outbox: String, + pub(crate) preferred_username: String, + pub(crate) public_key: PublicKey, + pub(crate) summary: String, + pub(crate) tag: Vec>, + #[serde(rename = "type")] + pub(crate) actor_type: String, + pub(crate) url: String, + #[serde(rename = "vcard:bday")] + pub(crate) vcard_bday: Option, + pub(crate) webfinger: String, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct Attachment { + pub(crate) name: String, + #[serde(rename = "type")] + pub(crate) attachment_type: String, + pub(crate) value: String, + pub(crate) verified_at: Option, +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Capabilities { + pub(crate) accepts_chat_messages: bool, +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(untagged)] +pub enum ContextElement { + ContextClass(ContextClass), + String(String), +} + +#[derive(Debug, Clone, Deserialize)] +pub struct ContextClass { + #[serde(rename = "@language")] + pub(crate) language: String, +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Endpoints { + pub(crate) oauth_authorization_endpoint: String, + pub(crate) oauth_registration_endpoint: String, + pub(crate) oauth_token_endpoint: String, + pub(crate) shared_inbox: String, + pub(crate) upload_media: String, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct Icon { + #[serde(rename = "type")] + pub(crate) icon_type: String, + pub(crate) url: String, +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PublicKey { + pub(crate) id: String, + pub(crate) owner: String, + pub(crate) public_key_pem: String, +} diff --git a/style.css b/style.css new file mode 100644 index 0000000..9fcbc73 --- /dev/null +++ b/style.css @@ -0,0 +1,47 @@ +body { + background: #fafbfc; + font-family: sans-serif; +} + +main { + padding: 2rem; +} + +hr { + max-width: 800px; + border: none; + height: 1px; + background: linear-gradient(to right in oklab, #ffffffff 0%, #d9d9d9ff 51%, #ffffffff 100%); +} + +footer { + text-align: center; +} + +.activity { + margin: 2em 0; +} + +.activity-content { + padding-left: calc(48px + 1em); +} + +.activity:after { + content: ""; + display: block; + clear: both; +} + +.activity-reply-to { + +} + +.actor-icon { + width: 48px; + float: left; +} + +time { + float: right; + margin-left: 1em; +}