MastoAPI: add lists.
[akkoma] / lib / pleroma / web / mastodon_api / mastodon_api_controller.ex
index dc92e30c5cec024ebe1ba138e6a97b5dc9557ab7..ff52b2aab6cafdfe63b00cd2607100568c2c0749 100644 (file)
@@ -2,7 +2,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
   use Pleroma.Web, :controller
   alias Pleroma.{Repo, Activity, User, Notification, Stats}
   alias Pleroma.Web
-  alias Pleroma.Web.MastodonAPI.{StatusView, AccountView, MastodonView}
+  alias Pleroma.Web.MastodonAPI.{StatusView, AccountView, MastodonView, ListView}
   alias Pleroma.Web.ActivityPub.ActivityPub
   alias Pleroma.Web.{CommonAPI, OStatus}
   alias Pleroma.Web.OAuth.{Authorization, Token, App}
@@ -102,13 +102,14 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
   end
 
   @instance Application.get_env(:pleroma, :instance)
+  @mastodon_api_level "2.3.3"
 
   def masto_instance(conn, _params) do
     response = %{
       uri: Web.base_url(),
       title: Keyword.get(@instance, :name),
       description: "A Pleroma instance, an alternative fediverse server",
-      version: Keyword.get(@instance, :version),
+      version: "#{@mastodon_api_level} (compatible; #{Keyword.get(@instance, :version)})",
       email: Keyword.get(@instance, :email),
       urls: %{
         streaming_api: String.replace(Web.base_url(), ["http", "https"], "wss")
@@ -211,9 +212,14 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
         |> Map.put("actor_id", ap_id)
         |> Map.put("whole_db", true)
 
-      activities =
-        ActivityPub.fetch_public_activities(params)
-        |> Enum.reverse()
+      if params["pinned"] == "true" do
+        # Since Pleroma has no "pinned" posts feature, we'll just set an empty list here
+        activities = []
+      else
+        activities =
+          ActivityPub.fetch_public_activities(params)
+          |> Enum.reverse()
+      end
 
       conn
       |> add_link_headers(:user_statuses, activities, params["id"])
@@ -494,6 +500,10 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
       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)
         else
           _e -> []
         end
@@ -503,20 +513,24 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
       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
+        limit: 20,
+        order_by: [desc: :id]
       )
 
     statuses = Repo.all(q) ++ fetched
-    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)
+
+    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)
 
     res = %{
       "accounts" => AccountView.render("accounts.json", users: accounts, for: user, as: :user),
@@ -551,6 +565,102 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
     |> render(StatusView, "index.json", %{activities: activities, for: user, as: :activity})
   end
 
+  def get_lists(%{assigns: %{user: user}} = conn, opts) do
+    lists = Pleroma.List.for_user(user, opts)
+    res = ListView.render("lists.json", lists: lists)
+    json(conn, res)
+  end
+
+  def get_list(%{assigns: %{user: user}} = conn, %{"id" => id}) do
+    with %Pleroma.List{} = list <- Pleroma.List.get(user, id) do
+      res = ListView.render("list.json", list: list)
+      json(conn, res)
+    else
+      _e -> json(conn, "error")
+    end
+  end
+
+  def delete_list(%{assigns: %{user: user}} = conn, %{"id" => id}) do
+    with %Pleroma.List{} = list <- Pleroma.List.get(user, id),
+         {:ok, _list} <- Pleroma.List.delete(list) do
+      json(conn, %{})
+    else
+      _e ->
+        json(conn, "error")
+    end
+  end
+
+  def create_list(%{assigns: %{user: user}} = conn, %{"title" => title}) do
+    with {:ok, %Pleroma.List{} = list} <- Pleroma.List.create(title, user) do
+      res = ListView.render("list.json", list: list)
+      json(conn, res)
+    end
+  end
+
+  def add_to_list(%{assigns: %{user: user}} = conn, %{"id" => id, "account_ids" => accounts}) do
+    accounts
+    |> Enum.each(fn account_id ->
+      with %Pleroma.List{} = list <- Pleroma.List.get(user, id),
+           %User{} = followed <- Repo.get(User, account_id) do
+        ret = Pleroma.List.follow(list, followed)
+      end
+    end)
+
+    json(conn, %{})
+  end
+
+  def remove_from_list(%{assigns: %{user: user}} = conn, %{"id" => id, "account_ids" => accounts}) do
+    accounts
+    |> Enum.each(fn account_id ->
+      with %Pleroma.List{} = list <- Pleroma.List.get(user, id),
+           %User{} = followed <- Repo.get(Pleroma.User, account_id) do
+        Pleroma.List.unfollow(list, followed)
+      end
+    end)
+
+    json(conn, %{})
+  end
+
+  def list_accounts(%{assigns: %{user: user}} = conn, %{"id" => id}) do
+    with %Pleroma.List{} = list <- Pleroma.List.get(user, id),
+         {:ok, users} = Pleroma.List.get_following(list) do
+      render(conn, AccountView, "accounts.json", %{users: users, as: :user})
+    end
+  end
+
+  def rename_list(%{assigns: %{user: user}} = conn, %{"id" => id, "title" => title}) do
+    with %Pleroma.List{} = list <- Pleroma.List.get(user, id),
+         {:ok, list} <- Pleroma.List.rename(list, title) do
+      res = ListView.render("list.json", list: list)
+      json(conn, res)
+    else
+      _e ->
+        json(conn, "error")
+    end
+  end
+
+  def list_timeline(%{assigns: %{user: user}} = conn, %{"list_id" => id} = params) do
+    with %Pleroma.List{title: title, following: following} <- Pleroma.List.get(user, id) do
+      params =
+        params
+        |> Map.put("type", "Create")
+        |> Map.put("blocking_user", user)
+
+      # adding title is a hack to not make empty lists function like a public timeline
+      activities =
+        ActivityPub.fetch_activities([title | following], params)
+        |> Enum.reverse()
+
+      conn
+      |> render(StatusView, "index.json", %{activities: activities, for: user, as: :activity})
+    else
+      _e ->
+        conn
+        |> put_status(403)
+        |> json(%{error: "Error."})
+    end
+  end
+
   def index(%{assigns: %{user: user}} = conn, _params) do
     token =
       conn
@@ -597,35 +707,37 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
               "video\/mp4"
             ]
           },
-          settings: %{
-            onboarded: true,
-            home: %{
-              shows: %{
-                reblog: true,
-                reply: true
-              }
-            },
-            notifications: %{
-              alerts: %{
-                follow: true,
-                favourite: true,
-                reblog: true,
-                mention: true
+          settings:
+            Map.get(user.info, "settings") ||
+              %{
+                onboarded: true,
+                home: %{
+                  shows: %{
+                    reblog: true,
+                    reply: true
+                  }
+                },
+                notifications: %{
+                  alerts: %{
+                    follow: true,
+                    favourite: true,
+                    reblog: true,
+                    mention: true
+                  },
+                  shows: %{
+                    follow: true,
+                    favourite: true,
+                    reblog: true,
+                    mention: true
+                  },
+                  sounds: %{
+                    follow: true,
+                    favourite: true,
+                    reblog: true,
+                    mention: true
+                  }
+                }
               },
-              shows: %{
-                follow: true,
-                favourite: true,
-                reblog: true,
-                mention: true
-              },
-              sounds: %{
-                follow: true,
-                favourite: true,
-                reblog: true,
-                mention: true
-              }
-            }
-          },
           push_subscription: nil,
           accounts: accounts,
           custom_emojis: mastodon_emoji,
@@ -642,6 +754,19 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
     end
   end
 
+  def put_settings(%{assigns: %{user: user}} = conn, %{"data" => settings} = _params) do
+    with new_info <- Map.put(user.info, "settings", settings),
+         change <- User.info_changeset(user, %{info: new_info}),
+         {:ok, _user} <- User.update_and_set_cache(change) do
+      conn
+      |> json(%{})
+    else
+      e ->
+        conn
+        |> json(%{error: inspect(e)})
+    end
+  end
+
   def login(conn, _) do
     conn
     |> render(MastodonView, "login.html", %{error: false})
@@ -664,7 +789,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
   end
 
   def login_post(conn, %{"authorization" => %{"name" => name, "password" => password}}) do
-    with %User{} = user <- User.get_cached_by_nickname(name),
+    with %User{} = user <- User.get_by_nickname_or_email(name),
          true <- Pbkdf2.checkpw(password, user.password_hash),
          {:ok, app} <- get_or_make_app(),
          {:ok, auth} <- Authorization.create_authorization(app, user),