]> Untitled Git - lemmy.git/commitdiff
Pleroma federation2 (#1855)
authorNutomic <me@nutomic.com>
Thu, 21 Oct 2021 17:25:35 +0000 (17:25 +0000)
committerGitHub <noreply@github.com>
Thu, 21 Oct 2021 17:25:35 +0000 (13:25 -0400)
* Allow fetching person from Pleroma, including test case (ref #1461)

* Added test case for parsing community from apub json

- fixed a bug with objectid (de)serialization
- fixed a bug with outbox fetching (ref #1582)

* Added apub test for post

* Ignore errors when reading community outbox (fixes #1582)

* Dont fetch community outbox/moderators during tests

* added test for lemmy comment

* Added federation test for pleroma comment

* Added html2md crate to parse comment html from pleroma (fixes #1461)

* some fixes for update_apub_test_files.sh

* Add tests for ToApub, private message, remove update script

* Delete objects from db at the end of each test

35 files changed:
Cargo.lock
config/config.hjson
crates/apub/Cargo.toml
crates/apub/assets/lemmy-comment.json [new file with mode: 0644]
crates/apub/assets/lemmy-community.json [new file with mode: 0644]
crates/apub/assets/lemmy-person.json [new file with mode: 0644]
crates/apub/assets/lemmy-post.json [new file with mode: 0644]
crates/apub/assets/lemmy-private-message.json [new file with mode: 0644]
crates/apub/assets/pleroma-comment.json [new file with mode: 0644]
crates/apub/assets/pleroma-person.json [new file with mode: 0644]
crates/apub/src/activities/comment/create_or_update.rs
crates/apub/src/activities/community/add_mod.rs
crates/apub/src/activities/community/announce.rs
crates/apub/src/activities/community/block_user.rs
crates/apub/src/activities/community/remove_mod.rs
crates/apub/src/activities/community/undo_block_user.rs
crates/apub/src/activities/community/update.rs
crates/apub/src/activities/deletion/delete.rs
crates/apub/src/activities/deletion/undo_delete.rs
crates/apub/src/activities/mod.rs
crates/apub/src/activities/post/create_or_update.rs
crates/apub/src/activities/undo_remove.rs
crates/apub/src/activities/voting/undo_vote.rs
crates/apub/src/activities/voting/vote.rs
crates/apub/src/fetcher/community.rs
crates/apub/src/fetcher/object_id.rs
crates/apub/src/migrations.rs
crates/apub/src/objects/comment.rs
crates/apub/src/objects/community.rs
crates/apub/src/objects/mod.rs
crates/apub/src/objects/person.rs
crates/apub/src/objects/post.rs
crates/apub/src/objects/private_message.rs
crates/db_schema/src/impls/private_message.rs
crates/websocket/src/lib.rs

index 120f6827843081239a9f54f740553b22c9f85127..ba4e738516ddf57e1fc89633b2e5966840d1db73 100644 (file)
@@ -315,6 +315,16 @@ version = "0.5.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
 
+[[package]]
+name = "assert-json-diff"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50f1c3703dd33532d7f0ca049168930e9099ecac238e23cf932f3a69c42f06da"
+dependencies = [
+ "serde",
+ "serde_json",
+]
+
 [[package]]
 name = "async-mutex"
 version = "1.4.0"
@@ -614,6 +624,12 @@ version = "1.0.71"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "79c2681d6594606957bbb8631c4b90a7fcaaa72cdb714743a437b156d6a7eedd"
 
+[[package]]
+name = "cesu8"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c"
+
 [[package]]
 name = "cfg-if"
 version = "1.0.0"
@@ -667,6 +683,16 @@ version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
 
+[[package]]
+name = "combine"
+version = "4.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a909e4d93292cd8e9c42e189f61681eff9d67b6541f96b8a1a737f23737bd001"
+dependencies = [
+ "bytes",
+ "memchr",
+]
+
 [[package]]
 name = "comrak"
 version = "0.12.1"
@@ -1371,6 +1397,20 @@ version = "3.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8a164bb2ceaeff4f42542bdb847c41517c78a60f5649671b2a07312b6e117549"
 
+[[package]]
+name = "html2md"
+version = "0.2.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "61f6bf799d9770725ec13d66f4af9344e96285dc14d8e71e0fe02d272690667f"
+dependencies = [
+ "html5ever 0.25.1",
+ "jni",
+ "lazy_static",
+ "markup5ever_rcdom",
+ "percent-encoding",
+ "regex",
+]
+
 [[package]]
 name = "html5ever"
 version = "0.22.5"
@@ -1601,6 +1641,26 @@ version = "0.4.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4"
 
+[[package]]
+name = "jni"
+version = "0.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c6df18c2e3db7e453d3c6ac5b3e9d5182664d28788126d39b91f2d1e22b017ec"
+dependencies = [
+ "cesu8",
+ "combine",
+ "jni-sys",
+ "log",
+ "thiserror",
+ "walkdir",
+]
+
+[[package]]
+name = "jni-sys"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130"
+
 [[package]]
 name = "jpeg-decoder"
 version = "0.1.22"
@@ -1761,6 +1821,7 @@ dependencies = [
  "actix-rt",
  "actix-web",
  "anyhow",
+ "assert-json-diff",
  "async-trait",
  "awc",
  "background-jobs",
@@ -1768,6 +1829,7 @@ dependencies = [
  "chrono",
  "diesel",
  "futures",
+ "html2md",
  "http",
  "http-signature-normalization-actix",
  "itertools",
@@ -1785,6 +1847,7 @@ dependencies = [
  "serde",
  "serde_json",
  "serde_with",
+ "serial_test",
  "sha2",
  "strum",
  "strum_macros",
@@ -3103,6 +3166,15 @@ version = "1.0.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
 
+[[package]]
+name = "same-file"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
+dependencies = [
+ "winapi-util",
+]
+
 [[package]]
 name = "schannel"
 version = "0.1.19"
@@ -4032,6 +4104,17 @@ version = "0.9.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
 
+[[package]]
+name = "walkdir"
+version = "2.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56"
+dependencies = [
+ "same-file",
+ "winapi",
+ "winapi-util",
+]
+
 [[package]]
 name = "want"
 version = "0.3.0"
index 252fca25044f07c0075e52174f23c88f3d37d2ce..7c73aaafa925ab3744a08d20b53bde13808b3621 100644 (file)
@@ -2,4 +2,7 @@
 # https://join-lemmy.org/docs/en/administration/configuration.html
 {
   hostname: lemmy-alpha
+  federation: {
+    enabled: true
+  }
 }
index 2bc69c4a6c9b2b044c5078ecb8fdda524e7f005e..260bed1c40f2663aeae3f7cd5934b1374d971dff 100644 (file)
@@ -49,4 +49,8 @@ anyhow = "1.0.44"
 thiserror = "1.0.29"
 background-jobs = "0.9.0"
 reqwest = { version = "0.11.4", features = ["json"] }
+html2md = "0.2.13"
 
+[dev-dependencies]
+serial_test = "0.5.1"
+assert-json-diff = "2.0.1"
\ No newline at end of file
diff --git a/crates/apub/assets/lemmy-comment.json b/crates/apub/assets/lemmy-comment.json
new file mode 100644 (file)
index 0000000..4781c9e
--- /dev/null
@@ -0,0 +1,36 @@
+{
+  "@context": [
+    "https://www.w3.org/ns/activitystreams",
+    {
+      "comments_enabled": {
+        "id": "pt:commentsEnabled",
+        "type": "sc:Boolean"
+      },
+      "matrixUserId": {
+        "id": "as:alsoKnownAs",
+        "type": "sc:Text"
+      },
+      "moderators": "as:moderators",
+      "pt": "https://join-lemmy.org#",
+      "sc": "http://schema.org#",
+      "sensitive": "as:sensitive",
+      "stickied": "as:stickied"
+    },
+    "https://w3id.org/security/v1"
+  ],
+  "attributedTo": "https://lemmy.ml/u/nutomic",
+  "content": "While I very much get and respect the general sentiment, I think from the perspective of a Central European non-English person in a country with a significant number of, also non-English speaking Nazis, the current approach of filtering slurs based on an English regex is fatally flawed. You can happily use Lemmy to create a hostile far right community where everyone is easily able to use whatever hurtful slurs they want as long as they are not the few specifically blocked English ones. \n\nOn the other hand you create a situation where people feel the need to question the choice of software of their community because they read about censorship or whatever to be used in Lemmy and might stay away and move to other software even though the would maybe never be affected by the slur-filter as the number is not so large and the overlap with other languages not very big.\n\nSo I would argue that this specific implementation of a slur-filter just doesn't achieve what it aims to achieve and should be fundamentally rethought, maybe as configurable per instance.",
+  "id": "https://lemmy.ml/comment/38741",
+  "inReplyTo": [
+    "https://lemmy.ml/post/55143"
+  ],
+  "mediaType": "text/html",
+  "published": "2021-03-01T13:42:43.966208+00:00",
+  "source": {
+    "content": "While I very much get and respect the general sentiment, I think from the perspective of a Central European non-English person in a country with a significant number of, also non-English speaking Nazis, the current approach of filtering slurs based on an English regex is fatally flawed. You can happily use Lemmy to create a hostile far right community where everyone is easily able to use whatever hurtful slurs they want as long as they are not the few specifically blocked English ones. \n\nOn the other hand you create a situation where people feel the need to question the choice of software of their community because they read about censorship or whatever to be used in Lemmy and might stay away and move to other software even though the would maybe never be affected by the slur-filter as the number is not so large and the overlap with other languages not very big.\n\nSo I would argue that this specific implementation of a slur-filter just doesn't achieve what it aims to achieve and should be fundamentally rethought, maybe as configurable per instance.",
+    "mediaType": "text/markdown"
+  },
+  "to": ["https://www.w3.org/ns/activitystreams#Public"],
+  "type": "Note",
+  "updated": "2021-03-01T13:43:03.955787+00:00"
+}
diff --git a/crates/apub/assets/lemmy-community.json b/crates/apub/assets/lemmy-community.json
new file mode 100644 (file)
index 0000000..e0f3c68
--- /dev/null
@@ -0,0 +1,50 @@
+{
+  "@context": [
+    "https://www.w3.org/ns/activitystreams",
+    {
+      "comments_enabled": {
+        "id": "pt:commentsEnabled",
+        "type": "sc:Boolean"
+      },
+      "matrixUserId": {
+        "id": "as:alsoKnownAs",
+        "type": "sc:Text"
+      },
+      "moderators": "as:moderators",
+      "pt": "https://join-lemmy.org#",
+      "sc": "http://schema.org#",
+      "sensitive": "as:sensitive",
+      "stickied": "as:stickied"
+    },
+    "https://w3id.org/security/v1"
+  ],
+  "content": "<p>Lemmy Announcements</p>\n<hr />\n<p>Feel free to announce new communities here.</p>\n<p>Other than that, this is <em>reserved for admin use only</em>.</p>\n",
+  "endpoints": {
+    "sharedInbox": "https://lemmy.ml/inbox"
+  },
+  "followers": "https://lemmy.ml/c/announcements/followers",
+  "icon": {
+    "type": "Image",
+    "url": "https://lemmy.ml/pictrs/image/waqyZwLAy4.webp"
+  },
+  "id": "https://lemmy.ml/c/announcements",
+  "inbox": "https://lemmy.ml/c/announcements/inbox",
+  "mediaType": "text/html",
+  "moderators": "https://lemmy.ml/c/announcements/moderators",
+  "name": "Announcements",
+  "outbox": "https://lemmy.ml/c/announcements/outbox",
+  "preferredUsername": "announcements",
+  "publicKey": {
+    "id": "https://lemmy.ml/c/announcements#main-key",
+    "owner": "https://lemmy.ml/c/announcements",
+    "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzRjKTNtvDCmugplwEh+g\nx1bhKm6BHUZfXfpscgMMm7tXFswSDzUQirMgfkxa9ubfr1PDFKffA2vQ9x6CyuO/\n70xTafdOHyV1tSqzgKz0ZvFZ/VCOo6qy1mYWVkrtBm/fKzM+87MdkKYB/zI4VyEJ\nLfLQgjwxBAEYUH3CBG71U0gO0TwbimWNN0vqlfp0QfThNe1WYObF88ZVzMLgFbr7\nRHBItZjlZ/d8foPDidlIR3l2dJjy0EsD8F9JM340jtX7LXqFmU4j1AQKNHTDLnUF\nwYVhzuQGNJ504l5LZkFG54XfIFT7dx2QwuuM9bSnfPv/98RYrq1Si6tCkxEt1cVe\n4wIDAQAB\n-----END PUBLIC KEY-----\n"
+  },
+  "published": "2019-06-02T16:43:50.799554+00:00",
+  "sensitive": false,
+  "source": {
+    "content": "Lemmy Announcements\n\n---\n\nFeel free to announce new communities here.\n\nOther than that, this is *reserved for admin use only*.",
+    "mediaType": "text/markdown"
+  },
+  "type": "Group",
+  "updated": "2021-03-10T17:18:10.498868+00:00"
+}
diff --git a/crates/apub/assets/lemmy-person.json b/crates/apub/assets/lemmy-person.json
new file mode 100644 (file)
index 0000000..6514b6e
--- /dev/null
@@ -0,0 +1,46 @@
+{
+  "@context": [
+    "https://www.w3.org/ns/activitystreams",
+    {
+      "comments_enabled": {
+        "id": "pt:commentsEnabled",
+        "type": "sc:Boolean"
+      },
+      "matrixUserId": {
+        "id": "as:alsoKnownAs",
+        "type": "sc:Text"
+      },
+      "moderators": "as:moderators",
+      "pt": "https://join-lemmy.org#",
+      "sc": "http://schema.org#",
+      "sensitive": "as:sensitive",
+      "stickied": "as:stickied"
+    },
+    "https://w3id.org/security/v1"
+  ],
+  "content": "<p>Lemmy maintainer. Interested in politics, video games, and many other things.</p>\n",
+  "endpoints": {
+    "sharedInbox": "https://lemmy.ml/inbox"
+  },
+  "icon": {
+    "type": "Image",
+    "url": "https://lemmy.ml/pictrs/image/ed9ej7.jpg"
+  },
+  "id": "https://lemmy.ml/u/nutomic",
+  "inbox": "https://lemmy.ml/u/nutomic/inbox",
+  "mediaType": "text/html",
+  "outbox": "https://lemmy.ml/u/nutomic/outbox",
+  "preferredUsername": "nutomic",
+  "publicKey": {
+    "id": "https://lemmy.ml/u/nutomic#main-key",
+    "owner": "https://lemmy.ml/u/nutomic",
+    "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0lP99/s5Vv+XbPdkeqIJ\nwoD4GFnHmBnBHdEKChEUWfWj1TtioC/rGNoXFQeXQA3Amhy4nxSceiDnUgwkkuQY\nv0MtIW58NzgknEavtllxL+LSds5pg3gANaDIk8UiWTkqXTg0GnlJMpCK1Chen0l/\nszL6DEvUyTSuS5ZYDXFgewF89Pe7U0S15V5U2Harv7AgJYDyxmUL0D1pGuUCRqcE\nl5MTHJjrXeNnH1w2g8aly8YlO/Cr0L51rFg/lBF23vni7ZLv8HbmWh6YpaAf1R8h\nE45zKR7OHqymdjzrg1ITBwovefpwMkVgnJ+Wdr4HPnFlBSkXPoZeM11+Z8L0anzA\nXwIDAQAB\n-----END PUBLIC KEY-----\n"
+  },
+  "published": "2020-01-17T01:38:22.348392+00:00",
+  "source": {
+    "content": "Lemmy maintainer. Interested in politics, video games, and many other things.",
+    "mediaType": "text/markdown"
+  },
+  "type": "Person",
+  "updated": "2021-08-13T00:11:15.941990+00:00"
+}
diff --git a/crates/apub/assets/lemmy-post.json b/crates/apub/assets/lemmy-post.json
new file mode 100644 (file)
index 0000000..fba112f
--- /dev/null
@@ -0,0 +1,39 @@
+{
+  "@context": [
+    "https://www.w3.org/ns/activitystreams",
+    {
+      "comments_enabled": {
+        "id": "pt:commentsEnabled",
+        "type": "sc:Boolean"
+      },
+      "matrixUserId": {
+        "id": "as:alsoKnownAs",
+        "type": "sc:Text"
+      },
+      "moderators": "as:moderators",
+      "pt": "https://join-lemmy.org#",
+      "sc": "http://schema.org#",
+      "sensitive": "as:sensitive",
+      "stickied": "as:stickied"
+    },
+    "https://w3id.org/security/v1"
+  ],
+  "attributedTo": "https://lemmy.ml/u/nutomic",
+  "commentsEnabled": true,
+  "content": "<p>Recently there have been some discussions about the political stances of the Lemmy developers and site admins. To clear up some misconceptions: Lemmy is run by a team of people with different ideologies, including anti-capitalist, communist, anarchist, and others. While @dessalines and I are communists, we take decisions collectively, and don't demand that anyone adopt our views or convert to our ideologies. We wouldn't devote so much time to building a federated site otherwise.</p>\n<p>What's important to us is that you follow the site rules and <a href=\"https://join.lemmy.ml/docs/en/code_of_conduct.html\">Code of Conduct</a>. Meaning primarily, no-bigotry, and being respectful towards others. As long as that is the case, we can get along perfectly fine.</p>\n<p>In general we are open for constructive feedback, so please contact any member of the admin team if you have an idea how to improve Lemmy.</p>\n<h2>Slur Filter</h2>\n<p>We also noticed a consistent criticism of the built-in slur filter in Lemmy. Not so much on lemmy.ml itself, but whenever Lemmy is recommended elsewhere, a few usual suspects keep bringing it up. To these people we say the following: we are using the slur filter as a tool to keep a friendly atmosphere, and prevent racists, sexists and other bigots from using Lemmy. Its existence alone has lead many of them to not make an account, or run an instance: a clear net positive.</p>\n<p>You can see for yourself the words which are blocked (content warning, <a href=\"https://github.com/LemmyNet/lemmy/blob/main/crates/utils/src/utils.rs#L10\">link here</a>). Note that it doesn't include any simple swear words, but only slurs which are used to insult and attack other people. If you want to use any of these words, then please stay on one of the many platforms that permit them. Lemmy is not for you, and we don't want you here.</p>\n<p>We are fully aware that the slur filter is not perfect. It is made for American English, and can give false positives in other languages or dialects. We are totally willing to fix such problems on a case by case basis, simply open an issue in our <a href=\"https://github.com/LemmyNet/lemmy/issues\">repo </a>with a description of the problem.</p>\n",
+  "id": "https://lemmy.ml/post/55143",
+  "mediaType": "text/html",
+  "name": "Statement on Politics of Lemmy.ml",
+  "published": "2021-02-26T12:35:34.292626+00:00",
+  "sensitive": false,
+  "source": {
+    "content": "Recently there have been some discussions about the political stances of the Lemmy developers and site admins. To clear up some misconceptions: Lemmy is run by a team of people with different ideologies, including anti-capitalist, communist, anarchist, and others. While @dessalines and I are communists, we take decisions collectively, and don't demand that anyone adopt our views or convert to our ideologies. We wouldn't devote so much time to building a federated site otherwise.\n\nWhat's important to us is that you follow the site rules and [Code of Conduct](https://join.lemmy.ml/docs/en/code_of_conduct.html). Meaning primarily, no-bigotry, and being respectful towards others. As long as that is the case, we can get along perfectly fine.\n\nIn general we are open for constructive feedback, so please contact any member of the admin team if you have an idea how to improve Lemmy. \n\n## Slur Filter\n\nWe also noticed a consistent criticism of the built-in slur filter in Lemmy. Not so much on lemmy.ml itself, but whenever Lemmy is recommended elsewhere, a few usual suspects keep bringing it up. To these people we say the following: we are using the slur filter as a tool to keep a friendly atmosphere, and prevent racists, sexists and other bigots from using Lemmy. Its existence alone has lead many of them to not make an account, or run an instance: a clear net positive.\n\nYou can see for yourself the words which are blocked (content warning, [link here](https://github.com/LemmyNet/lemmy/blob/main/crates/utils/src/utils.rs#L10)). Note that it doesn't include any simple swear words, but only slurs which are used to insult and attack other people. If you want to use any of these words, then please stay on one of the many platforms that permit them. Lemmy is not for you, and we don't want you here.\n\nWe are fully aware that the slur filter is not perfect. It is made for American English, and can give false positives in other languages or dialects. We are totally willing to fix such problems on a case by case basis, simply open an issue in our [repo ](https://github.com/LemmyNet/lemmy/issues)with a description of the problem.",
+    "mediaType": "text/markdown"
+  },
+  "stickied": true,
+  "to": [
+    "https://lemmy.ml/c/announcements",
+    "https://www.w3.org/ns/activitystreams#Public"
+  ],
+  "type": "Page"
+}
diff --git a/crates/apub/assets/lemmy-private-message.json b/crates/apub/assets/lemmy-private-message.json
new file mode 100644 (file)
index 0000000..981cf25
--- /dev/null
@@ -0,0 +1,32 @@
+{
+  "@context": [
+    "https://www.w3.org/ns/activitystreams",
+    {
+      "comments_enabled": {
+        "id": "pt:commentsEnabled",
+        "type": "sc:Boolean"
+      },
+      "matrixUserId": {
+        "id": "as:alsoKnownAs",
+        "type": "sc:Text"
+      },
+      "moderators": "as:moderators",
+      "pt": "https://join-lemmy.org#",
+      "sc": "http://schema.org#",
+      "sensitive": "as:sensitive",
+      "stickied": "as:stickied"
+    },
+    "https://w3id.org/security/v1"
+  ],
+  "attributedTo": "https://lemmy.ml/u/nutomic",
+  "content": "test",
+  "id": "https://lemmy.ml/private_message/1621",
+  "mediaType": "text/html",
+  "published": "2021-10-21T10:13:14.597721+00:00",
+  "source": {
+    "content": "test",
+    "mediaType": "text/markdown"
+  },
+  "to": "https://queer.hacktivis.me/users/lanodan",
+  "type": "Note"
+}
\ No newline at end of file
diff --git a/crates/apub/assets/pleroma-comment.json b/crates/apub/assets/pleroma-comment.json
new file mode 100644 (file)
index 0000000..af5db78
--- /dev/null
@@ -0,0 +1,36 @@
+{
+  "@context": [
+    "https://www.w3.org/ns/activitystreams",
+    "https://queer.hacktivis.me/schemas/litepub-0.1.jsonld",
+    {
+      "@language": "und"
+    }
+  ],
+  "actor": "https://queer.hacktivis.me/users/lanodan",
+  "attachment": [],
+  "attributedTo": "https://queer.hacktivis.me/users/lanodan",
+  "cc": [
+    "https://www.w3.org/ns/activitystreams#Public"
+  ],
+  "content": "<span class=\"h-card\"><a class=\"u-url mention\" data-user=\"9zkUX4o3WxGM8vGPfU\" href=\"https://pleroma.popolon.org/users/popolon\" rel=\"ugc\">@<span>popolon</span></a></span> Have what?",
+  "context": "https://queer.hacktivis.me/contexts/34cba3d2-2f35-4169-aeff-56af9bfeb753",
+  "conversation": "https://queer.hacktivis.me/contexts/34cba3d2-2f35-4169-aeff-56af9bfeb753",
+  "id": "https://queer.hacktivis.me/objects/8d4973f4-53de-49cd-8c27-df160e16a9c2",
+  "inReplyTo": "https://lemmy.ml/post/55143",
+  "published": "2021-10-07T18:06:52.555500Z",
+  "sensitive": null,
+  "source": "@popolon@pleroma.popolon.org Have what?",
+  "summary": "",
+  "tag": [
+    {
+      "href": "https://pleroma.popolon.org/users/popolon",
+      "name": "@popolon@pleroma.popolon.org",
+      "type": "Mention"
+    }
+  ],
+  "to": [
+    "https://pleroma.popolon.org/users/popolon",
+    "https://queer.hacktivis.me/users/lanodan/followers"
+  ],
+  "type": "Note"
+}
diff --git a/crates/apub/assets/pleroma-person.json b/crates/apub/assets/pleroma-person.json
new file mode 100644 (file)
index 0000000..bc9008b
--- /dev/null
@@ -0,0 +1,79 @@
+{
+  "@context": [
+    "https://www.w3.org/ns/activitystreams",
+    "https://queer.hacktivis.me/schemas/litepub-0.1.jsonld",
+    {
+      "@language": "und"
+    }
+  ],
+  "alsoKnownAs": [],
+  "attachment": [],
+  "capabilities": {
+    "acceptsChatMessages": true
+  },
+  "discoverable": false,
+  "endpoints": {
+    "oauthAuthorizationEndpoint": "https://queer.hacktivis.me/oauth/authorize",
+    "oauthRegistrationEndpoint": "https://queer.hacktivis.me/api/v1/apps",
+    "oauthTokenEndpoint": "https://queer.hacktivis.me/oauth/token",
+    "sharedInbox": "https://queer.hacktivis.me/inbox",
+    "uploadMedia": "https://queer.hacktivis.me/api/ap/upload_media"
+  },
+  "featured": "https://queer.hacktivis.me/users/lanodan/collections/featured",
+  "followers": "https://queer.hacktivis.me/users/lanodan/followers",
+  "following": "https://queer.hacktivis.me/users/lanodan/following",
+  "icon": {
+    "type": "Image",
+    "url": "https://queer.hacktivis.me/media/d23cf9b0-5586-4592-aca5-9a52777a6042/avatar_HD.png"
+  },
+  "id": "https://queer.hacktivis.me/users/lanodan",
+  "image": {
+    "type": "Image",
+    "url": "https://queer.hacktivis.me/media/37b6ce56-8c24-4e64-bd70-a76e84ab0c69/53a48a3a49ed5e5637a84e4f3663df17f8d764244bbc1027ba03cfc446e8b7bd.jpg"
+  },
+  "inbox": "https://queer.hacktivis.me/users/lanodan/inbox",
+  "manuallyApprovesFollowers": false,
+  "name": "Haelwenn /элвэн/ :bzh: ",
+  "outbox": "https://queer.hacktivis.me/users/lanodan/outbox",
+  "preferredUsername": "lanodan",
+  "publicKey": {
+    "id": "https://queer.hacktivis.me/users/lanodan#main-key",
+    "owner": "https://queer.hacktivis.me/users/lanodan",
+    "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsWOgdjSMc010qvxC3njI\nXJlFWMJ5gJ8QXCW/PajYdsHPM6d+jxBNJ6zp9/tIRa2m7bWHTSkuHQ7QthOpt6vu\n+dAWpKRLS607SPLItn/qUcyXvgN+H8shfyhMxvkVs9jXdtlBsLUVE7UNpN0dxzqe\nI79QWbf7o4amgaIWGRYB+OYMnIxKt+GzIkivZdSVSYjfxNnBYkMCeUxm5EpPIxKS\nP5bBHAVRRambD5NUmyKILuC60/rYuc/C+vmgpY2HCWFS2q6o34dPr9enwL6t4b3m\nS1t/EJHk9rGaaDqSGkDEfyQI83/7SDebWKuETMKKFLZi1vMgQIFuOYCIhN6bIiZm\npQIDAQAB\n-----END PUBLIC KEY-----\n\n"
+  },
+  "summary": "---<br/>Website: <a href=\"https://hacktivis.me/\">https://hacktivis.me/</a><br/>Lang: Français(natif), English(fluent), LSF(🤏~👌), русский (еле-еле), <br/>Politics: Anarchist as in DIY/DIWO, freedom of association, anti-authoritarian, anti-identitarianism<br/><br/>Pronouns: meh, pick any, have fun<br/>Timezone: Let&#39;s say Mars, I have a non-24h cycle<br/>```<br/>🦊🦄⚧🂡ⓥ :anarchy: 👿🐧 :gentoo:<br/>Pleroma maintainer (mostly backend)<br/>BadWolf developer<br/>Gentoo contributor<br/><br/>Dayjob: yogoko.fr<br/><br/>That person which uses HJKL in games<br/><br/>Just because computer bad: X5O!P%@AP[4\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*<br/><br/>banner from: <a href=\"https://soc.flyingcube.tech/objects/56f79be2-9013-4559-9826-f7dc392417db\">https://soc.flyingcube.tech/objects/56f79be2-9013-4559-9826-f7dc392417db</a><br/>Federation-bots: <a class=\"hashtag\" data-tag=\"nobot\" href=\"https://queer.hacktivis.me/tag/nobot\">#nobot</a>",
+  "tag": [
+    {
+      "icon": {
+        "type": "Image",
+        "url": "https://queer.hacktivis.me/emoji/custom/symbols/anarchy.png"
+      },
+      "id": "https://queer.hacktivis.me/emoji/custom/symbols/anarchy.png",
+      "name": ":anarchy:",
+      "type": "Emoji",
+      "updated": "1970-01-01T00:00:00Z"
+    },
+    {
+      "icon": {
+        "type": "Image",
+        "url": "https://queer.hacktivis.me/emoji/custom/mastodon.xyz/bzh.png"
+      },
+      "id": "https://queer.hacktivis.me/emoji/custom/mastodon.xyz/bzh.png",
+      "name": ":bzh:",
+      "type": "Emoji",
+      "updated": "1970-01-01T00:00:00Z"
+    },
+    {
+      "icon": {
+        "type": "Image",
+        "url": "https://queer.hacktivis.me/emoji/custom/gentoo.png"
+      },
+      "id": "https://queer.hacktivis.me/emoji/custom/gentoo.png",
+      "name": ":gentoo:",
+      "type": "Emoji",
+      "updated": "1970-01-01T00:00:00Z"
+    }
+  ],
+  "type": "Person",
+  "url": "https://queer.hacktivis.me/users/lanodan"
+}
index c4591c7c84b3b5e8818817a426e1ef64ad8c87fb..43bde246597cc40c6b02a4c1d15bf083b7c6bb8c 100644 (file)
@@ -11,6 +11,7 @@ use crate::{
   },
   context::lemmy_context,
   fetcher::object_id::ObjectId,
+  migrations::PublicUrlMigration,
   objects::{
     comment::{ApubComment, Note},
     community::ApubCommunity,
@@ -22,7 +23,6 @@ use lemmy_api_common::{blocking, check_post_deleted_or_removed};
 use lemmy_apub_lib::{
   data::Data,
   traits::{ActivityFields, ActivityHandler, ActorType, FromApub, ToApub},
-  values::PublicUrl,
   verify::verify_domains_match,
 };
 use lemmy_db_schema::{
@@ -38,7 +38,7 @@ use url::Url;
 #[serde(rename_all = "camelCase")]
 pub struct CreateOrUpdateComment {
   actor: ObjectId<ApubPerson>,
-  to: [PublicUrl; 1],
+  to: PublicUrlMigration,
   object: Note,
   cc: Vec<Url>,
   tag: Vec<Mention>,
@@ -76,7 +76,7 @@ impl CreateOrUpdateComment {
 
     let create_or_update = CreateOrUpdateComment {
       actor: ObjectId::new(actor.actor_id()),
-      to: [PublicUrl::Public],
+      to: PublicUrlMigration::create(),
       object: comment.to_apub(context.pool()).await?,
       cc: maa.ccs,
       tag: maa.tags,
index e1cf03e0d0238be91c6fdc0c1d5f9bf1594db9da..a5b597f52bdd6629e5966a486be119380f6ddea3 100644 (file)
@@ -10,6 +10,7 @@ use crate::{
   context::lemmy_context,
   fetcher::object_id::ObjectId,
   generate_moderators_url,
+  migrations::PublicUrlMigration,
   objects::{community::ApubCommunity, person::ApubPerson},
 };
 use activitystreams::{
@@ -22,7 +23,6 @@ use lemmy_api_common::blocking;
 use lemmy_apub_lib::{
   data::Data,
   traits::{ActivityFields, ActivityHandler, ActorType},
-  values::PublicUrl,
 };
 use lemmy_db_schema::{
   source::community::{CommunityModerator, CommunityModeratorForm},
@@ -37,7 +37,7 @@ use url::Url;
 #[serde(rename_all = "camelCase")]
 pub struct AddMod {
   actor: ObjectId<ApubPerson>,
-  to: [PublicUrl; 1],
+  to: PublicUrlMigration,
   object: ObjectId<ApubPerson>,
   target: Url,
   cc: [ObjectId<ApubCommunity>; 1],
@@ -63,7 +63,7 @@ impl AddMod {
     )?;
     let add = AddMod {
       actor: ObjectId::new(actor.actor_id()),
-      to: [PublicUrl::Public],
+      to: PublicUrlMigration::create(),
       object: ObjectId::new(added_mod.actor_id()),
       target: generate_moderators_url(&community.actor_id)?.into(),
       cc: [ObjectId::new(community.actor_id())],
index 183dbfd5f90eaaef83701175aabf31b09cd90b78..3fefac15f5364ee58b3812e40a1ed04f5697eadf 100644 (file)
@@ -21,6 +21,7 @@ use crate::{
   fetcher::object_id::ObjectId,
   http::is_activity_already_known,
   insert_activity,
+  migrations::PublicUrlMigration,
   objects::community::ApubCommunity,
   send_lemmy_activity,
   CommunityType,
@@ -34,7 +35,6 @@ use activitystreams::{
 use lemmy_apub_lib::{
   data::Data,
   traits::{ActivityFields, ActivityHandler, ActorType},
-  values::PublicUrl,
 };
 use lemmy_utils::LemmyError;
 use lemmy_websocket::LemmyContext;
@@ -63,7 +63,7 @@ pub enum AnnouncableActivities {
 #[serde(rename_all = "camelCase")]
 pub struct AnnounceActivity {
   actor: ObjectId<ApubCommunity>,
-  to: [PublicUrl; 1],
+  to: PublicUrlMigration,
   object: AnnouncableActivities,
   cc: Vec<Url>,
   #[serde(rename = "type")]
@@ -84,7 +84,7 @@ impl AnnounceActivity {
   ) -> Result<(), LemmyError> {
     let announce = AnnounceActivity {
       actor: ObjectId::new(community.actor_id()),
-      to: [PublicUrl::Public],
+      to: PublicUrlMigration::create(),
       object,
       cc: vec![community.followers_url()],
       kind: AnnounceType::Announce,
index a1c76c23b88d47d4c21116dd8b8868447ce3a60f..d8e131fc3fe562d836f6037d55be74bb1e432825 100644 (file)
@@ -8,6 +8,7 @@ use crate::{
   },
   context::lemmy_context,
   fetcher::object_id::ObjectId,
+  migrations::PublicUrlMigration,
   objects::{community::ApubCommunity, person::ApubPerson},
 };
 use activitystreams::{
@@ -20,7 +21,6 @@ use lemmy_api_common::blocking;
 use lemmy_apub_lib::{
   data::Data,
   traits::{ActivityFields, ActivityHandler, ActorType},
-  values::PublicUrl,
 };
 use lemmy_db_schema::{
   source::community::{
@@ -40,7 +40,7 @@ use url::Url;
 #[serde(rename_all = "camelCase")]
 pub struct BlockUserFromCommunity {
   actor: ObjectId<ApubPerson>,
-  to: [PublicUrl; 1],
+  to: PublicUrlMigration,
   pub(in crate::activities::community) object: ObjectId<ApubPerson>,
   cc: [ObjectId<ApubCommunity>; 1],
   #[serde(rename = "type")]
@@ -61,7 +61,7 @@ impl BlockUserFromCommunity {
   ) -> Result<BlockUserFromCommunity, LemmyError> {
     Ok(BlockUserFromCommunity {
       actor: ObjectId::new(actor.actor_id()),
-      to: [PublicUrl::Public],
+      to: PublicUrlMigration::create(),
       object: ObjectId::new(target.actor_id()),
       cc: [ObjectId::new(community.actor_id())],
       kind: BlockType::Block,
index b145e7171f9d539a3c7a3565b7a83849b6e6f097..d7186114ddaf6a735d9356cf979724f08decdef5 100644 (file)
@@ -11,6 +11,7 @@ use crate::{
   context::lemmy_context,
   fetcher::object_id::ObjectId,
   generate_moderators_url,
+  migrations::PublicUrlMigration,
   objects::{community::ApubCommunity, person::ApubPerson},
 };
 use activitystreams::{
@@ -23,7 +24,6 @@ use lemmy_api_common::blocking;
 use lemmy_apub_lib::{
   data::Data,
   traits::{ActivityFields, ActivityHandler, ActorType},
-  values::PublicUrl,
 };
 use lemmy_db_schema::{
   source::community::{CommunityModerator, CommunityModeratorForm},
@@ -38,7 +38,7 @@ use url::Url;
 #[serde(rename_all = "camelCase")]
 pub struct RemoveMod {
   actor: ObjectId<ApubPerson>,
-  to: [PublicUrl; 1],
+  to: PublicUrlMigration,
   pub(in crate::activities) object: ObjectId<ApubPerson>,
   cc: [ObjectId<ApubCommunity>; 1],
   #[serde(rename = "type")]
@@ -65,7 +65,7 @@ impl RemoveMod {
     )?;
     let remove = RemoveMod {
       actor: ObjectId::new(actor.actor_id()),
-      to: [PublicUrl::Public],
+      to: PublicUrlMigration::create(),
       object: ObjectId::new(removed_mod.actor_id()),
       target: Some(generate_moderators_url(&community.actor_id)?.into()),
       id: id.clone(),
index 1614de672d9b131439ddbf425326fca5950c50f0..6b405cf48d9964ea9028b5eddcfa8a132e8de6c4 100644 (file)
@@ -12,6 +12,7 @@ use crate::{
   },
   context::lemmy_context,
   fetcher::object_id::ObjectId,
+  migrations::PublicUrlMigration,
   objects::{community::ApubCommunity, person::ApubPerson},
 };
 use activitystreams::{
@@ -24,7 +25,6 @@ use lemmy_api_common::blocking;
 use lemmy_apub_lib::{
   data::Data,
   traits::{ActivityFields, ActivityHandler, ActorType},
-  values::PublicUrl,
 };
 use lemmy_db_schema::{
   source::community::{CommunityPersonBan, CommunityPersonBanForm},
@@ -39,7 +39,7 @@ use url::Url;
 #[serde(rename_all = "camelCase")]
 pub struct UndoBlockUserFromCommunity {
   actor: ObjectId<ApubPerson>,
-  to: [PublicUrl; 1],
+  to: PublicUrlMigration,
   object: BlockUserFromCommunity,
   cc: [ObjectId<ApubCommunity>; 1],
   #[serde(rename = "type")]
@@ -66,7 +66,7 @@ impl UndoBlockUserFromCommunity {
     )?;
     let undo = UndoBlockUserFromCommunity {
       actor: ObjectId::new(actor.actor_id()),
-      to: [PublicUrl::Public],
+      to: PublicUrlMigration::create(),
       object: block,
       cc: [ObjectId::new(community.actor_id())],
       kind: UndoType::Undo,
index 1120bd8bb70d136a2f23f24133b940c6782740b3..3a7b44fee31cf9c45d110a0ad904cd0c7bba9669 100644 (file)
@@ -8,6 +8,7 @@ use crate::{
   },
   context::lemmy_context,
   fetcher::object_id::ObjectId,
+  migrations::PublicUrlMigration,
   objects::{
     community::{ApubCommunity, Group},
     person::ApubPerson,
@@ -23,7 +24,6 @@ use lemmy_api_common::blocking;
 use lemmy_apub_lib::{
   data::Data,
   traits::{ActivityFields, ActivityHandler, ActorType, ToApub},
-  values::PublicUrl,
 };
 use lemmy_db_schema::{
   source::community::{Community, CommunityForm},
@@ -40,7 +40,7 @@ use url::Url;
 #[serde(rename_all = "camelCase")]
 pub struct UpdateCommunity {
   actor: ObjectId<ApubPerson>,
-  to: [PublicUrl; 1],
+  to: PublicUrlMigration,
   // TODO: would be nice to use a separate struct here, which only contains the fields updated here
   object: Group,
   cc: [ObjectId<ApubCommunity>; 1],
@@ -65,7 +65,7 @@ impl UpdateCommunity {
     )?;
     let update = UpdateCommunity {
       actor: ObjectId::new(actor.actor_id()),
-      to: [PublicUrl::Public],
+      to: PublicUrlMigration::create(),
       object: community.to_apub(context.pool()).await?,
       cc: [ObjectId::new(community.actor_id())],
       kind: UpdateType::Update,
index 02df7dc38a2d87de5f133179fa09689e55060e3a..acfc1ba7ef3ad84264eeb37406f4f29a00c6321f 100644 (file)
@@ -12,6 +12,7 @@ use crate::{
   },
   context::lemmy_context,
   fetcher::object_id::ObjectId,
+  migrations::PublicUrlMigration,
   objects::{community::ApubCommunity, person::ApubPerson},
 };
 use activitystreams::{
@@ -25,7 +26,6 @@ use lemmy_api_common::blocking;
 use lemmy_apub_lib::{
   data::Data,
   traits::{ActivityFields, ActivityHandler, ActorType},
-  values::PublicUrl,
 };
 use lemmy_db_schema::{
   source::{
@@ -66,7 +66,7 @@ use url::Url;
 #[serde(rename_all = "camelCase")]
 pub struct Delete {
   actor: ObjectId<ApubPerson>,
-  to: [PublicUrl; 1],
+  to: PublicUrlMigration,
   pub(in crate::activities::deletion) object: Url,
   pub(in crate::activities::deletion) cc: [ObjectId<ApubCommunity>; 1],
   #[serde(rename = "type")]
@@ -144,7 +144,7 @@ impl Delete {
   ) -> Result<Delete, LemmyError> {
     Ok(Delete {
       actor: ObjectId::new(actor.actor_id()),
-      to: [PublicUrl::Public],
+      to: PublicUrlMigration::create(),
       object: object_id,
       cc: [ObjectId::new(community.actor_id())],
       kind: DeleteType::Delete,
index 327bf86c03a6ac28888d733b62f149f36c4fc6da..2527a97172da5274fe83eb111a817dfd774532d4 100644 (file)
@@ -13,6 +13,7 @@ use crate::{
   },
   context::lemmy_context,
   fetcher::object_id::ObjectId,
+  migrations::PublicUrlMigration,
   objects::{community::ApubCommunity, person::ApubPerson},
 };
 use activitystreams::{
@@ -26,7 +27,6 @@ use lemmy_api_common::blocking;
 use lemmy_apub_lib::{
   data::Data,
   traits::{ActivityFields, ActivityHandler, ActorType},
-  values::PublicUrl,
 };
 use lemmy_db_schema::source::{comment::Comment, community::Community, post::Post};
 use lemmy_utils::LemmyError;
@@ -42,7 +42,7 @@ use url::Url;
 #[serde(rename_all = "camelCase")]
 pub struct UndoDelete {
   actor: ObjectId<ApubPerson>,
-  to: [PublicUrl; 1],
+  to: PublicUrlMigration,
   object: Delete,
   cc: [ObjectId<ApubCommunity>; 1],
   #[serde(rename = "type")]
@@ -117,7 +117,7 @@ impl UndoDelete {
     )?;
     let undo = UndoDelete {
       actor: ObjectId::new(actor.actor_id()),
-      to: [PublicUrl::Public],
+      to: PublicUrlMigration::create(),
       object,
       cc: [ObjectId::new(community.actor_id())],
       kind: UndoType::Undo,
index 0832a85c8086d48d72e2e284db9bc339e9200ba1..f59a93850468c375042bfba25f84ffebe1508670 100644 (file)
@@ -5,6 +5,7 @@ use crate::{
   generate_moderators_url,
   objects::{community::ApubCommunity, person::ApubPerson},
 };
+use activitystreams::public;
 use anyhow::anyhow;
 use lemmy_api_common::blocking;
 use lemmy_apub_lib::{traits::ActivityFields, verify::verify_domains_match};
@@ -136,6 +137,13 @@ fn verify_add_remove_moderator_target(
   Ok(())
 }
 
+pub(crate) fn verify_is_public(to: &[Url]) -> Result<(), LemmyError> {
+  if !to.contains(&public()) {
+    return Err(anyhow!("Object is not public").into());
+  }
+  Ok(())
+}
+
 pub(crate) fn check_community_deleted_or_removed(community: &Community) -> Result<(), LemmyError> {
   if community.deleted || community.removed {
     Err(anyhow!("New post or comment cannot be created in deleted or removed community").into())
index cc7ef5c97c015110616957304f74af2b44a06c32..cda78ce1edbe07a6b285732625e548d6170e6f05 100644 (file)
@@ -10,6 +10,7 @@ use crate::{
   },
   context::lemmy_context,
   fetcher::object_id::ObjectId,
+  migrations::PublicUrlMigration,
   objects::{
     community::ApubCommunity,
     person::ApubPerson,
@@ -22,7 +23,6 @@ use lemmy_api_common::blocking;
 use lemmy_apub_lib::{
   data::Data,
   traits::{ActivityFields, ActivityHandler, ActorType, FromApub, ToApub},
-  values::PublicUrl,
   verify::{verify_domains_match, verify_urls_match},
 };
 use lemmy_db_schema::{source::community::Community, traits::Crud};
@@ -35,7 +35,7 @@ use url::Url;
 #[serde(rename_all = "camelCase")]
 pub struct CreateOrUpdatePost {
   actor: ObjectId<ApubPerson>,
-  to: [PublicUrl; 1],
+  to: PublicUrlMigration,
   object: Page,
   cc: [ObjectId<ApubCommunity>; 1],
   #[serde(rename = "type")]
@@ -67,7 +67,7 @@ impl CreateOrUpdatePost {
     )?;
     let create_or_update = CreateOrUpdatePost {
       actor: ObjectId::new(actor.actor_id()),
-      to: [PublicUrl::Public],
+      to: PublicUrlMigration::create(),
       object: post.to_apub(context.pool()).await?,
       cc: [ObjectId::new(community.actor_id())],
       kind,
index 5b41e08687dc30cea74eb5fcccb8c8acd289eceb..57346490c27e45236cc62674a441f271259f846e 100644 (file)
@@ -5,6 +5,7 @@ use crate::{
     verify_activity,
   },
   fetcher::object_id::ObjectId,
+  migrations::PublicUrlMigration,
   objects::{community::ApubCommunity, person::ApubPerson},
 };
 use activitystreams::{
@@ -16,7 +17,6 @@ use activitystreams::{
 use lemmy_apub_lib::{
   data::Data,
   traits::{ActivityFields, ActivityHandler},
-  values::PublicUrl,
 };
 use lemmy_utils::LemmyError;
 use lemmy_websocket::LemmyContext;
@@ -27,7 +27,7 @@ use url::Url;
 #[serde(rename_all = "camelCase")]
 pub struct UndoRemovePostCommentOrCommunity {
   actor: ObjectId<ApubPerson>,
-  to: [PublicUrl; 1],
+  to: PublicUrlMigration,
   // Note, there is no such thing as Undo/Remove/Mod, so we ignore that
   object: RemoveMod,
   cc: [ObjectId<ApubCommunity>; 1],
index d72b5245dce641f5bae0a0e472b108c371d4efe6..fa7a1119556becab41ee3eac7ec76d747b2c39b4 100644 (file)
@@ -12,6 +12,7 @@ use crate::{
   },
   context::lemmy_context,
   fetcher::object_id::ObjectId,
+  migrations::PublicUrlMigration,
   objects::{community::ApubCommunity, person::ApubPerson},
   PostOrComment,
 };
@@ -25,7 +26,6 @@ use lemmy_api_common::blocking;
 use lemmy_apub_lib::{
   data::Data,
   traits::{ActivityFields, ActivityHandler, ActorType},
-  values::PublicUrl,
   verify::verify_urls_match,
 };
 use lemmy_db_schema::{newtypes::CommunityId, source::community::Community, traits::Crud};
@@ -39,7 +39,7 @@ use url::Url;
 #[serde(rename_all = "camelCase")]
 pub struct UndoVote {
   actor: ObjectId<ApubPerson>,
-  to: [PublicUrl; 1],
+  to: PublicUrlMigration,
   object: Vote,
   cc: [ObjectId<ApubCommunity>; 1],
   #[serde(rename = "type")]
@@ -72,7 +72,7 @@ impl UndoVote {
     )?;
     let undo_vote = UndoVote {
       actor: ObjectId::new(actor.actor_id()),
-      to: [PublicUrl::Public],
+      to: PublicUrlMigration::create(),
       object,
       cc: [ObjectId::new(community.actor_id())],
       kind: UndoType::Undo,
index 5d4c9066e51f63bee436928f9964b8399e458b1d..847ffbd4c23a0524b491c09bf7764c8266c41f37 100644 (file)
@@ -8,6 +8,7 @@ use crate::{
   },
   context::lemmy_context,
   fetcher::object_id::ObjectId,
+  migrations::PublicUrlMigration,
   objects::{community::ApubCommunity, person::ApubPerson},
   PostOrComment,
 };
@@ -17,7 +18,6 @@ use lemmy_api_common::blocking;
 use lemmy_apub_lib::{
   data::Data,
   traits::{ActivityFields, ActivityHandler, ActorType},
-  values::PublicUrl,
 };
 use lemmy_db_schema::{newtypes::CommunityId, source::community::Community, traits::Crud};
 use lemmy_utils::LemmyError;
@@ -58,7 +58,7 @@ impl From<&VoteType> for i16 {
 #[serde(rename_all = "camelCase")]
 pub struct Vote {
   actor: ObjectId<ApubPerson>,
-  to: [PublicUrl; 1],
+  to: PublicUrlMigration,
   pub(in crate::activities::voting) object: ObjectId<PostOrComment>,
   cc: [ObjectId<ApubCommunity>; 1],
   #[serde(rename = "type")]
@@ -80,7 +80,7 @@ impl Vote {
   ) -> Result<Vote, LemmyError> {
     Ok(Vote {
       actor: ObjectId::new(actor.actor_id()),
-      to: [PublicUrl::Public],
+      to: PublicUrlMigration::create(),
       object: ObjectId::new(object.ap_id()),
       cc: [ObjectId::new(community.actor_id())],
       kind: kind.clone(),
index aff571b709728e686c0bb5d9db4710c4caabcd0b..6044a55b655a1264cab5789f5fad39e403d683a4 100644 (file)
@@ -3,7 +3,10 @@ use crate::{
   fetcher::{fetch::fetch_remote_object, object_id::ObjectId},
   objects::{community::Group, person::ApubPerson},
 };
-use activitystreams::collection::{CollectionExt, OrderedCollection};
+use activitystreams::{
+  base::AnyBase,
+  collection::{CollectionExt, OrderedCollection},
+};
 use anyhow::Context;
 use lemmy_api_common::blocking;
 use lemmy_apub_lib::{data::Data, traits::ActivityHandler};
@@ -85,19 +88,33 @@ pub(crate) async fn fetch_community_outbox(
     outbox_activities = outbox_activities[0..20].to_vec();
   }
 
-  for announce in outbox_activities {
-    // TODO: instead of converting like this, we should create a struct CommunityOutbox with
-    //       AnnounceActivity as inner type, but that gives me stackoverflow
-    let ser = serde_json::to_string(&announce)?;
-    let announce: AnnounceActivity = serde_json::from_str(&ser)?;
-    announce
-      .receive(&Data::new(context.clone()), recursion_counter)
-      .await?;
+  // We intentionally ignore errors here. This is because the outbox might contain posts from old
+  // Lemmy versions, or from other software which we cant parse. In that case, we simply skip the
+  // item and only parse the ones that work.
+  for activity in outbox_activities {
+    parse_outbox_item(activity, context, recursion_counter)
+      .await
+      .ok();
   }
 
   Ok(())
 }
 
+async fn parse_outbox_item(
+  announce: AnyBase,
+  context: &LemmyContext,
+  request_counter: &mut i32,
+) -> Result<(), LemmyError> {
+  // TODO: instead of converting like this, we should create a struct CommunityOutbox with
+  //       AnnounceActivity as inner type, but that gives me stackoverflow
+  let ser = serde_json::to_string(&announce)?;
+  let announce: AnnounceActivity = serde_json::from_str(&ser)?;
+  announce
+    .receive(&Data::new(context.clone()), request_counter)
+    .await?;
+  Ok(())
+}
+
 async fn fetch_community_mods(
   context: &LemmyContext,
   group: &Group,
index 4e439fb7fa862f16863ec423bc83572590b431fc..38eb14cc72ba6c83093a00af5afbcd236119be89 100644 (file)
@@ -22,6 +22,7 @@ use url::Url;
 static REQUEST_LIMIT: i32 = 25;
 
 #[derive(Clone, PartialEq, Serialize, Deserialize, Debug)]
+#[serde(transparent)]
 pub struct ObjectId<Kind>(Url, #[serde(skip)] PhantomData<Kind>)
 where
   Kind: FromApub<DataType = LemmyContext> + ApubObject<DataType = LemmyContext> + Send + 'static,
index 493518fe30f2e7f97c16ef2ee7a8847456b20785..e8b075c45353d1e4a1a8aae91f0013c367e5a2cb 100644 (file)
@@ -1,4 +1,5 @@
 use crate::fetcher::{object_id::ObjectId, post_or_comment::PostOrComment};
+use lemmy_apub_lib::values::PublicUrl;
 use serde::{Deserialize, Serialize};
 use url::Url;
 
@@ -28,3 +29,21 @@ pub enum CommentInReplyToMigration {
 //
 // For v0.13, delete [`UndoRemovePostCommentOrCommunity`], and don't handle object deletion in
 // [`RemoveMod`] handler.
+
+/// Migrate value of field `to` from single value to vec.
+///
+/// v0.14: send as single value, accept both
+/// v0.15: send as vec, accept both
+/// v0.16: send and accept only vec
+#[derive(Serialize, Deserialize, Debug, Clone)]
+#[serde(untagged)]
+pub enum PublicUrlMigration {
+  Old(PublicUrl),
+  New([PublicUrl; 1]),
+}
+
+impl PublicUrlMigration {
+  pub(crate) fn create() -> PublicUrlMigration {
+    PublicUrlMigration::Old(PublicUrl::Public)
+  }
+}
index 5745778c79bda95c8bcc7182a97c56fdb54c5b58..fc02b8f3731874ff3048d827d6c6acdda083dee6 100644 (file)
@@ -1,5 +1,5 @@
 use crate::{
-  activities::verify_person_in_community,
+  activities::{verify_is_public, verify_person_in_community},
   context::lemmy_context,
   fetcher::object_id::ObjectId,
   migrations::CommentInReplyToMigration,
@@ -11,14 +11,16 @@ use activitystreams::{
   chrono::NaiveDateTime,
   object::{kind::NoteType, Tombstone},
   primitives::OneOrMany,
+  public,
   unparsed::Unparsed,
 };
 use anyhow::{anyhow, Context};
 use chrono::{DateTime, FixedOffset};
+use html2md::parse_html;
 use lemmy_api_common::blocking;
 use lemmy_apub_lib::{
   traits::{ApubObject, FromApub, ToApub},
-  values::{MediaTypeHtml, MediaTypeMarkdown, PublicUrl},
+  values::{MediaTypeHtml, MediaTypeMarkdown},
   verify::verify_domains_match,
 };
 use lemmy_db_schema::{
@@ -55,17 +57,26 @@ pub struct Note {
   /// Indicates that the object is publicly readable. Unlike [`Post.to`], this one doesn't contain
   /// the community ID, as it would be incompatible with Pleroma (and we can get the community from
   /// the post in [`in_reply_to`]).
-  to: PublicUrl,
+  to: Vec<Url>,
   content: String,
-  media_type: MediaTypeHtml,
-  source: Source,
+  media_type: Option<MediaTypeHtml>,
+  source: SourceCompat,
   in_reply_to: CommentInReplyToMigration,
-  published: DateTime<FixedOffset>,
+  published: Option<DateTime<FixedOffset>>,
   updated: Option<DateTime<FixedOffset>>,
   #[serde(flatten)]
   unparsed: Unparsed,
 }
 
+/// Pleroma puts a raw string in the source, so we have to handle it here for deserialization to work
+#[derive(Clone, Debug, Deserialize, Serialize)]
+#[serde(rename_all = "camelCase")]
+#[serde(untagged)]
+enum SourceCompat {
+  Lemmy(Source),
+  Pleroma(String),
+}
+
 impl Note {
   pub(crate) fn id_unchecked(&self) -> &Url {
     &self.id
@@ -144,6 +155,7 @@ impl Note {
       request_counter,
     )
     .await?;
+    verify_is_public(&self.to)?;
     Ok(())
   }
 }
@@ -222,15 +234,15 @@ impl ToApub for ApubComment {
       r#type: NoteType::Note,
       id: self.ap_id.to_owned().into_inner(),
       attributed_to: ObjectId::new(creator.actor_id),
-      to: PublicUrl::Public,
+      to: vec![public()],
       content: self.content.clone(),
-      media_type: MediaTypeHtml::Html,
-      source: Source {
+      media_type: Some(MediaTypeHtml::Html),
+      source: SourceCompat::Lemmy(Source {
         content: self.content.clone(),
         media_type: MediaTypeMarkdown::Markdown,
-      },
+      }),
       in_reply_to: CommentInReplyToMigration::Old(in_reply_to_vec),
-      published: convert_datetime(self.published),
+      published: Some(convert_datetime(self.published)),
       updated: self.updated.map(convert_datetime),
       unparsed: Default::default(),
     };
@@ -272,8 +284,12 @@ impl FromApub for ApubComment {
       return Err(anyhow!("Post is locked").into());
     }
 
-    let content = &note.source.content;
-    let content_slurs_removed = remove_slurs(content, &context.settings().slur_regex());
+    let content = if let SourceCompat::Lemmy(source) = &note.source {
+      source.content.clone()
+    } else {
+      parse_html(&note.content)
+    };
+    let content_slurs_removed = remove_slurs(&content, &context.settings().slur_regex());
 
     let form = CommentForm {
       creator_id: creator.id,
@@ -282,7 +298,7 @@ impl FromApub for ApubComment {
       content: content_slurs_removed,
       removed: None,
       read: None,
-      published: Some(note.published.naive_local()),
+      published: note.published.map(|u| u.to_owned().naive_local()),
       updated: note.updated.map(|u| u.to_owned().naive_local()),
       deleted: None,
       ap_id,
@@ -292,3 +308,100 @@ impl FromApub for ApubComment {
     Ok(comment.into())
   }
 }
+
+#[cfg(test)]
+mod tests {
+  use super::*;
+  use crate::objects::{
+    community::ApubCommunity,
+    tests::{file_to_json_object, init_context},
+  };
+  use assert_json_diff::assert_json_include;
+  use serial_test::serial;
+
+  async fn prepare_comment_test(
+    url: &Url,
+    context: &LemmyContext,
+  ) -> (ApubPerson, ApubCommunity, ApubPost) {
+    let person_json = file_to_json_object("assets/lemmy-person.json");
+    let person = ApubPerson::from_apub(&person_json, context, url, &mut 0)
+      .await
+      .unwrap();
+    let community_json = file_to_json_object("assets/lemmy-community.json");
+    let community = ApubCommunity::from_apub(&community_json, context, url, &mut 0)
+      .await
+      .unwrap();
+    let post_json = file_to_json_object("assets/lemmy-post.json");
+    let post = ApubPost::from_apub(&post_json, context, url, &mut 0)
+      .await
+      .unwrap();
+    (person, community, post)
+  }
+
+  fn cleanup(data: (ApubPerson, ApubCommunity, ApubPost), context: &LemmyContext) {
+    Post::delete(&*context.pool().get().unwrap(), data.2.id).unwrap();
+    Community::delete(&*context.pool().get().unwrap(), data.1.id).unwrap();
+    Person::delete(&*context.pool().get().unwrap(), data.0.id).unwrap();
+  }
+
+  #[actix_rt::test]
+  #[serial]
+  async fn test_fetch_lemmy_comment() {
+    let context = init_context();
+    let url = Url::parse("https://lemmy.ml/comment/38741").unwrap();
+    let data = prepare_comment_test(&url, &context).await;
+
+    let json = file_to_json_object("assets/lemmy-comment.json");
+    let mut request_counter = 0;
+    let comment = ApubComment::from_apub(&json, &context, &url, &mut request_counter)
+      .await
+      .unwrap();
+
+    assert_eq!(comment.ap_id.clone().into_inner(), url);
+    assert_eq!(comment.content.len(), 1063);
+    assert!(!comment.local);
+    assert_eq!(request_counter, 0);
+
+    let to_apub = comment.to_apub(context.pool()).await.unwrap();
+    assert_json_include!(actual: json, expected: to_apub);
+
+    Comment::delete(&*context.pool().get().unwrap(), comment.id).unwrap();
+    cleanup(data, &context);
+  }
+
+  #[actix_rt::test]
+  #[serial]
+  async fn test_fetch_pleroma_comment() {
+    let context = init_context();
+    let url = Url::parse("https://lemmy.ml/comment/38741").unwrap();
+    let data = prepare_comment_test(&url, &context).await;
+
+    let pleroma_url =
+      Url::parse("https://queer.hacktivis.me/objects/8d4973f4-53de-49cd-8c27-df160e16a9c2")
+        .unwrap();
+    let person_json = file_to_json_object("assets/pleroma-person.json");
+    ApubPerson::from_apub(&person_json, &context, &pleroma_url, &mut 0)
+      .await
+      .unwrap();
+    let json = file_to_json_object("assets/pleroma-comment.json");
+    let mut request_counter = 0;
+    let comment = ApubComment::from_apub(&json, &context, &pleroma_url, &mut request_counter)
+      .await
+      .unwrap();
+
+    assert_eq!(comment.ap_id.clone().into_inner(), pleroma_url);
+    assert_eq!(comment.content.len(), 64);
+    assert!(!comment.local);
+    assert_eq!(request_counter, 0);
+
+    Comment::delete(&*context.pool().get().unwrap(), comment.id).unwrap();
+    cleanup(data, &context);
+  }
+
+  #[actix_rt::test]
+  #[serial]
+  async fn test_html_to_markdown_sanitize() {
+    let parsed = parse_html("<script></script><b>hello</b>");
+    assert_eq!(parsed, "**hello**");
+  }
+}
index 56a86cc85c5bdc384d95b9c86334d7e92b291583..525ae854d5e3b874146a73765a81501c4276e21a 100644 (file)
@@ -36,6 +36,7 @@ use lemmy_utils::{
   LemmyError,
 };
 use lemmy_websocket::LemmyContext;
+use log::debug;
 use serde::{Deserialize, Serialize};
 use serde_with::skip_serializing_none;
 use std::ops::Deref;
@@ -69,7 +70,7 @@ pub struct Group {
   followers: Url,
   endpoints: Endpoints<Url>,
   public_key: PublicKey,
-  published: DateTime<FixedOffset>,
+  published: Option<DateTime<FixedOffset>>,
   updated: Option<DateTime<FixedOffset>>,
   #[serde(flatten)]
   unparsed: Unparsed,
@@ -101,7 +102,7 @@ impl Group {
       title,
       description,
       removed: None,
-      published: Some(group.published.naive_local()),
+      published: group.published.map(|u| u.naive_local()),
       updated: group.updated.map(|u| u.naive_local()),
       deleted: None,
       nsfw: Some(group.sensitive.unwrap_or(false)),
@@ -232,7 +233,7 @@ impl ToApub for ApubCommunity {
         ..Default::default()
       },
       public_key: self.get_public_key()?,
-      published: convert_datetime(self.published),
+      published: Some(convert_datetime(self.published)),
       updated: self.updated.map(convert_datetime),
       unparsed: Default::default(),
     };
@@ -263,11 +264,19 @@ impl FromApub for ApubCommunity {
   ) -> Result<ApubCommunity, LemmyError> {
     let form = Group::from_apub_to_form(group, expected_domain, &context.settings()).await?;
 
+    // Fetching mods and outbox is not necessary for Lemmy to work, so ignore errors. Besides,
+    // we need to ignore these errors so that tests can work entirely offline.
     let community = blocking(context.pool(), move |conn| Community::upsert(conn, &form)).await??;
-    update_community_mods(group, &community, context, request_counter).await?;
+    update_community_mods(group, &community, context, request_counter)
+      .await
+      .map_err(|e| debug!("{}", e))
+      .ok();
 
     // TODO: doing this unconditionally might cause infinite loop for some reason
-    fetch_community_outbox(context, &group.outbox, request_counter).await?;
+    fetch_community_outbox(context, &group.outbox, request_counter)
+      .await
+      .map_err(|e| debug!("{}", e))
+      .ok();
 
     Ok(community.into())
   }
@@ -304,3 +313,42 @@ impl CommunityType for Community {
     Ok(inboxes)
   }
 }
+
+#[cfg(test)]
+mod tests {
+  use super::*;
+  use crate::objects::tests::{file_to_json_object, init_context};
+  use assert_json_diff::assert_json_include;
+  use lemmy_db_schema::traits::Crud;
+  use serial_test::serial;
+
+  #[actix_rt::test]
+  #[serial]
+  async fn test_fetch_lemmy_community() {
+    let context = init_context();
+    let mut json: Group = file_to_json_object("assets/lemmy-community.json");
+    let json_orig = json.clone();
+    // change these links so they dont fetch over the network
+    json.moderators = Some(Url::parse("https://lemmy.ml/c/announcements/not_moderators").unwrap());
+    json.outbox = Url::parse("https://lemmy.ml/c/announcements/not_outbox").unwrap();
+
+    let url = Url::parse("https://lemmy.ml/c/announcements").unwrap();
+    let mut request_counter = 0;
+    let community = ApubCommunity::from_apub(&json, &context, &url, &mut request_counter)
+      .await
+      .unwrap();
+
+    assert_eq!(community.actor_id.clone().into_inner(), url);
+    assert_eq!(community.title, "Announcements");
+    assert!(community.public_key.is_some());
+    assert!(!community.local);
+    assert_eq!(community.description.as_ref().unwrap().len(), 126);
+    // this makes two requests to the (intentionally) broken outbox/moderators collections
+    assert_eq!(request_counter, 2);
+
+    let to_apub = community.to_apub(context.pool()).await.unwrap();
+    assert_json_include!(actual: json_orig, expected: to_apub);
+
+    Community::delete(&*context.pool().get().unwrap(), community.id).unwrap();
+  }
+}
index c5f20c2921088cb414cdea0e98c1afd9291fb80c..61f0f2808debd8900dd9177cff00c571ae12102c 100644 (file)
@@ -53,3 +53,80 @@ where
     Err(anyhow!("Cant convert object to tombstone if it wasnt deleted").into())
   }
 }
+
+#[cfg(test)]
+mod tests {
+  use super::*;
+  use actix::Actor;
+  use diesel::{
+    r2d2::{ConnectionManager, Pool},
+    PgConnection,
+  };
+  use lemmy_apub_lib::activity_queue::create_activity_queue;
+  use lemmy_db_schema::{
+    establish_unpooled_connection,
+    get_database_url_from_env,
+    source::secret::Secret,
+  };
+  use lemmy_utils::{
+    rate_limit::{rate_limiter::RateLimiter, RateLimit},
+    request::build_user_agent,
+    settings::structs::Settings,
+  };
+  use lemmy_websocket::{chat_server::ChatServer, LemmyContext};
+  use reqwest::Client;
+  use serde::de::DeserializeOwned;
+  use std::{fs::File, io::BufReader, sync::Arc};
+  use tokio::sync::Mutex;
+
+  // TODO: would be nice if we didnt have to use a full context for tests.
+  //       or at least write a helper function so this code is shared with main.rs
+  pub(crate) fn init_context() -> LemmyContext {
+    // call this to run migrations
+    establish_unpooled_connection();
+    let settings = Settings::init().unwrap();
+    let rate_limiter = RateLimit {
+      rate_limiter: Arc::new(Mutex::new(RateLimiter::default())),
+      rate_limit_config: settings.rate_limit.to_owned().unwrap_or_default(),
+    };
+    let client = Client::builder()
+      .user_agent(build_user_agent(&settings))
+      .build()
+      .unwrap();
+    let activity_queue = create_activity_queue();
+    let secret = Secret {
+      id: 0,
+      jwt_secret: "".to_string(),
+    };
+    let db_url = match get_database_url_from_env() {
+      Ok(url) => url,
+      Err(_) => settings.get_database_url(),
+    };
+    let manager = ConnectionManager::<PgConnection>::new(&db_url);
+    let pool = Pool::builder()
+      .max_size(settings.database.pool_size)
+      .build(manager)
+      .unwrap_or_else(|_| panic!("Error connecting to {}", db_url));
+    async fn x() -> Result<String, LemmyError> {
+      Ok("".to_string())
+    }
+    let chat_server = ChatServer::startup(
+      pool.clone(),
+      rate_limiter,
+      |_, _, _, _| Box::pin(x()),
+      |_, _, _, _| Box::pin(x()),
+      client.clone(),
+      activity_queue.clone(),
+      settings.clone(),
+      secret.clone(),
+    )
+    .start();
+    LemmyContext::create(pool, chat_server, client, activity_queue, settings, secret)
+  }
+
+  pub(crate) fn file_to_json_object<T: DeserializeOwned>(path: &str) -> T {
+    let file = File::open(path).unwrap();
+    let reader = BufReader::new(file);
+    serde_json::from_reader(reader).unwrap()
+  }
+}
index 53503d007ff2d5d323b962f767c4a31d99f509b4..7d5b99234a758664034e4bda71c4d4a06c3d8ce6 100644 (file)
@@ -67,7 +67,7 @@ pub struct Person {
   outbox: Url,
   endpoints: Endpoints<Url>,
   public_key: PublicKey,
-  published: DateTime<FixedOffset>,
+  published: Option<DateTime<FixedOffset>>,
   updated: Option<DateTime<FixedOffset>>,
   #[serde(flatten)]
   unparsed: Unparsed,
@@ -192,7 +192,7 @@ impl ToApub for ApubPerson {
       icon,
       image,
       matrix_user_id: self.matrix_user_id.clone(),
-      published: convert_datetime(self.published),
+      published: Some(convert_datetime(self.published)),
       outbox: generate_outbox_url(&self.actor_id)?.into(),
       endpoints: Endpoints {
         shared_inbox: self.shared_inbox_url.clone().map(|s| s.into()),
@@ -245,7 +245,7 @@ impl FromApub for ApubPerson {
       deleted: None,
       avatar: Some(person.icon.clone().map(|i| i.url.into())),
       banner: Some(person.image.clone().map(|i| i.url.into())),
-      published: Some(person.published.naive_local()),
+      published: person.published.map(|u| u.clone().naive_local()),
       updated: person.updated.map(|u| u.clone().naive_local()),
       actor_id,
       bio: Some(bio),
@@ -266,3 +266,58 @@ impl FromApub for ApubPerson {
     Ok(person.into())
   }
 }
+
+#[cfg(test)]
+mod tests {
+  use super::*;
+  use crate::objects::tests::{file_to_json_object, init_context};
+  use assert_json_diff::assert_json_include;
+  use lemmy_db_schema::traits::Crud;
+  use serial_test::serial;
+
+  #[actix_rt::test]
+  #[serial]
+  async fn test_fetch_lemmy_person() {
+    let context = init_context();
+    let json = file_to_json_object("assets/lemmy-person.json");
+    let url = Url::parse("https://lemmy.ml/u/nutomic").unwrap();
+    let mut request_counter = 0;
+    let person = ApubPerson::from_apub(&json, &context, &url, &mut request_counter)
+      .await
+      .unwrap();
+
+    assert_eq!(person.actor_id.clone().into_inner(), url);
+    assert_eq!(person.name, "nutomic");
+    assert!(person.public_key.is_some());
+    assert!(!person.local);
+    assert!(person.bio.is_some());
+    assert_eq!(request_counter, 0);
+
+    let to_apub = person.to_apub(context.pool()).await.unwrap();
+    assert_json_include!(actual: json, expected: to_apub);
+
+    DbPerson::delete(&*context.pool().get().unwrap(), person.id).unwrap();
+  }
+
+  #[actix_rt::test]
+  #[serial]
+  async fn test_fetch_pleroma_person() {
+    let context = init_context();
+    let json = file_to_json_object("assets/pleroma-person.json");
+    let url = Url::parse("https://queer.hacktivis.me/users/lanodan").unwrap();
+    let mut request_counter = 0;
+    let person = ApubPerson::from_apub(&json, &context, &url, &mut request_counter)
+      .await
+      .unwrap();
+
+    assert_eq!(person.actor_id.clone().into_inner(), url);
+    assert_eq!(person.name, "lanodan");
+    assert!(person.public_key.is_some());
+    assert!(!person.local);
+    assert_eq!(request_counter, 0);
+    // TODO: pleroma uses summary for user profile, while we use content
+    //assert!(person.bio.is_some());
+
+    DbPerson::delete(&*context.pool().get().unwrap(), person.id).unwrap();
+  }
+}
index dc6d965180383597b42360af48b768baa2649df9..47b4b99a930e574667ee2cf509a3f00271822c62 100644 (file)
@@ -1,5 +1,5 @@
 use crate::{
-  activities::{extract_community, verify_person_in_community},
+  activities::{extract_community, verify_is_public, verify_person_in_community},
   context::lemmy_context,
   fetcher::object_id::ObjectId,
   objects::{create_tombstone, person::ApubPerson, ImageObject, Source},
@@ -51,17 +51,17 @@ pub struct Page {
   r#type: PageType,
   id: Url,
   pub(crate) attributed_to: ObjectId<ApubPerson>,
-  to: [Url; 2],
+  to: Vec<Url>,
   name: String,
   content: Option<String>,
-  media_type: MediaTypeHtml,
+  media_type: Option<MediaTypeHtml>,
   source: Option<Source>,
   url: Option<Url>,
   image: Option<ImageObject>,
   pub(crate) comments_enabled: Option<bool>,
   sensitive: Option<bool>,
   pub(crate) stickied: Option<bool>,
-  published: DateTime<FixedOffset>,
+  published: Option<DateTime<FixedOffset>>,
   updated: Option<DateTime<FixedOffset>>,
   #[serde(flatten)]
   unparsed: Unparsed,
@@ -109,6 +109,7 @@ impl Page {
       request_counter,
     )
     .await?;
+    verify_is_public(&self.to.clone())?;
     Ok(())
   }
 }
@@ -186,17 +187,17 @@ impl ToApub for ApubPost {
       r#type: PageType::Page,
       id: self.ap_id.clone().into(),
       attributed_to: ObjectId::new(creator.actor_id),
-      to: [community.actor_id.into(), public()],
+      to: vec![community.actor_id.into(), public()],
       name: self.name.clone(),
       content: self.body.as_ref().map(|b| markdown_to_html(b)),
-      media_type: MediaTypeHtml::Html,
+      media_type: Some(MediaTypeHtml::Html),
       source,
       url: self.url.clone().map(|u| u.into()),
       image,
       comments_enabled: Some(!self.locked),
       sensitive: Some(self.nsfw),
       stickied: Some(self.stickied),
-      published: convert_datetime(self.published),
+      published: Some(convert_datetime(self.published)),
       updated: self.updated.map(convert_datetime),
       unparsed: Default::default(),
     };
@@ -260,7 +261,7 @@ impl FromApub for ApubPost {
       community_id: community.id,
       removed: None,
       locked: page.comments_enabled.map(|e| !e),
-      published: Some(page.published.naive_local()),
+      published: page.published.map(|u| u.naive_local()),
       updated: page.updated.map(|u| u.naive_local()),
       deleted: None,
       nsfw: page.sensitive,
@@ -276,3 +277,49 @@ impl FromApub for ApubPost {
     Ok(post.into())
   }
 }
+
+#[cfg(test)]
+mod tests {
+  use super::*;
+  use crate::objects::{
+    community::ApubCommunity,
+    tests::{file_to_json_object, init_context},
+  };
+  use assert_json_diff::assert_json_include;
+  use serial_test::serial;
+
+  #[actix_rt::test]
+  #[serial]
+  async fn test_fetch_lemmy_post() {
+    let context = init_context();
+    let url = Url::parse("https://lemmy.ml/post/55143").unwrap();
+    let community_json = file_to_json_object("assets/lemmy-community.json");
+    let community = ApubCommunity::from_apub(&community_json, &context, &url, &mut 0)
+      .await
+      .unwrap();
+    let person_json = file_to_json_object("assets/lemmy-person.json");
+    let person = ApubPerson::from_apub(&person_json, &context, &url, &mut 0)
+      .await
+      .unwrap();
+    let json = file_to_json_object("assets/lemmy-post.json");
+    let mut request_counter = 0;
+    let post = ApubPost::from_apub(&json, &context, &url, &mut request_counter)
+      .await
+      .unwrap();
+
+    assert_eq!(post.ap_id.clone().into_inner(), url);
+    assert_eq!(post.name, "Statement on Politics of Lemmy.ml");
+    assert!(post.body.is_some());
+    assert_eq!(post.body.as_ref().unwrap().len(), 2144);
+    assert!(!post.locked);
+    assert!(post.stickied);
+    assert_eq!(request_counter, 0);
+
+    let to_apub = post.to_apub(context.pool()).await.unwrap();
+    assert_json_include!(actual: json, expected: to_apub);
+
+    Post::delete(&*context.pool().get().unwrap(), post.id).unwrap();
+    Person::delete(&*context.pool().get().unwrap(), person.id).unwrap();
+    Community::delete(&*context.pool().get().unwrap(), community.id).unwrap();
+  }
+}
index cde1966ed8228335443b9b765431fe4abc0ee781..cf622eccbdaf7a3bb4c7ee44cbb1034503e6b378 100644 (file)
@@ -46,7 +46,7 @@ pub struct Note {
   content: String,
   media_type: MediaTypeHtml,
   source: Source,
-  published: DateTime<FixedOffset>,
+  published: Option<DateTime<FixedOffset>>,
   updated: Option<DateTime<FixedOffset>>,
   #[serde(flatten)]
   unparsed: Unparsed,
@@ -146,7 +146,7 @@ impl ToApub for ApubPrivateMessage {
         content: self.content.clone(),
         media_type: MediaTypeMarkdown::Markdown,
       },
-      published: convert_datetime(self.published),
+      published: Some(convert_datetime(self.published)),
       updated: self.updated.map(convert_datetime),
       unparsed: Default::default(),
     };
@@ -185,7 +185,7 @@ impl FromApub for ApubPrivateMessage {
       creator_id: creator.id,
       recipient_id: recipient.id,
       content: note.source.content.clone(),
-      published: Some(note.published.naive_local()),
+      published: note.published.map(|u| u.to_owned().naive_local()),
       updated: note.updated.map(|u| u.to_owned().naive_local()),
       deleted: None,
       read: None,
@@ -199,3 +199,43 @@ impl FromApub for ApubPrivateMessage {
     Ok(pm.into())
   }
 }
+
+#[cfg(test)]
+mod tests {
+  use super::*;
+  use crate::objects::tests::{file_to_json_object, init_context};
+  use assert_json_diff::assert_json_include;
+  use serial_test::serial;
+
+  #[actix_rt::test]
+  #[serial]
+  async fn test_fetch_lemmy_pm() {
+    let context = init_context();
+    let url = Url::parse("https://lemmy.ml/private_message/1621").unwrap();
+    let lemmy_person = file_to_json_object("assets/lemmy-person.json");
+    let person1 = ApubPerson::from_apub(&lemmy_person, &context, &url, &mut 0)
+      .await
+      .unwrap();
+    let pleroma_person = file_to_json_object("assets/pleroma-person.json");
+    let pleroma_url = Url::parse("https://queer.hacktivis.me/users/lanodan").unwrap();
+    let person2 = ApubPerson::from_apub(&pleroma_person, &context, &pleroma_url, &mut 0)
+      .await
+      .unwrap();
+    let json = file_to_json_object("assets/lemmy-private-message.json");
+    let mut request_counter = 0;
+    let pm = ApubPrivateMessage::from_apub(&json, &context, &url, &mut request_counter)
+      .await
+      .unwrap();
+
+    assert_eq!(pm.ap_id.clone().into_inner(), url);
+    assert_eq!(pm.content.len(), 4);
+    assert_eq!(request_counter, 0);
+
+    let to_apub = pm.to_apub(context.pool()).await.unwrap();
+    assert_json_include!(actual: json, expected: to_apub);
+
+    PrivateMessage::delete(&*context.pool().get().unwrap(), pm.id).unwrap();
+    Person::delete(&*context.pool().get().unwrap(), person1.id).unwrap();
+    Person::delete(&*context.pool().get().unwrap(), person2.id).unwrap();
+  }
+}
index 735c775e00fe3fe55457e52329e8319fded12555..6a2e9ee8496ff9608c00d86cf6fad405271bbd23 100644 (file)
@@ -33,6 +33,10 @@ impl Crud for PrivateMessage {
       .set(private_message_form)
       .get_result::<Self>(conn)
   }
+  fn delete(conn: &PgConnection, pm_id: Self::IdType) -> Result<usize, Error> {
+    use crate::schema::private_message::dsl::*;
+    diesel::delete(private_message.find(pm_id)).execute(conn)
+  }
 }
 
 impl PrivateMessage {
index fbf9d25a3409ba6ece8cf17c72f37d3f5de2458b..b7ab5bf28b17680efb4f90b02094909361cabfc0 100644 (file)
@@ -16,12 +16,12 @@ pub mod routes;
 pub mod send;
 
 pub struct LemmyContext {
-  pub pool: DbPool,
-  pub chat_server: Addr<ChatServer>,
-  pub client: Client,
-  pub activity_queue: QueueHandle,
-  pub settings: Settings,
-  pub secret: Secret,
+  pool: DbPool,
+  chat_server: Addr<ChatServer>,
+  client: Client,
+  activity_queue: QueueHandle,
+  settings: Settings,
+  secret: Secret,
 }
 
 impl LemmyContext {