First
[anni] / lib / pleroma / web / pleroma_api / controllers / chat_controller.ex
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4 defmodule Pleroma.Web.PleromaAPI.ChatController do
5   use Pleroma.Web, :controller
6
7   import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2]
8
9   alias Pleroma.Activity
10   alias Pleroma.Chat
11   alias Pleroma.Chat.MessageReference
12   alias Pleroma.Object
13   alias Pleroma.Pagination
14   alias Pleroma.Repo
15   alias Pleroma.User
16   alias Pleroma.Web.CommonAPI
17   alias Pleroma.Web.PleromaAPI.Chat.MessageReferenceView
18   alias Pleroma.Web.Plugs.OAuthScopesPlug
19
20   import Ecto.Query
21
22   action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
23
24   plug(
25     OAuthScopesPlug,
26     %{scopes: ["write:chats"]}
27     when action in [
28            :post_chat_message,
29            :create,
30            :mark_as_read,
31            :mark_message_as_read,
32            :delete_message
33          ]
34   )
35
36   plug(
37     OAuthScopesPlug,
38     %{scopes: ["read:chats"]} when action in [:messages, :index, :index2, :show]
39   )
40
41   plug(Pleroma.Web.ApiSpec.CastAndValidate)
42
43   defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.ChatOperation
44
45   def delete_message(%{assigns: %{user: %{id: user_id} = user}} = conn, %{
46         message_id: message_id,
47         id: chat_id
48       }) do
49     with %MessageReference{} = cm_ref <-
50            MessageReference.get_by_id(message_id),
51          ^chat_id <- to_string(cm_ref.chat_id),
52          %Chat{user_id: ^user_id} <- Chat.get_by_id(chat_id),
53          {:ok, _} <- remove_or_delete(cm_ref, user) do
54       conn
55       |> put_view(MessageReferenceView)
56       |> render("show.json", chat_message_reference: cm_ref)
57     else
58       _e ->
59         {:error, :could_not_delete}
60     end
61   end
62
63   defp remove_or_delete(
64          %{object: %{data: %{"actor" => actor, "id" => id}}},
65          %{ap_id: actor} = user
66        ) do
67     with %Activity{} = activity <- Activity.get_create_by_object_ap_id(id) do
68       CommonAPI.delete(activity.id, user)
69     end
70   end
71
72   defp remove_or_delete(cm_ref, _), do: MessageReference.delete(cm_ref)
73
74   def post_chat_message(
75         %{body_params: params, assigns: %{user: user}} = conn,
76         %{id: id}
77       ) do
78     with {:ok, chat} <- Chat.get_by_user_and_id(user, id),
79          %User{} = recipient <- User.get_cached_by_ap_id(chat.recipient),
80          {:ok, activity} <-
81            CommonAPI.post_chat_message(user, recipient, params[:content],
82              media_id: params[:media_id],
83              idempotency_key: idempotency_key(conn)
84            ),
85          message <- Object.normalize(activity, fetch: false),
86          cm_ref <- MessageReference.for_chat_and_object(chat, message) do
87       conn
88       |> put_view(MessageReferenceView)
89       |> render("show.json", chat_message_reference: cm_ref)
90     else
91       {:reject, message} ->
92         conn
93         |> put_status(:unprocessable_entity)
94         |> json(%{error: message})
95
96       {:error, message} ->
97         conn
98         |> put_status(:bad_request)
99         |> json(%{error: message})
100     end
101   end
102
103   def mark_message_as_read(
104         %{assigns: %{user: %{id: user_id}}} = conn,
105         %{id: chat_id, message_id: message_id}
106       ) do
107     with %MessageReference{} = cm_ref <- MessageReference.get_by_id(message_id),
108          ^chat_id <- to_string(cm_ref.chat_id),
109          %Chat{user_id: ^user_id} <- Chat.get_by_id(chat_id),
110          {:ok, cm_ref} <- MessageReference.mark_as_read(cm_ref) do
111       conn
112       |> put_view(MessageReferenceView)
113       |> render("show.json", chat_message_reference: cm_ref)
114     end
115   end
116
117   def mark_as_read(
118         %{body_params: %{last_read_id: last_read_id}, assigns: %{user: user}} = conn,
119         %{id: id}
120       ) do
121     with {:ok, chat} <- Chat.get_by_user_and_id(user, id),
122          {_n, _} <- MessageReference.set_all_seen_for_chat(chat, last_read_id) do
123       render(conn, "show.json", chat: chat)
124     end
125   end
126
127   def messages(%{assigns: %{user: user}} = conn, %{id: id} = params) do
128     with {:ok, chat} <- Chat.get_by_user_and_id(user, id) do
129       chat_message_refs =
130         chat
131         |> MessageReference.for_chat_query()
132         |> Pagination.fetch_paginated(params)
133
134       conn
135       |> add_link_headers(chat_message_refs)
136       |> put_view(MessageReferenceView)
137       |> render("index.json", chat_message_references: chat_message_refs)
138     end
139   end
140
141   def index(%{assigns: %{user: user}} = conn, params) do
142     chats =
143       index_query(user, params)
144       |> Repo.all()
145
146     render(conn, "index.json", chats: chats)
147   end
148
149   def index2(%{assigns: %{user: user}} = conn, params) do
150     chats =
151       index_query(user, params)
152       |> Pagination.fetch_paginated(params)
153
154     conn
155     |> add_link_headers(chats)
156     |> render("index.json", chats: chats)
157   end
158
159   defp index_query(%{id: user_id} = user, params) do
160     exclude_users =
161       User.cached_blocked_users_ap_ids(user) ++
162         if params[:with_muted], do: [], else: User.cached_muted_users_ap_ids(user)
163
164     user_id
165     |> Chat.for_user_query()
166     |> where([c], c.recipient not in ^exclude_users)
167   end
168
169   def create(%{assigns: %{user: user}} = conn, %{id: id}) do
170     with %User{ap_id: recipient} <- User.get_cached_by_id(id),
171          {:ok, %Chat{} = chat} <- Chat.get_or_create(user.id, recipient) do
172       render(conn, "show.json", chat: chat)
173     end
174   end
175
176   def show(%{assigns: %{user: user}} = conn, %{id: id}) do
177     with {:ok, chat} <- Chat.get_by_user_and_id(user, id) do
178       render(conn, "show.json", chat: chat)
179     end
180   end
181
182   defp idempotency_key(conn) do
183     case get_req_header(conn, "idempotency-key") do
184       [key] -> key
185       _ -> nil
186     end
187   end
188 end