total rebase
[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, replace_params: false)
42
43   defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.ChatOperation
44
45   def delete_message(
46         %{
47           assigns: %{user: %{id: user_id} = user},
48           private: %{
49             open_api_spex: %{
50               params: %{
51                 message_id: message_id,
52                 id: chat_id
53               }
54             }
55           }
56         } = conn,
57         _
58       ) do
59     with %MessageReference{} = cm_ref <-
60            MessageReference.get_by_id(message_id),
61          ^chat_id <- to_string(cm_ref.chat_id),
62          %Chat{user_id: ^user_id} <- Chat.get_by_id(chat_id),
63          {:ok, _} <- remove_or_delete(cm_ref, user) do
64       conn
65       |> put_view(MessageReferenceView)
66       |> render("show.json", chat_message_reference: cm_ref)
67     else
68       _e ->
69         {:error, :could_not_delete}
70     end
71   end
72
73   defp remove_or_delete(
74          %{object: %{data: %{"actor" => actor, "id" => id}}},
75          %{ap_id: actor} = user
76        ) do
77     with %Activity{} = activity <- Activity.get_create_by_object_ap_id(id) do
78       CommonAPI.delete(activity.id, user)
79     end
80   end
81
82   defp remove_or_delete(cm_ref, _), do: MessageReference.delete(cm_ref)
83
84   def post_chat_message(
85         %{
86           private: %{open_api_spex: %{body_params: params, params: %{id: id}}},
87           assigns: %{user: user}
88         } = conn,
89         _
90       ) do
91     with {:ok, chat} <- Chat.get_by_user_and_id(user, id),
92          {_, %User{} = recipient} <- {:user, User.get_cached_by_ap_id(chat.recipient)},
93          {:ok, activity} <-
94            CommonAPI.post_chat_message(user, recipient, params[:content],
95              media_id: params[:media_id],
96              idempotency_key: idempotency_key(conn)
97            ),
98          message <- Object.normalize(activity, fetch: false),
99          cm_ref <- MessageReference.for_chat_and_object(chat, message) do
100       conn
101       |> put_view(MessageReferenceView)
102       |> render("show.json", chat_message_reference: cm_ref)
103     else
104       {:reject, message} ->
105         conn
106         |> put_status(:unprocessable_entity)
107         |> json(%{error: message})
108
109       {:error, message} ->
110         conn
111         |> put_status(:bad_request)
112         |> json(%{error: message})
113
114       {:user, nil} ->
115         conn
116         |> put_status(:bad_request)
117         |> json(%{error: "Recipient does not exist"})
118     end
119   end
120
121   def mark_message_as_read(
122         %{
123           assigns: %{user: %{id: user_id}},
124           private: %{open_api_spex: %{params: %{id: chat_id, message_id: message_id}}}
125         } = conn,
126         _
127       ) do
128     with %MessageReference{} = cm_ref <- MessageReference.get_by_id(message_id),
129          ^chat_id <- to_string(cm_ref.chat_id),
130          %Chat{user_id: ^user_id} <- Chat.get_by_id(chat_id),
131          {:ok, cm_ref} <- MessageReference.mark_as_read(cm_ref) do
132       conn
133       |> put_view(MessageReferenceView)
134       |> render("show.json", chat_message_reference: cm_ref)
135     end
136   end
137
138   def mark_as_read(
139         %{
140           assigns: %{user: user},
141           private: %{
142             open_api_spex: %{
143               body_params: %{last_read_id: last_read_id},
144               params: %{id: id}
145             }
146           }
147         } = conn,
148         _
149       ) do
150     with {:ok, chat} <- Chat.get_by_user_and_id(user, id),
151          {_n, _} <- MessageReference.set_all_seen_for_chat(chat, last_read_id) do
152       render(conn, "show.json", chat: chat)
153     end
154   end
155
156   def messages(
157         %{
158           assigns: %{user: user},
159           private: %{open_api_spex: %{params: %{id: id} = params}}
160         } = conn,
161         _
162       ) do
163     with {:ok, chat} <- Chat.get_by_user_and_id(user, id) do
164       chat_message_refs =
165         chat
166         |> MessageReference.for_chat_query()
167         |> Pagination.fetch_paginated(params)
168
169       conn
170       |> add_link_headers(chat_message_refs)
171       |> put_view(MessageReferenceView)
172       |> render("index.json", chat_message_references: chat_message_refs)
173     end
174   end
175
176   def index(%{assigns: %{user: user}, private: %{open_api_spex: %{params: params}}} = conn, _) do
177     chats =
178       index_query(user, params)
179       |> Repo.all()
180
181     render(conn, "index.json", chats: chats)
182   end
183
184   def index2(%{assigns: %{user: user}, private: %{open_api_spex: %{params: params}}} = conn, _) do
185     chats =
186       index_query(user, params)
187       |> Pagination.fetch_paginated(params)
188
189     conn
190     |> add_link_headers(chats)
191     |> render("index.json", chats: chats)
192   end
193
194   defp index_query(%{id: user_id} = user, params) do
195     exclude_users =
196       User.cached_blocked_users_ap_ids(user) ++
197         if params[:with_muted], do: [], else: User.cached_muted_users_ap_ids(user)
198
199     user_id
200     |> Chat.for_user_query()
201     |> where([c], c.recipient not in ^exclude_users)
202   end
203
204   def create(%{assigns: %{user: user}, private: %{open_api_spex: %{params: %{id: id}}}} = conn, _) do
205     with %User{ap_id: recipient} <- User.get_cached_by_id(id),
206          {:ok, %Chat{} = chat} <- Chat.get_or_create(user.id, recipient) do
207       render(conn, "show.json", chat: chat)
208     end
209   end
210
211   def show(%{assigns: %{user: user}, private: %{open_api_spex: %{params: %{id: id}}}} = conn, _) do
212     with {:ok, chat} <- Chat.get_by_user_and_id(user, id) do
213       render(conn, "show.json", chat: chat)
214     end
215   end
216
217   defp idempotency_key(conn) do
218     case get_req_header(conn, "idempotency-key") do
219       [key] -> key
220       _ -> nil
221     end
222   end
223 end