diff options
Diffstat (limited to 'lib/pleroma')
26 files changed, 396 insertions, 135 deletions
diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex index 60cba1580..e81816e35 100644 --- a/lib/pleroma/application.ex +++ b/lib/pleroma/application.ex @@ -22,6 +22,8 @@ defmodule Pleroma.Application do def start(_type, _args) do import Cachex.Spec + Pleroma.Config.DeprecationWarnings.warn() + # Define workers and child supervisors to be supervised children = [ @@ -99,13 +101,16 @@ defmodule Pleroma.Application do ], id: :cachex_idem ), - worker(Pleroma.FlakeId, []), - worker(Pleroma.Web.Federator.RetryQueue, []), - worker(Pleroma.Stats, []), - worker(Pleroma.Web.Push, []), - worker(Pleroma.Jobs, []), - worker(Task, [&Pleroma.Web.Federator.init/0], restart: :temporary) + worker(Pleroma.FlakeId, []) ] ++ + hackney_pool_children() ++ + [ + worker(Pleroma.Web.Federator.RetryQueue, []), + worker(Pleroma.Stats, []), + worker(Pleroma.Web.Push, []), + worker(Pleroma.Jobs, []), + worker(Task, [&Pleroma.Web.Federator.init/0], restart: :temporary) + ] ++ streamer_child() ++ chat_child() ++ [ @@ -120,6 +125,20 @@ defmodule Pleroma.Application do Supervisor.start_link(children, opts) end + def enabled_hackney_pools() do + [:media] ++ + if Application.get_env(:tesla, :adapter) == Tesla.Adapter.Hackney do + [:federation] + else + [] + end ++ + if Pleroma.Config.get([Pleroma.Uploader, :proxy_remote]) do + [:upload] + else + [] + end + end + if Mix.env() == :test do defp streamer_child(), do: [] defp chat_child(), do: [] @@ -136,4 +155,11 @@ defmodule Pleroma.Application do end end end + + defp hackney_pool_children() do + for pool <- enabled_hackney_pools() do + options = Pleroma.Config.get([:hackney_pools, pool]) + :hackney_pool.child_spec(pool, options) + end + end end diff --git a/lib/pleroma/config/deprecation_warnings.ex b/lib/pleroma/config/deprecation_warnings.ex new file mode 100644 index 000000000..dc50682ee --- /dev/null +++ b/lib/pleroma/config/deprecation_warnings.ex @@ -0,0 +1,20 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Config.DeprecationWarnings do + require Logger + + def check_frontend_config_mechanism() do + if Pleroma.Config.get(:fe) do + Logger.warn(""" + !!!DEPRECATION WARNING!!! + You are using the old configuration mechanism for the frontend. Please check config.md. + """) + end + end + + def warn do + check_frontend_config_mechanism() + end +end diff --git a/lib/pleroma/flake_id.ex b/lib/pleroma/flake_id.ex index 26399ae05..69ab8ccf9 100644 --- a/lib/pleroma/flake_id.ex +++ b/lib/pleroma/flake_id.ex @@ -33,6 +33,10 @@ defmodule Pleroma.FlakeId do def to_string(s), do: s + def from_string(int) when is_integer(int) do + from_string(Kernel.to_string(int)) + end + for i <- [-1, 0] do def from_string(unquote(i)), do: <<0::integer-size(128)>> def from_string(unquote(Kernel.to_string(i))), do: <<0::integer-size(128)>> @@ -90,7 +94,7 @@ defmodule Pleroma.FlakeId do @impl GenServer def init([]) do - {:ok, %FlakeId{node: mac(), time: time()}} + {:ok, %FlakeId{node: worker_id(), time: time()}} end @impl GenServer @@ -161,23 +165,8 @@ defmodule Pleroma.FlakeId do 1_000_000_000 * mega_seconds + seconds * 1000 + :erlang.trunc(micro_seconds / 1000) end - def mac do - {:ok, addresses} = :inet.getifaddrs() - - macids = - Enum.reduce(addresses, [], fn {_iface, attrs}, acc -> - case attrs[:hwaddr] do - [0, 0, 0 | _] -> acc - mac when is_list(mac) -> [mac_to_worker_id(mac) | acc] - _ -> acc - end - end) - - List.first(macids) - end - - def mac_to_worker_id(mac) do - <<worker::integer-size(48)>> = :binary.list_to_bin(mac) + defp worker_id() do + <<worker::integer-size(48)>> = :crypto.strong_rand_bytes(6) worker end end diff --git a/lib/pleroma/html.ex b/lib/pleroma/html.ex index f5c6e5033..bf5daa948 100644 --- a/lib/pleroma/html.ex +++ b/lib/pleroma/html.ex @@ -58,6 +58,20 @@ defmodule Pleroma.HTML do "#{signature}#{to_string(scrubber)}" end) end + + def extract_first_external_url(object, content) do + key = "URL|#{object.id}" + + Cachex.fetch!(:scrubber_cache, key, fn _key -> + result = + content + |> Floki.filter_out("a.mention") + |> Floki.attribute("a", "href") + |> Enum.at(0) + + {:commit, {:ok, result}} + end) + end end defmodule Pleroma.HTML.Scrubber.TwitterText do diff --git a/lib/pleroma/http/connection.ex b/lib/pleroma/http/connection.ex index 699d80cd7..b798eaa5a 100644 --- a/lib/pleroma/http/connection.ex +++ b/lib/pleroma/http/connection.ex @@ -10,7 +10,8 @@ defmodule Pleroma.HTTP.Connection do @hackney_options [ timeout: 10000, recv_timeout: 20000, - follow_redirect: true + follow_redirect: true, + pool: :federation ] @adapter Application.get_env(:tesla, :adapter) diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex index e47145601..2364d36da 100644 --- a/lib/pleroma/notification.ex +++ b/lib/pleroma/notification.ex @@ -35,7 +35,8 @@ defmodule Pleroma.Notification do n in Notification, where: n.user_id == ^user.id, order_by: [desc: n.id], - preload: [:activity], + join: activity in assoc(n, :activity), + preload: [activity: activity], limit: 20 ) @@ -66,7 +67,8 @@ defmodule Pleroma.Notification do from( n in Notification, where: n.id == ^id, - preload: [:activity] + join: activity in assoc(n, :activity), + preload: [activity: activity] ) notification = Repo.one(query) diff --git a/lib/pleroma/plugs/oauth_plug.ex b/lib/pleroma/plugs/oauth_plug.ex index 437aa95b3..945a1d49f 100644 --- a/lib/pleroma/plugs/oauth_plug.ex +++ b/lib/pleroma/plugs/oauth_plug.ex @@ -33,7 +33,12 @@ defmodule Pleroma.Plugs.OAuthPlug do # @spec fetch_user_and_token(String.t()) :: {:ok, User.t(), Token.t()} | nil defp fetch_user_and_token(token) do - query = from(q in Token, where: q.token == ^token, preload: [:user]) + query = + from(t in Token, + where: t.token == ^token, + join: user in assoc(t, :user), + preload: [user: user] + ) with %Token{user: %{info: %{deactivated: false} = _} = user} = token_record <- Repo.one(query) do {:ok, user, token_record} diff --git a/lib/pleroma/uploaders/mdii.ex b/lib/pleroma/uploaders/mdii.ex index 530b34362..320b07abd 100644 --- a/lib/pleroma/uploaders/mdii.ex +++ b/lib/pleroma/uploaders/mdii.ex @@ -24,7 +24,8 @@ defmodule Pleroma.Uploaders.MDII do extension = String.split(upload.name, ".") |> List.last() query = "#{cgi}?#{extension}" - with {:ok, %{status: 200, body: body}} <- @httpoison.post(query, file_data) do + with {:ok, %{status: 200, body: body}} <- + @httpoison.post(query, file_data, adapter: [pool: :default]) do remote_file_name = String.split(body) |> List.first() public_url = "#{files}/#{remote_file_name}.#{extension}" {:ok, {:url, public_url}} diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 1468cc133..bd797db40 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -309,20 +309,21 @@ defmodule Pleroma.User do @doc "A mass follow for local users. Ignores blocks and has no side effects" @spec follow_all(User.t(), list(User.t())) :: {atom(), User.t()} def follow_all(follower, followeds) do - following = - (follower.following ++ Enum.map(followeds, fn %{follower_address: fa} -> fa end)) - |> Enum.uniq() + followed_addresses = Enum.map(followeds, fn %{follower_address: fa} -> fa end) - {:ok, follower} = - follower - |> follow_changeset(%{following: following}) - |> update_and_set_cache + q = + from(u in User, + where: u.id == ^follower.id, + update: [set: [following: fragment("array_cat(?, ?)", u.following, ^followed_addresses)]] + ) + + {1, [follower]} = Repo.update_all(q, [], returning: true) Enum.each(followeds, fn followed -> update_follower_count(followed) end) - {:ok, follower} + set_cache(follower) end def follow(%User{} = follower, %User{info: info} = followed) do @@ -343,18 +344,17 @@ defmodule Pleroma.User do Websub.subscribe(follower, followed) end - following = - [ap_followers | follower.following] - |> Enum.uniq() + q = + from(u in User, + where: u.id == ^follower.id, + update: [push: [following: ^ap_followers]] + ) - follower = - follower - |> follow_changeset(%{following: following}) - |> update_and_set_cache + {1, [follower]} = Repo.update_all(q, [], returning: true) {:ok, _} = update_follower_count(followed) - follower + set_cache(follower) end end @@ -362,17 +362,18 @@ defmodule Pleroma.User do ap_followers = followed.follower_address if following?(follower, followed) and follower.ap_id != followed.ap_id do - following = - follower.following - |> List.delete(ap_followers) + q = + from(u in User, + where: u.id == ^follower.id, + update: [pull: [following: ^ap_followers]] + ) - {:ok, follower} = - follower - |> follow_changeset(%{following: following}) - |> update_and_set_cache + {1, [follower]} = Repo.update_all(q, [], returning: true) {:ok, followed} = update_follower_count(followed) + set_cache(follower) + {:ok, follower, Utils.fetch_latest_follow(follower, followed)} else {:error, "Not subscribed!"} @@ -423,12 +424,16 @@ defmodule Pleroma.User do get_by_nickname(nickname) end + def set_cache(user) do + Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user) + Cachex.put(:user_cache, "nickname:#{user.nickname}", user) + Cachex.put(:user_cache, "user_info:#{user.id}", user_info(user)) + {:ok, user} + end + def update_and_set_cache(changeset) do with {:ok, user} <- Repo.update(changeset) do - Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user) - Cachex.put(:user_cache, "nickname:#{user.nickname}", user) - Cachex.put(:user_cache, "user_info:#{user.id}", user_info(user)) - {:ok, user} + set_cache(user) else e -> e end diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 5be359887..6d784717e 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -64,7 +64,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do end end - defp check_remote_limit(%{"object" => %{"content" => content}}) do + defp check_remote_limit(%{"object" => %{"content" => content}}) when not is_nil(content) do limit = Pleroma.Config.get([:instance, :remote_limit]) String.length(content) <= limit end @@ -88,6 +88,10 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do recipients: recipients }) + Task.start(fn -> + Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity) + end) + Notification.create_notifications(activity) stream_out(activity) {:ok, activity} @@ -426,7 +430,34 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do defp restrict_since(query, _), do: query - defp restrict_tag(query, %{"tag" => tag}) do + defp restrict_tag_reject(query, %{"tag_reject" => tag_reject}) + when is_list(tag_reject) and tag_reject != [] do + from( + activity in query, + where: fragment("(not (? #> '{\"object\",\"tag\"}') \\?| ?)", activity.data, ^tag_reject) + ) + end + + defp restrict_tag_reject(query, _), do: query + + defp restrict_tag_all(query, %{"tag_all" => tag_all}) + when is_list(tag_all) and tag_all != [] do + from( + activity in query, + where: fragment("(? #> '{\"object\",\"tag\"}') \\?& ?", activity.data, ^tag_all) + ) + end + + defp restrict_tag_all(query, _), do: query + + defp restrict_tag(query, %{"tag" => tag}) when is_list(tag) do + from( + activity in query, + where: fragment("(? #> '{\"object\",\"tag\"}') \\?| ?", activity.data, ^tag) + ) + end + + defp restrict_tag(query, %{"tag" => tag}) when is_binary(tag) do from( activity in query, where: fragment("? <@ (? #> '{\"object\",\"tag\"}')", ^tag, activity.data) @@ -575,6 +606,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do base_query |> restrict_recipients(recipients, opts["user"]) |> restrict_tag(opts) + |> restrict_tag_reject(opts) + |> restrict_tag_all(opts) |> restrict_since(opts) |> restrict_local(opts) |> restrict_limit(opts) diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 6656a11c6..43725c3db 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -141,11 +141,11 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do |> 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 + # Check for standardisation + # This is what Peertube does + # curl -H 'Accept: application/activity+json' $likes | jq .totalItems + # Prismo returns only an integer (count) as "likes" + def fix_likes(%{"likes" => likes} = object) when not is_map(likes) do object |> Map.put("likes", []) |> Map.put("like_count", 0) @@ -453,9 +453,9 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do {:ok, follow_activity} <- Utils.update_follow_state(follow_activity, "reject"), %User{local: true} = follower <- User.get_cached_by_ap_id(follow_activity.data["actor"]), {:ok, activity} <- - ActivityPub.accept(%{ + ActivityPub.reject(%{ to: follow_activity.data["to"], - type: "Accept", + type: "Reject", actor: followed.ap_id, object: follow_activity.data["id"], local: false diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex index 2ff8a1e8a..3d254498c 100644 --- a/lib/pleroma/web/activity_pub/utils.ex +++ b/lib/pleroma/web/activity_pub/utils.ex @@ -316,6 +316,25 @@ defmodule Pleroma.Web.ActivityPub.Utils do @doc """ Updates a follow activity's state (for locked accounts). """ + def update_follow_state( + %Activity{data: %{"actor" => actor, "object" => object, "state" => "pending"}} = activity, + state + ) do + try do + Ecto.Adapters.SQL.query!( + Repo, + "UPDATE activities SET data = jsonb_set(data, '{state}', $1) WHERE data->>'type' = 'Follow' AND data->>'actor' = $2 AND data->>'object' = $3 AND data->>'state' = 'pending'", + [state, actor, object] + ) + + activity = Repo.get(Activity, activity.id) + {:ok, activity} + rescue + e -> + {:error, e} + end + end + def update_follow_state(%Activity{} = activity, state) do with new_data <- activity.data diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex index f4736fcb5..a92645ca3 100644 --- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex @@ -499,7 +499,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do def upload(%{assigns: %{user: user}} = conn, %{"file" => file} = data) do with {:ok, object} <- - ActivityPub.upload(file, + ActivityPub.upload( + file, actor: User.ap_id(user), description: Map.get(data, "description") ) do @@ -540,15 +541,34 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do def hashtag_timeline(%{assigns: %{user: user}} = conn, params) do local_only = params["local"] in [true, "True", "true", "1"] - params = + tags = + [params["tag"], params["any"]] + |> List.flatten() + |> Enum.uniq() + |> Enum.filter(& &1) + |> Enum.map(&String.downcase(&1)) + + tag_all = + params["all"] || + [] + |> Enum.map(&String.downcase(&1)) + + tag_reject = + params["none"] || + [] + |> Enum.map(&String.downcase(&1)) + + query_params = params |> Map.put("type", "Create") |> Map.put("local_only", local_only) |> Map.put("blocking_user", user) - |> Map.put("tag", String.downcase(params["tag"])) + |> Map.put("tag", tags) + |> Map.put("tag_all", tag_all) + |> Map.put("tag_reject", tag_reject) activities = - ActivityPub.fetch_public_activities(params) + ActivityPub.fetch_public_activities(query_params) |> Enum.reverse() conn @@ -1081,7 +1101,9 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do def login(conn, _) do with {:ok, app} <- get_or_make_app() do path = - o_auth_path(conn, :authorize, + o_auth_path( + conn, + :authorize, response_type: "code", client_id: app.client_id, redirect_uri: ".", @@ -1289,7 +1311,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do [], adapter: [ timeout: timeout, - recv_timeout: timeout + recv_timeout: timeout, + pool: :default ] ), {:ok, data} <- Jason.decode(body) do @@ -1322,6 +1345,22 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end end + def status_card(conn, %{"id" => status_id}) do + with %Activity{} = activity <- Repo.get(Activity, status_id), + true <- ActivityPub.is_public?(activity) do + data = + StatusView.render( + "card.json", + Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity) + ) + + json(conn, data) + else + _e -> + %{} + end + end + def try_render(conn, target, params) when is_binary(target) do res = render(conn, target, params) diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex index bfd6b8b22..0ba4289da 100644 --- a/lib/pleroma/web/mastodon_api/views/account_view.ex +++ b/lib/pleroma/web/mastodon_api/views/account_view.ex @@ -112,7 +112,9 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do # Pleroma extension pleroma: %{ confirmation_pending: user_info.confirmation_pending, - tags: user.tags + tags: user.tags, + is_moderator: user.info.is_moderator, + is_admin: user.info.is_admin } } end diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index 7a384e941..b14ca9f5d 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -49,12 +49,11 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do replied_to_activities = get_replied_to_activities(opts.activities) opts.activities - |> render_many( + |> safe_render_many( StatusView, "status.json", Map.put(opts, :replied_to_activities, replied_to_activities) ) - |> Enum.filter(fn x -> not is_nil(x) end) end def render( @@ -140,6 +139,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do __MODULE__ ) + card = render("card.json", Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity)) + %{ id: to_string(activity.id), uri: object["id"], @@ -148,6 +149,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, + card: card, content: content, created_at: created_at, reblogs_count: announcement_count, @@ -176,6 +178,29 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do nil end + def render("card.json", %{rich_media: rich_media, page_url: page_url}) do + page_url = rich_media[:url] || page_url + page_url_data = URI.parse(page_url) + site_name = rich_media[:site_name] || page_url_data.host + + %{ + type: "link", + provider_name: site_name, + provider_url: page_url_data.scheme <> "://" <> page_url_data.host, + url: page_url, + image: rich_media[:image] |> MediaProxy.url(), + title: rich_media[:title], + description: rich_media[:description], + pleroma: %{ + opengraph: rich_media + } + } + end + + def render("card.json", _) do + nil + end + def render("attachment.json", %{attachment: attachment}) do [attachment_url | _] = attachment["url"] media_type = attachment_url["mediaType"] || attachment_url["mimeType"] || "image" diff --git a/lib/pleroma/web/oauth/fallback_controller.ex b/lib/pleroma/web/oauth/fallback_controller.ex index 1eeda3d24..f0fe3b578 100644 --- a/lib/pleroma/web/oauth/fallback_controller.ex +++ b/lib/pleroma/web/oauth/fallback_controller.ex @@ -9,7 +9,8 @@ defmodule Pleroma.Web.OAuth.FallbackController do # No user/password def call(conn, _) do conn + |> put_status(:unauthorized) |> put_flash(:error, "Invalid Username/Password") - |> OAuthController.authorize(conn.params) + |> OAuthController.authorize(conn.params["authorization"]) end end diff --git a/lib/pleroma/web/ostatus/ostatus_controller.ex b/lib/pleroma/web/ostatus/ostatus_controller.ex index 845bc60bb..ffd3a24dc 100644 --- a/lib/pleroma/web/ostatus/ostatus_controller.ex +++ b/lib/pleroma/web/ostatus/ostatus_controller.ex @@ -166,10 +166,13 @@ defmodule Pleroma.Web.OStatus.OStatusController do end else {:public?, false} -> - {:error, :not_found} + conn + |> put_status(404) + |> Fallback.RedirectController.redirector(nil, 404) {:activity, nil} -> - {:error, :not_found} + conn + |> Fallback.RedirectController.redirector(nil, 404) e -> e diff --git a/lib/pleroma/web/rich_media/controllers/rich_media_controller.ex b/lib/pleroma/web/rich_media/controllers/rich_media_controller.ex deleted file mode 100644 index 91019961d..000000000 --- a/lib/pleroma/web/rich_media/controllers/rich_media_controller.ex +++ /dev/null @@ -1,17 +0,0 @@ -defmodule Pleroma.Web.RichMedia.RichMediaController do - use Pleroma.Web, :controller - - import Pleroma.Web.ControllerHelper, only: [json_response: 3] - - def parse(conn, %{"url" => url}) do - case Pleroma.Web.RichMedia.Parser.parse(url) do - {:ok, data} -> - conn - |> json_response(200, data) - - {:error, msg} -> - conn - |> json_response(404, msg) - end - end -end diff --git a/lib/pleroma/web/rich_media/helpers.ex b/lib/pleroma/web/rich_media/helpers.ex new file mode 100644 index 000000000..71fdddef9 --- /dev/null +++ b/lib/pleroma/web/rich_media/helpers.ex @@ -0,0 +1,18 @@ +# Pleroma: A lightweight social networking server +# Copyright _ 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.RichMedia.Helpers do + alias Pleroma.{Activity, Object, HTML} + alias Pleroma.Web.RichMedia.Parser + + def fetch_data_for_activity(%Activity{} = activity) do + with %Object{} = object <- Object.normalize(activity.data["object"]), + {:ok, page_url} <- HTML.extract_first_external_url(object, object.data["content"]), + {:ok, rich_media} <- Parser.parse(page_url) do + %{page_url: page_url, rich_media: rich_media} + else + _ -> %{} + end + end +end diff --git a/lib/pleroma/web/rich_media/parser.ex b/lib/pleroma/web/rich_media/parser.ex index 6da83c6e4..e67ecc47d 100644 --- a/lib/pleroma/web/rich_media/parser.ex +++ b/lib/pleroma/web/rich_media/parser.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Web.RichMedia.Parser do @parsers [ Pleroma.Web.RichMedia.Parsers.OGP, @@ -5,17 +9,32 @@ defmodule Pleroma.Web.RichMedia.Parser do Pleroma.Web.RichMedia.Parsers.OEmbed ] + def parse(nil), do: {:error, "No URL provided"} + if Mix.env() == :test do def parse(url), do: parse_url(url) else - def parse(url), - do: Cachex.fetch!(:rich_media_cache, url, fn _ -> parse_url(url) end) + def parse(url) do + try do + Cachex.fetch!(:rich_media_cache, url, fn _ -> + {:commit, parse_url(url)} + end) + rescue + e -> + {:error, "Cachex error: #{inspect(e)}"} + end + end end defp parse_url(url) do - {:ok, %Tesla.Env{body: html}} = Pleroma.HTTP.get(url) + try do + {:ok, %Tesla.Env{body: html}} = Pleroma.HTTP.get(url, [], adapter: [pool: :media]) - html |> maybe_parse() |> get_parsed_data() + html |> maybe_parse() |> get_parsed_data() + rescue + e -> + {:error, "Parsing error: #{inspect(e)}"} + end end defp maybe_parse(html) do @@ -27,11 +46,11 @@ defmodule Pleroma.Web.RichMedia.Parser do end) end - defp get_parsed_data(data) when data == %{} do - {:error, "No metadata found"} + defp get_parsed_data(%{title: title} = data) when is_binary(title) and byte_size(title) > 0 do + {:ok, data} end defp get_parsed_data(data) do - {:ok, data} + {:error, "Found metadata was invalid or incomplete: #{inspect(data)}"} end end diff --git a/lib/pleroma/web/rich_media/parsers/oembed_parser.ex b/lib/pleroma/web/rich_media/parsers/oembed_parser.ex index ca7226faf..2530b8c9d 100644 --- a/lib/pleroma/web/rich_media/parsers/oembed_parser.ex +++ b/lib/pleroma/web/rich_media/parsers/oembed_parser.ex @@ -20,8 +20,12 @@ defmodule Pleroma.Web.RichMedia.Parsers.OEmbed do end defp get_oembed_data(url) do - {:ok, %Tesla.Env{body: json}} = Pleroma.HTTP.get(url) + {:ok, %Tesla.Env{body: json}} = Pleroma.HTTP.get(url, [], adapter: [pool: :media]) - {:ok, Poison.decode!(json)} + {:ok, data} = Jason.decode(json) + + data = data |> Map.new(fn {k, v} -> {String.to_atom(k), v} end) + + {:ok, data} end end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index b83790858..bfa10451a 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -239,12 +239,6 @@ defmodule Pleroma.Web.Router do put("/settings", MastodonAPIController, :put_settings) end - scope "/api", Pleroma.Web.RichMedia do - pipe_through(:authenticated_api) - - get("/rich_media/parse", RichMediaController, :parse) - end - scope "/api/v1", Pleroma.Web.MastodonAPI do pipe_through(:api) get("/instance", MastodonAPIController, :masto_instance) @@ -258,7 +252,7 @@ defmodule Pleroma.Web.Router do get("/statuses/:id", MastodonAPIController, :get_status) get("/statuses/:id/context", MastodonAPIController, :get_context) - get("/statuses/:id/card", MastodonAPIController, :empty_object) + get("/statuses/:id/card", MastodonAPIController, :status_card) get("/statuses/:id/favourited_by", MastodonAPIController, :favourited_by) get("/statuses/:id/reblogged_by", MastodonAPIController, :reblogged_by) @@ -284,6 +278,7 @@ defmodule Pleroma.Web.Router do post("/help/test", TwitterAPI.UtilController, :help_test) get("/statusnet/config", TwitterAPI.UtilController, :config) get("/statusnet/version", TwitterAPI.UtilController, :version) + get("/pleroma/frontend_configurations", TwitterAPI.UtilController, :frontend_configurations) end scope "/api", Pleroma.Web do @@ -523,10 +518,10 @@ defmodule Fallback.RedirectController do alias Pleroma.Web.Metadata alias Pleroma.User - def redirector(conn, _params) do + def redirector(conn, _params, code \\ 200) do conn |> put_resp_content_type("text/html") - |> send_file(200, index_file_path()) + |> send_file(code, index_file_path()) end def redirector_with_meta(conn, %{"maybe_nickname_or_id" => maybe_nickname_or_id} = params) do diff --git a/lib/pleroma/web/twitter_api/controllers/util_controller.ex b/lib/pleroma/web/twitter_api/controllers/util_controller.ex index a79072f3d..b347faa71 100644 --- a/lib/pleroma/web/twitter_api/controllers/util_controller.ex +++ b/lib/pleroma/web/twitter_api/controllers/util_controller.ex @@ -183,25 +183,31 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do invitesEnabled: if(Keyword.get(instance, :invites_enabled, false), do: "1", else: "0") } - pleroma_fe = %{ - theme: Keyword.get(instance_fe, :theme), - background: Keyword.get(instance_fe, :background), - logo: Keyword.get(instance_fe, :logo), - logoMask: Keyword.get(instance_fe, :logo_mask), - logoMargin: Keyword.get(instance_fe, :logo_margin), - 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), - formattingOptionsEnabled: Keyword.get(instance_fe, :formatting_options_enabled), - collapseMessageWithSubject: Keyword.get(instance_fe, :collapse_message_with_subject), - hidePostStats: Keyword.get(instance_fe, :hide_post_stats), - hideUserStats: Keyword.get(instance_fe, :hide_user_stats), - scopeCopy: Keyword.get(instance_fe, :scope_copy), - subjectLineBehavior: Keyword.get(instance_fe, :subject_line_behavior), - alwaysShowSubjectInput: Keyword.get(instance_fe, :always_show_subject_input) - } + pleroma_fe = + if instance_fe do + %{ + theme: Keyword.get(instance_fe, :theme), + background: Keyword.get(instance_fe, :background), + logo: Keyword.get(instance_fe, :logo), + logoMask: Keyword.get(instance_fe, :logo_mask), + logoMargin: Keyword.get(instance_fe, :logo_margin), + 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), + formattingOptionsEnabled: Keyword.get(instance_fe, :formatting_options_enabled), + collapseMessageWithSubject: + Keyword.get(instance_fe, :collapse_message_with_subject), + hidePostStats: Keyword.get(instance_fe, :hide_post_stats), + hideUserStats: Keyword.get(instance_fe, :hide_user_stats), + scopeCopy: Keyword.get(instance_fe, :scope_copy), + subjectLineBehavior: Keyword.get(instance_fe, :subject_line_behavior), + alwaysShowSubjectInput: Keyword.get(instance_fe, :always_show_subject_input) + } + else + Pleroma.Config.get([:frontend_configurations, :pleroma_fe]) + end managed_config = Keyword.get(instance, :managed_config) @@ -216,6 +222,14 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do end end + def frontend_configurations(conn, _params) do + config = + Pleroma.Config.get(:frontend_configurations, %{}) + |> Enum.into(%{}) + + json(conn, config) + end + def version(conn, _params) do version = Pleroma.Application.named_version() diff --git a/lib/pleroma/web/twitter_api/representers/activity_representer.ex b/lib/pleroma/web/twitter_api/representers/activity_representer.ex index 19b723586..c4025cbd7 100644 --- a/lib/pleroma/web/twitter_api/representers/activity_representer.ex +++ b/lib/pleroma/web/twitter_api/representers/activity_representer.ex @@ -12,6 +12,7 @@ defmodule Pleroma.Web.TwitterAPI.Representers.ActivityRepresenter do alias Pleroma.Web.CommonAPI.Utils alias Pleroma.Formatter alias Pleroma.HTML + alias Pleroma.Web.MastodonAPI.StatusView defp user_by_ap_id(user_list, ap_id) do Enum.find(user_list, fn %{ap_id: user_id} -> ap_id == user_id end) @@ -186,6 +187,12 @@ defmodule Pleroma.Web.TwitterAPI.Representers.ActivityRepresenter do summary = HTML.strip_tags(object["summary"]) + card = + StatusView.render( + "card.json", + Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity) + ) + %{ "id" => activity.id, "uri" => activity.data["object"]["id"], @@ -214,7 +221,8 @@ defmodule Pleroma.Web.TwitterAPI.Representers.ActivityRepresenter do "possibly_sensitive" => possibly_sensitive, "visibility" => Pleroma.Web.MastodonAPI.StatusView.get_visibility(object), "summary" => summary, - "summary_html" => summary |> Formatter.emojify(object["emoji"]) + "summary_html" => summary |> Formatter.emojify(object["emoji"]), + "card" => card } end diff --git a/lib/pleroma/web/twitter_api/views/activity_view.ex b/lib/pleroma/web/twitter_api/views/activity_view.ex index e0a52d94a..d0d1221c3 100644 --- a/lib/pleroma/web/twitter_api/views/activity_view.ex +++ b/lib/pleroma/web/twitter_api/views/activity_view.ex @@ -10,6 +10,7 @@ defmodule Pleroma.Web.TwitterAPI.ActivityView do alias Pleroma.Web.TwitterAPI.ActivityView alias Pleroma.Web.TwitterAPI.TwitterAPI alias Pleroma.Web.TwitterAPI.Representers.ObjectRepresenter + alias Pleroma.Web.MastodonAPI.StatusView alias Pleroma.Activity alias Pleroma.HTML alias Pleroma.Object @@ -114,7 +115,7 @@ defmodule Pleroma.Web.TwitterAPI.ActivityView do |> Map.put(:context_ids, context_ids) |> Map.put(:users, users) - render_many( + safe_render_many( opts.activities, ActivityView, "activity.json", @@ -274,6 +275,12 @@ defmodule Pleroma.Web.TwitterAPI.ActivityView do summary = HTML.strip_tags(summary) + card = + StatusView.render( + "card.json", + Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity) + ) + %{ "id" => activity.id, "uri" => activity.data["object"]["id"], @@ -300,9 +307,10 @@ defmodule Pleroma.Web.TwitterAPI.ActivityView do "tags" => tags, "activity_type" => "post", "possibly_sensitive" => possibly_sensitive, - "visibility" => Pleroma.Web.MastodonAPI.StatusView.get_visibility(object), + "visibility" => StatusView.get_visibility(object), "summary" => summary, - "summary_html" => summary |> Formatter.emojify(object["emoji"]) + "summary_html" => summary |> Formatter.emojify(object["emoji"]), + "card" => card } end diff --git a/lib/pleroma/web/web.ex b/lib/pleroma/web/web.ex index 74b13f929..30558e692 100644 --- a/lib/pleroma/web/web.ex +++ b/lib/pleroma/web/web.ex @@ -38,6 +38,33 @@ defmodule Pleroma.Web do import Phoenix.Controller, only: [get_csrf_token: 0, get_flash: 2, view_module: 1] import Pleroma.Web.{ErrorHelpers, Gettext, Router.Helpers} + + require Logger + + @doc "Same as `render/3` but wrapped in a rescue block" + def safe_render(view, template, assigns \\ %{}) do + Phoenix.View.render(view, template, assigns) + rescue + error -> + Logger.error( + "#{__MODULE__} failed to render #{inspect({view, template})}: #{inspect(error)}" + ) + + Logger.error(inspect(__STACKTRACE__)) + nil + end + + @doc """ + Same as `render_many/4` but wrapped in rescue block. + """ + def safe_render_many(collection, view, template, assigns \\ %{}) do + Enum.map(collection, fn resource -> + as = Map.get(assigns, :as) || view.__resource__ + assigns = Map.put(assigns, as, resource) + safe_render(view, template, assigns) + end) + |> Enum.filter(& &1) + end end end |