X-Git-Url: http://git.squeep.com/?a=blobdiff_plain;f=lib%2Fpleroma%2Fweb%2Fmastodon_api%2Fmastodon_api_controller.ex;h=06f8703935674c887cb323d3a0ed0cc8a9ec064a;hb=6c8d15da110e86f799052c82df8b7b2404f8f722;hp=95d0f849c0a34e0a3fac19d02710e06f47617b2e;hpb=0b54c3d6432dea77542596e34057f8d3fc69ca4c;p=akkoma diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex index 95d0f849c..06f870393 100644 --- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex @@ -4,34 +4,44 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do use Pleroma.Web, :controller - alias Pleroma.{Repo, Object, Activity, User, Notification, Stats} + alias Pleroma.Activity + alias Pleroma.Config + alias Pleroma.Filter + alias Pleroma.Notification + alias Pleroma.Object + alias Pleroma.Repo + alias Pleroma.Stats + alias Pleroma.User alias Pleroma.Web - - alias Pleroma.Web.MastodonAPI.{ - StatusView, - AccountView, - MastodonView, - ListView, - FilterView, - PushSubscriptionView - } - - alias Pleroma.Web.ActivityPub.ActivityPub - alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.CommonAPI - alias Pleroma.Web.OAuth.{Authorization, Token, App} alias Pleroma.Web.MediaProxy + alias Pleroma.Web.Push + alias Push.Subscription + + alias Pleroma.Web.MastodonAPI.AccountView + alias Pleroma.Web.MastodonAPI.FilterView + alias Pleroma.Web.MastodonAPI.ListView + alias Pleroma.Web.MastodonAPI.MastodonView + alias Pleroma.Web.MastodonAPI.PushSubscriptionView + alias Pleroma.Web.MastodonAPI.StatusView + alias Pleroma.Web.ActivityPub.ActivityPub + alias Pleroma.Web.ActivityPub.Utils + alias Pleroma.Web.OAuth.App + alias Pleroma.Web.OAuth.Authorization + alias Pleroma.Web.OAuth.Token import Ecto.Query require Logger @httpoison Application.get_env(:pleroma, :httpoison) + @local_mastodon_name "Mastodon-Local" 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 + with cs <- App.register_changeset(%App{}, params), + false <- cs.changes[:client_name] == @local_mastodon_name, + {:ok, app} <- Repo.insert(cs) do res = %{ id: app.id |> to_string, name: app.client_name, @@ -129,7 +139,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do @mastodon_api_level "2.5.0" def masto_instance(conn, _params) do - instance = Pleroma.Config.get(:instance) + instance = Config.get(:instance) response = %{ uri: Web.base_url(), @@ -138,7 +148,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do version: "#{@mastodon_api_level} (compatible; #{Pleroma.Application.named_version()})", email: Keyword.get(instance, :email), urls: %{ - streaming_api: String.replace(Pleroma.Web.Endpoint.static_url(), "http", "ws") + streaming_api: Pleroma.Web.Endpoint.websocket_url() }, stats: Stats.get_stats(), thumbnail: Web.base_url() <> "/instance/thumbnail.jpeg", @@ -225,7 +235,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do |> Map.put("user", user) activities = - ActivityPub.fetch_activities([user.ap_id | user.following], params) + [user.ap_id | user.following] + |> ActivityPub.fetch_activities(params) |> ActivityPub.contain_timeline(user) |> Enum.reverse() @@ -238,14 +249,12 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do def public_timeline(%{assigns: %{user: user}} = conn, params) do local_only = params["local"] in [true, "True", "true", "1"] - params = + activities = params |> Map.put("type", ["Create", "Announce"]) |> Map.put("local_only", local_only) |> Map.put("blocking_user", user) - - activities = - ActivityPub.fetch_public_activities(params) + |> ActivityPub.fetch_public_activities() |> Enum.reverse() conn @@ -256,13 +265,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do def user_statuses(%{assigns: %{user: reading_user}} = conn, params) do with %User{} = user <- Repo.get(User, params["id"]) do - # Since Pleroma has no "pinned" posts feature, we'll just set an empty list here - activities = - if params["pinned"] == "true" do - [] - else - ActivityPub.fetch_user_activities(user, reading_user, params) - end + activities = ActivityPub.fetch_user_activities(user, reading_user, params) conn |> add_link_headers(:user_statuses, activities, params["id"]) @@ -320,6 +323,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do as: :activity ) |> Enum.reverse(), + # credo:disable-for-previous-line Credo.Check.Refactor.PipeChainStart descendants: StatusView.render( "index.json", @@ -328,6 +332,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do as: :activity ) |> Enum.reverse() + # credo:disable-for-previous-line Credo.Check.Refactor.PipeChainStart } json(conn, result) @@ -347,7 +352,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do params = params |> Map.put("in_reply_to_status_id", params["in_reply_to_id"]) - |> Map.put("no_attachment_links", true) idempotency_key = case get_req_header(conn, "idempotency-key") do @@ -384,7 +388,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do def unreblog_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do with {:ok, _unannounce, %{data: %{"id" => id}}} <- CommonAPI.unrepeat(ap_id_or_id, user), - %Activity{} = activity <- Activity.get_create_activity_by_object_ap_id(id) do + %Activity{} = activity <- Activity.get_create_by_object_ap_id(id) do conn |> put_view(StatusView) |> try_render("status.json", %{activity: activity, for: user, as: :activity}) @@ -393,7 +397,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do def fav_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do with {:ok, _fav, %{data: %{"id" => id}}} <- CommonAPI.favorite(ap_id_or_id, user), - %Activity{} = activity <- Activity.get_create_activity_by_object_ap_id(id) do + %Activity{} = activity <- Activity.get_create_by_object_ap_id(id) do conn |> put_view(StatusView) |> try_render("status.json", %{activity: activity, for: user, as: :activity}) @@ -402,7 +406,50 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do def unfav_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do with {:ok, _, _, %{data: %{"id" => id}}} <- CommonAPI.unfavorite(ap_id_or_id, user), - %Activity{} = activity <- Activity.get_create_activity_by_object_ap_id(id) do + %Activity{} = activity <- Activity.get_create_by_object_ap_id(id) do + conn + |> put_view(StatusView) + |> try_render("status.json", %{activity: activity, for: user, as: :activity}) + end + end + + def pin_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do + with {:ok, activity} <- CommonAPI.pin(ap_id_or_id, user) do + conn + |> put_view(StatusView) + |> try_render("status.json", %{activity: activity, for: user, as: :activity}) + else + {:error, reason} -> + conn + |> put_resp_content_type("application/json") + |> send_resp(:bad_request, Jason.encode!(%{"error" => reason})) + end + end + + def unpin_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do + with {:ok, activity} <- CommonAPI.unpin(ap_id_or_id, user) do + conn + |> put_view(StatusView) + |> try_render("status.json", %{activity: activity, for: user, as: :activity}) + end + end + + def bookmark_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do + with %Activity{} = activity <- Repo.get(Activity, id), + %User{} = user <- User.get_by_nickname(user.nickname), + true <- ActivityPub.visible_for_user?(activity, user), + {:ok, user} <- User.bookmark(user, activity.data["object"]["id"]) do + conn + |> put_view(StatusView) + |> try_render("status.json", %{activity: activity, for: user, as: :activity}) + end + end + + def unbookmark_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do + with %Activity{} = activity <- Repo.get(Activity, id), + %User{} = user <- User.get_by_nickname(user.nickname), + true <- ActivityPub.visible_for_user?(activity, user), + {:ok, user} <- User.unbookmark(user, activity.data["object"]["id"]) do conn |> put_view(StatusView) |> try_render("status.json", %{activity: activity, for: user, as: :activity}) @@ -413,9 +460,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do notifications = Notification.for_user(user, params) result = - Enum.map(notifications, fn x -> - render_notification(user, x) - end) + notifications + |> Enum.map(fn x -> render_notification(user, x) end) |> Enum.filter(& &1) conn @@ -485,7 +531,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 @@ -526,15 +573,32 @@ 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)) + + activities = params |> Map.put("type", "Create") |> Map.put("local_only", local_only) |> Map.put("blocking_user", user) - |> Map.put("tag", String.downcase(params["tag"])) - - activities = - ActivityPub.fetch_public_activities(params) + |> Map.put("tag", tags) + |> Map.put("tag_all", tag_all) + |> Map.put("tag_reject", tag_reject) + |> ActivityPub.fetch_public_activities() |> Enum.reverse() conn @@ -549,7 +613,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do followers = cond do for_user && user.id == for_user.id -> followers - user.info.hide_network -> [] + user.info.hide_followers -> [] true -> followers end @@ -565,7 +629,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do followers = cond do for_user && user.id == for_user.id -> followers - user.info.hide_network -> [] + user.info.hide_follows -> [] true -> followers end @@ -634,7 +698,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do {:ok, _activity} <- ActivityPub.follow(follower, followed), {:ok, follower, followed} <- User.wait_and_refresh( - Pleroma.Config.get([:activitypub, :follow_handshake_timeout]), + Config.get([:activitypub, :follow_handshake_timeout]), follower, followed ) do @@ -725,11 +789,13 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do json(conn, %{}) end - def status_search(query) do + def status_search(user, query) do 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"])] + with {:ok, object} <- ActivityPub.fetch_object_from_id(query), + %Activity{} = activity <- Activity.get_create_by_object_ap_id(object.data["id"]), + true <- ActivityPub.visible_for_user?(activity, user) do + [activity] else _e -> [] end @@ -754,14 +820,15 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end def search2(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do - accounts = User.search(query, params["resolve"] == "true") + accounts = User.search(query, params["resolve"] == "true", user) - statuses = status_search(query) + statuses = status_search(user, query) tags_path = Web.base_url() <> "/tag/" tags = - String.split(query) + query + |> String.split() |> Enum.uniq() |> Enum.filter(fn tag -> String.starts_with?(tag, "#") end) |> Enum.map(fn tag -> String.slice(tag, 1..-1) end) @@ -778,12 +845,13 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end def search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do - accounts = User.search(query, params["resolve"] == "true") + accounts = User.search(query, params["resolve"] == "true", user) - statuses = status_search(query) + statuses = status_search(user, query) tags = - String.split(query) + query + |> String.split() |> Enum.uniq() |> Enum.filter(fn tag -> String.starts_with?(tag, "#") end) |> Enum.map(fn tag -> String.slice(tag, 1..-1) end) @@ -799,22 +867,34 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end def account_search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do - accounts = User.search(query, params["resolve"] == "true") + accounts = User.search(query, params["resolve"] == "true", user) res = AccountView.render("accounts.json", users: accounts, for: user, as: :user) json(conn, res) end - def favourites(%{assigns: %{user: user}} = conn, _) do - params = - %{} + def favourites(%{assigns: %{user: user}} = conn, params) do + activities = + params |> Map.put("type", "Create") |> Map.put("favorited_by", user.ap_id) |> Map.put("blocking_user", user) + |> ActivityPub.fetch_public_activities() + |> Enum.reverse() + + conn + |> add_link_headers(:favourites, activities) + |> put_view(StatusView) + |> render("index.json", %{activities: activities, for: user, as: :activity}) + end + + def bookmarks(%{assigns: %{user: user}} = conn, _) do + user = Repo.get(User, user.id) activities = - ActivityPub.fetch_public_activities(params) + user.bookmarks + |> Enum.map(fn id -> Activity.get_create_by_object_ap_id(id) end) |> Enum.reverse() conn @@ -833,7 +913,10 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do res = ListView.render("list.json", list: list) json(conn, res) else - _e -> json(conn, "error") + _e -> + conn + |> put_status(404) + |> json(%{error: "Record not found"}) end end @@ -913,12 +996,10 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do # we must filter the following list for the user to avoid leaking statuses the user # does not actually have permission to see (for more info, peruse security issue #270). - following_to = + activities = following |> Enum.filter(fn x -> x in user.following end) - - activities = - ActivityPub.fetch_activities_bounded(following_to, following, params) + |> ActivityPub.fetch_activities_bounded(following, params) |> Enum.reverse() conn @@ -940,7 +1021,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do if user && token do mastodon_emoji = mastodonized_emoji() - limit = Pleroma.Config.get([:instance, :limit]) + limit = Config.get([:instance, :limit]) accounts = Map.put(%{}, user.id, AccountView.render("account.json", %{user: user, for: user})) @@ -964,8 +1045,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do max_toot_chars: limit }, rights: %{ - delete_others_notice: !!user.info.is_moderator, - admin: !!user.info.is_admin + delete_others_notice: present?(user.info.is_moderator), + admin: present?(user.info.is_admin) }, compose: %{ me: "#{user.id}", @@ -1064,7 +1145,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: ".", @@ -1077,16 +1160,13 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end defp get_or_make_app() do - with %App{} = app <- Repo.get_by(App, client_name: "Mastodon-Local") do + find_attrs = %{client_name: @local_mastodon_name, redirect_uris: "."} + + with %App{} = app <- Repo.get_by(App, find_attrs) do {:ok, app} else _e -> - cs = - App.register_changeset(%App{}, %{ - client_name: "Mastodon-Local", - redirect_uris: ".", - scopes: "read,write,follow" - }) + cs = App.register_changeset(%App{}, Map.put(find_attrs, :scopes, "read,write,follow")) Repo.insert(cs) end @@ -1120,7 +1200,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do def render_notification(user, %{id: id, activity: activity, inserted_at: created_at} = _params) do actor = User.get_cached_by_ap_id(activity.data["actor"]) - parent_activity = Activity.get_create_activity_by_object_ap_id(activity.data["object"]) + parent_activity = Activity.get_create_by_object_ap_id(activity.data["object"]) mastodon_type = Activity.mastodon_notification_type(activity) response = %{ @@ -1158,7 +1238,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end def get_filters(%{assigns: %{user: user}} = conn, _) do - filters = Pleroma.Filter.get_filters(user) + filters = Filter.get_filters(user) res = FilterView.render("filters.json", filters: filters) json(conn, res) end @@ -1167,7 +1247,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do %{assigns: %{user: user}} = conn, %{"phrase" => phrase, "context" => context} = params ) do - query = %Pleroma.Filter{ + query = %Filter{ user_id: user.id, phrase: phrase, context: context, @@ -1176,13 +1256,13 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do # expires_at } - {:ok, response} = Pleroma.Filter.create(query) + {:ok, response} = Filter.create(query) res = FilterView.render("filter.json", filter: response) json(conn, res) end def get_filter(%{assigns: %{user: user}} = conn, %{"id" => filter_id}) do - filter = Pleroma.Filter.get(filter_id, user) + filter = Filter.get(filter_id, user) res = FilterView.render("filter.json", filter: filter) json(conn, res) end @@ -1191,7 +1271,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do %{assigns: %{user: user}} = conn, %{"phrase" => phrase, "context" => context, "id" => filter_id} = params ) do - query = %Pleroma.Filter{ + query = %Filter{ user_id: user.id, filter_id: filter_id, phrase: phrase, @@ -1201,32 +1281,32 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do # expires_at } - {:ok, response} = Pleroma.Filter.update(query) + {:ok, response} = Filter.update(query) res = FilterView.render("filter.json", filter: response) json(conn, res) end def delete_filter(%{assigns: %{user: user}} = conn, %{"id" => filter_id}) do - query = %Pleroma.Filter{ + query = %Filter{ user_id: user.id, filter_id: filter_id } - {:ok, _} = Pleroma.Filter.delete(query) + {:ok, _} = Filter.delete(query) json(conn, %{}) end def create_push_subscription(%{assigns: %{user: user, token: token}} = conn, params) do - true = Pleroma.Web.Push.enabled() - Pleroma.Web.Push.Subscription.delete_if_exists(user, token) - {:ok, subscription} = Pleroma.Web.Push.Subscription.create(user, token, params) + true = Push.enabled() + Subscription.delete_if_exists(user, token) + {:ok, subscription} = Subscription.create(user, token, params) view = PushSubscriptionView.render("push_subscription.json", subscription: subscription) json(conn, view) end def get_push_subscription(%{assigns: %{user: user, token: token}} = conn, _params) do - true = Pleroma.Web.Push.enabled() - subscription = Pleroma.Web.Push.Subscription.get(user, token) + true = Push.enabled() + subscription = Subscription.get(user, token) view = PushSubscriptionView.render("push_subscription.json", subscription: subscription) json(conn, view) end @@ -1235,15 +1315,15 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do %{assigns: %{user: user, token: token}} = conn, params ) do - true = Pleroma.Web.Push.enabled() - {:ok, subscription} = Pleroma.Web.Push.Subscription.update(user, token, params) + true = Push.enabled() + {:ok, subscription} = Subscription.update(user, token, params) view = PushSubscriptionView.render("push_subscription.json", subscription: subscription) json(conn, view) end def delete_push_subscription(%{assigns: %{user: user, token: token}} = conn, _params) do - true = Pleroma.Web.Push.enabled() - {:ok, _response} = Pleroma.Web.Push.Subscription.delete(user, token) + true = Push.enabled() + {:ok, _response} = Subscription.delete(user, token) json(conn, %{}) end @@ -1254,17 +1334,21 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end def suggestions(%{assigns: %{user: user}} = conn, _) do - suggestions = Pleroma.Config.get(:suggestions) + suggestions = Config.get(:suggestions) if Keyword.get(suggestions, :enabled, false) do api = Keyword.get(suggestions, :third_party_engine, "") timeout = Keyword.get(suggestions, :timeout, 5000) limit = Keyword.get(suggestions, :limit, 23) - host = Pleroma.Config.get([Pleroma.Web.Endpoint, :url, :host]) + host = Config.get([Pleroma.Web.Endpoint, :url, :host]) user = user.nickname - url = String.replace(api, "{{host}}", host) |> String.replace("{{user}}", user) + + url = + api + |> String.replace("{{host}}", host) + |> String.replace("{{user}}", user) with {:ok, %{status: 200, body: body}} <- @httpoison.get( @@ -1272,12 +1356,14 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do [], adapter: [ timeout: timeout, - recv_timeout: timeout + recv_timeout: timeout, + pool: :default ] ), {:ok, data} <- Jason.decode(body) do - data2 = - Enum.slice(data, 0, limit) + data = + data + |> Enum.slice(0, limit) |> Enum.map(fn x -> Map.put( x, @@ -1296,7 +1382,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end) conn - |> json(data2) + |> json(data) else e -> Logger.error("Could not retrieve suggestions at fetch #{url}, #{inspect(e)}") end @@ -1305,6 +1391,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) @@ -1323,4 +1425,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do |> put_status(501) |> json(%{error: "Can't display this activity"}) end + + defp present?(nil), do: false + defp present?(false), do: false + defp present?(_), do: true end