X-Git-Url: http://git.squeep.com/?a=blobdiff_plain;f=lib%2Fpleroma%2Fweb%2Fmastodon_api%2Fmastodon_api_controller.ex;h=65b61202624a2bb76bb4ac9862074960901516ab;hb=19afd9f81ff0b0e966a432db6ea15930d3712c85;hp=726807f0aa3590cef98329fa7b3807dcc1a1536f;hpb=28478a9c4f652e67de659adb93155a0dfda4b09f;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 726807f0a..26921d386 100644 --- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex @@ -1,33 +1,55 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + 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.CommonAPI + alias Pleroma.Web.MediaProxy - alias Pleroma.Web.MastodonAPI.{ - StatusView, - AccountView, - MastodonView, - ListView, - FilterView, - PushSubscriptionView - } - + alias Pleroma.Web.MastodonAPI.AccountView + alias Pleroma.Web.MastodonAPI.FilterView + alias Pleroma.Web.MastodonAPI.ListView + alias Pleroma.Web.MastodonAPI.MastodonView + alias Pleroma.Web.MastodonAPI.StatusView + alias Pleroma.Web.MastodonAPI.ReportView 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.ActivityPub.Visibility + alias Pleroma.Web.OAuth.App + alias Pleroma.Web.OAuth.Authorization + alias Pleroma.Web.OAuth.Token + import Pleroma.Web.ControllerHelper, only: [oauth_scopes: 2] 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 + scopes = oauth_scopes(params, ["read"]) + + app_attrs = + params + |> Map.drop(["scope", "scopes"]) + |> Map.put("scopes", scopes) + + with cs <- App.register_changeset(%App{}, app_attrs), + false <- cs.changes[:client_name] == @local_mastodon_name, + {:ok, app} <- Repo.insert(cs) do res = %{ id: app.id |> to_string, name: app.client_name, @@ -110,7 +132,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end def user(%{assigns: %{user: for_user}} = conn, %{"id" => id}) do - with %User{} = user <- Repo.get(User, id) do + with %User{} = user <- Repo.get(User, id), + true <- User.auth_active?(user) || user.id == for_user.id || User.superuser?(for_user) do account = AccountView.render("account.json", %{user: user, for: for_user}) json(conn, account) else @@ -124,7 +147,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(), @@ -133,7 +156,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", @@ -167,6 +190,11 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end defp add_link_headers(conn, method, activities, param \\ nil, params \\ %{}) do + params = + conn.params + |> Map.drop(["since_id", "max_id"]) + |> Map.merge(params) + last = List.last(activities) first = List.first(activities) @@ -217,49 +245,47 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do params |> Map.put("type", ["Create", "Announce"]) |> Map.put("blocking_user", user) + |> Map.put("muting_user", user) |> 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() conn |> add_link_headers(:home_timeline, activities) - |> render(StatusView, "index.json", %{activities: activities, for: user, as: :activity}) + |> put_view(StatusView) + |> render("index.json", %{activities: activities, for: user, as: :activity}) end 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) + |> Map.put("muting_user", user) + |> ActivityPub.fetch_public_activities() |> Enum.reverse() conn |> add_link_headers(:public_timeline, activities, false, %{"local" => local_only}) - |> render(StatusView, "index.json", %{activities: activities, for: user, as: :activity}) + |> put_view(StatusView) + |> render("index.json", %{activities: activities, for: user, as: :activity}) end 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"]) - |> render(StatusView, "index.json", %{ + |> put_view(StatusView) + |> render("index.json", %{ activities: activities, for: reading_user, as: :activity @@ -268,23 +294,30 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end def dm_timeline(%{assigns: %{user: user}} = conn, params) do - query = - ActivityPub.fetch_activities_query( - [user.ap_id], - Map.merge(params, %{"type" => "Create", visibility: "direct"}) - ) + params = + params + |> Map.put("type", "Create") + |> Map.put("blocking_user", user) + |> Map.put("user", user) + |> Map.put(:visibility, "direct") - activities = Repo.all(query) + activities = + [user.ap_id] + |> ActivityPub.fetch_activities_query(params) + |> Repo.all() conn |> add_link_headers(:dm_timeline, activities) - |> render(StatusView, "index.json", %{activities: activities, for: user, as: :activity}) + |> put_view(StatusView) + |> render("index.json", %{activities: activities, for: user, as: :activity}) end def get_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do with %Activity{} = activity <- Repo.get(Activity, id), - true <- ActivityPub.visible_for_user?(activity, user) do - try_render(conn, StatusView, "status.json", %{activity: activity, for: user}) + true <- Visibility.visible_for_user?(activity, user) do + conn + |> put_view(StatusView) + |> try_render("status.json", %{activity: activity, for: user}) end end @@ -309,6 +342,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", @@ -317,6 +351,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do as: :activity ) |> Enum.reverse() + # credo:disable-for-previous-line Credo.Check.Refactor.PipeChainStart } json(conn, result) @@ -336,7 +371,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 @@ -347,7 +381,9 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do {:ok, activity} = Cachex.fetch!(:idempotency_cache, idempotency_key, fn _ -> CommonAPI.post(user, params) end) - try_render(conn, StatusView, "status.json", %{activity: activity, for: user, as: :activity}) + conn + |> put_view(StatusView) + |> try_render("status.json", %{activity: activity, for: user, as: :activity}) end def delete_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do @@ -363,28 +399,104 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do def reblog_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do with {:ok, announce, _activity} <- CommonAPI.repeat(ap_id_or_id, user) do - try_render(conn, StatusView, "status.json", %{activity: announce, for: user, as: :activity}) + conn + |> put_view(StatusView) + |> try_render("status.json", %{activity: announce, for: user, as: :activity}) end end 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 - try_render(conn, StatusView, "status.json", %{activity: activity, for: user, as: :activity}) + %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 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 - try_render(conn, StatusView, "status.json", %{activity: activity, for: user, as: :activity}) + %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 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 - try_render(conn, StatusView, "status.json", %{activity: activity, for: user, as: :activity}) + %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 <- Visibility.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 <- Visibility.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}) + end + end + + def mute_conversation(%{assigns: %{user: user}} = conn, %{"id" => id}) do + activity = Activity.get_by_id(id) + + with {:ok, activity} <- CommonAPI.add_mute(user, activity) 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 unmute_conversation(%{assigns: %{user: user}} = conn, %{"id" => id}) do + activity = Activity.get_by_id(id) + + with {:ok, activity} <- CommonAPI.remove_mute(user, activity) do + conn + |> put_view(StatusView) + |> try_render("status.json", %{activity: activity, for: user, as: :activity}) end end @@ -392,9 +504,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 @@ -433,7 +544,10 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do id = List.wrap(id) q = from(u in User, where: u.id in ^id) targets = Repo.all(q) - render(conn, AccountView, "relationships.json", %{user: user, targets: targets}) + + conn + |> put_view(AccountView) + |> render("relationships.json", %{user: user, targets: targets}) end # Instead of returning a 400 when no "id" params is present, Mastodon returns an empty array. @@ -452,18 +566,25 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do |> Repo.update() attachment_data = Map.put(new_data, "id", object.id) - render(conn, StatusView, "attachment.json", %{attachment: attachment_data}) + + conn + |> put_view(StatusView) + |> render("attachment.json", %{attachment: attachment_data}) end end 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 attachment_data = Map.put(object.data, "id", object.id) - render(conn, StatusView, "attachment.json", %{attachment: attachment_data}) + + conn + |> put_view(StatusView) + |> render("attachment.json", %{attachment: attachment_data}) end end @@ -471,7 +592,10 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do with %Activity{data: %{"object" => %{"likes" => likes}}} <- Repo.get(Activity, id) do q = from(u in User, where: u.ap_id in ^likes) users = Repo.all(q) - render(conn, AccountView, "accounts.json", %{users: users, as: :user}) + + conn + |> put_view(AccountView) + |> render(AccountView, "accounts.json", %{users: users, as: :user}) else _ -> json(conn, []) end @@ -481,7 +605,10 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do with %Activity{data: %{"object" => %{"announcements" => announces}}} <- Repo.get(Activity, id) do q = from(u in User, where: u.ap_id in ^announces) users = Repo.all(q) - render(conn, AccountView, "accounts.json", %{users: users, as: :user}) + + conn + |> put_view(AccountView) + |> render("accounts.json", %{users: users, as: :user}) else _ -> json(conn, []) end @@ -490,20 +617,39 @@ 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("muting_user", user) + |> Map.put("tag", tags) + |> Map.put("tag_all", tag_all) + |> Map.put("tag_reject", tag_reject) + |> ActivityPub.fetch_public_activities() |> Enum.reverse() conn |> add_link_headers(:hashtag_timeline, activities, params["tag"], %{"local" => local_only}) - |> render(StatusView, "index.json", %{activities: activities, for: user, as: :activity}) + |> put_view(StatusView) + |> render("index.json", %{activities: activities, for: user, as: :activity}) end def followers(%{assigns: %{user: for_user}} = conn, %{"id" => id}) do @@ -512,11 +658,13 @@ 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 - render(conn, AccountView, "accounts.json", %{users: followers, as: :user}) + conn + |> put_view(AccountView) + |> render("accounts.json", %{users: followers, as: :user}) end end @@ -526,17 +674,21 @@ 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 - render(conn, AccountView, "accounts.json", %{users: followers, as: :user}) + conn + |> put_view(AccountView) + |> render("accounts.json", %{users: followers, as: :user}) end end def follow_requests(%{assigns: %{user: followed}} = conn, _params) do with {:ok, follow_requests} <- User.get_follow_requests(followed) do - render(conn, AccountView, "accounts.json", %{users: follow_requests, as: :user}) + conn + |> put_view(AccountView) + |> render("accounts.json", %{users: follow_requests, as: :user}) end end @@ -548,11 +700,13 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do {:ok, _activity} <- ActivityPub.accept(%{ to: [follower.ap_id], - actor: followed.ap_id, + actor: followed, object: follow_activity.data["id"], type: "Accept" }) do - render(conn, AccountView, "relationship.json", %{user: followed, target: follower}) + conn + |> put_view(AccountView) + |> render("relationship.json", %{user: followed, target: follower}) else {:error, message} -> conn @@ -568,11 +722,13 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do {:ok, _activity} <- ActivityPub.reject(%{ to: [follower.ap_id], - actor: followed.ap_id, + actor: followed, object: follow_activity.data["id"], type: "Reject" }) do - render(conn, AccountView, "relationship.json", %{user: followed, target: follower}) + conn + |> put_view(AccountView) + |> render("relationship.json", %{user: followed, target: follower}) else {:error, message} -> conn @@ -583,15 +739,10 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do def follow(%{assigns: %{user: follower}} = conn, %{"id" => id}) do with %User{} = followed <- Repo.get(User, id), - {:ok, follower} <- User.maybe_direct_follow(follower, followed), - {:ok, _activity} <- ActivityPub.follow(follower, followed), - {:ok, follower, followed} <- - User.wait_and_refresh( - Pleroma.Config.get([:activitypub, :follow_handshake_timeout]), - follower, - followed - ) do - render(conn, AccountView, "relationship.json", %{user: follower, target: followed}) + {:ok, follower, followed, _} <- CommonAPI.follow(follower, followed) do + conn + |> put_view(AccountView) + |> render("relationship.json", %{user: follower, target: followed}) else {:error, message} -> conn @@ -602,9 +753,10 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do def follow(%{assigns: %{user: follower}} = conn, %{"uri" => uri}) do with %User{} = followed <- Repo.get_by(User, nickname: uri), - {:ok, follower} <- User.maybe_direct_follow(follower, followed), - {:ok, _activity} <- ActivityPub.follow(follower, followed) do - render(conn, AccountView, "account.json", %{user: followed, for: follower}) + {:ok, follower, followed, _} <- CommonAPI.follow(follower, followed) do + conn + |> put_view(AccountView) + |> render("account.json", %{user: followed, for: follower}) else {:error, message} -> conn @@ -617,7 +769,44 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do with %User{} = followed <- Repo.get(User, id), {:ok, _activity} <- ActivityPub.unfollow(follower, followed), {:ok, follower, _} <- User.unfollow(follower, followed) do - render(conn, AccountView, "relationship.json", %{user: follower, target: followed}) + conn + |> put_view(AccountView) + |> render("relationship.json", %{user: follower, target: followed}) + end + end + + def mute(%{assigns: %{user: muter}} = conn, %{"id" => id}) do + with %User{} = muted <- Repo.get(User, id), + {:ok, muter} <- User.mute(muter, muted) do + conn + |> put_view(AccountView) + |> render("relationship.json", %{user: muter, target: muted}) + else + {:error, message} -> + conn + |> put_resp_content_type("application/json") + |> send_resp(403, Jason.encode!(%{"error" => message})) + end + end + + def unmute(%{assigns: %{user: muter}} = conn, %{"id" => id}) do + with %User{} = muted <- Repo.get(User, id), + {:ok, muter} <- User.unmute(muter, muted) do + conn + |> put_view(AccountView) + |> render("relationship.json", %{user: muter, target: muted}) + else + {:error, message} -> + conn + |> put_resp_content_type("application/json") + |> send_resp(403, Jason.encode!(%{"error" => message})) + end + end + + def mutes(%{assigns: %{user: user}} = conn, _) do + with muted_accounts <- User.muted_users(user) do + res = AccountView.render("accounts.json", users: muted_accounts, for: user, as: :user) + json(conn, res) end end @@ -625,7 +814,9 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do with %User{} = blocked <- Repo.get(User, id), {:ok, blocker} <- User.block(blocker, blocked), {:ok, _activity} <- ActivityPub.block(blocker, blocked) do - render(conn, AccountView, "relationship.json", %{user: blocker, target: blocked}) + conn + |> put_view(AccountView) + |> render("relationship.json", %{user: blocker, target: blocked}) else {:error, message} -> conn @@ -638,7 +829,9 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do with %User{} = blocked <- Repo.get(User, id), {:ok, blocker} <- User.unblock(blocker, blocked), {:ok, _activity} <- ActivityPub.unblock(blocker, blocked) do - render(conn, AccountView, "relationship.json", %{user: blocker, target: blocked}) + conn + |> put_view(AccountView) + |> render("relationship.json", %{user: blocker, target: blocked}) else {:error, message} -> conn @@ -647,11 +840,9 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end end - # TODO: Use proper query def blocks(%{assigns: %{user: user}} = conn, _) do - with blocked_users <- user.info.blocks || [], - accounts <- Enum.map(blocked_users, fn ap_id -> User.get_cached_by_ap_id(ap_id) end) do - res = AccountView.render("accounts.json", users: accounts, for: user, as: :user) + with blocked_accounts <- User.blocked_users(user) do + res = AccountView.render("accounts.json", users: blocked_accounts, for: user, as: :user) json(conn, res) end end @@ -670,11 +861,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 <- Visibility.visible_for_user?(activity, user) do + [activity] else _e -> [] end @@ -699,14 +892,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, resolve: params["resolve"] == "true", for_user: 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) @@ -723,12 +917,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, resolve: params["resolve"] == "true", for_user: 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) @@ -744,26 +939,39 @@ 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, resolve: params["resolve"] == "true", for_user: 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 - |> render(StatusView, "index.json", %{activities: activities, for: user, as: :activity}) + |> put_view(StatusView) + |> render("index.json", %{activities: activities, for: user, as: :activity}) end def get_lists(%{assigns: %{user: user}} = conn, opts) do @@ -777,7 +985,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 @@ -831,7 +1042,9 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do def list_accounts(%{assigns: %{user: user}} = conn, %{"id" => id}) do with %Pleroma.List{} = list <- Pleroma.List.get(id, user), {:ok, users} = Pleroma.List.get_following(list) do - render(conn, AccountView, "accounts.json", %{users: users, as: :user}) + conn + |> put_view(AccountView) + |> render("accounts.json", %{users: users, as: :user}) end end @@ -852,19 +1065,19 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do params |> Map.put("type", "Create") |> Map.put("blocking_user", user) + |> Map.put("muting_user", user) # 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 - |> render(StatusView, "index.json", %{activities: activities, for: user, as: :activity}) + |> put_view(StatusView) + |> render("index.json", %{activities: activities, for: user, as: :activity}) else _e -> conn @@ -881,11 +1094,13 @@ 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})) + flavour = get_user_flavour(user) + initial_state = %{ meta: %{ @@ -905,7 +1120,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do max_toot_chars: limit }, rights: %{ - delete_others_notice: !!user.info.is_moderator + delete_others_notice: present?(user.info.is_moderator), + admin: present?(user.info.is_admin) }, compose: %{ me: "#{user.id}", @@ -968,7 +1184,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do conn |> put_layout(false) - |> render(MastodonView, "index.html", %{initial_state: initial_state}) + |> put_view(MastodonView) + |> render("index.html", %{initial_state: initial_state, flavour: flavour}) else conn |> redirect(to: "/web/login") @@ -990,6 +1207,43 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end end + @supported_flavours ["glitch", "vanilla"] + + def set_flavour(%{assigns: %{user: user}} = conn, %{"flavour" => flavour} = _params) + when flavour in @supported_flavours do + flavour_cng = User.Info.mastodon_flavour_update(user.info, flavour) + + with changeset <- Ecto.Changeset.change(user), + changeset <- Ecto.Changeset.put_embed(changeset, :info, flavour_cng), + {:ok, user} <- User.update_and_set_cache(changeset), + flavour <- user.info.flavour do + json(conn, flavour) + else + e -> + conn + |> put_resp_content_type("application/json") + |> send_resp(500, Jason.encode!(%{"error" => inspect(e)})) + end + end + + def set_flavour(conn, _params) do + conn + |> put_status(400) + |> json(%{error: "Unsupported flavour"}) + end + + def get_flavour(%{assigns: %{user: user}} = conn, _params) do + json(conn, get_user_flavour(user)) + end + + defp get_user_flavour(%User{info: %{flavour: flavour}}) when flavour in @supported_flavours do + flavour + end + + defp get_user_flavour(_) do + "glitch" + end + def login(conn, %{"code" => code}) do with {:ok, app} <- get_or_make_app(), %Authorization{} = auth <- Repo.get_by(Authorization, token: code, app_id: app.id), @@ -1003,11 +1257,13 @@ 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: ".", - scope: app.scopes + scope: Enum.join(app.scopes, " ") ) conn @@ -1016,16 +1272,27 @@ 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: "."} + scopes = ["read", "write", "follow", "push"] + + with %App{} = app <- Repo.get_by(App, find_attrs) do + {:ok, app} = + if app.scopes == scopes do + {:ok, app} + else + app + |> Ecto.Changeset.change(%{scopes: scopes}) + |> Repo.update() + end + {:ok, app} else _e -> cs = - App.register_changeset(%App{}, %{ - client_name: "Mastodon-Local", - redirect_uris: ".", - scopes: "read,write,follow" - }) + App.register_changeset( + %App{}, + Map.put(find_attrs, :scopes, scopes) + ) Repo.insert(cs) end @@ -1041,7 +1308,9 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do Logger.debug("Unimplemented, returning unmodified relationship") with %User{} = target <- Repo.get(User, id) do - render(conn, AccountView, "relationship.json", %{user: user, target: target}) + conn + |> put_view(AccountView) + |> render("relationship.json", %{user: user, target: target}) end end @@ -1057,7 +1326,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 = %{ @@ -1095,7 +1364,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 @@ -1104,7 +1373,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, @@ -1113,13 +1382,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 @@ -1128,7 +1397,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, @@ -1138,52 +1407,23 @@ 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) - 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) - 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) - view = PushSubscriptionView.render("push_subscription.json", subscription: subscription) - json(conn, view) - end - - def update_push_subscription( - %{assigns: %{user: user, token: token}} = conn, - params - ) do - true = Pleroma.Web.Push.enabled() - {:ok, subscription} = Pleroma.Web.Push.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) + {:ok, _} = Filter.delete(query) json(conn, %{}) end + # fallback action + # def errors(conn, _) do conn |> put_status(500) @@ -1191,30 +1431,35 @@ 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( url, [], 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, @@ -1233,7 +1478,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 @@ -1242,9 +1487,39 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end end - def try_render(conn, renderer, target, params) + def status_card(%{assigns: %{user: user}} = conn, %{"id" => status_id}) do + with %Activity{} = activity <- Repo.get(Activity, status_id), + true <- Visibility.visible_for_user?(activity, user) do + data = + StatusView.render( + "card.json", + Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity) + ) + + json(conn, data) + else + _e -> + %{} + end + end + + def reports(%{assigns: %{user: user}} = conn, params) do + case CommonAPI.report(user, params) do + {:ok, activity} -> + conn + |> put_view(ReportView) + |> try_render("report.json", %{activity: activity}) + + {:error, err} -> + conn + |> put_status(:bad_request) + |> json(%{error: err}) + end + end + + def try_render(conn, target, params) when is_binary(target) do - res = render(conn, renderer, target, params) + res = render(conn, target, params) if res == nil do conn @@ -1255,9 +1530,13 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end end - def try_render(conn, _, _, _) do + def try_render(conn, _, _) do conn |> 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