Skip to content

Commit

Permalink
Merge branch 'main' into production
Browse files Browse the repository at this point in the history
  • Loading branch information
mouse-reeve committed May 30, 2023
2 parents a65e6ce + 65e3a31 commit 6400a8e
Show file tree
Hide file tree
Showing 61 changed files with 1,343 additions and 887 deletions.
16 changes: 8 additions & 8 deletions bookwyrm/activitystreams.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ def _get_audience(self, status): # pylint: disable=no-self-use
)
# direct messages don't appear in feeds, direct comments/reviews/etc do
if status.privacy == "direct" and status.status_type == "Note":
return []
return models.User.objects.none()

# everybody who could plausibly see this status
audience = models.User.objects.filter(
Expand Down Expand Up @@ -152,11 +152,11 @@ def _get_audience(self, status): # pylint: disable=no-self-use
def get_audience(self, status):
"""given a status, what users should see it"""
trace.get_current_span().set_attribute("stream_id", self.key)
audience = self._get_audience(status)
audience = self._get_audience(status).values_list("id", flat=True)
status_author = models.User.objects.filter(
is_active=True, local=True, id=status.user.id
)
return list({user.id for user in list(audience) + list(status_author)})
).values_list("id", flat=True)
return list(set(list(audience) + list(status_author)))

def get_stores_for_users(self, user_ids):
"""convert a list of user ids into redis store ids"""
Expand Down Expand Up @@ -186,12 +186,12 @@ def get_audience(self, status):
if not audience:
return []
# if the user is following the author
audience = audience.filter(following=status.user)
audience = audience.filter(following=status.user).values_list("id", flat=True)
# if the user is the post's author
status_author = models.User.objects.filter(
is_active=True, local=True, id=status.user.id
)
return list({user.id for user in list(audience) + list(status_author)})
).values_list("id", flat=True)
return list(set(list(audience) + list(status_author)))

def get_statuses_for_user(self, user):
return models.Status.privacy_filter(
Expand Down Expand Up @@ -240,7 +240,7 @@ def _get_audience(self, status):

audience = super()._get_audience(status)
if not audience:
return []
return models.User.objects.none()
return audience.filter(shelfbook__book__parent_work=work).distinct()

def get_audience(self, status):
Expand Down
12 changes: 12 additions & 0 deletions bookwyrm/management/commands/merge_works.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
""" PROCEED WITH CAUTION: uses deduplication fields to permanently
merge work data objects """
from bookwyrm import models
from bookwyrm.management.merge_command import MergeCommand


class Command(MergeCommand):
"""merges two works by ID"""

help = "merges specified works into one"

MODEL = models.Work
20 changes: 18 additions & 2 deletions bookwyrm/models/activitypub_mixin.py
Original file line number Diff line number Diff line change
Expand Up @@ -529,7 +529,7 @@ async def async_broadcast(recipients: List[str], sender, data: str):


async def sign_and_send(
session: aiohttp.ClientSession, sender, data: str, destination: str
session: aiohttp.ClientSession, sender, data: str, destination: str, **kwargs
):
"""Sign the messages and send them in an asynchronous bundle"""
now = http_date()
Expand All @@ -539,11 +539,19 @@ async def sign_and_send(
raise ValueError("No private key found for sender")

digest = make_digest(data)
signature = make_signature(
"post",
sender,
destination,
now,
digest=digest,
use_legacy_key=kwargs.get("use_legacy_key"),
)

headers = {
"Date": now,
"Digest": digest,
"Signature": make_signature("post", sender, destination, now, digest),
"Signature": signature,
"Content-Type": "application/activity+json; charset=utf-8",
"User-Agent": USER_AGENT,
}
Expand All @@ -554,6 +562,14 @@ async def sign_and_send(
logger.exception(
"Failed to send broadcast to %s: %s", destination, response.reason
)
if kwargs.get("use_legacy_key") is not True:
logger.info("Trying again with legacy keyId header value")
asyncio.ensure_future(
sign_and_send(
session, sender, data, destination, use_legacy_key=True
)
)

return response
except asyncio.TimeoutError:
logger.info("Connection timed out for url: %s", destination)
Expand Down
9 changes: 7 additions & 2 deletions bookwyrm/models/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -371,15 +371,20 @@ def field_to_activity(self, value):
tags.append(
activitypub.Link(
href=item.remote_id,
name=getattr(item, item.name_field),
name=f"@{getattr(item, item.name_field)}",
type=activity_type,
)
)
return tags

def field_from_activity(self, value, allow_external_connections=True):
if not isinstance(value, list):
return None
# GoToSocial DMs and single-user mentions are
# sent as objects, not as an array of objects
if isinstance(value, dict):
value = [value]
else:
return None
items = []
for link_json in value:
link = activitypub.Link(**link_json)
Expand Down
11 changes: 9 additions & 2 deletions bookwyrm/models/status.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,10 +142,17 @@ def ignore_activity(
# keep notes if they mention local users
if activity.tag == MISSING or activity.tag is None:
return True
tags = [l["href"] for l in activity.tag if l["type"] == "Mention"]
# GoToSocial sends single tags as objects
# not wrapped in a list
tags = activity.tag if isinstance(activity.tag, list) else [activity.tag]
user_model = apps.get_model("bookwyrm.User", require_ready=True)
for tag in tags:
if user_model.objects.filter(remote_id=tag, local=True).exists():
if (
tag["type"] == "Mention"
and user_model.objects.filter(
remote_id=tag["href"], local=True
).exists()
):
# we found a mention of a known use boost
return False
return True
Expand Down
23 changes: 19 additions & 4 deletions bookwyrm/models/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,7 @@ def save(self, *args, **kwargs):
# this is a new remote user, we need to set their remote server field
if not self.local:
super().save(*args, **kwargs)
transaction.on_commit(lambda: set_remote_server.delay(self.id))
transaction.on_commit(lambda: set_remote_server(self.id))
return

with transaction.atomic():
Expand Down Expand Up @@ -470,17 +470,29 @@ def save(self, *args, **kwargs):


@app.task(queue=LOW)
def set_remote_server(user_id):
def set_remote_server(user_id, allow_external_connections=False):
"""figure out the user's remote server in the background"""
user = User.objects.get(id=user_id)
actor_parts = urlparse(user.remote_id)
user.federated_server = get_or_create_remote_server(actor_parts.netloc)
federated_server = get_or_create_remote_server(
actor_parts.netloc, allow_external_connections=allow_external_connections
)
# if we were unable to find the server, we need to create a new entry for it
if not federated_server:
# and to do that, we will call this function asynchronously.
if not allow_external_connections:
set_remote_server.delay(user_id, allow_external_connections=True)
return

user.federated_server = federated_server
user.save(broadcast=False, update_fields=["federated_server"])
if user.bookwyrm_user and user.outbox:
get_remote_reviews.delay(user.outbox)


def get_or_create_remote_server(domain, refresh=False):
def get_or_create_remote_server(
domain, allow_external_connections=False, refresh=False
):
"""get info on a remote server"""
server = FederatedServer()
try:
Expand All @@ -490,6 +502,9 @@ def get_or_create_remote_server(domain, refresh=False):
except FederatedServer.DoesNotExist:
pass

if not allow_external_connections:
return None

try:
data = get_data(f"https://{domain}/.well-known/nodeinfo")
try:
Expand Down
4 changes: 2 additions & 2 deletions bookwyrm/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
env = Env()
env.read_env()
DOMAIN = env("DOMAIN")
VERSION = "0.6.2"
VERSION = "0.6.3"

RELEASE_API = env(
"RELEASE_API",
Expand All @@ -22,7 +22,7 @@
PAGE_LENGTH = env.int("PAGE_LENGTH", 15)
DEFAULT_LANGUAGE = env("DEFAULT_LANGUAGE", "English")

JS_CACHE = "ea91d7df"
JS_CACHE = "d993847c"

# email
EMAIL_BACKEND = env("EMAIL_BACKEND", "django.core.mail.backends.smtp.EmailBackend")
Expand Down
11 changes: 9 additions & 2 deletions bookwyrm/signatures.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def create_key_pair():
return private_key, public_key


def make_signature(method, sender, destination, date, digest=None):
def make_signature(method, sender, destination, date, **kwargs):
"""uses a private key to sign an outgoing message"""
inbox_parts = urlparse(destination)
signature_headers = [
Expand All @@ -31,15 +31,22 @@ def make_signature(method, sender, destination, date, digest=None):
f"date: {date}",
]
headers = "(request-target) host date"
digest = kwargs.get("digest")
if digest is not None:
signature_headers.append(f"digest: {digest}")
headers = "(request-target) host date digest"

message_to_sign = "\n".join(signature_headers)
signer = pkcs1_15.new(RSA.import_key(sender.key_pair.private_key))
signed_message = signer.sign(SHA256.new(message_to_sign.encode("utf8")))
# For legacy reasons we need to use an incorrect keyId for older Bookwyrm versions
key_id = (
f"{sender.remote_id}#main-key"
if kwargs.get("use_legacy_key")
else f"{sender.remote_id}/#main-key"
)
signature = {
"keyId": f"{sender.remote_id}#main-key",
"keyId": key_id,
"algorithm": "rsa-sha256",
"headers": headers,
"signature": b64encode(signed_message).decode("utf8"),
Expand Down
4 changes: 4 additions & 0 deletions bookwyrm/static/css/bookwyrm/components/_stars.scss
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
white-space: nowrap;
}

.stars .no-rating {
font-style: italic;
}

/** Stars in a review form
*
* Specificity makes hovering taking over checked inputs.
Expand Down
16 changes: 9 additions & 7 deletions bookwyrm/templates/book/book.html
Original file line number Diff line number Diff line change
Expand Up @@ -190,13 +190,15 @@ <h1 class="title" itemprop="name" dir="auto">
<meta itemprop="bestRating" content="5">
<meta itemprop="reviewCount" content="{{ review_count }}">

{% include 'snippets/stars.html' with rating=rating %}
<span>
{% include 'snippets/stars.html' with rating=rating %}

{% blocktrans count counter=review_count trimmed %}
({{ review_count }} review)
{% plural %}
({{ review_count }} reviews)
{% endblocktrans %}
{% blocktrans count counter=review_count trimmed %}
({{ review_count }} review)
{% plural %}
({{ review_count }} reviews)
{% endblocktrans %}
</span>
</div>

{% with full=book|book_description itemprop='abstract' %}
Expand Down Expand Up @@ -256,7 +258,7 @@ <h2 class="title is-5">
{% endif %}
{% for shelf in other_edition_shelves %}
<p>
{% blocktrans with book_path=shelf.book.local_path shelf_path=shelf.shelf.local_path shelf_name=shelf.shelf.name %}A <a href="{{ book_path }}">different edition</a> of this book is on your <a href="{{ shelf_path }}">{{ shelf_name }}</a> shelf.{% endblocktrans %}
{% blocktrans with book_path=shelf.book.local_path shelf_path=shelf.shelf.local_path shelf_name=shelf.shelf|translate_shelf_name %}A <a href="{{ book_path }}">different edition</a> of this book is on your <a href="{{ shelf_path }}">{{ shelf_name }}</a> shelf.{% endblocktrans %}
{% include 'snippets/switch_edition_button.html' with edition=book %}
</p>
{% endfor %}
Expand Down
4 changes: 2 additions & 2 deletions bookwyrm/templates/settings/reports/reports.html
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@
{% block panel %}
<div class="tabs">
<ul>
<li class="{% if not resolved %}is-active{% endif %}"{% if not resolved == 'open' %} aria-current="page"{% endif %}>
<li class="{% if not resolved %}is-active{% endif %}"{% if not resolved %} aria-current="page"{% endif %}>
<a href="{% url 'settings-reports' %}?resolved=false">{% trans "Open" %}</a>
</li>
<li class="{% if resolved %}is-active{% endif %}"{% if resolved %} aria-current="page"{% endif %}>
<li class="{% if resolved and resolved != "all" %}is-active{% endif %}"{% if resolved and resolved != "all" %} aria-current="page"{% endif %}>
<a href="{% url 'settings-reports' %}?resolved=true">{% trans "Resolved" %}</a>
</li>
</ul>
Expand Down
2 changes: 1 addition & 1 deletion bookwyrm/templates/settings/users/user_info.html
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ <h4 class="title is-4">{% trans "User details" %}</h4>
<dd>
{{ report_count|intcomma }}
{% if report_count > 0 %}
<a href="{% url 'settings-reports' %}?username={{ user.username }}">
<a href="{% url 'settings-reports' %}?username={{ user.username }}&resolved=all">
{% trans "(View reports)" %}
</a>
{% endif %}
Expand Down
4 changes: 3 additions & 1 deletion bookwyrm/templates/snippets/shelf_selector.html
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,9 @@
{% csrf_token %}
<input type="hidden" name="book" value="{{ book.id }}">
<input type="hidden" name="shelf" value="{{ shelf.id }}">
<button class="button is-fullwidth is-small is-radiusless is-danger is-light" type="submit">{% trans "Remove from" %} {{ shelf.name }}</button>
<button class="button is-fullwidth is-small is-radiusless is-danger is-light" type="submit">
{% blocktrans with name=shelf|translate_shelf_name %}Remove from {{ name }}{% endblocktrans %}
</button>
</form>
</li>
{% endif %}
Expand Down
31 changes: 15 additions & 16 deletions bookwyrm/templates/snippets/stars.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,25 @@
{% load i18n %}

<span class="stars">
<span class="is-sr-only">
{% if rating %}
{% if rating %}
<span class="is-sr-only">
{% blocktranslate trimmed with rating=rating|floatformat:0 count counter=rating|floatformat:0|add:0 %}
{{ rating }} star
{% plural %}
{{ rating }} stars
{% endblocktranslate %}
{% else %}
{% trans "No rating" %}
{% endif %}
</span>

{% for i in '12345'|make_list %}
<span
class="
icon is-small mr-1
icon-star-{% if rating >= forloop.counter %}full{% elif rating|floatformat:0 >= forloop.counter|floatformat:0 %}half{% else %}empty{% endif %}
"
aria-hidden="true"
></span>
{% endfor %}
</span>
{% for i in '12345'|make_list %}
<span
class="
icon is-small mr-1
icon-star-{% if rating >= forloop.counter %}full{% elif rating|floatformat:0 >= forloop.counter|floatformat:0 %}half{% else %}empty{% endif %}
"
aria-hidden="true"
></span>
{% endfor %}
{% else %}
<span class="no-rating">{% trans "No rating" %}</span>
{% endif %}
</span>
{% endspaceless %}
2 changes: 1 addition & 1 deletion bookwyrm/tests/models/test_fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -404,7 +404,7 @@ def test_tag_field(self, *_):
self.assertIsInstance(result, list)
self.assertEqual(len(result), 1)
self.assertEqual(result[0].href, "https://e.b/c")
self.assertEqual(result[0].name, "Name")
self.assertEqual(result[0].name, "@Name")
self.assertEqual(result[0].type, "Serializable")

def test_tag_field_from_activity(self, *_):
Expand Down
Loading

0 comments on commit 6400a8e

Please sign in to comment.