We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Remote follow silently failing
When I try to follow a remote user, the remote user is not notified and the relationship is not confirmed. No errors show up locally that I've found.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `bookwyrm/signatures.py`
Content:
```
1 ''' signs activitypub activities '''
2 import hashlib
3 from urllib.parse import urlparse
4 import datetime
5 from base64 import b64encode, b64decode
6 
7 from Crypto import Random
8 from Crypto.PublicKey import RSA
9 from Crypto.Signature import pkcs1_15 #pylint: disable=no-name-in-module
10 from Crypto.Hash import SHA256
11 
12 MAX_SIGNATURE_AGE = 300
13 
14 def create_key_pair():
15     ''' a new public/private key pair, used for creating new users '''
16     random_generator = Random.new().read
17     key = RSA.generate(1024, random_generator)
18     private_key = key.export_key().decode('utf8')
19     public_key = key.publickey().export_key().decode('utf8')
20 
21     return private_key, public_key
22 
23 
24 def make_signature(sender, destination, date, digest):
25     ''' uses a private key to sign an outgoing message '''
26     inbox_parts = urlparse(destination)
27     signature_headers = [
28         '(request-target): post %s' % inbox_parts.path,
29         'host: %s' % inbox_parts.netloc,
30         'date: %s' % date,
31         'digest: %s' % digest,
32     ]
33     message_to_sign = '\n'.join(signature_headers)
34     signer = pkcs1_15.new(RSA.import_key(sender.private_key))
35     signed_message = signer.sign(SHA256.new(message_to_sign.encode('utf8')))
36     signature = {
37         'keyId': '%s#main-key' % sender.remote_id,
38         'algorithm': 'rsa-sha256',
39         'headers': '(request-target) host date digest',
40         'signature': b64encode(signed_message).decode('utf8'),
41     }
42     return ','.join('%s="%s"' % (k, v) for (k, v) in signature.items())
43 
44 
45 def make_digest(data):
46     ''' creates a message digest for signing '''
47     return 'SHA-256=' + b64encode(hashlib.sha256(data).digest()).decode('utf-8')
48 
49 
50 def verify_digest(request):
51     ''' checks if a digest is syntactically valid and matches the message '''
52     algorithm, digest = request.headers['digest'].split('=', 1)
53     if algorithm == 'SHA-256':
54         hash_function = hashlib.sha256
55     elif algorithm == 'SHA-512':
56         hash_function = hashlib.sha512
57     else:
58         raise ValueError("Unsupported hash function: {}".format(algorithm))
59 
60     expected = hash_function(request.body).digest()
61     if b64decode(digest) != expected:
62         raise ValueError("Invalid HTTP Digest header")
63 
64 class Signature:
65     ''' read and validate incoming signatures '''
66     def __init__(self, key_id, headers, signature):
67         self.key_id = key_id
68         self.headers = headers
69         self.signature = signature
70 
71     @classmethod
72     def parse(cls, request):
73         ''' extract and parse a signature from an http request '''
74         signature_dict = {}
75         for pair in request.headers['Signature'].split(','):
76             k, v = pair.split('=', 1)
77             v = v.replace('"', '')
78             signature_dict[k] = v
79 
80         try:
81             key_id = signature_dict['keyId']
82             headers = signature_dict['headers']
83             signature = b64decode(signature_dict['signature'])
84         except KeyError:
85             raise ValueError('Invalid auth header')
86 
87         return cls(key_id, headers, signature)
88 
89     def verify(self, public_key, request):
90         ''' verify rsa signature '''
91         if http_date_age(request.headers['date']) > MAX_SIGNATURE_AGE:
92             raise ValueError(
93                 "Request too old: %s" % (request.headers['date'],))
94         public_key = RSA.import_key(public_key)
95 
96         comparison_string = []
97         for signed_header_name in self.headers.split(' '):
98             if signed_header_name == '(request-target)':
99                 comparison_string.append(
100                     '(request-target): post %s' % request.path)
101             else:
102                 if signed_header_name == 'digest':
103                     verify_digest(request)
104                 comparison_string.append('%s: %s' % (
105                     signed_header_name,
106                     request.headers[signed_header_name]
107                 ))
108         comparison_string = '\n'.join(comparison_string)
109 
110         signer = pkcs1_15.new(public_key)
111         digest = SHA256.new()
112         digest.update(comparison_string.encode())
113 
114         # raises a ValueError if it fails
115         signer.verify(digest, self.signature)
116 
117 
118 def http_date_age(datestr):
119     ''' age of a signature in seconds '''
120     parsed = datetime.datetime.strptime(datestr, '%a, %d %b %Y %H:%M:%S GMT')
121     delta = datetime.datetime.utcnow() - parsed
122     return delta.total_seconds()
123
```

Path: `bookwyrm/broadcast.py`
Content:
```
1 ''' send out activitypub messages '''
2 import json
3 from django.utils.http import http_date
4 import requests
5 
6 from bookwyrm import models
7 from bookwyrm.activitypub import ActivityEncoder
8 from bookwyrm.tasks import app
9 from bookwyrm.signatures import make_signature, make_digest
10 
11 
12 def get_public_recipients(user, software=None):
13     ''' everybody and their public inboxes '''
14     followers = user.followers.filter(local=False)
15     if software:
16         followers = followers.filter(bookwyrm_user=(software == 'bookwyrm'))
17 
18     # we want shared inboxes when available
19     shared = followers.filter(
20         shared_inbox__isnull=False
21     ).values_list('shared_inbox', flat=True).distinct()
22 
23     # if a user doesn't have a shared inbox, we need their personal inbox
24     # iirc pixelfed doesn't have shared inboxes
25     inboxes = followers.filter(
26         shared_inbox__isnull=True
27     ).values_list('inbox', flat=True)
28 
29     return list(shared) + list(inboxes)
30 
31 
32 def broadcast(sender, activity, software=None, \
33               privacy='public', direct_recipients=None):
34     ''' send out an event '''
35     # start with parsing the direct recipients
36     recipients = [u.inbox for u in direct_recipients or []]
37     # and then add any other recipients
38     if privacy == 'public':
39         recipients += get_public_recipients(sender, software=software)
40     broadcast_task.delay(
41         sender.id,
42         json.dumps(activity, cls=ActivityEncoder),
43         recipients
44     )
45 
46 
47 @app.task
48 def broadcast_task(sender_id, activity, recipients):
49     ''' the celery task for broadcast '''
50     sender = models.User.objects.get(id=sender_id)
51     errors = []
52     for recipient in recipients:
53         try:
54             sign_and_send(sender, activity, recipient)
55         except requests.exceptions.HTTPError as e:
56             errors.append({
57                 'error': str(e),
58                 'recipient': recipient,
59                 'activity': activity,
60             })
61     return errors
62 
63 
64 def sign_and_send(sender, activity, destination):
65     ''' crpyto whatever and http junk '''
66     now = http_date()
67 
68     if not sender.private_key:
69         # this shouldn't happen. it would be bad if it happened.
70         raise ValueError('No private key found for sender')
71 
72     data = json.dumps(activity).encode('utf-8')
73     digest = make_digest(data)
74 
75     response = requests.post(
76         destination,
77         data=data,
78         headers={
79             'Date': now,
80             'Digest': digest,
81             'Signature': make_signature(sender, destination, now, digest),
82             'Content-Type': 'application/activity+json; charset=utf-8',
83         },
84     )
85     if not response.ok:
86         response.raise_for_status()
87     return response
88
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/bookwyrm/broadcast.py b/bookwyrm/broadcast.py
--- a/bookwyrm/broadcast.py
+++ b/bookwyrm/broadcast.py
@@ -61,7 +61,7 @@
     return errors
 
 
-def sign_and_send(sender, activity, destination):
+def sign_and_send(sender, data, destination):
     ''' crpyto whatever and http junk '''
     now = http_date()
 
@@ -69,7 +69,6 @@
         # this shouldn't happen. it would be bad if it happened.
         raise ValueError('No private key found for sender')
 
-    data = json.dumps(activity).encode('utf-8')
     digest = make_digest(data)
 
     response = requests.post(
diff --git a/bookwyrm/signatures.py b/bookwyrm/signatures.py
--- a/bookwyrm/signatures.py
+++ b/bookwyrm/signatures.py
@@ -44,7 +44,8 @@
 
 def make_digest(data):
     ''' creates a message digest for signing '''
-    return 'SHA-256=' + b64encode(hashlib.sha256(data).digest()).decode('utf-8')
+    return 'SHA-256=' + b64encode(hashlib.sha256(data.encode('utf-8'))\
+                        .digest()).decode('utf-8')
 
 
 def verify_digest(request):

{"golden_diff": "diff --git a/bookwyrm/broadcast.py b/bookwyrm/broadcast.py\n--- a/bookwyrm/broadcast.py\n+++ b/bookwyrm/broadcast.py\n@@ -61,7 +61,7 @@\n     return errors\n \n \n-def sign_and_send(sender, activity, destination):\n+def sign_and_send(sender, data, destination):\n     ''' crpyto whatever and http junk '''\n     now = http_date()\n \n@@ -69,7 +69,6 @@\n         # this shouldn't happen. it would be bad if it happened.\n         raise ValueError('No private key found for sender')\n \n-    data = json.dumps(activity).encode('utf-8')\n     digest = make_digest(data)\n \n     response = requests.post(\ndiff --git a/bookwyrm/signatures.py b/bookwyrm/signatures.py\n--- a/bookwyrm/signatures.py\n+++ b/bookwyrm/signatures.py\n@@ -44,7 +44,8 @@\n \n def make_digest(data):\n     ''' creates a message digest for signing '''\n-    return 'SHA-256=' + b64encode(hashlib.sha256(data).digest()).decode('utf-8')\n+    return 'SHA-256=' + b64encode(hashlib.sha256(data.encode('utf-8'))\\\n+                        .digest()).decode('utf-8')\n \n \n def verify_digest(request):\n", "issue": "Remote follow silently failing\nWhen I try to follow a remote user, the remote user is not notified and the relationship is not confirmed. No errors show up locally that I've found.\n", "before_files": [{"content": "''' signs activitypub activities '''\nimport hashlib\nfrom urllib.parse import urlparse\nimport datetime\nfrom base64 import b64encode, b64decode\n\nfrom Crypto import Random\nfrom Crypto.PublicKey import RSA\nfrom Crypto.Signature import pkcs1_15 #pylint: disable=no-name-in-module\nfrom Crypto.Hash import SHA256\n\nMAX_SIGNATURE_AGE = 300\n\ndef create_key_pair():\n    ''' a new public/private key pair, used for creating new users '''\n    random_generator = Random.new().read\n    key = RSA.generate(1024, random_generator)\n    private_key = key.export_key().decode('utf8')\n    public_key = key.publickey().export_key().decode('utf8')\n\n    return private_key, public_key\n\n\ndef make_signature(sender, destination, date, digest):\n    ''' uses a private key to sign an outgoing message '''\n    inbox_parts = urlparse(destination)\n    signature_headers = [\n        '(request-target): post %s' % inbox_parts.path,\n        'host: %s' % inbox_parts.netloc,\n        'date: %s' % date,\n        'digest: %s' % digest,\n    ]\n    message_to_sign = '\\n'.join(signature_headers)\n    signer = pkcs1_15.new(RSA.import_key(sender.private_key))\n    signed_message = signer.sign(SHA256.new(message_to_sign.encode('utf8')))\n    signature = {\n        'keyId': '%s#main-key' % sender.remote_id,\n        'algorithm': 'rsa-sha256',\n        'headers': '(request-target) host date digest',\n        'signature': b64encode(signed_message).decode('utf8'),\n    }\n    return ','.join('%s=\"%s\"' % (k, v) for (k, v) in signature.items())\n\n\ndef make_digest(data):\n    ''' creates a message digest for signing '''\n    return 'SHA-256=' + b64encode(hashlib.sha256(data).digest()).decode('utf-8')\n\n\ndef verify_digest(request):\n    ''' checks if a digest is syntactically valid and matches the message '''\n    algorithm, digest = request.headers['digest'].split('=', 1)\n    if algorithm == 'SHA-256':\n        hash_function = hashlib.sha256\n    elif algorithm == 'SHA-512':\n        hash_function = hashlib.sha512\n    else:\n        raise ValueError(\"Unsupported hash function: {}\".format(algorithm))\n\n    expected = hash_function(request.body).digest()\n    if b64decode(digest) != expected:\n        raise ValueError(\"Invalid HTTP Digest header\")\n\nclass Signature:\n    ''' read and validate incoming signatures '''\n    def __init__(self, key_id, headers, signature):\n        self.key_id = key_id\n        self.headers = headers\n        self.signature = signature\n\n    @classmethod\n    def parse(cls, request):\n        ''' extract and parse a signature from an http request '''\n        signature_dict = {}\n        for pair in request.headers['Signature'].split(','):\n            k, v = pair.split('=', 1)\n            v = v.replace('\"', '')\n            signature_dict[k] = v\n\n        try:\n            key_id = signature_dict['keyId']\n            headers = signature_dict['headers']\n            signature = b64decode(signature_dict['signature'])\n        except KeyError:\n            raise ValueError('Invalid auth header')\n\n        return cls(key_id, headers, signature)\n\n    def verify(self, public_key, request):\n        ''' verify rsa signature '''\n        if http_date_age(request.headers['date']) > MAX_SIGNATURE_AGE:\n            raise ValueError(\n                \"Request too old: %s\" % (request.headers['date'],))\n        public_key = RSA.import_key(public_key)\n\n        comparison_string = []\n        for signed_header_name in self.headers.split(' '):\n            if signed_header_name == '(request-target)':\n                comparison_string.append(\n                    '(request-target): post %s' % request.path)\n            else:\n                if signed_header_name == 'digest':\n                    verify_digest(request)\n                comparison_string.append('%s: %s' % (\n                    signed_header_name,\n                    request.headers[signed_header_name]\n                ))\n        comparison_string = '\\n'.join(comparison_string)\n\n        signer = pkcs1_15.new(public_key)\n        digest = SHA256.new()\n        digest.update(comparison_string.encode())\n\n        # raises a ValueError if it fails\n        signer.verify(digest, self.signature)\n\n\ndef http_date_age(datestr):\n    ''' age of a signature in seconds '''\n    parsed = datetime.datetime.strptime(datestr, '%a, %d %b %Y %H:%M:%S GMT')\n    delta = datetime.datetime.utcnow() - parsed\n    return delta.total_seconds()\n", "path": "bookwyrm/signatures.py"}, {"content": "''' send out activitypub messages '''\nimport json\nfrom django.utils.http import http_date\nimport requests\n\nfrom bookwyrm import models\nfrom bookwyrm.activitypub import ActivityEncoder\nfrom bookwyrm.tasks import app\nfrom bookwyrm.signatures import make_signature, make_digest\n\n\ndef get_public_recipients(user, software=None):\n    ''' everybody and their public inboxes '''\n    followers = user.followers.filter(local=False)\n    if software:\n        followers = followers.filter(bookwyrm_user=(software == 'bookwyrm'))\n\n    # we want shared inboxes when available\n    shared = followers.filter(\n        shared_inbox__isnull=False\n    ).values_list('shared_inbox', flat=True).distinct()\n\n    # if a user doesn't have a shared inbox, we need their personal inbox\n    # iirc pixelfed doesn't have shared inboxes\n    inboxes = followers.filter(\n        shared_inbox__isnull=True\n    ).values_list('inbox', flat=True)\n\n    return list(shared) + list(inboxes)\n\n\ndef broadcast(sender, activity, software=None, \\\n              privacy='public', direct_recipients=None):\n    ''' send out an event '''\n    # start with parsing the direct recipients\n    recipients = [u.inbox for u in direct_recipients or []]\n    # and then add any other recipients\n    if privacy == 'public':\n        recipients += get_public_recipients(sender, software=software)\n    broadcast_task.delay(\n        sender.id,\n        json.dumps(activity, cls=ActivityEncoder),\n        recipients\n    )\n\n\n@app.task\ndef broadcast_task(sender_id, activity, recipients):\n    ''' the celery task for broadcast '''\n    sender = models.User.objects.get(id=sender_id)\n    errors = []\n    for recipient in recipients:\n        try:\n            sign_and_send(sender, activity, recipient)\n        except requests.exceptions.HTTPError as e:\n            errors.append({\n                'error': str(e),\n                'recipient': recipient,\n                'activity': activity,\n            })\n    return errors\n\n\ndef sign_and_send(sender, activity, destination):\n    ''' crpyto whatever and http junk '''\n    now = http_date()\n\n    if not sender.private_key:\n        # this shouldn't happen. it would be bad if it happened.\n        raise ValueError('No private key found for sender')\n\n    data = json.dumps(activity).encode('utf-8')\n    digest = make_digest(data)\n\n    response = requests.post(\n        destination,\n        data=data,\n        headers={\n            'Date': now,\n            'Digest': digest,\n            'Signature': make_signature(sender, destination, now, digest),\n            'Content-Type': 'application/activity+json; charset=utf-8',\n        },\n    )\n    if not response.ok:\n        response.raise_for_status()\n    return response\n", "path": "bookwyrm/broadcast.py"}], "after_files": [{"content": "''' signs activitypub activities '''\nimport hashlib\nfrom urllib.parse import urlparse\nimport datetime\nfrom base64 import b64encode, b64decode\n\nfrom Crypto import Random\nfrom Crypto.PublicKey import RSA\nfrom Crypto.Signature import pkcs1_15 #pylint: disable=no-name-in-module\nfrom Crypto.Hash import SHA256\n\nMAX_SIGNATURE_AGE = 300\n\ndef create_key_pair():\n    ''' a new public/private key pair, used for creating new users '''\n    random_generator = Random.new().read\n    key = RSA.generate(1024, random_generator)\n    private_key = key.export_key().decode('utf8')\n    public_key = key.publickey().export_key().decode('utf8')\n\n    return private_key, public_key\n\n\ndef make_signature(sender, destination, date, digest):\n    ''' uses a private key to sign an outgoing message '''\n    inbox_parts = urlparse(destination)\n    signature_headers = [\n        '(request-target): post %s' % inbox_parts.path,\n        'host: %s' % inbox_parts.netloc,\n        'date: %s' % date,\n        'digest: %s' % digest,\n    ]\n    message_to_sign = '\\n'.join(signature_headers)\n    signer = pkcs1_15.new(RSA.import_key(sender.private_key))\n    signed_message = signer.sign(SHA256.new(message_to_sign.encode('utf8')))\n    signature = {\n        'keyId': '%s#main-key' % sender.remote_id,\n        'algorithm': 'rsa-sha256',\n        'headers': '(request-target) host date digest',\n        'signature': b64encode(signed_message).decode('utf8'),\n    }\n    return ','.join('%s=\"%s\"' % (k, v) for (k, v) in signature.items())\n\n\ndef make_digest(data):\n    ''' creates a message digest for signing '''\n    return 'SHA-256=' + b64encode(hashlib.sha256(data.encode('utf-8'))\\\n                        .digest()).decode('utf-8')\n\n\ndef verify_digest(request):\n    ''' checks if a digest is syntactically valid and matches the message '''\n    algorithm, digest = request.headers['digest'].split('=', 1)\n    if algorithm == 'SHA-256':\n        hash_function = hashlib.sha256\n    elif algorithm == 'SHA-512':\n        hash_function = hashlib.sha512\n    else:\n        raise ValueError(\"Unsupported hash function: {}\".format(algorithm))\n\n    expected = hash_function(request.body).digest()\n    if b64decode(digest) != expected:\n        raise ValueError(\"Invalid HTTP Digest header\")\n\nclass Signature:\n    ''' read and validate incoming signatures '''\n    def __init__(self, key_id, headers, signature):\n        self.key_id = key_id\n        self.headers = headers\n        self.signature = signature\n\n    @classmethod\n    def parse(cls, request):\n        ''' extract and parse a signature from an http request '''\n        signature_dict = {}\n        for pair in request.headers['Signature'].split(','):\n            k, v = pair.split('=', 1)\n            v = v.replace('\"', '')\n            signature_dict[k] = v\n\n        try:\n            key_id = signature_dict['keyId']\n            headers = signature_dict['headers']\n            signature = b64decode(signature_dict['signature'])\n        except KeyError:\n            raise ValueError('Invalid auth header')\n\n        return cls(key_id, headers, signature)\n\n    def verify(self, public_key, request):\n        ''' verify rsa signature '''\n        if http_date_age(request.headers['date']) > MAX_SIGNATURE_AGE:\n            raise ValueError(\n                \"Request too old: %s\" % (request.headers['date'],))\n        public_key = RSA.import_key(public_key)\n\n        comparison_string = []\n        for signed_header_name in self.headers.split(' '):\n            if signed_header_name == '(request-target)':\n                comparison_string.append(\n                    '(request-target): post %s' % request.path)\n            else:\n                if signed_header_name == 'digest':\n                    verify_digest(request)\n                comparison_string.append('%s: %s' % (\n                    signed_header_name,\n                    request.headers[signed_header_name]\n                ))\n        comparison_string = '\\n'.join(comparison_string)\n\n        signer = pkcs1_15.new(public_key)\n        digest = SHA256.new()\n        digest.update(comparison_string.encode())\n\n        # raises a ValueError if it fails\n        signer.verify(digest, self.signature)\n\n\ndef http_date_age(datestr):\n    ''' age of a signature in seconds '''\n    parsed = datetime.datetime.strptime(datestr, '%a, %d %b %Y %H:%M:%S GMT')\n    delta = datetime.datetime.utcnow() - parsed\n    return delta.total_seconds()\n", "path": "bookwyrm/signatures.py"}, {"content": "''' send out activitypub messages '''\nimport json\nfrom django.utils.http import http_date\nimport requests\n\nfrom bookwyrm import models\nfrom bookwyrm.activitypub import ActivityEncoder\nfrom bookwyrm.tasks import app\nfrom bookwyrm.signatures import make_signature, make_digest\n\n\ndef get_public_recipients(user, software=None):\n    ''' everybody and their public inboxes '''\n    followers = user.followers.filter(local=False)\n    if software:\n        followers = followers.filter(bookwyrm_user=(software == 'bookwyrm'))\n\n    # we want shared inboxes when available\n    shared = followers.filter(\n        shared_inbox__isnull=False\n    ).values_list('shared_inbox', flat=True).distinct()\n\n    # if a user doesn't have a shared inbox, we need their personal inbox\n    # iirc pixelfed doesn't have shared inboxes\n    inboxes = followers.filter(\n        shared_inbox__isnull=True\n    ).values_list('inbox', flat=True)\n\n    return list(shared) + list(inboxes)\n\n\ndef broadcast(sender, activity, software=None, \\\n              privacy='public', direct_recipients=None):\n    ''' send out an event '''\n    # start with parsing the direct recipients\n    recipients = [u.inbox for u in direct_recipients or []]\n    # and then add any other recipients\n    if privacy == 'public':\n        recipients += get_public_recipients(sender, software=software)\n    broadcast_task.delay(\n        sender.id,\n        json.dumps(activity, cls=ActivityEncoder),\n        recipients\n    )\n\n\n@app.task\ndef broadcast_task(sender_id, activity, recipients):\n    ''' the celery task for broadcast '''\n    sender = models.User.objects.get(id=sender_id)\n    errors = []\n    for recipient in recipients:\n        try:\n            sign_and_send(sender, activity, recipient)\n        except requests.exceptions.HTTPError as e:\n            errors.append({\n                'error': str(e),\n                'recipient': recipient,\n                'activity': activity,\n            })\n    return errors\n\n\ndef sign_and_send(sender, data, destination):\n    ''' crpyto whatever and http junk '''\n    now = http_date()\n\n    if not sender.private_key:\n        # this shouldn't happen. it would be bad if it happened.\n        raise ValueError('No private key found for sender')\n\n    digest = make_digest(data)\n\n    response = requests.post(\n        destination,\n        data=data,\n        headers={\n            'Date': now,\n            'Digest': digest,\n            'Signature': make_signature(sender, destination, now, digest),\n            'Content-Type': 'application/activity+json; charset=utf-8',\n        },\n    )\n    if not response.ok:\n        response.raise_for_status()\n    return response\n", "path": "bookwyrm/broadcast.py"}]}