Repository: bookwyrm-social/bookwyrm

Issue:
Boosts do not propagate to Mastodon
**Describe the bug**
When I boost a status with a BW-account, it is shown on my profile and on other BW instances. It is not shown for following Mastodon users though. Also, the OP will not get notified that you boosted the statust in some cases, e.g. if they are on Mastodon. Sometimes happens across bw instances too, could not reproduce this yet.

**To Reproduce**
Steps to reproduce the behavior:
1. Boost a status with a BW account
2. Check the BW account remote from Mastodon and the notifications of the OP

**Expected behavior**
The status should get boosted into Mastodon (and other AP platforms) user's timelines and the OPs should get notified.

**Screenshots**
Me boosting a 21h old status on bw:
![image](https://user-images.githubusercontent.com/66644064/111921668-0d6e6c00-8a96-11eb-90b2-d2df217e5c23.png)

My account from Mastodon (has been followed all the time), last activity my own status 5d ago:
![image](https://user-images.githubusercontent.com/66644064/111921700-43135500-8a96-11eb-8e11-4c9bc9da6ca9.png)



Discussion:
Huh! I just replicated and I have a sucessful celery task, so it's not a broadcast issue. There may be something wrong with the activitypub that's causing mastodon to reject this; I'll take a closer look at the response I'm getting from mastodon.

Resolving patch:
diff --git a/bookwyrm/activitypub/base_activity.py b/bookwyrm/activitypub/base_activity.py
--- a/bookwyrm/activitypub/base_activity.py
+++ b/bookwyrm/activitypub/base_activity.py
@@ -248,12 +248,14 @@ def get_model_from_type(activity_type):
     return model[0]
 
 
-def resolve_remote_id(remote_id, model=None, refresh=False, save=True):
+def resolve_remote_id(
+    remote_id, model=None, refresh=False, save=True, get_activity=False
+):
     """ take a remote_id and return an instance, creating if necessary """
     if model:  # a bonus check we can do if we already know the model
         result = model.find_existing_by_remote_id(remote_id)
         if result and not refresh:
-            return result
+            return result if not get_activity else result.to_activity_dataclass()
 
     # load the data and create the object
     try:
@@ -269,8 +271,11 @@ def resolve_remote_id(remote_id, model=None, refresh=False, save=True):
     # check for existing items with shared unique identifiers
     result = model.find_existing(data)
     if result and not refresh:
-        return result
+        return result if not get_activity else result.to_activity_dataclass()
 
     item = model.activity_serializer(**data)
+    if get_activity:
+        return item
+
     # if we're refreshing, "result" will be set and we'll update it
     return item.to_model(model=model, instance=result, save=save)
diff --git a/bookwyrm/activitypub/verbs.py b/bookwyrm/activitypub/verbs.py
--- a/bookwyrm/activitypub/verbs.py
+++ b/bookwyrm/activitypub/verbs.py
@@ -1,5 +1,5 @@
 """ undo wrapper activity """
-from dataclasses import dataclass
+from dataclasses import dataclass, field
 from typing import List
 from django.apps import apps
 
@@ -191,6 +191,9 @@ def action(self):
 class Announce(Verb):
     """ boosting a status """
 
+    published: str
+    to: List[str] = field(default_factory=lambda: [])
+    cc: List[str] = field(default_factory=lambda: [])
     object: str
     type: str = "Announce"
 
diff --git a/bookwyrm/models/status.py b/bookwyrm/models/status.py
--- a/bookwyrm/models/status.py
+++ b/bookwyrm/models/status.py
@@ -123,12 +123,14 @@ def ignore_activity(cls, activity):  # pylint: disable=too-many-return-statement
         """ keep notes if they are replies to existing statuses """
         if activity.type == "Announce":
             try:
-                boosted = activitypub.resolve_remote_id(activity.object, save=False)
+                boosted = activitypub.resolve_remote_id(
+                    activity.object, get_activity=True
+                )
             except activitypub.ActivitySerializerError:
                 # if we can't load the status, definitely ignore it
                 return True
             # keep the boost if we would keep the status
-            return cls.ignore_activity(boosted.to_activity_dataclass())
+            return cls.ignore_activity(boosted)
 
         # keep if it if it's a custom type
         if activity.type != "Note":
@@ -375,7 +377,7 @@ def __init__(self, *args, **kwargs):
         """ the user field is "actor" here instead of "attributedTo" """
         super().__init__(*args, **kwargs)
 
-        reserve_fields = ["user", "boosted_status"]
+        reserve_fields = ["user", "boosted_status", "published_date", "privacy"]
         self.simple_fields = [f for f in self.simple_fields if f.name in reserve_fields]
         self.activity_fields = self.simple_fields
         self.many_to_many_fields = []


Test patch:
diff --git a/bookwyrm/tests/models/test_status_model.py b/bookwyrm/tests/models/test_status_model.py
--- a/bookwyrm/tests/models/test_status_model.py
+++ b/bookwyrm/tests/models/test_status_model.py
@@ -441,6 +441,9 @@ def test_ignore_activity_boost(self, *_):
             id="http://www.faraway.com/boost/12",
             actor=self.remote_user.remote_id,
             object="http://fish.com/nothing",
+            published="2021-03-24T18:59:41.841208+00:00",
+            cc="",
+            to="",
         )
 
         responses.add(responses.GET, "http://fish.com/nothing", status=404)
diff --git a/bookwyrm/tests/views/test_inbox.py b/bookwyrm/tests/views/test_inbox.py
--- a/bookwyrm/tests/views/test_inbox.py
+++ b/bookwyrm/tests/views/test_inbox.py
@@ -559,6 +559,9 @@ def test_handle_boost(self, _):
             "id": "%s/boost" % self.status.remote_id,
             "actor": self.remote_user.remote_id,
             "object": self.status.remote_id,
+            "to": ["https://www.w3.org/ns/activitystreams#public"],
+            "cc": ["https://example.com/user/mouse/followers"],
+            "published": "Mon, 25 May 2020 19:31:20 GMT",
         }
         with patch("bookwyrm.models.status.Status.ignore_activity") as discarder:
             discarder.return_value = False
@@ -607,6 +610,9 @@ def test_handle_unboost(self):
                 "id": boost.remote_id,
                 "actor": self.remote_user.remote_id,
                 "object": self.status.remote_id,
+                "to": ["https://www.w3.org/ns/activitystreams#public"],
+                "cc": ["https://example.com/user/mouse/followers"],
+                "published": "Mon, 25 May 2020 19:31:20 GMT",
             },
         }
         with patch(
@@ -614,6 +620,7 @@ def test_handle_unboost(self):
         ) as redis_mock:
             views.inbox.activity_task(activity)
             self.assertTrue(redis_mock.called)
+        self.assertFalse(models.Boost.objects.exists())
 
     def test_handle_unboost_unknown_boost(self):
         """ undo a boost """