diff options
Diffstat (limited to 'lib')
38 files changed, 869 insertions, 181 deletions
diff --git a/lib/mix/tasks/generate_invite_token.ex b/lib/mix/tasks/generate_invite_token.ex new file mode 100644 index 000000000..c4daa9a6c --- /dev/null +++ b/lib/mix/tasks/generate_invite_token.ex @@ -0,0 +1,25 @@ +defmodule Mix.Tasks.GenerateInviteToken do + use Mix.Task + + @shortdoc "Generate invite token for user" + def run([]) do + Mix.Task.run("app.start") + + with {:ok, token} <- Pleroma.UserInviteToken.create_token() do + IO.puts("Generated user invite token") + + IO.puts( + "Url: #{ + Pleroma.Web.Router.Helpers.redirect_url( + Pleroma.Web.Endpoint, + :registration_page, + token.token + ) + }" + ) + else + _ -> + IO.puts("Error creating token") + end + end +end diff --git a/lib/pleroma/activity.ex b/lib/pleroma/activity.ex index dd6805125..bed96861f 100644 --- a/lib/pleroma/activity.ex +++ b/lib/pleroma/activity.ex @@ -78,4 +78,8 @@ defmodule Pleroma.Activity do end def get_create_activity_by_object_ap_id(_), do: nil + + def normalize(obj) when is_map(obj), do: Activity.get_by_ap_id(obj["id"]) + def normalize(ap_id) when is_binary(ap_id), do: Activity.get_by_ap_id(ap_id) + def normalize(_), do: nil end diff --git a/lib/pleroma/formatter.ex b/lib/pleroma/formatter.ex index df7ffbc41..cf2944c38 100644 --- a/lib/pleroma/formatter.ex +++ b/lib/pleroma/formatter.ex @@ -16,7 +16,7 @@ defmodule Pleroma.Formatter do 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 + ~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) |> List.flatten() @@ -116,7 +116,28 @@ defmodule Pleroma.Formatter do _ -> [] end) - @emoji @finmoji_with_filenames ++ @emoji_from_file + @emoji_from_globs ( + static_path = Path.join(:code.priv_dir(:pleroma), "static") + + globs = + Application.get_env(:pleroma, :emoji, []) + |> Keyword.get(:shortcode_globs, []) + + paths = + Enum.map(globs, fn glob -> + Path.join(static_path, glob) + |> Path.wildcard() + end) + |> Enum.concat() + + Enum.map(paths, fn path -> + shortcode = Path.basename(path, Path.extname(path)) + external_path = Path.join("/", Path.relative_to(path, static_path)) + {shortcode, external_path} + end) + ) + + @emoji @finmoji_with_filenames ++ @emoji_from_globs ++ @emoji_from_file def emojify(text, emoji \\ @emoji) def emojify(text, nil), do: text @@ -144,8 +165,29 @@ defmodule Pleroma.Formatter do @emoji end - @link_regex ~r/https?:\/\/[\w\.\/?=\-#\+%&@~'\(\):]+[\w\/]/u + @link_regex ~r/[0-9a-z+\-\.]+:[0-9a-z$-_.+!*'(),]+/ui + + # IANA got a list https://www.iana.org/assignments/uri-schemes/ but + # Stuff like ipfs isn’t in it + # There is very niche stuff + @uri_schemes [ + "https://", + "http://", + "dat://", + "dweb://", + "gopher://", + "ipfs://", + "ipns://", + "irc:", + "ircs:", + "magnet:", + "mailto:", + "mumble:", + "ssb://", + "xmpp:" + ] + # TODO: make it use something other than @link_regex def html_escape(text) do Regex.split(@link_regex, text, include_captures: true) |> Enum.map_every(2, fn chunk -> @@ -155,11 +197,18 @@ defmodule Pleroma.Formatter do |> Enum.join("") end - @doc "changes http:... links to html links" + @doc "changes scheme:... urls to html links" def add_links({subs, text}) do + additionnal_schemes = + Application.get_env(:pleroma, :uri_schemes, []) + |> Keyword.get(:additionnal_schemes, []) + links = - Regex.scan(@link_regex, text) - |> Enum.map(fn [url] -> {Ecto.UUID.generate(), url} end) + text + |> String.split([" ", "\t", "<br>"]) + |> Enum.filter(fn word -> String.starts_with?(word, @uri_schemes ++ additionnal_schemes) end) + |> Enum.filter(fn word -> Regex.match?(@link_regex, word) end) + |> Enum.map(fn url -> {Ecto.UUID.generate(), url} end) |> Enum.sort_by(fn {_, url} -> -String.length(url) end) uuid_text = @@ -223,8 +272,8 @@ defmodule Pleroma.Formatter do subs = subs ++ - Enum.map(tags, fn {_, tag, uuid} -> - url = "<a href='#{Pleroma.Web.base_url()}/tag/#{tag}' rel='tag'>##{tag}</a>" + Enum.map(tags, fn {tag_text, tag, uuid} -> + url = "<a href='#{Pleroma.Web.base_url()}/tag/#{tag}' rel='tag'>#{tag_text}</a>" {uuid, url} end) diff --git a/lib/pleroma/gopher/server.ex b/lib/pleroma/gopher/server.ex index f6abcd4d0..97a1dea77 100644 --- a/lib/pleroma/gopher/server.ex +++ b/lib/pleroma/gopher/server.ex @@ -54,7 +54,7 @@ defmodule Pleroma.Gopher.Server.ProtocolHandler do String.split(text, "\r") |> Enum.map(fn text -> - "i#{text}\tfake\(NULL)\t0\r\n" + "i#{text}\tfake\t(NULL)\t0\r\n" end) |> Enum.join("") end @@ -77,14 +77,14 @@ defmodule Pleroma.Gopher.Server.ProtocolHandler do link("Post ##{activity.id} by #{user.nickname}", "/notices/#{activity.id}") <> info("#{like_count} likes, #{announcement_count} repeats") <> - "\r\n" <> + "i\tfake\t(NULL)\t0\r\n" <> info( HtmlSanitizeEx.strip_tags( String.replace(activity.data["object"]["content"], "<br>", "\r") ) ) end) - |> Enum.join("\r\n") + |> Enum.join("i\tfake\t(NULL)\t0\r\n") end def response("") do diff --git a/lib/pleroma/http/http.ex b/lib/pleroma/http/http.ex index 84f34eb4a..c19bccf60 100644 --- a/lib/pleroma/http/http.ex +++ b/lib/pleroma/http/http.ex @@ -1,5 +1,23 @@ defmodule Pleroma.HTTP do - use HTTPoison.Base + require HTTPoison + + def request(method, url, body \\ "", headers \\ [], options \\ []) do + options = + process_request_options(options) + |> process_sni_options(url) + + HTTPoison.request(method, url, body, headers, options) + end + + defp process_sni_options(options, url) do + uri = URI.parse(url) + host = uri.host |> to_charlist() + + case uri.scheme do + "https" -> options ++ [ssl: [server_name_indication: host]] + _ -> options + end + end def process_request_options(options) do config = Application.get_env(:pleroma, :http, []) @@ -10,4 +28,9 @@ defmodule Pleroma.HTTP do _ -> options ++ [proxy: proxy] end end + + def get(url, headers \\ [], options \\ []), do: request(:get, url, "", headers, options) + + def post(url, body, headers \\ [], options \\ []), + do: request(:post, url, body, headers, options) end diff --git a/lib/pleroma/plugs/digest.ex b/lib/pleroma/plugs/digest.ex new file mode 100644 index 000000000..9d6bbb085 --- /dev/null +++ b/lib/pleroma/plugs/digest.ex @@ -0,0 +1,10 @@ +defmodule Pleroma.Web.Plugs.DigestPlug do + alias Plug.Conn + require Logger + + def read_body(conn, opts) do + {:ok, body, conn} = Conn.read_body(conn, opts) + digest = "SHA-256=" <> (:crypto.hash(:sha256, body) |> Base.encode64()) + {:ok, body, Conn.assign(conn, :digest, digest)} + end +end diff --git a/lib/pleroma/plugs/http_signature.ex b/lib/pleroma/plugs/http_signature.ex index 38bcd3a78..9e53371b7 100644 --- a/lib/pleroma/plugs/http_signature.ex +++ b/lib/pleroma/plugs/http_signature.ex @@ -19,6 +19,8 @@ defmodule Pleroma.Web.Plugs.HTTPSignaturePlug do cond do signature && String.contains?(signature, user) -> + # set (request-target) header to the appropriate value + # we also replace the digest header with the one we computed conn = conn |> put_req_header( @@ -26,6 +28,14 @@ defmodule Pleroma.Web.Plugs.HTTPSignaturePlug do String.downcase("#{conn.method}") <> " #{conn.request_path}" ) + conn = + if conn.assigns[:digest] do + conn + |> put_req_header("digest", conn.assigns[:digest]) + else + conn + end + assign(conn, :valid_signature, HTTPSignatures.validate_conn(conn)) signature -> diff --git a/lib/pleroma/upload.ex b/lib/pleroma/upload.ex index 43df0d418..e0cb545b0 100644 --- a/lib/pleroma/upload.ex +++ b/lib/pleroma/upload.ex @@ -18,8 +18,10 @@ defmodule Pleroma.Upload do File.cp!(file.path, result_file) end + strip_exif_data(content_type, result_file) + %{ - "type" => "Image", + "type" => "Document", "url" => [ %{ "type" => "Link", @@ -67,6 +69,8 @@ defmodule Pleroma.Upload do File.rename(uuidpath, result_file) end + strip_exif_data(content_type, result_file) + %{ "type" => "Image", "url" => [ @@ -80,6 +84,16 @@ defmodule Pleroma.Upload do } end + def strip_exif_data(content_type, file) do + settings = Application.get_env(:pleroma, Pleroma.Upload) + do_strip = Keyword.fetch!(settings, :strip_exif) + [filetype, ext] = String.split(content_type, "/") + + if filetype == "image" and do_strip == true do + Mogrify.open(file) |> Mogrify.custom("strip") |> Mogrify.save(in_place: true) + end + end + def upload_path do settings = Application.get_env(:pleroma, Pleroma.Upload) Keyword.fetch!(settings, :uploads) @@ -110,20 +124,20 @@ defmodule Pleroma.Upload do if should_dedupe do create_name(uuid, List.last(String.split(file.filename, ".")), type) else - unless String.contains?(file.filename, ".") do - case type do - "image/png" -> file.filename <> ".png" - "image/jpeg" -> file.filename <> ".jpg" - "image/gif" -> file.filename <> ".gif" - "video/webm" -> file.filename <> ".webm" - "video/mp4" -> file.filename <> ".mp4" - "audio/mpeg" -> file.filename <> ".mp3" - "audio/ogg" -> file.filename <> ".ogg" - "audio/wav" -> file.filename <> ".wav" - _ -> file.filename + parts = String.split(file.filename, ".") + + new_filename = + if length(parts) > 1 do + Enum.drop(parts, -1) |> Enum.join(".") + else + Enum.join(parts) end - else - file.filename + + case type do + "application/octet-stream" -> file.filename + "audio/mpeg" -> new_filename <> ".mp3" + "image/jpeg" -> new_filename <> ".jpg" + _ -> Enum.join([new_filename, String.split(type, "/") |> List.last()], ".") end end end diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index d68aef52a..7d7f3b23e 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -169,6 +169,9 @@ defmodule Pleroma.User do end def maybe_direct_follow(%User{} = follower, %User{info: info} = followed) do + user_config = Application.get_env(:pleroma, :user) + deny_follow_blocked = Keyword.get(user_config, :deny_follow_blocked) + user_info = user_info(followed) should_direct_follow = @@ -178,7 +181,8 @@ defmodule Pleroma.User do false # if the users are blocking each other, we shouldn't even be here, but check for it anyway - User.blocks?(follower, followed) == true or User.blocks?(followed, follower) == true -> + deny_follow_blocked and + (User.blocks?(follower, followed) or User.blocks?(followed, follower)) -> false # if OStatus, then there is no three-way handshake to follow @@ -206,13 +210,16 @@ defmodule Pleroma.User do end def follow(%User{} = follower, %User{info: info} = followed) do + user_config = Application.get_env(:pleroma, :user) + deny_follow_blocked = Keyword.get(user_config, :deny_follow_blocked) + ap_followers = followed.follower_address cond do following?(follower, followed) or info["deactivated"] -> {:error, "Could not follow user: #{followed.nickname} is already on your list."} - blocks?(followed, follower) -> + deny_follow_blocked and blocks?(followed, follower) -> {:error, "Could not follow user: #{followed.nickname} blocked you."} true -> @@ -391,6 +398,7 @@ defmodule Pleroma.User do Enum.map(reqs, fn req -> req.actor end) |> Enum.uniq() |> Enum.map(fn ap_id -> get_by_ap_id(ap_id) end) + |> Enum.filter(fn u -> !following?(u, user) end) {:ok, users} end @@ -521,15 +529,33 @@ defmodule Pleroma.User do Repo.all(q) end - def block(user, %{ap_id: ap_id}) do - blocks = user.info["blocks"] || [] + def block(blocker, %User{ap_id: ap_id} = blocked) do + # sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213) + blocker = + if following?(blocker, blocked) do + {:ok, blocker, _} = unfollow(blocker, blocked) + blocker + else + blocker + end + + if following?(blocked, blocker) do + unfollow(blocked, blocker) + end + + blocks = blocker.info["blocks"] || [] new_blocks = Enum.uniq([ap_id | blocks]) - new_info = Map.put(user.info, "blocks", new_blocks) + new_info = Map.put(blocker.info, "blocks", new_blocks) - cs = User.info_changeset(user, %{info: new_info}) + cs = User.info_changeset(blocker, %{info: new_info}) update_and_set_cache(cs) end + # helper to handle the block given only an actor's AP id + def block(blocker, %{ap_id: ap_id}) do + block(blocker, User.get_by_ap_id(ap_id)) + end + def unblock(user, %{ap_id: ap_id}) do blocks = user.info["blocks"] || [] new_blocks = List.delete(blocks, ap_id) @@ -598,7 +624,7 @@ defmodule Pleroma.User do |> Enum.each(fn activity -> case activity.data["type"] do "Create" -> - ActivityPub.delete(Object.get_by_ap_id(activity.data["object"]["id"])) + ActivityPub.delete(Object.normalize(activity.data["object"])) # TODO: Do something with likes, follows, repeats. _ -> diff --git a/lib/pleroma/user_invite_token.ex b/lib/pleroma/user_invite_token.ex new file mode 100644 index 000000000..48ee1019a --- /dev/null +++ b/lib/pleroma/user_invite_token.ex @@ -0,0 +1,40 @@ +defmodule Pleroma.UserInviteToken do + use Ecto.Schema + + import Ecto.Changeset + + alias Pleroma.{User, UserInviteToken, Repo} + + schema "user_invite_tokens" do + field(:token, :string) + field(:used, :boolean, default: false) + + timestamps() + end + + def create_token do + token = :crypto.strong_rand_bytes(32) |> Base.url_encode64() + + token = %UserInviteToken{ + used: false, + token: token + } + + Repo.insert(token) + end + + def used_changeset(struct) do + struct + |> cast(%{}, []) + |> put_change(:used, true) + end + + def mark_as_used(token) do + with %{used: false} = token <- Repo.get_by(UserInviteToken, %{token: token}), + {:ok, token} <- Repo.update(used_changeset(token)) do + {:ok, token} + else + _e -> {:error, token} + end + end +end diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index a4b49e73c..298d7817a 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -48,7 +48,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do end def insert(map, local \\ true) when is_map(map) do - with nil <- Activity.get_by_ap_id(map["id"]), + with nil <- Activity.normalize(map), map <- lazy_put_activity_defaults(map), :ok <- check_actor_is_active(map["actor"]), {:ok, map} <- MRF.filter(map), @@ -83,6 +83,14 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do if activity.local do Pleroma.Web.Streamer.stream("public:local", activity) end + + if activity.data["object"]["attachment"] != [] do + Pleroma.Web.Streamer.stream("public:media", activity) + + if activity.local do + Pleroma.Web.Streamer.stream("public:local:media", activity) + end + end else if !Enum.member?(activity.data["cc"] || [], public) && !Enum.member?( @@ -261,16 +269,25 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do end def block(blocker, blocked, activity_id \\ nil, local \\ true) do - follow_activity = fetch_latest_follow(blocker, blocked) + ap_config = Application.get_env(:pleroma, :activitypub) + unfollow_blocked = Keyword.get(ap_config, :unfollow_blocked) + outgoing_blocks = Keyword.get(ap_config, :outgoing_blocks) - if follow_activity do - unfollow(blocker, blocked, nil, local) + with true <- unfollow_blocked do + follow_activity = fetch_latest_follow(blocker, blocked) + + if follow_activity do + unfollow(blocker, blocked, nil, local) + end end - with block_data <- make_block_data(blocker, blocked, activity_id), + with true <- outgoing_blocks, + block_data <- make_block_data(blocker, blocked, activity_id), {:ok, activity} <- insert(block_data, local), :ok <- maybe_federate(activity) do {:ok, activity} + else + _e -> {:ok, nil} end end @@ -448,6 +465,15 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do defp restrict_media(query, _), do: query + defp restrict_replies(query, %{"exclude_replies" => val}) when val == "true" or val == "1" do + from( + activity in query, + where: fragment("?->'object'->>'inReplyTo' is null", activity.data) + ) + end + + defp restrict_replies(query, _), do: query + # Only search through last 100_000 activities by default defp restrict_recent(query, %{"whole_db" => true}), do: query @@ -505,6 +531,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do |> restrict_blocked(opts) |> restrict_media(opts) |> restrict_visibility(opts) + |> restrict_replies(opts) end def fetch_activities(recipients, opts \\ %{}) do @@ -556,7 +583,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do def fetch_and_prepare_user_from_ap_id(ap_id) do with {:ok, %{status_code: 200, body: body}} <- - @httpoison.get(ap_id, Accept: "application/activity+json"), + @httpoison.get(ap_id, [Accept: "application/activity+json"], follow_redirect: true), {:ok, data} <- Jason.decode(body) do user_data_from_user_object(data) else @@ -632,13 +659,23 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do Logger.info("Federating #{id} to #{inbox}") host = URI.parse(inbox).host + digest = "SHA-256=" <> (:crypto.hash(:sha256, json) |> Base.encode64()) + signature = - Pleroma.Web.HTTPSignatures.sign(actor, %{host: host, "content-length": byte_size(json)}) + Pleroma.Web.HTTPSignatures.sign(actor, %{ + host: host, + "content-length": byte_size(json), + digest: digest + }) @httpoison.post( inbox, json, - [{"Content-Type", "application/activity+json"}, {"signature", signature}], + [ + {"Content-Type", "application/activity+json"}, + {"signature", signature}, + {"digest", digest} + ], hackney: [pool: :default] ) end @@ -661,7 +698,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do recv_timeout: 20000 ), {:ok, data} <- Jason.decode(body), - nil <- Object.get_by_ap_id(data["id"]), + nil <- Object.normalize(data), params <- %{ "type" => "Create", "to" => data["to"], @@ -670,7 +707,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do "object" => data }, {:ok, activity} <- Transmogrifier.handle_incoming(params) do - {:ok, Object.get_by_ap_id(activity.data["object"]["id"])} + {:ok, Object.normalize(activity.data["object"])} else object = %Object{} -> {:ok, object} @@ -679,7 +716,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do Logger.info("Couldn't get object via AP, trying out OStatus fetching...") case OStatus.fetch_activity_from_url(id) do - {:ok, [activity | _]} -> {:ok, Object.get_by_ap_id(activity.data["object"]["id"])} + {:ok, [activity | _]} -> {:ok, Object.normalize(activity.data["object"])} e -> e end end diff --git a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex index 8d770387d..7fecb8a4f 100644 --- a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex @@ -4,6 +4,15 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do @mrf_policy Application.get_env(:pleroma, :mrf_simple) + @accept Keyword.get(@mrf_policy, :accept) + defp check_accept(actor_info, object) do + if length(@accept) > 0 and not (actor_info.host in @accept) do + {:reject, nil} + else + {:ok, object} + end + end + @reject Keyword.get(@mrf_policy, :reject) defp check_reject(actor_info, object) do if actor_info.host in @reject do @@ -74,7 +83,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do def filter(object) do actor_info = URI.parse(object["actor"]) - with {:ok, object} <- check_reject(actor_info, object), + with {:ok, object} <- check_accept(actor_info, object), + {:ok, object} <- check_reject(actor_info, object), {:ok, object} <- check_media_removal(actor_info, object), {:ok, object} <- check_media_nsfw(actor_info, object), {:ok, object} <- check_ftl_removal(actor_info, object) do diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 300e0fcdd..1367bc7e3 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -13,17 +13,73 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do require Logger + def get_actor(%{"actor" => actor}) when is_binary(actor) do + actor + end + + def get_actor(%{"actor" => actor}) when is_list(actor) do + if is_binary(Enum.at(actor, 0)) do + Enum.at(actor, 0) + else + Enum.find(actor, fn %{"type" => type} -> type == "Person" end) + |> Map.get("id") + end + end + + def get_actor(%{"actor" => actor}) when is_map(actor) do + actor["id"] + end + @doc """ Modifies an incoming AP object (mastodon format) to our internal format. """ def fix_object(object) do object - |> Map.put("actor", object["attributedTo"]) + |> fix_actor |> fix_attachments |> fix_context |> fix_in_reply_to |> fix_emoji |> fix_tag + |> fix_content_map + |> fix_likes + |> fix_addressing + end + + def fix_addressing_list(map, field) do + if is_binary(map[field]) do + map + |> Map.put(field, [map[field]]) + else + map + end + end + + def fix_addressing(map) do + map + |> fix_addressing_list("to") + |> fix_addressing_list("cc") + |> fix_addressing_list("bto") + |> fix_addressing_list("bcc") + end + + def fix_actor(%{"attributedTo" => actor} = object) do + object + |> Map.put("actor", get_actor(%{"actor" => actor})) + end + + def fix_likes(%{"likes" => likes} = object) + when is_bitstring(likes) do + # Check for standardisation + # This is what Peertube does + # curl -H 'Accept: application/activity+json' $likes | jq .totalItems + object + |> Map.put("likes", []) + |> Map.put("like_count", 0) + end + + def fix_likes(object) do + object end def fix_in_reply_to(%{"inReplyTo" => in_reply_to_id} = object) @@ -53,8 +109,11 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do def fix_in_reply_to(object), do: object def fix_context(object) do + context = object["context"] || object["conversation"] || Utils.generate_context_id() + object - |> Map.put("context", object["conversation"]) + |> Map.put("context", context) + |> Map.put("conversation", context) end def fix_attachments(object) do @@ -107,10 +166,34 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do |> Map.put("tag", combined) end + # content map usually only has one language so this will do for now. + def fix_content_map(%{"contentMap" => content_map} = object) do + content_groups = Map.to_list(content_map) + {_, content} = Enum.at(content_groups, 0) + + object + |> Map.put("content", content) + end + + def fix_content_map(object), do: object + + # disallow objects with bogus IDs + def handle_incoming(%{"id" => nil}), do: :error + def handle_incoming(%{"id" => ""}), do: :error + # length of https:// = 8, should validate better, but good enough for now. + def handle_incoming(%{"id" => id}) when not (is_binary(id) and length(id) > 8), do: :error + # TODO: validate those with a Ecto scheme # - tags # - emoji - def handle_incoming(%{"type" => "Create", "object" => %{"type" => "Note"} = object} = data) do + def handle_incoming(%{"type" => "Create", "object" => %{"type" => objtype} = object} = data) + when objtype in ["Article", "Note", "Video"] do + actor = get_actor(data) + + data = + Map.put(data, "actor", actor) + |> fix_addressing + with nil <- Activity.get_create_activity_by_object_ap_id(object["id"]), %User{} = user <- User.get_or_fetch_by_ap_id(data["actor"]) do object = fix_object(data["object"]) @@ -400,7 +483,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do def handle_incoming(_), do: :error def get_obj_helper(id) do - if object = Object.get_by_ap_id(id), do: {:ok, object}, else: nil + if object = Object.normalize(id), do: {:ok, object}, else: nil end def set_reply_to_uri(%{"inReplyTo" => inReplyTo} = object) do @@ -448,14 +531,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do # Mastodon Accept/Reject requires a non-normalized object containing the actor URIs, # because of course it does. def prepare_outgoing(%{"type" => "Accept"} = data) do - follow_activity_id = - if is_binary(data["object"]) do - data["object"] - else - data["object"]["id"] - end - - with follow_activity <- Activity.get_by_ap_id(follow_activity_id) do + with follow_activity <- Activity.normalize(data["object"]) do object = %{ "actor" => follow_activity.actor, "object" => follow_activity.data["object"], @@ -473,14 +549,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do end def prepare_outgoing(%{"type" => "Reject"} = data) do - follow_activity_id = - if is_binary(data["object"]) do - data["object"] - else - data["object"]["id"] - end - - with follow_activity <- Activity.get_by_ap_id(follow_activity_id) do + with follow_activity <- Activity.normalize(data["object"]) do object = %{ "actor" => follow_activity.actor, "object" => follow_activity.data["object"], diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex index 64329b710..7cdc1656b 100644 --- a/lib/pleroma/web/activity_pub/utils.ex +++ b/lib/pleroma/web/activity_pub/utils.ex @@ -128,7 +128,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do Inserts a full object if it is contained in an activity. """ def insert_full_object(%{"object" => %{"type" => type} = object_data}) - when is_map(object_data) and type in ["Note"] do + when is_map(object_data) and type in ["Article", "Note", "Video"] do with {:ok, _} <- Object.create(object_data) do :ok end @@ -204,13 +204,17 @@ defmodule Pleroma.Web.ActivityPub.Utils do end def add_like_to_object(%Activity{data: %{"actor" => actor}}, object) do - with likes <- [actor | object.data["likes"] || []] |> Enum.uniq() do + likes = if is_list(object.data["likes"]), do: object.data["likes"], else: [] + + with likes <- [actor | likes] |> Enum.uniq() do update_likes_in_object(likes, object) end end def remove_like_from_object(%Activity{data: %{"actor" => actor}}, object) do - with likes <- (object.data["likes"] || []) |> List.delete(actor) do + likes = if is_list(object.data["likes"]), do: object.data["likes"], else: [] + + with likes <- likes |> List.delete(actor) do update_likes_in_object(likes, object) end end @@ -357,13 +361,19 @@ defmodule Pleroma.Web.ActivityPub.Utils do end def add_announce_to_object(%Activity{data: %{"actor" => actor}}, object) do - with announcements <- [actor | object.data["announcements"] || []] |> Enum.uniq() do + announcements = + if is_list(object.data["announcements"]), do: object.data["announcements"], else: [] + + with announcements <- [actor | announcements] |> Enum.uniq() do update_element_in_object("announcement", announcements, object) end end def remove_announce_from_object(%Activity{data: %{"actor" => actor}}, object) do - with announcements <- (object.data["announcements"] || []) |> List.delete(actor) do + announcements = + if is_list(object.data["announcements"]), do: object.data["announcements"], else: [] + + with announcements <- announcements |> List.delete(actor) do update_element_in_object("announcement", announcements, object) end end diff --git a/lib/pleroma/web/activity_pub/views/user_view.ex b/lib/pleroma/web/activity_pub/views/user_view.ex index f4b2e0610..fc76f2940 100644 --- a/lib/pleroma/web/activity_pub/views/user_view.ex +++ b/lib/pleroma/web/activity_pub/views/user_view.ex @@ -12,7 +12,7 @@ defmodule Pleroma.Web.ActivityPub.UserView do def render("user.json", %{user: user}) do {:ok, user} = WebFinger.ensure_keys_present(user) {:ok, _, public_key} = Salmon.keys_from_pem(user.info["keys"]) - public_key = :public_key.pem_entry_encode(:RSAPublicKey, public_key) + public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key) public_key = :public_key.pem_encode([public_key]) %{ @@ -42,7 +42,8 @@ defmodule Pleroma.Web.ActivityPub.UserView do "image" => %{ "type" => "Image", "url" => User.banner_url(user) - } + }, + "tag" => user.info["source_data"]["tag"] || [] } |> Map.merge(Utils.make_json_ld_header()) end @@ -98,9 +99,6 @@ defmodule Pleroma.Web.ActivityPub.UserView do info = User.user_info(user) params = %{ - "type" => ["Create", "Announce"], - "actor_id" => user.ap_id, - "whole_db" => true, "limit" => "10" } @@ -111,10 +109,8 @@ defmodule Pleroma.Web.ActivityPub.UserView do params end - activities = ActivityPub.fetch_public_activities(params) - min_id = Enum.at(activities, 0).id - - activities = Enum.reverse(activities) + activities = ActivityPub.fetch_user_activities(user, nil, params) + min_id = Enum.at(Enum.reverse(activities), 0).id max_id = Enum.at(activities, 0).id collection = diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex index 8845419c2..125c57d05 100644 --- a/lib/pleroma/web/common_api/common_api.ex +++ b/lib/pleroma/web/common_api/common_api.ex @@ -1,5 +1,5 @@ defmodule Pleroma.Web.CommonAPI do - alias Pleroma.{Repo, Activity, Object} + alias Pleroma.{User, Repo, Activity, Object} alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Formatter @@ -7,7 +7,7 @@ defmodule Pleroma.Web.CommonAPI do def delete(activity_id, user) do with %Activity{data: %{"object" => %{"id" => object_id}}} <- Repo.get(Activity, activity_id), - %Object{} = object <- Object.get_by_ap_id(object_id), + %Object{} = object <- Object.normalize(object_id), true <- user.info["is_moderator"] || user.ap_id == object.data["actor"], {:ok, delete} <- ActivityPub.delete(object) do {:ok, delete} @@ -16,7 +16,7 @@ defmodule Pleroma.Web.CommonAPI do def repeat(id_or_ap_id, user) do with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id), - object <- Object.get_by_ap_id(activity.data["object"]["id"]) do + object <- Object.normalize(activity.data["object"]["id"]) do ActivityPub.announce(user, object) else _ -> @@ -26,7 +26,7 @@ defmodule Pleroma.Web.CommonAPI do def unrepeat(id_or_ap_id, user) do with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id), - object <- Object.get_by_ap_id(activity.data["object"]["id"]) do + object <- Object.normalize(activity.data["object"]["id"]) do ActivityPub.unannounce(user, object) else _ -> @@ -37,7 +37,7 @@ defmodule Pleroma.Web.CommonAPI do def favorite(id_or_ap_id, user) do with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id), false <- activity.data["actor"] == user.ap_id, - object <- Object.get_by_ap_id(activity.data["object"]["id"]) do + object <- Object.normalize(activity.data["object"]["id"]) do ActivityPub.like(user, object) else _ -> @@ -48,7 +48,7 @@ defmodule Pleroma.Web.CommonAPI do def unfavorite(id_or_ap_id, user) do with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id), false <- activity.data["actor"] == user.ap_id, - object <- Object.get_by_ap_id(activity.data["object"]["id"]) do + object <- Object.normalize(activity.data["object"]["id"]) do ActivityPub.unlike(user, object) else _ -> @@ -61,8 +61,13 @@ defmodule Pleroma.Web.CommonAPI do do: visibility def get_visibility(%{"in_reply_to_status_id" => status_id}) when not is_nil(status_id) do - inReplyTo = get_replied_to_activity(status_id) - Pleroma.Web.MastodonAPI.StatusView.get_visibility(inReplyTo.data["object"]) + case get_replied_to_activity(status_id) do + nil -> + "public" + + inReplyTo -> + Pleroma.Web.MastodonAPI.StatusView.get_visibility(inReplyTo.data["object"]) + end end def get_visibility(_), do: "public" @@ -118,6 +123,18 @@ defmodule Pleroma.Web.CommonAPI do end def update(user) do + user = + with emoji <- emoji_from_profile(user), + source_data <- (user.info["source_data"] || %{}) |> Map.put("tag", emoji), + new_info <- Map.put(user.info, "source_data", source_data), + change <- User.info_changeset(user, %{info: new_info}), + {:ok, user} <- User.update_and_set_cache(change) do + user + else + _e -> + user + end + ActivityPub.update(%{ local: true, to: [user.follower_address], diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex index 30089f553..358ca22ac 100644 --- a/lib/pleroma/web/common_api/utils.ex +++ b/lib/pleroma/web/common_api/utils.ex @@ -1,6 +1,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do alias Pleroma.{Repo, Object, Formatter, Activity} alias Pleroma.Web.ActivityPub.Utils + alias Pleroma.Web.Endpoint alias Pleroma.User alias Calendar.Strftime alias Comeonin.Pbkdf2 @@ -64,7 +65,6 @@ defmodule Pleroma.Web.CommonAPI.Utils do def make_content_html(status, mentions, attachments, tags, no_attachment_links \\ false) do status - |> String.replace("\r", "") |> format_input(mentions, tags) |> maybe_add_attachments(attachments, no_attachment_links) end @@ -95,7 +95,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do def format_input(text, mentions, tags) do text |> Formatter.html_escape() - |> String.replace("\n", "<br>") + |> String.replace(~r/\r?\n/, "<br>") |> (&{[], &1}).() |> Formatter.add_links() |> Formatter.add_user_links(mentions) @@ -109,7 +109,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do |> Enum.sort_by(fn {tag, _} -> -String.length(tag) end) Enum.reduce(tags, text, fn {full, tag}, text -> - url = "#<a href='#{Pleroma.Web.base_url()}/tag/#{tag}' rel='tag'>#{tag}</a>" + url = "<a href='#{Pleroma.Web.base_url()}/tag/#{tag}' rel='tag'>##{tag}</a>" String.replace(text, full, url) end) end @@ -196,4 +196,15 @@ defmodule Pleroma.Web.CommonAPI.Utils do _ -> {:error, "Invalid password."} end end + + def emoji_from_profile(%{info: info} = user) do + (Formatter.get_emoji(user.bio) ++ Formatter.get_emoji(user.name)) + |> Enum.map(fn {shortcode, url} -> + %{ + "type" => "Emoji", + "icon" => %{"type" => "Image", "url" => "#{Endpoint.url()}#{url}"}, + "name" => ":#{shortcode}:" + } + end) + end end diff --git a/lib/pleroma/web/endpoint.ex b/lib/pleroma/web/endpoint.ex index 1a012c1b4..cbedca004 100644 --- a/lib/pleroma/web/endpoint.ex +++ b/lib/pleroma/web/endpoint.ex @@ -35,7 +35,8 @@ defmodule Pleroma.Web.Endpoint do parsers: [:urlencoded, :multipart, :json], pass: ["*/*"], json_decoder: Jason, - length: Application.get_env(:pleroma, :instance) |> Keyword.get(:upload_limit) + length: Application.get_env(:pleroma, :instance) |> Keyword.get(:upload_limit), + body_reader: {Pleroma.Web.Plugs.DigestPlug, :read_body, []} ) plug(Plug.MethodOverride) diff --git a/lib/pleroma/web/federator/federator.ex b/lib/pleroma/web/federator/federator.ex index 8ca530031..ccefb0bdf 100644 --- a/lib/pleroma/web/federator/federator.ex +++ b/lib/pleroma/web/federator/federator.ex @@ -95,7 +95,7 @@ defmodule Pleroma.Web.Federator do params = Utils.normalize_params(params) with {:ok, _user} <- ap_enabled_actor(params["actor"]), - nil <- Activity.get_by_ap_id(params["id"]), + nil <- Activity.normalize(params["id"]), {:ok, _activity} <- Transmogrifier.handle_incoming(params) do else %Activity{} -> diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex index 8a8d1e050..f482de6fd 100644 --- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex @@ -1,25 +1,30 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do use Pleroma.Web, :controller - alias Pleroma.{Repo, Activity, User, Notification, Stats} + alias Pleroma.{Repo, Object, Activity, User, Notification, Stats} alias Pleroma.Web alias Pleroma.Web.MastodonAPI.{StatusView, AccountView, MastodonView, ListView} alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.Utils - alias Pleroma.Web.{CommonAPI, OStatus} + alias Pleroma.Web.CommonAPI alias Pleroma.Web.OAuth.{Authorization, Token, App} alias Comeonin.Pbkdf2 import Ecto.Query require Logger + @httpoison Application.get_env(:pleroma, :httpoison) + action_fallback(:errors) def create_app(conn, params) do with cs <- App.register_changeset(%App{}, params) |> IO.inspect(), {:ok, app} <- Repo.insert(cs) |> IO.inspect() do res = %{ - id: app.id, + id: app.id |> to_string, + name: app.client_name, client_id: app.client_id, - client_secret: app.client_secret + client_secret: app.client_secret, + redirect_uri: app.redirect_uris, + website: app.website } json(conn, res) @@ -125,7 +130,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do response = %{ uri: Web.base_url(), title: Keyword.get(@instance, :name), - description: "A Pleroma instance, an alternative fediverse server", + description: Keyword.get(@instance, :description), version: "#{@mastodon_api_level} (compatible; #{Keyword.get(@instance, :version)})", email: Keyword.get(@instance, :email), urls: %{ @@ -428,16 +433,43 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do render(conn, AccountView, "relationships.json", %{user: user, targets: targets}) end - def upload(%{assigns: %{user: _}} = conn, %{"file" => file}) do - with {:ok, object} <- ActivityPub.upload(file) do + def update_media(%{assigns: %{user: _}} = conn, data) do + with %Object{} = object <- Repo.get(Object, data["id"]), + true <- is_binary(data["description"]), + description <- data["description"] do + new_data = %{object.data | "name" => description} + + change = Object.change(object, %{data: new_data}) + {:ok, media_obj} = Repo.update(change) + data = - object.data + new_data |> Map.put("id", object.id) render(conn, StatusView, "attachment.json", %{attachment: data}) end end + def upload(%{assigns: %{user: _}} = conn, %{"file" => file} = data) do + with {:ok, object} <- ActivityPub.upload(file) do + objdata = + if Map.has_key?(data, "description") do + Map.put(object.data, "name", data["description"]) + else + object.data + end + + change = Object.change(object, %{data: objdata}) + {:ok, object} = Repo.update(change) + + objdata = + objdata + |> Map.put("id", object.id) + + render(conn, StatusView, "attachment.json", %{attachment: objdata}) + end + end + def favourited_by(conn, %{"id" => id}) do with %Activity{data: %{"object" => %{"likes" => likes}}} <- Repo.get(Activity, id) do q = from(u in User, where: u.ap_id in ^likes) @@ -621,17 +653,61 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do json(conn, %{}) end + def search2(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do + accounts = User.search(query, params["resolve"] == "true") + + fetched = + if Regex.match?(~r/https?:/, query) do + with {:ok, object} <- ActivityPub.fetch_object_from_id(query) do + [Activity.get_create_activity_by_object_ap_id(object.data["id"])] + else + _e -> [] + end + end || [] + + q = + from( + a in Activity, + where: fragment("?->>'type' = 'Create'", a.data), + where: "https://www.w3.org/ns/activitystreams#Public" in a.recipients, + where: + fragment( + "to_tsvector('english', ?->'object'->>'content') @@ plainto_tsquery('english', ?)", + a.data, + ^query + ), + limit: 20, + order_by: [desc: :id] + ) + + statuses = Repo.all(q) ++ fetched + + tags_path = Web.base_url() <> "/tag/" + + tags = + String.split(query) + |> Enum.uniq() + |> Enum.filter(fn tag -> String.starts_with?(tag, "#") end) + |> Enum.map(fn tag -> String.slice(tag, 1..-1) end) + |> Enum.map(fn tag -> %{name: tag, url: tags_path <> tag} end) + + res = %{ + "accounts" => AccountView.render("accounts.json", users: accounts, for: user, as: :user), + "statuses" => + StatusView.render("index.json", activities: statuses, for: user, as: :activity), + "hashtags" => tags + } + + json(conn, res) + end + def search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do accounts = User.search(query, params["resolve"] == "true") fetched = if Regex.match?(~r/https?:/, query) do - with {:ok, activities} <- OStatus.fetch_activity_from_url(query) do - activities - |> Enum.filter(fn - %{data: %{"type" => "Create"}} -> true - _ -> false - end) + with {:ok, object} <- ActivityPub.fetch_object_from_id(query) do + [Activity.get_create_activity_by_object_ap_id(object.data["id"])] else _e -> [] end @@ -812,11 +888,16 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do boost_modal: false, delete_modal: true, auto_play_gif: false, - reduce_motion: false + display_sensitive_media: false, + reduce_motion: false, + max_toot_chars: Keyword.get(@instance, :limit) + }, + rights: %{ + delete_others_notice: !!user.info["is_moderator"] }, compose: %{ me: "#{user.id}", - default_privacy: "public", + default_privacy: user.info["default_scope"] || "public", default_sensitive: false }, media_attachments: %{ @@ -1013,4 +1094,45 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do |> put_status(500) |> json("Something went wrong") end + + @suggestions Application.get_env(:pleroma, :suggestions) + + def suggestions(%{assigns: %{user: user}} = conn, _) do + if Keyword.get(@suggestions, :enabled, false) do + api = Keyword.get(@suggestions, :third_party_engine, "") + timeout = Keyword.get(@suggestions, :timeout, 5000) + + host = + Application.get_env(:pleroma, Pleroma.Web.Endpoint) + |> Keyword.get(:url) + |> Keyword.get(:host) + + user = user.nickname + url = String.replace(api, "{{host}}", host) |> String.replace("{{user}}", user) + + with {:ok, %{status_code: 200, body: body}} <- + @httpoison.get(url, [], timeout: timeout, recv_timeout: timeout), + {:ok, data} <- Jason.decode(body) do + data2 = + Enum.slice(data, 0, 40) + |> Enum.map(fn x -> + Map.put( + x, + "id", + case User.get_or_fetch(x["acct"]) do + %{id: id} -> id + _ -> 0 + end + ) + end) + + conn + |> json(data2) + else + e -> Logger.error("Could not retrieve suggestions at fetch #{url}, #{inspect(e)}") + end + else + json(conn, []) + end + end end diff --git a/lib/pleroma/web/mastodon_api/mastodon_socket.ex b/lib/pleroma/web/mastodon_api/mastodon_socket.ex index 46648c366..174293906 100644 --- a/lib/pleroma/web/mastodon_api/mastodon_socket.ex +++ b/lib/pleroma/web/mastodon_api/mastodon_socket.ex @@ -15,8 +15,16 @@ defmodule Pleroma.Web.MastodonAPI.MastodonSocket do with token when not is_nil(token) <- params["access_token"], %Token{user_id: user_id} <- Repo.get_by(Token, token: token), %User{} = user <- Repo.get(User, user_id), - stream when stream in ["public", "public:local", "user", "direct", "list"] <- - params["stream"] do + stream + when stream in [ + "public", + "public:local", + "public:media", + "public:local:media", + "user", + "direct", + "list" + ] <- params["stream"] do topic = if stream == "list", do: "list:#{params["list"]}", else: stream socket = diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex index 9db683f44..d9edcae7f 100644 --- a/lib/pleroma/web/mastodon_api/views/account_view.ex +++ b/lib/pleroma/web/mastodon_api/views/account_view.ex @@ -14,6 +14,18 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do header = User.banner_url(user) |> MediaProxy.url() user_info = User.user_info(user) + emojis = + (user.info["source_data"]["tag"] || []) + |> Enum.filter(fn %{"type" => t} -> t == "Emoji" end) + |> Enum.map(fn %{"icon" => %{"url" => url}, "name" => name} -> + %{ + "shortcode" => String.trim(name, ":"), + "url" => MediaProxy.url(url), + "static_url" => MediaProxy.url(url), + "visible_in_picker" => false + } + end) + %{ id: to_string(user.id), username: hd(String.split(user.nickname, "@")), @@ -24,12 +36,14 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do followers_count: user_info.follower_count, following_count: user_info.following_count, statuses_count: user_info.note_count, - note: user.bio || "", + note: HtmlSanitizeEx.basic_html(user.bio) || "", url: user.ap_id, avatar: image, avatar_static: image, header: header, header_static: header, + emojis: emojis, + fields: [], source: %{ note: "", privacy: "public", diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index 59898457b..6962aa54f 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -54,8 +54,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do %{ id: to_string(activity.id), uri: object, - # TODO: This might be wrong, check with mastodon. - url: nil, + url: object, account: AccountView.render("account.json", %{user: user}), in_reply_to_id: nil, in_reply_to_account_id: nil, @@ -100,8 +99,9 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do repeated = opts[:for] && opts[:for].ap_id in (object["announcements"] || []) favorited = opts[:for] && opts[:for].ap_id in (object["likes"] || []) - attachments = - render_many(object["attachment"] || [], StatusView, "attachment.json", as: :attachment) + attachment_data = object["attachment"] || [] + attachment_data = attachment_data ++ if object["type"] == "Video", do: [object], else: [] + attachments = render_many(attachment_data, StatusView, "attachment.json", as: :attachment) created_at = Utils.to_masto_date(object["published"]) @@ -128,7 +128,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do in_reply_to_id: reply_to && to_string(reply_to.id), in_reply_to_account_id: reply_to_user && to_string(reply_to_user.id), reblog: nil, - content: HtmlSanitizeEx.basic_html(object["content"]), + content: render_content(object), created_at: created_at, reblogs_count: announcement_count, favourites_count: like_count, @@ -152,7 +152,9 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do end def render("attachment.json", %{attachment: attachment}) do - [%{"mediaType" => media_type, "href" => href} | _] = attachment["url"] + [attachment_url | _] = attachment["url"] + media_type = attachment_url["mediaType"] || attachment_url["mimeType"] + href = attachment_url["href"] type = cond do @@ -170,7 +172,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do remote_url: href, preview_url: MediaProxy.url(href), text_url: href, - type: type + type: type, + description: attachment["name"] } end @@ -207,4 +210,34 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do "direct" end end + + def render_content(%{"type" => "Video"} = object) do + name = object["name"] + + content = + if !!name and name != "" do + "<p><a href=\"#{object["id"]}\">#{name}</a></p>#{object["content"]}" + else + object["content"] + end + + HtmlSanitizeEx.basic_html(content) + end + + def render_content(%{"type" => "Article"} = object) do + summary = object["name"] + + content = + if !!summary and summary != "" do + "<p><a href=\"#{object["url"]}\">#{summary}</a></p>#{object["content"]}" + else + object["content"] + end + + HtmlSanitizeEx.basic_html(content) + end + + def render_content(object) do + HtmlSanitizeEx.basic_html(object["content"]) + end end diff --git a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex index aec77168a..2fab60274 100644 --- a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex +++ b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex @@ -4,8 +4,6 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do alias Pleroma.Stats alias Pleroma.Web - @instance Application.get_env(:pleroma, :instance) - def schemas(conn, _params) do response = %{ links: [ @@ -21,20 +19,23 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do # Schema definition: https://github.com/jhass/nodeinfo/blob/master/schemas/2.0/schema.json def nodeinfo(conn, %{"version" => "2.0"}) do + instance = Application.get_env(:pleroma, :instance) + media_proxy = Application.get_env(:pleroma, :media_proxy) + suggestions = Application.get_env(:pleroma, :suggestions) stats = Stats.get_stats() response = %{ version: "2.0", software: %{ name: "pleroma", - version: Keyword.get(@instance, :version) + version: Keyword.get(instance, :version) }, protocols: ["ostatus", "activitypub"], services: %{ inbound: [], outbound: [] }, - openRegistrations: Keyword.get(@instance, :registrations_open), + openRegistrations: Keyword.get(instance, :registrations_open), usage: %{ users: %{ total: stats.user_count || 0 @@ -42,7 +43,16 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do localPosts: stats.status_count || 0 }, metadata: %{ - nodeName: Keyword.get(@instance, :name) + nodeName: Keyword.get(instance, :name), + nodeDescription: Keyword.get(instance, :description), + mediaProxy: Keyword.get(media_proxy, :enabled), + private: !Keyword.get(instance, :public, true), + suggestions: %{ + enabled: Keyword.get(suggestions, :enabled, false), + thirdPartyEngine: Keyword.get(suggestions, :third_party_engine, ""), + timeout: Keyword.get(suggestions, :timeout, 5000), + web: Keyword.get(suggestions, :web, "") + } } } diff --git a/lib/pleroma/web/ostatus/activity_representer.ex b/lib/pleroma/web/ostatus/activity_representer.ex index 4c4a0c233..537bd9f77 100644 --- a/lib/pleroma/web/ostatus/activity_representer.ex +++ b/lib/pleroma/web/ostatus/activity_representer.ex @@ -249,7 +249,7 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenter do author = if with_author, do: [{:author, UserRepresenter.to_simple_form(user)}], else: [] mentions = (activity.recipients || []) |> get_mentions - follow_activity = Activity.get_by_ap_id(follow_activity["id"]) + follow_activity = Activity.normalize(follow_activity) [ {:"activity:object-type", ['http://activitystrea.ms/schema/1.0/activity']}, diff --git a/lib/pleroma/web/ostatus/handlers/delete_handler.ex b/lib/pleroma/web/ostatus/handlers/delete_handler.ex index 4f3016b65..6330d7f64 100644 --- a/lib/pleroma/web/ostatus/handlers/delete_handler.ex +++ b/lib/pleroma/web/ostatus/handlers/delete_handler.ex @@ -6,7 +6,7 @@ defmodule Pleroma.Web.OStatus.DeleteHandler do def handle_delete(entry, _doc \\ nil) do with id <- XML.string_from_xpath("//id", entry), - object when not is_nil(object) <- Object.get_by_ap_id(id), + %Object{} = object <- Object.normalize(id), {:ok, delete} <- ActivityPub.delete(object, false) do delete end diff --git a/lib/pleroma/web/ostatus/ostatus.ex b/lib/pleroma/web/ostatus/ostatus.ex index f0ff0624f..916c894eb 100644 --- a/lib/pleroma/web/ostatus/ostatus.ex +++ b/lib/pleroma/web/ostatus/ostatus.ex @@ -89,7 +89,7 @@ defmodule Pleroma.Web.OStatus do def make_share(entry, doc, retweeted_activity) do with {:ok, actor} <- find_make_or_update_user(doc), - %Object{} = object <- Object.get_by_ap_id(retweeted_activity.data["object"]["id"]), + %Object{} = object <- Object.normalize(retweeted_activity.data["object"]), id when not is_nil(id) <- string_from_xpath("/entry/id", entry), {:ok, activity, _object} = ActivityPub.announce(actor, object, id, false) do {:ok, activity} @@ -107,7 +107,7 @@ defmodule Pleroma.Web.OStatus do def make_favorite(entry, doc, favorited_activity) do with {:ok, actor} <- find_make_or_update_user(doc), - %Object{} = object <- Object.get_by_ap_id(favorited_activity.data["object"]["id"]), + %Object{} = object <- Object.normalize(favorited_activity.data["object"]), id when not is_nil(id) <- string_from_xpath("/entry/id", entry), {:ok, activity, _object} = ActivityPub.like(actor, object, id, false) do {:ok, activity} diff --git a/lib/pleroma/web/ostatus/ostatus_controller.ex b/lib/pleroma/web/ostatus/ostatus_controller.ex index 2f72fdb16..09d1b1110 100644 --- a/lib/pleroma/web/ostatus/ostatus_controller.ex +++ b/lib/pleroma/web/ostatus/ostatus_controller.ex @@ -6,6 +6,7 @@ defmodule Pleroma.Web.OStatus.OStatusController do alias Pleroma.Repo alias Pleroma.Web.{OStatus, Federator} alias Pleroma.Web.XML + alias Pleroma.Web.ActivityPub.ObjectView alias Pleroma.Web.ActivityPub.ActivityPubController alias Pleroma.Web.ActivityPub.ActivityPub @@ -90,7 +91,7 @@ defmodule Pleroma.Web.OStatus.OStatusController do %User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do case get_format(conn) do "html" -> redirect(conn, to: "/notice/#{activity.id}") - _ -> represent_activity(conn, activity, user) + _ -> represent_activity(conn, nil, activity, user) end else {:public?, false} -> @@ -107,12 +108,12 @@ defmodule Pleroma.Web.OStatus.OStatusController do def activity(conn, %{"uuid" => uuid}) do with id <- o_status_url(conn, :activity, uuid), - {_, %Activity{} = activity} <- {:activity, Activity.get_by_ap_id(id)}, + {_, %Activity{} = activity} <- {:activity, Activity.normalize(id)}, {_, true} <- {:public?, ActivityPub.is_public?(activity)}, %User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do - case get_format(conn) do + case format = get_format(conn) do "html" -> redirect(conn, to: "/notice/#{activity.id}") - _ -> represent_activity(conn, activity, user) + _ -> represent_activity(conn, format, activity, user) end else {:public?, false} -> @@ -130,14 +131,14 @@ defmodule Pleroma.Web.OStatus.OStatusController do with {_, %Activity{} = activity} <- {:activity, Repo.get(Activity, id)}, {_, true} <- {:public?, ActivityPub.is_public?(activity)}, %User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do - case get_format(conn) do + case format = get_format(conn) do "html" -> conn |> put_resp_content_type("text/html") |> send_file(200, "priv/static/index.html") _ -> - represent_activity(conn, activity, user) + represent_activity(conn, format, activity, user) end else {:public?, false} -> @@ -151,7 +152,13 @@ defmodule Pleroma.Web.OStatus.OStatusController do end end - defp represent_activity(conn, activity, user) do + defp represent_activity(conn, "activity+json", activity, user) do + conn + |> put_resp_header("content-type", "application/activity+json") + |> json(ObjectView.render("object.json", %{object: activity})) + end + + defp represent_activity(conn, _, activity, user) do response = activity |> ActivityRepresenter.to_simple_form(user, true) diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 13bd393ab..68e159f6a 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -8,8 +8,19 @@ defmodule Pleroma.Web.Router do @public Keyword.get(@instance, :public) @registrations_open Keyword.get(@instance, :registrations_open) - def user_fetcher(username) do - {:ok, Repo.get_by(User, %{nickname: username})} + def user_fetcher(username_or_email) do + { + :ok, + cond do + # First, try logging in as if it was a name + user = Repo.get_by(User, %{nickname: username_or_email}) -> + user + + # If we get nil, we try using it as an email + user = Repo.get_by(User, %{email: username_or_email}) -> + user + end + } end pipeline :api do @@ -127,6 +138,7 @@ defmodule Pleroma.Web.Router do get("/notifications/:id", MastodonAPIController, :get_notification) post("/media", MastodonAPIController, :upload) + put("/media/:id", MastodonAPIController, :update_media) get("/lists", MastodonAPIController, :get_lists) get("/lists/:id", MastodonAPIController, :get_list) @@ -140,6 +152,8 @@ defmodule Pleroma.Web.Router do get("/domain_blocks", MastodonAPIController, :domain_blocks) post("/domain_blocks", MastodonAPIController, :block_domain) delete("/domain_blocks", MastodonAPIController, :unblock_domain) + + get("/suggestions", MastodonAPIController, :suggestions) end scope "/api/web", Pleroma.Web.MastodonAPI do @@ -170,9 +184,16 @@ defmodule Pleroma.Web.Router do get("/accounts/:id/following", MastodonAPIController, :following) get("/accounts/:id", MastodonAPIController, :user) + get("/trends", MastodonAPIController, :empty_array) + get("/search", MastodonAPIController, :search) end + scope "/api/v2", Pleroma.Web.MastodonAPI do + pipe_through(:api) + get("/search", MastodonAPIController, :search2) + end + scope "/api", Pleroma.Web do pipe_through(:config) @@ -194,9 +215,7 @@ defmodule Pleroma.Web.Router do get("/statuses/show/:id", TwitterAPI.Controller, :fetch_status) get("/statusnet/conversation/:id", TwitterAPI.Controller, :fetch_conversation) - if @registrations_open do - post("/account/register", TwitterAPI.Controller, :register) - end + post("/account/register", TwitterAPI.Controller, :register) get("/search", TwitterAPI.Controller, :search) get("/statusnet/tags/timeline/:tag", TwitterAPI.Controller, :public_and_external_timeline) @@ -269,6 +288,7 @@ defmodule Pleroma.Web.Router do get("/friendships/no_retweets/ids", TwitterAPI.Controller, :empty_array) get("/mutes/users/ids", TwitterAPI.Controller, :empty_array) + get("/qvitter/mutes", TwitterAPI.Controller, :raw_empty_array) get("/externalprofile/show", TwitterAPI.Controller, :external_profile) end @@ -347,6 +367,7 @@ defmodule Pleroma.Web.Router do end scope "/", Fallback do + get("/registration/:token", RedirectController, :registration_page) get("/*path", RedirectController, :redirector) end end @@ -361,4 +382,8 @@ defmodule Fallback.RedirectController do |> send_file(200, "priv/static/index.html") end end + + def registration_page(conn, params) do + redirector(conn, params) + end end diff --git a/lib/pleroma/web/streamer.ex b/lib/pleroma/web/streamer.ex index ce38f3cc3..c61bad830 100644 --- a/lib/pleroma/web/streamer.ex +++ b/lib/pleroma/web/streamer.ex @@ -158,7 +158,7 @@ defmodule Pleroma.Web.Streamer do user = User.get_cached_by_ap_id(socket.assigns[:user].ap_id) blocks = user.info["blocks"] || [] - parent = Object.get_by_ap_id(item.data["object"]) + parent = Object.normalize(item.data["object"]) unless is_nil(parent) or item.actor in blocks or parent.data["actor"] in blocks do send(socket.transport_pid, {:text, represent_update(item, user)}) diff --git a/lib/pleroma/web/templates/mastodon_api/mastodon/index.html.eex b/lib/pleroma/web/templates/mastodon_api/mastodon/index.html.eex index 6a00b9e2c..0862412ea 100644 --- a/lib/pleroma/web/templates/mastodon_api/mastodon/index.html.eex +++ b/lib/pleroma/web/templates/mastodon_api/mastodon/index.html.eex @@ -19,7 +19,7 @@ <script id='initial-state' type='application/json'><%= raw @initial_state %></script> <script src="/packs/application.js"></script> </head> -<body class='app-body no-reduce-motion'> +<body class='app-body no-reduce-motion system-font'> <div class='app-holder' data-props='{"locale":"en"}' id='mastodon'> </div> </body> diff --git a/lib/pleroma/web/twitter_api/controllers/util_controller.ex b/lib/pleroma/web/twitter_api/controllers/util_controller.ex index 7a0c37ce9..d1ecebf61 100644 --- a/lib/pleroma/web/twitter_api/controllers/util_controller.ex +++ b/lib/pleroma/web/twitter_api/controllers/util_controller.ex @@ -99,6 +99,10 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do conn |> render("followed.html", %{error: false}) else + # Was already following user + {:error, "Could not follow user:" <> _rest} -> + render(conn, "followed.html", %{error: false}) + _e -> conn |> render("follow_login.html", %{ @@ -117,6 +121,11 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do conn |> render("followed.html", %{error: false}) else + # Was already following user + {:error, "Could not follow user:" <> _rest} -> + conn + |> render("followed.html", %{error: false}) + e -> Logger.debug("Remote follow failed with error #{inspect(e)}") @@ -126,6 +135,8 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do end @instance Application.get_env(:pleroma, :instance) + @instance_fe Application.get_env(:pleroma, :fe) + @instance_chat Application.get_env(:pleroma, :chat) def config(conn, _params) do case get_format(conn) do "xml" -> @@ -148,9 +159,23 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do json(conn, %{ site: %{ name: Keyword.get(@instance, :name), + description: Keyword.get(@instance, :description), server: Web.base_url(), textlimit: to_string(Keyword.get(@instance, :limit)), - closed: if(Keyword.get(@instance, :registrations_open), do: "0", else: "1") + closed: if(Keyword.get(@instance, :registrations_open), do: "0", else: "1"), + private: if(Keyword.get(@instance, :public, true), do: "0", else: "1"), + pleromafe: %{ + theme: Keyword.get(@instance_fe, :theme), + background: Keyword.get(@instance_fe, :background), + logo: Keyword.get(@instance_fe, :logo), + redirectRootNoLogin: Keyword.get(@instance_fe, :redirect_root_no_login), + redirectRootLogin: Keyword.get(@instance_fe, :redirect_root_login), + chatDisabled: !Keyword.get(@instance_chat, :enabled), + showInstanceSpecificPanel: Keyword.get(@instance_fe, :show_instance_panel), + scopeOptionsEnabled: Keyword.get(@instance_fe, :scope_options_enabled), + collapseMessageWithSubject: + Keyword.get(@instance_fe, :collapse_message_with_subject) + } } }) end diff --git a/lib/pleroma/web/twitter_api/representers/activity_representer.ex b/lib/pleroma/web/twitter_api/representers/activity_representer.ex index 57837205e..9abea59a7 100644 --- a/lib/pleroma/web/twitter_api/representers/activity_representer.ex +++ b/lib/pleroma/web/twitter_api/representers/activity_representer.ex @@ -4,7 +4,7 @@ defmodule Pleroma.Web.TwitterAPI.Representers.ActivityRepresenter do use Pleroma.Web.TwitterAPI.Representers.BaseRepresenter alias Pleroma.Web.TwitterAPI.Representers.ObjectRepresenter alias Pleroma.{Activity, User} - alias Pleroma.Web.TwitterAPI.{TwitterAPI, UserView} + alias Pleroma.Web.TwitterAPI.{TwitterAPI, UserView, ActivityView} alias Pleroma.Web.CommonAPI.Utils alias Pleroma.Formatter @@ -164,19 +164,21 @@ defmodule Pleroma.Web.TwitterAPI.Representers.ActivityRepresenter do tags = if possibly_sensitive, do: Enum.uniq(["nsfw" | tags]), else: tags - summary = activity.data["object"]["summary"] - - content = - if !!summary and summary != "" do - "<span>#{activity.data["object"]["summary"]}</span><br />#{content}</span>" - else - content - end + {summary, content} = ActivityView.render_content(object) html = HtmlSanitizeEx.basic_html(content) |> Formatter.emojify(object["emoji"]) + video = + if object["type"] == "Video" do + vid = [object] + else + [] + end + + attachments = (object["attachment"] || []) ++ video + %{ "id" => activity.id, "uri" => activity.data["object"]["id"], @@ -188,7 +190,7 @@ defmodule Pleroma.Web.TwitterAPI.Representers.ActivityRepresenter do "created_at" => created_at, "in_reply_to_status_id" => object["inReplyToStatusId"], "statusnet_conversation_id" => conversation_id, - "attachments" => (object["attachment"] || []) |> ObjectRepresenter.enum_to_list(opts), + "attachments" => attachments |> ObjectRepresenter.enum_to_list(opts), "attentions" => attentions, "fave_num" => like_count, "repeat_num" => announcement_count, @@ -198,7 +200,8 @@ defmodule Pleroma.Web.TwitterAPI.Representers.ActivityRepresenter do "tags" => tags, "activity_type" => "post", "possibly_sensitive" => possibly_sensitive, - "visibility" => Pleroma.Web.MastodonAPI.StatusView.get_visibility(object) + "visibility" => Pleroma.Web.MastodonAPI.StatusView.get_visibility(object), + "summary" => object["summary"] } end diff --git a/lib/pleroma/web/twitter_api/representers/object_representer.ex b/lib/pleroma/web/twitter_api/representers/object_representer.ex index 9af8a1691..6aa794a59 100644 --- a/lib/pleroma/web/twitter_api/representers/object_representer.ex +++ b/lib/pleroma/web/twitter_api/representers/object_representer.ex @@ -7,18 +7,20 @@ defmodule Pleroma.Web.TwitterAPI.Representers.ObjectRepresenter do %{ url: url["href"] |> Pleroma.Web.MediaProxy.url(), - mimetype: url["mediaType"], + mimetype: url["mediaType"] || url["mimeType"], id: data["uuid"], - oembed: false + oembed: false, + description: data["name"] } end def to_map(%Object{data: %{"url" => url} = data}, _opts) when is_binary(url) do %{ url: url |> Pleroma.Web.MediaProxy.url(), - mimetype: data["mediaType"], + mimetype: data["mediaType"] || url["mimeType"], id: data["uuid"], - oembed: false + oembed: false, + description: data["name"] } end diff --git a/lib/pleroma/web/twitter_api/twitter_api.ex b/lib/pleroma/web/twitter_api/twitter_api.ex index c23b3c2c4..dbad08e66 100644 --- a/lib/pleroma/web/twitter_api/twitter_api.ex +++ b/lib/pleroma/web/twitter_api/twitter_api.ex @@ -1,11 +1,13 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do - alias Pleroma.{User, Activity, Repo, Object} + alias Pleroma.{UserInviteToken, User, Activity, Repo, Object} alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.TwitterAPI.UserView alias Pleroma.Web.{OStatus, CommonAPI} import Ecto.Query + @instance Application.get_env(:pleroma, :instance) @httpoison Application.get_env(:pleroma, :httpoison) + @registrations_open Keyword.get(@instance, :registrations_open) def create_status(%User{} = user, %{"status" => _} = data) do CommonAPI.post(user, data) @@ -120,6 +122,8 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do end def register_user(params) do + tokenString = params["token"] + params = %{ nickname: params["nickname"], name: params["fullname"], @@ -129,17 +133,33 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do password_confirmation: params["confirm"] } - changeset = User.register_changeset(%User{}, params) + # no need to query DB if registration is open + token = + unless @registrations_open || is_nil(tokenString) do + Repo.get_by(UserInviteToken, %{token: tokenString}) + end - with {:ok, user} <- Repo.insert(changeset) do - {:ok, user} - else - {:error, changeset} -> - errors = - Ecto.Changeset.traverse_errors(changeset, fn {msg, _opts} -> msg end) - |> Jason.encode!() + cond do + @registrations_open || (!is_nil(token) && !token.used) -> + changeset = User.register_changeset(%User{}, params) + + with {:ok, user} <- Repo.insert(changeset) do + !@registrations_open && UserInviteToken.mark_as_used(token.token) + {:ok, user} + else + {:error, changeset} -> + errors = + Ecto.Changeset.traverse_errors(changeset, fn {msg, _opts} -> msg end) + |> Jason.encode!() + + {:error, %{error: errors}} + end + + !@registrations_open && is_nil(token) -> + {:error, "Invalid token"} - {:error, %{error: errors}} + !@registrations_open && token.used -> + {:error, "Expired token"} end end diff --git a/lib/pleroma/web/twitter_api/twitter_api_controller.ex b/lib/pleroma/web/twitter_api/twitter_api_controller.ex index ff5921807..b3a56b27e 100644 --- a/lib/pleroma/web/twitter_api/twitter_api_controller.ex +++ b/lib/pleroma/web/twitter_api/twitter_api_controller.ex @@ -1,7 +1,9 @@ defmodule Pleroma.Web.TwitterAPI.Controller do use Pleroma.Web, :controller + alias Pleroma.Formatter alias Pleroma.Web.TwitterAPI.{TwitterAPI, UserView, ActivityView, NotificationView} alias Pleroma.Web.CommonAPI + alias Pleroma.Web.CommonAPI.Utils, as: CommonUtils alias Pleroma.{Repo, Activity, User, Notification} alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.Utils @@ -404,11 +406,25 @@ defmodule Pleroma.Web.TwitterAPI.Controller do json(conn, Jason.encode!([])) end + def raw_empty_array(conn, _params) do + json(conn, []) + end + def update_profile(%{assigns: %{user: user}} = conn, params) do params = if bio = params["description"] do - bio_brs = Regex.replace(~r/\r?\n/, bio, "<br>") - Map.put(params, "bio", bio_brs) + mentions = Formatter.parse_mentions(bio) + tags = Formatter.parse_tags(bio) + + emoji = + (user.info["source_data"]["tag"] || []) + |> Enum.filter(fn %{"type" => t} -> t == "Emoji" end) + |> Enum.map(fn %{"icon" => %{"url" => url}, "name" => name} -> + {String.trim(name, ":"), url} + end) + + bio_html = CommonUtils.format_input(bio, mentions, tags) + Map.put(params, "bio", bio_html |> Formatter.emojify(emoji)) else params end @@ -427,6 +443,19 @@ defmodule Pleroma.Web.TwitterAPI.Controller do user end + user = + if default_scope = params["default_scope"] do + with new_info <- Map.put(user.info, "default_scope", default_scope), + change <- User.info_changeset(user, %{info: new_info}), + {:ok, user} <- User.update_and_set_cache(change) do + user + else + _e -> user + end + else + user + end + with changeset <- User.update_changeset(user, params), {:ok, user} <- User.update_and_set_cache(changeset) do CommonAPI.update(user) diff --git a/lib/pleroma/web/twitter_api/views/activity_view.ex b/lib/pleroma/web/twitter_api/views/activity_view.ex index 62ce3b7b5..55b5287f5 100644 --- a/lib/pleroma/web/twitter_api/views/activity_view.ex +++ b/lib/pleroma/web/twitter_api/views/activity_view.ex @@ -228,15 +228,7 @@ defmodule Pleroma.Web.TwitterAPI.ActivityView do tags = if possibly_sensitive, do: Enum.uniq(["nsfw" | tags]), else: tags - summary = activity.data["object"]["summary"] - content = object["content"] - - content = - if !!summary and summary != "" do - "<span>#{activity.data["object"]["summary"]}</span><br />#{content}</span>" - else - content - end + {summary, content} = render_content(object) html = HtmlSanitizeEx.basic_html(content) @@ -263,7 +255,41 @@ defmodule Pleroma.Web.TwitterAPI.ActivityView do "tags" => tags, "activity_type" => "post", "possibly_sensitive" => possibly_sensitive, - "visibility" => Pleroma.Web.MastodonAPI.StatusView.get_visibility(object) + "visibility" => Pleroma.Web.MastodonAPI.StatusView.get_visibility(object), + "summary" => summary } end + + def render_content(%{"type" => "Note"} = object) do + summary = object["summary"] + + content = + if !!summary and summary != "" do + "<p>#{summary}</p>#{object["content"]}" + else + object["content"] + end + + {summary, content} + end + + def render_content(%{"type" => "Article"} = object) do + summary = object["name"] || object["summary"] + + content = + if !!summary and summary != "" do + "<p><a href=\"#{object["url"]}\">#{summary}</a></p>#{object["content"]}" + else + object["content"] + end + + {summary, content} + end + + def render_content(object) do + summary = object["summary"] || "Unhandled activity type: #{object["type"]}" + content = "<p>#{summary}</p>#{object["content"]}" + + {summary, content} + end end diff --git a/lib/pleroma/web/twitter_api/views/user_view.ex b/lib/pleroma/web/twitter_api/views/user_view.ex index 711008973..32f93153d 100644 --- a/lib/pleroma/web/twitter_api/views/user_view.ex +++ b/lib/pleroma/web/twitter_api/views/user_view.ex @@ -1,6 +1,7 @@ defmodule Pleroma.Web.TwitterAPI.UserView do use Pleroma.Web, :view alias Pleroma.User + alias Pleroma.Formatter alias Pleroma.Web.CommonAPI.Utils alias Pleroma.Web.MediaProxy @@ -28,9 +29,18 @@ defmodule Pleroma.Web.TwitterAPI.UserView do user_info = User.get_cached_user_info(user) + emoji = + (user.info["source_data"]["tag"] || []) + |> Enum.filter(fn %{"type" => t} -> t == "Emoji" end) + |> Enum.map(fn %{"icon" => %{"url" => url}, "name" => name} -> + {String.trim(name, ":"), url} + end) + data = %{ "created_at" => user.inserted_at |> Utils.format_naive_asctime(), - "description" => HtmlSanitizeEx.strip_tags(user.bio), + "description" => + HtmlSanitizeEx.strip_tags((user.bio || "") |> String.replace("<br>", "\n")), + "description_html" => HtmlSanitizeEx.basic_html(user.bio), "favourites_count" => 0, "followers_count" => user_info[:follower_count], "following" => following, @@ -39,6 +49,7 @@ defmodule Pleroma.Web.TwitterAPI.UserView do "friends_count" => user_info[:following_count], "id" => user.id, "name" => user.name, + "name_html" => HtmlSanitizeEx.strip_tags(user.name) |> Formatter.emojify(emoji), "profile_image_url" => image, "profile_image_url_https" => image, "profile_image_url_profile_size" => image, @@ -52,7 +63,8 @@ defmodule Pleroma.Web.TwitterAPI.UserView do "cover_photo" => User.banner_url(user) |> MediaProxy.url(), "background_image" => image_url(user.info["background"]) |> MediaProxy.url(), "is_local" => user.local, - "locked" => !!user.info["locked"] + "locked" => !!user.info["locked"], + "default_scope" => user.info["default_scope"] || "public" } if assigns[:token] do |