Add behaviours to TwitterCard, remove some dumb stuff in Formatter.truncate
[akkoma] / lib / pleroma / formatter.ex
index 8024d97c2dc716454592cd5fc8e739d1962f4765..63e0acb21e29e5d923263cf797677058121ba44b 100644 (file)
@@ -1,15 +1,22 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
 defmodule Pleroma.Formatter do
   alias Pleroma.User
   alias Pleroma.Web.MediaProxy
   alias Pleroma.HTML
   alias Pleroma.Emoji
 
-  @tag_regex ~r/\#\w+/u
+  @tag_regex ~r/((?<=[^&])|\A)(\#)(\w+)/u
   @markdown_characters_regex ~r/(`|\*|_|{|}|[|]|\(|\)|#|\+|-|\.|!)/
 
+  # Modified from https://www.w3.org/TR/html5/forms.html#valid-e-mail-address
+  @mentions_regex ~r/@[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]*@?[a-zA-Z0-9_-](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*/u
+
   def parse_tags(text, data \\ %{}) do
     Regex.scan(@tag_regex, text)
-    |> Enum.map(fn ["#" <> tag = full_tag] -> {full_tag, String.downcase(tag)} end)
+    |> Enum.map(fn ["#" <> tag = full_tag | _] -> {full_tag, String.downcase(tag)} end)
     |> (fn map ->
           if data["sensitive"] in [true, "True", "true", "1"],
             do: [{"#nsfw", "nsfw"}] ++ map,
@@ -17,16 +24,15 @@ defmodule Pleroma.Formatter do
         end).()
   end
 
+  @doc "Parses mentions text and returns list {nickname, user}."
+  @spec parse_mentions(binary()) :: list({binary(), User.t()})
   def parse_mentions(text) do
-    # Modified from https://www.w3.org/TR/html5/forms.html#valid-e-mail-address
-    regex =
-      ~r/@[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]*@?[a-zA-Z0-9_-](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*/u
-
-    Regex.scan(regex, text)
+    Regex.scan(@mentions_regex, text)
     |> List.flatten()
     |> Enum.uniq()
-    |> Enum.map(fn "@" <> match = full_match ->
-      {full_match, User.get_cached_by_nickname(match)}
+    |> Enum.map(fn nickname ->
+      with nickname <- String.trim_leading(nickname, "@"),
+           do: {"@" <> nickname, User.get_cached_by_nickname(nickname)}
     end)
     |> Enum.filter(fn {_match, user} -> user end)
   end
@@ -155,7 +161,7 @@ defmodule Pleroma.Formatter do
     uuid_text =
       tags
       |> Enum.reduce(text, fn {match, _short, uuid}, text ->
-        String.replace(text, match, uuid)
+        String.replace(text, ~r/((?<=[^&])|(\A))#{match}/, uuid)
       end)
 
     subs =
@@ -178,21 +184,12 @@ defmodule Pleroma.Formatter do
     end)
   end
 
-  def truncate(text, opts \\ []) do
-    max_length = opts[:max_length] || 200
-    omission = opts[:omission] || "..."
-
-    cond do
-      not String.valid?(text) ->
-        text
-
-      String.length(text) < max_length ->
-        text
-
-      true ->
-        length_with_omission = max_length - String.length(omission)
-
-        "#{String.slice(text, 0, length_with_omission)}#{omission}"
+  def truncate(text, max_length \\ 200, omission \\ "...") do
+    if String.length(text) < max_length do
+      text
+    else
+      length_with_omission = max_length - String.length(omission)
+      String.slice(text, 0, length_with_omission) <> omission
     end
   end
 end