Merge branch 'use-media-proxy-in-suggestions-api' into 'develop'
[akkoma] / lib / pleroma / web / mastodon_api / mastodon_api_controller.ex
index 922b83ed0814791a0f1e9a5fa4e93485826d028c..bd6f04c55bd7a9707605d173b089744eceb4a1d0 100644 (file)
@@ -1,25 +1,31 @@
 defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
   use Pleroma.Web, :controller
-  alias Pleroma.{Repo, Activity, User, Notification, Stats}
+  alias Pleroma.{Repo, Object, Activity, User, Notification, Stats}
   alias Pleroma.Web
   alias Pleroma.Web.MastodonAPI.{StatusView, AccountView, MastodonView, ListView}
   alias Pleroma.Web.ActivityPub.ActivityPub
   alias Pleroma.Web.ActivityPub.Utils
-  alias Pleroma.Web.{CommonAPI, OStatus}
+  alias Pleroma.Web.CommonAPI
   alias Pleroma.Web.OAuth.{Authorization, Token, App}
+  alias Pleroma.Web.MediaProxy
   alias Comeonin.Pbkdf2
   import Ecto.Query
   require Logger
 
+  @httpoison Application.get_env(:pleroma, :httpoison)
+
   action_fallback(:errors)
 
   def create_app(conn, params) do
     with cs <- App.register_changeset(%App{}, params) |> IO.inspect(),
          {:ok, app} <- Repo.insert(cs) |> IO.inspect() do
       res = %{
-        id: app.id,
+        id: app.id |> to_string,
+        name: app.client_name,
         client_id: app.client_id,
-        client_secret: app.client_secret
+        client_secret: app.client_secret,
+        redirect_uri: app.redirect_uris,
+        website: app.website
       }
 
       json(conn, res)
@@ -125,7 +131,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
     response = %{
       uri: Web.base_url(),
       title: Keyword.get(@instance, :name),
-      description: "A Pleroma instance, an alternative fediverse server",
+      description: Keyword.get(@instance, :description),
       version: "#{@mastodon_api_level} (compatible; #{Keyword.get(@instance, :version)})",
       email: Keyword.get(@instance, :email),
       urls: %{
@@ -360,7 +366,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
   end
 
   def unreblog_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
-    with {:ok, _, _, %{data: %{"id" => id}}} <- CommonAPI.unrepeat(ap_id_or_id, user),
+    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
       render(conn, StatusView, "status.json", %{activity: activity, for: user, as: :activity})
     end
@@ -428,16 +434,43 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
     render(conn, AccountView, "relationships.json", %{user: user, targets: targets})
   end
 
-  def upload(%{assigns: %{user: _}} = conn, %{"file" => file}) do
-    with {:ok, object} <- ActivityPub.upload(file) do
+  def update_media(%{assigns: %{user: _}} = conn, data) do
+    with %Object{} = object <- Repo.get(Object, data["id"]),
+         true <- is_binary(data["description"]),
+         description <- data["description"] do
+      new_data = %{object.data | "name" => description}
+
+      change = Object.change(object, %{data: new_data})
+      {:ok, media_obj} = Repo.update(change)
+
       data =
-        object.data
+        new_data
         |> Map.put("id", object.id)
 
       render(conn, StatusView, "attachment.json", %{attachment: data})
     end
   end
 
+  def upload(%{assigns: %{user: _}} = conn, %{"file" => file} = data) do
+    with {:ok, object} <- ActivityPub.upload(file) do
+      objdata =
+        if Map.has_key?(data, "description") do
+          Map.put(object.data, "name", data["description"])
+        else
+          object.data
+        end
+
+      change = Object.change(object, %{data: objdata})
+      {:ok, object} = Repo.update(change)
+
+      objdata =
+        objdata
+        |> Map.put("id", object.id)
+
+      render(conn, StatusView, "attachment.json", %{attachment: objdata})
+    end
+  end
+
   def favourited_by(conn, %{"id" => id}) do
     with %Activity{data: %{"object" => %{"likes" => likes}}} <- Repo.get(Activity, id) do
       q = from(u in User, where: u.ap_id in ^likes)
@@ -621,17 +654,61 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
     json(conn, %{})
   end
 
+  def search2(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do
+    accounts = User.search(query, params["resolve"] == "true")
+
+    fetched =
+      if Regex.match?(~r/https?:/, query) do
+        with {:ok, object} <- ActivityPub.fetch_object_from_id(query) do
+          [Activity.get_create_activity_by_object_ap_id(object.data["id"])]
+        else
+          _e -> []
+        end
+      end || []
+
+    q =
+      from(
+        a in Activity,
+        where: fragment("?->>'type' = 'Create'", a.data),
+        where: "https://www.w3.org/ns/activitystreams#Public" in a.recipients,
+        where:
+          fragment(
+            "to_tsvector('english', ?->'object'->>'content') @@ plainto_tsquery('english', ?)",
+            a.data,
+            ^query
+          ),
+        limit: 20,
+        order_by: [desc: :id]
+      )
+
+    statuses = Repo.all(q) ++ fetched
+
+    tags_path = Web.base_url() <> "/tag/"
+
+    tags =
+      String.split(query)
+      |> Enum.uniq()
+      |> Enum.filter(fn tag -> String.starts_with?(tag, "#") end)
+      |> Enum.map(fn tag -> String.slice(tag, 1..-1) end)
+      |> Enum.map(fn tag -> %{name: tag, url: tags_path <> tag} end)
+
+    res = %{
+      "accounts" => AccountView.render("accounts.json", users: accounts, for: user, as: :user),
+      "statuses" =>
+        StatusView.render("index.json", activities: statuses, for: user, as: :activity),
+      "hashtags" => tags
+    }
+
+    json(conn, res)
+  end
+
   def search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do
     accounts = User.search(query, params["resolve"] == "true")
 
     fetched =
       if Regex.match?(~r/https?:/, query) do
-        with {:ok, activities} <- OStatus.fetch_activity_from_url(query) do
-          activities
-          |> Enum.filter(fn
-            %{data: %{"type" => "Create"}} -> true
-            _ -> false
-          end)
+        with {:ok, object} <- ActivityPub.fetch_object_from_id(query) do
+          [Activity.get_create_activity_by_object_ap_id(object.data["id"])]
         else
           _e -> []
         end
@@ -774,9 +851,14 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
         |> Map.put("type", "Create")
         |> Map.put("blocking_user", user)
 
-      # adding title is a hack to not make empty lists function like a public timeline
+      # 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 =
+        following
+        |> Enum.filter(fn x -> x in user.following end)
+
       activities =
-        ActivityPub.fetch_activities([title | following], params)
+        ActivityPub.fetch_activities_bounded(following_to, following, params)
         |> Enum.reverse()
 
       conn
@@ -812,11 +894,16 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
             boost_modal: false,
             delete_modal: true,
             auto_play_gif: false,
-            reduce_motion: false
+            display_sensitive_media: false,
+            reduce_motion: false,
+            max_toot_chars: Keyword.get(@instance, :limit)
+          },
+          rights: %{
+            delete_others_notice: !!user.info["is_moderator"]
           },
           compose: %{
             me: "#{user.id}",
-            default_privacy: "public",
+            default_privacy: user.info["default_scope"] || "public",
             default_sensitive: false
           },
           media_attachments: %{
@@ -1013,4 +1100,51 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
     |> put_status(500)
     |> json("Something went wrong")
   end
+
+  @suggestions Application.get_env(:pleroma, :suggestions)
+
+  def suggestions(%{assigns: %{user: user}} = conn, _) do
+    if Keyword.get(@suggestions, :enabled, false) do
+      api = Keyword.get(@suggestions, :third_party_engine, "")
+      timeout = Keyword.get(@suggestions, :timeout, 5000)
+
+      host =
+        Application.get_env(:pleroma, Pleroma.Web.Endpoint)
+        |> Keyword.get(:url)
+        |> Keyword.get(:host)
+
+      user = user.nickname
+      url = String.replace(api, "{{host}}", host) |> String.replace("{{user}}", user)
+
+      with {:ok, %{status_code: 200, body: body}} <-
+             @httpoison.get(url, [], timeout: timeout, recv_timeout: timeout),
+           {:ok, data} <- Jason.decode(body) do
+        data2 =
+          Enum.slice(data, 0, 40)
+          |> Enum.map(fn x ->
+            Map.put(
+              x,
+              "id",
+              case User.get_or_fetch(x["acct"]) do
+                %{id: id} -> id
+                _ -> 0
+              end
+            )
+          end)
+          |> Enum.map(fn x ->
+            Map.put(x, "avatar", MediaProxy.url(x["avatar"]))
+          end)
+          |> Enum.map(fn x ->
+            Map.put(x, "avatar_static", MediaProxy.url(x["avatar_static"]))
+          end)
+
+        conn
+        |> json(data2)
+      else
+        e -> Logger.error("Could not retrieve suggestions at fetch #{url}, #{inspect(e)}")
+      end
+    else
+      json(conn, [])
+    end
+  end
 end