Repository: bookwyrm-social/bookwyrm

Issue:
Hashtags are catching parts of urls
Links with anchors (https://www.example.com/test#three) are getting parsed as containing hashtags

(This was reported to me via email)


Discussion:

Resolving patch:
diff --git a/bookwyrm/views/status.py b/bookwyrm/views/status.py
--- a/bookwyrm/views/status.py
+++ b/bookwyrm/views/status.py
@@ -96,34 +96,22 @@ def post(self, request, status_type, existing_status_id=None):
 
         # inspect the text for user tags
         content = status.content
-        for (mention_text, mention_user) in find_mentions(
-            request.user, content
-        ).items():
+        mentions = find_mentions(request.user, content)
+        for (_, mention_user) in mentions.items():
             # add them to status mentions fk
             status.mention_users.add(mention_user)
+        content = format_mentions(content, mentions)
 
-            # turn the mention into a link
-            content = re.sub(
-                rf"{mention_text}\b(?!@)",
-                rf'<a href="{mention_user.remote_id}">{mention_text}</a>',
-                content,
-            )
         # add reply parent to mentions
         if status.reply_parent:
             status.mention_users.add(status.reply_parent.user)
 
         # inspect the text for hashtags
-        for (mention_text, mention_hashtag) in find_or_create_hashtags(content).items():
+        hashtags = find_or_create_hashtags(content)
+        for (_, mention_hashtag) in hashtags.items():
             # add them to status mentions fk
             status.mention_hashtags.add(mention_hashtag)
-
-            # turn the mention into a link
-            content = re.sub(
-                rf"{mention_text}\b(?!@)",
-                rf'<a href="{mention_hashtag.remote_id}" data-mention="hashtag">'
-                + rf"{mention_text}</a>",
-                content,
-            )
+        content = format_hashtags(content, hashtags)
 
         # deduplicate mentions
         status.mention_users.set(set(status.mention_users.all()))
@@ -150,6 +138,31 @@ def post(self, request, status_type, existing_status_id=None):
         return redirect_to_referer(request)
 
 
+def format_mentions(content, mentions):
+    """Detect @mentions and make them links"""
+    for (mention_text, mention_user) in mentions.items():
+        # turn the mention into a link
+        content = re.sub(
+            rf"(?<!/)\B{mention_text}\b(?!@)",
+            rf'<a href="{mention_user.remote_id}">{mention_text}</a>',
+            content,
+        )
+    return content
+
+
+def format_hashtags(content, hashtags):
+    """Detect #hashtags and make them links"""
+    for (mention_text, mention_hashtag) in hashtags.items():
+        # turn the mention into a link
+        content = re.sub(
+            rf"(?<!/)\B{mention_text}\b(?!@)",
+            rf'<a href="{mention_hashtag.remote_id}" data-mention="hashtag">'
+            + rf"{mention_text}</a>",
+            content,
+        )
+    return content
+
+
 @method_decorator(login_required, name="dispatch")
 class DeleteStatus(View):
     """tombstone that bad boy"""


Test patch:
diff --git a/bookwyrm/tests/views/test_status.py b/bookwyrm/tests/views/test_status.py
--- a/bookwyrm/tests/views/test_status.py
+++ b/bookwyrm/tests/views/test_status.py
@@ -456,6 +456,24 @@ def test_format_links_special_chars(self, *_):
             views.status.format_links(url), f'<a href="{url}">{url[8:]}</a>'
         )
 
+    def test_format_mentions_with_at_symbol_links(self, *_):
+        """A link with an @username shouldn't treat the username as a mention"""
+        content = "a link to https://example.com/user/@mouse"
+        mentions = views.status.find_mentions(self.local_user, content)
+        self.assertEqual(
+            views.status.format_mentions(content, mentions),
+            "a link to https://example.com/user/@mouse",
+        )
+
+    def test_format_hashtag_with_pound_symbol_links(self, *_):
+        """A link with an @username shouldn't treat the username as a mention"""
+        content = "a link to https://example.com/page#anchor"
+        hashtags = views.status.find_or_create_hashtags(content)
+        self.assertEqual(
+            views.status.format_hashtags(content, hashtags),
+            "a link to https://example.com/page#anchor",
+        )
+
     def test_to_markdown(self, *_):
         """this is mostly handled in other places, but nonetheless"""
         text = "_hi_ and http://fish.com is <marquee>rad</marquee>"