diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml
index 08661e9c2..a3117f7cb 100644
--- a/.github/workflows/pylint.yml
+++ b/.github/workflows/pylint.yml
@@ -21,8 +21,7 @@ jobs:
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- pip install pylint
- name: Analysing the code with pylint
run: |
- pylint bookwyrm/ --ignore=migrations --disable=E1101,E1135,E1136,R0903,R0901,R0902,W0707,W0511,W0406,R0401,R0801
+ pylint bookwyrm/
diff --git a/.pylintrc b/.pylintrc
new file mode 100644
index 000000000..464638853
--- /dev/null
+++ b/.pylintrc
@@ -0,0 +1,9 @@
+[MAIN]
+ignore=migrations
+load-plugins=pylint.extensions.no_self_use
+
+[MESSAGES CONTROL]
+disable=E1101,E1135,E1136,R0903,R0901,R0902,W0707,W0511,W0406,R0401,R0801,C3001,import-error
+
+[FORMAT]
+max-line-length=88
diff --git a/Dockerfile b/Dockerfile
index 349dd82b1..b3cd26e88 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -6,6 +6,7 @@ RUN mkdir /app /app/static /app/images
WORKDIR /app
+RUN apt-get update && apt-get install -y gettext libgettextpo-dev tidy && apt-get clean
+
COPY requirements.txt /app/
RUN pip install -r requirements.txt --no-cache-dir
-RUN apt-get update && apt-get install -y gettext libgettextpo-dev tidy && apt-get clean
diff --git a/README.md b/README.md
index 1d3eb5433..cf40d284d 100644
--- a/README.md
+++ b/README.md
@@ -16,7 +16,7 @@ If you'd like to join an instance, you can check out the [instances](https://joi
## Contributing
-See [contributing](https://docs.joinbookwyrm.com/how-to-contribute.html) for code, translation or monetary contributions.
+See [contributing](https://docs.joinbookwyrm.com/contributing.html) for code, translation or monetary contributions.
## About BookWyrm
### What it is and isn't
@@ -76,4 +76,4 @@ Deployment
## Set up BookWyrm
-The [documentation website](https://docs.joinbookwyrm.com/) has instruction on how to set up BookWyrm in a [developer environment](https://docs.joinbookwyrm.com/developer-environment.html) or [production](https://docs.joinbookwyrm.com/installing-in-production.html).
+The [documentation website](https://docs.joinbookwyrm.com/) has instruction on how to set up BookWyrm in a [developer environment](https://docs.joinbookwyrm.com/install-dev.html) or [production](https://docs.joinbookwyrm.com/install-prod.html).
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 000000000..c4e5e9cf9
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,5 @@
+# Security Policy
+
+## Reporting a Vulnerability
+
+Please report security issues to `mousereeve@riseup.net`
\ No newline at end of file
diff --git a/bookwyrm/activitypub/base_activity.py b/bookwyrm/activitypub/base_activity.py
index 6bee25f62..fa1535694 100644
--- a/bookwyrm/activitypub/base_activity.py
+++ b/bookwyrm/activitypub/base_activity.py
@@ -1,6 +1,7 @@
""" basics for an activitypub serializer """
from dataclasses import dataclass, fields, MISSING
from json import JSONEncoder
+import logging
from django.apps import apps
from django.db import IntegrityError, transaction
@@ -8,6 +9,8 @@ from django.db import IntegrityError, transaction
from bookwyrm.connectors import ConnectorException, get_data
from bookwyrm.tasks import app
+logger = logging.getLogger(__name__)
+
class ActivitySerializerError(ValueError):
"""routine problems serializing activitypub json"""
@@ -39,12 +42,12 @@ def naive_parse(activity_objects, activity_json, serializer=None):
activity_json["type"] = "PublicKey"
activity_type = activity_json.get("type")
+ if activity_type in ["Question", "Article"]:
+ return None
try:
serializer = activity_objects[activity_type]
except KeyError as err:
# we know this exists and that we can't handle it
- if activity_type in ["Question"]:
- return None
raise ActivitySerializerError(err)
return serializer(activity_objects=activity_objects, **activity_json)
@@ -65,7 +68,7 @@ class ActivityObject:
try:
value = kwargs[field.name]
if value in (None, MISSING, {}):
- raise KeyError()
+ raise KeyError("Missing required field", field.name)
try:
is_subclass = issubclass(field.type, ActivityObject)
except TypeError:
@@ -268,9 +271,9 @@ def resolve_remote_id(
try:
data = get_data(remote_id)
except ConnectorException:
- raise ActivitySerializerError(
- f"Could not connect to host for remote_id: {remote_id}"
- )
+ logger.exception("Could not connect to host for remote_id: %s", remote_id)
+ return None
+
# determine the model implicitly, if not provided
# or if it's a model with subclasses like Status, check again
if not model or hasattr(model.objects, "select_subclasses"):
diff --git a/bookwyrm/activitystreams.py b/bookwyrm/activitystreams.py
index f2dd43fb2..a90d7943b 100644
--- a/bookwyrm/activitystreams.py
+++ b/bookwyrm/activitystreams.py
@@ -298,8 +298,9 @@ def add_status_on_create_command(sender, instance, created):
priority = HIGH
# check if this is an old status, de-prioritize if so
# (this will happen if federation is very slow, or, more expectedly, on csv import)
- one_day = 60 * 60 * 24
- if (instance.created_date - instance.published_date).seconds > one_day:
+ if instance.published_date < timezone.now() - timedelta(
+ days=1
+ ) or instance.created_date < instance.published_date - timedelta(days=1):
priority = LOW
add_status_task.apply_async(
diff --git a/bookwyrm/book_search.py b/bookwyrm/book_search.py
index e42a6d8c3..4b0a6eab9 100644
--- a/bookwyrm/book_search.py
+++ b/bookwyrm/book_search.py
@@ -148,8 +148,8 @@ class SearchResult:
def __repr__(self):
# pylint: disable=consider-using-f-string
- return " (comment on '
- f'"{self.book.title}") (comment on '
+ f'"{self.book.title}", page {self.progress}) (comment on '
+ f'"{self.book.title}") ", ' "', self.quote)
quote = re.sub(r"
-- ' + f'"{self.book.title}", page {self.position}
{self.content}' + ) + else: + return_value = ( + f'{quote} {self.content}' + ) + return return_value activity_serializer = activitypub.Quotation @@ -377,7 +388,7 @@ class Review(BookStatus): def save(self, *args, **kwargs): """clear rating caches""" if self.book.parent_work: - cache.delete(f"book-rating-{self.book.parent_work.id}-*") + cache.delete(f"book-rating-{self.book.parent_work.id}") super().save(*args, **kwargs) diff --git a/bookwyrm/models/user.py b/bookwyrm/models/user.py index be5c19922..dce74022c 100644 --- a/bookwyrm/models/user.py +++ b/bookwyrm/models/user.py @@ -374,6 +374,10 @@ class User(OrderedCollectionPageMixin, AbstractUser): "name": "Read", "identifier": "read", }, + { + "name": "Stopped Reading", + "identifier": "stopped-reading", + }, ] for shelf in shelves: diff --git a/bookwyrm/sanitize_html.py b/bookwyrm/sanitize_html.py deleted file mode 100644 index 4edd2818e..000000000 --- a/bookwyrm/sanitize_html.py +++ /dev/null @@ -1,71 +0,0 @@ -""" html parser to clean up incoming text from unknown sources """ -from html.parser import HTMLParser - - -class InputHtmlParser(HTMLParser): # pylint: disable=abstract-method - """Removes any html that isn't allowed_tagsed from a block""" - - def __init__(self): - HTMLParser.__init__(self) - self.allowed_tags = [ - "p", - "blockquote", - "br", - "b", - "i", - "strong", - "em", - "pre", - "a", - "span", - "ul", - "ol", - "li", - ] - self.allowed_attrs = ["href", "rel", "src", "alt"] - self.tag_stack = [] - self.output = [] - # if the html appears invalid, we just won't allow any at all - self.allow_html = True - - def handle_starttag(self, tag, attrs): - """check if the tag is valid""" - if self.allow_html and tag in self.allowed_tags: - allowed_attrs = " ".join( - f'{a}="{v}"' for a, v in attrs if a in self.allowed_attrs - ) - reconstructed = f"<{tag}" - if allowed_attrs: - reconstructed += " " + allowed_attrs - reconstructed += ">" - self.output.append(("tag", reconstructed)) - self.tag_stack.append(tag) - else: - self.output.append(("data", "")) - - def handle_endtag(self, tag): - """keep the close tag""" - if not self.allow_html or tag not in self.allowed_tags: - self.output.append(("data", "")) - return - - if not self.tag_stack or self.tag_stack[-1] != tag: - # the end tag doesn't match the most recent start tag - self.allow_html = False - self.output.append(("data", "")) - return - - self.tag_stack = self.tag_stack[:-1] - self.output.append(("tag", f"{tag}>")) - - def handle_data(self, data): - """extract the answer, if we're in an answer tag""" - self.output.append(("data", data)) - - def get_output(self): - """convert the output from a list of tuples to a string""" - if self.tag_stack: - self.allow_html = False - if not self.allow_html: - return "".join(v for (k, v) in self.output if k == "data") - return "".join(v for (k, v) in self.output) diff --git a/bookwyrm/settings.py b/bookwyrm/settings.py index 416610e49..e67fb5e1e 100644 --- a/bookwyrm/settings.py +++ b/bookwyrm/settings.py @@ -11,7 +11,7 @@ from django.utils.translation import gettext_lazy as _ env = Env() env.read_env() DOMAIN = env("DOMAIN") -VERSION = "0.3.4" +VERSION = "0.4.1" RELEASE_API = env( "RELEASE_API", @@ -21,7 +21,7 @@ RELEASE_API = env( PAGE_LENGTH = env("PAGE_LENGTH", 15) DEFAULT_LANGUAGE = env("DEFAULT_LANGUAGE", "English") -JS_CACHE = "bc93172a" +JS_CACHE = "e678183b" # email EMAIL_BACKEND = env("EMAIL_BACKEND", "django.core.mail.backends.smtp.EmailBackend") @@ -212,7 +212,7 @@ STREAMS = [ # Search configuration # total time in seconds that the instance will spend searching connectors -SEARCH_TIMEOUT = int(env("SEARCH_TIMEOUT", 15)) +SEARCH_TIMEOUT = int(env("SEARCH_TIMEOUT", 8)) # timeout for a query to an individual connector QUERY_TIMEOUT = int(env("QUERY_TIMEOUT", 5)) diff --git a/bookwyrm/static/css/bookwyrm/utilities/_colors.scss b/bookwyrm/static/css/bookwyrm/utilities/_colors.scss index e44efee95..f38d2a40b 100644 --- a/bookwyrm/static/css/bookwyrm/utilities/_colors.scss +++ b/bookwyrm/static/css/bookwyrm/utilities/_colors.scss @@ -23,3 +23,8 @@ .has-background-tertiary { background-color: $background-tertiary !important; } + +/* Workaround for dark theme as .has-text-black doesn't give desired effect. */ +.has-text-default { + color: $text !important; +} diff --git a/bookwyrm/static/css/themes/bookwyrm-dark.scss b/bookwyrm/static/css/themes/bookwyrm-dark.scss index 96997c4a4..88ee865bb 100644 --- a/bookwyrm/static/css/themes/bookwyrm-dark.scss +++ b/bookwyrm/static/css/themes/bookwyrm-dark.scss @@ -53,6 +53,7 @@ $link-hover: $white-bis; $link-hover-border: #51595d; $link-focus: $white-bis; $link-active: $white-bis; +$link-light: #0d1c26; /* bulma overrides */ $background: $background-secondary; @@ -83,6 +84,13 @@ $progress-value-background-color: $border-light; $family-primary: $family-sans-serif; $family-secondary: $family-sans-serif; +.has-text-muted { + color: $grey-lighter !important; +} + +.has-text-more-muted { + color: $grey-light !important; +} @import "../bookwyrm.scss"; @import "../vendor/icons.css"; diff --git a/bookwyrm/static/css/themes/bookwyrm-light.scss b/bookwyrm/static/css/themes/bookwyrm-light.scss index 69c1a8063..75f05164b 100644 --- a/bookwyrm/static/css/themes/bookwyrm-light.scss +++ b/bookwyrm/static/css/themes/bookwyrm-light.scss @@ -57,5 +57,13 @@ $invisible-overlay-background-color: rgba($scheme-invert, 0.66); $family-primary: $family-sans-serif; $family-secondary: $family-sans-serif; +.has-text-muted { + color: $grey-dark !important; +} + +.has-text-more-muted { + color: $grey !important; +} + @import "../bookwyrm.scss"; @import "../vendor/icons.css"; diff --git a/bookwyrm/static/js/status_cache.js b/bookwyrm/static/js/status_cache.js index b19489c1d..0a9f3abc5 100644 --- a/bookwyrm/static/js/status_cache.js +++ b/bookwyrm/static/js/status_cache.js @@ -203,6 +203,8 @@ let StatusCache = new (class { .forEach((item) => (item.disabled = false)); next_identifier = next_identifier == "complete" ? "read" : next_identifier; + next_identifier = + next_identifier == "stopped-reading-complete" ? "stopped-reading" : next_identifier; // Disable the current state button.querySelector( diff --git a/bookwyrm/status.py b/bookwyrm/status.py index 09fbdc06e..de7682ee7 100644 --- a/bookwyrm/status.py +++ b/bookwyrm/status.py @@ -2,15 +2,13 @@ from django.db import transaction from bookwyrm import models -from bookwyrm.sanitize_html import InputHtmlParser +from bookwyrm.utils import sanitizer def create_generated_note(user, content, mention_books=None, privacy="public"): """a note created by the app about user activity""" # sanitize input html - parser = InputHtmlParser() - parser.feed(content) - content = parser.get_output() + content = sanitizer.clean(content) with transaction.atomic(): # create but don't save diff --git a/bookwyrm/templates/about/layout.html b/bookwyrm/templates/about/layout.html index 458e4b1d1..e921fcd29 100644 --- a/bookwyrm/templates/about/layout.html +++ b/bookwyrm/templates/about/layout.html @@ -50,7 +50,7 @@ -