Merge branch 'develop' into refactor/subscription
[akkoma] / lib / pleroma / web / twitter_api / controllers / util_controller.ex
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.Web.TwitterAPI.UtilController do
6 use Pleroma.Web, :controller
7
8 require Logger
9
10 alias Pleroma.Activity
11 alias Pleroma.Config
12 alias Pleroma.Emoji
13 alias Pleroma.Healthcheck
14 alias Pleroma.Notification
15 alias Pleroma.Plugs.AuthenticationPlug
16 alias Pleroma.User
17 alias Pleroma.Web
18 alias Pleroma.Web.CommonAPI
19 alias Pleroma.Web.WebFinger
20
21 plug(Pleroma.Plugs.SetFormatPlug when action in [:config, :version])
22
23 def help_test(conn, _params) do
24 json(conn, "ok")
25 end
26
27 def remote_subscribe(conn, %{"nickname" => nick, "profile" => _}) do
28 with %User{} = user <- User.get_cached_by_nickname(nick),
29 avatar = User.avatar_url(user) do
30 conn
31 |> render("subscribe.html", %{nickname: nick, avatar: avatar, error: false})
32 else
33 _e ->
34 render(conn, "subscribe.html", %{
35 nickname: nick,
36 avatar: nil,
37 error: "Could not find user"
38 })
39 end
40 end
41
42 def remote_subscribe(conn, %{"user" => %{"nickname" => nick, "profile" => profile}}) do
43 with {:ok, %{"subscribe_address" => template}} <- WebFinger.finger(profile),
44 %User{ap_id: ap_id} <- User.get_cached_by_nickname(nick) do
45 conn
46 |> Phoenix.Controller.redirect(external: String.replace(template, "{uri}", ap_id))
47 else
48 _e ->
49 render(conn, "subscribe.html", %{
50 nickname: nick,
51 avatar: nil,
52 error: "Something went wrong."
53 })
54 end
55 end
56
57 def remote_follow(%{assigns: %{user: user}} = conn, %{"acct" => acct}) do
58 if is_status?(acct) do
59 {:ok, object} = Pleroma.Object.Fetcher.fetch_object_from_id(acct)
60 %Activity{id: activity_id} = Activity.get_create_by_object_ap_id(object.data["id"])
61 redirect(conn, to: "/notice/#{activity_id}")
62 else
63 with {:ok, followee} <- User.get_or_fetch(acct) do
64 conn
65 |> render(follow_template(user), %{
66 error: false,
67 acct: acct,
68 avatar: User.avatar_url(followee),
69 name: followee.nickname,
70 id: followee.id
71 })
72 else
73 {:error, _reason} ->
74 render(conn, follow_template(user), %{error: :error})
75 end
76 end
77 end
78
79 defp follow_template(%User{} = _user), do: "follow.html"
80 defp follow_template(_), do: "follow_login.html"
81
82 defp is_status?(acct) do
83 case Pleroma.Object.Fetcher.fetch_and_contain_remote_object_from_id(acct) do
84 {:ok, %{"type" => type}} when type in ["Article", "Note", "Video", "Page", "Question"] ->
85 true
86
87 _ ->
88 false
89 end
90 end
91
92 def do_remote_follow(conn, %{
93 "authorization" => %{"name" => username, "password" => password, "id" => id}
94 }) do
95 with %User{} = followee <- User.get_cached_by_id(id),
96 {_, %User{} = user, _} <- {:auth, User.get_cached_by_nickname(username), followee},
97 {_, true, _} <- {
98 :auth,
99 AuthenticationPlug.checkpw(password, user.password_hash),
100 followee
101 },
102 {:ok, _follower, _followee, _activity} <- CommonAPI.follow(user, followee) do
103 conn
104 |> render("followed.html", %{error: false})
105 else
106 # Was already following user
107 {:error, "Could not follow user:" <> _rest} ->
108 render(conn, "followed.html", %{error: "Error following account"})
109
110 {:auth, _, followee} ->
111 conn
112 |> render("follow_login.html", %{
113 error: "Wrong username or password",
114 id: id,
115 name: followee.nickname,
116 avatar: User.avatar_url(followee)
117 })
118
119 e ->
120 Logger.debug("Remote follow failed with error #{inspect(e)}")
121 render(conn, "followed.html", %{error: "Something went wrong."})
122 end
123 end
124
125 def do_remote_follow(%{assigns: %{user: user}} = conn, %{"user" => %{"id" => id}}) do
126 with {:fetch_user, %User{} = followee} <- {:fetch_user, User.get_cached_by_id(id)},
127 {:ok, _follower, _followee, _activity} <- CommonAPI.follow(user, followee) do
128 conn
129 |> render("followed.html", %{error: false})
130 else
131 # Was already following user
132 {:error, "Could not follow user:" <> _rest} ->
133 render(conn, "followed.html", %{error: "Error following account"})
134
135 {:fetch_user, error} ->
136 Logger.debug("Remote follow failed with error #{inspect(error)}")
137 render(conn, "followed.html", %{error: "Could not find user"})
138
139 e ->
140 Logger.debug("Remote follow failed with error #{inspect(e)}")
141 render(conn, "followed.html", %{error: "Something went wrong."})
142 end
143 end
144
145 def notifications_read(%{assigns: %{user: user}} = conn, %{"id" => notification_id}) do
146 with {:ok, _} <- Notification.read_one(user, notification_id) do
147 json(conn, %{status: "success"})
148 else
149 {:error, message} ->
150 conn
151 |> put_resp_content_type("application/json")
152 |> send_resp(403, Jason.encode!(%{"error" => message}))
153 end
154 end
155
156 def config(%{assigns: %{format: "xml"}} = conn, _params) do
157 instance = Pleroma.Config.get(:instance)
158
159 response = """
160 <config>
161 <site>
162 <name>#{Keyword.get(instance, :name)}</name>
163 <site>#{Web.base_url()}</site>
164 <textlimit>#{Keyword.get(instance, :limit)}</textlimit>
165 <closed>#{!Keyword.get(instance, :registrations_open)}</closed>
166 </site>
167 </config>
168 """
169
170 conn
171 |> put_resp_content_type("application/xml")
172 |> send_resp(200, response)
173 end
174
175 def config(conn, _params) do
176 instance = Pleroma.Config.get(:instance)
177
178 vapid_public_key = Keyword.get(Pleroma.Web.Push.vapid_config(), :public_key)
179
180 uploadlimit = %{
181 uploadlimit: to_string(Keyword.get(instance, :upload_limit)),
182 avatarlimit: to_string(Keyword.get(instance, :avatar_upload_limit)),
183 backgroundlimit: to_string(Keyword.get(instance, :background_upload_limit)),
184 bannerlimit: to_string(Keyword.get(instance, :banner_upload_limit))
185 }
186
187 data = %{
188 name: Keyword.get(instance, :name),
189 description: Keyword.get(instance, :description),
190 server: Web.base_url(),
191 textlimit: to_string(Keyword.get(instance, :limit)),
192 uploadlimit: uploadlimit,
193 closed: bool_to_val(Keyword.get(instance, :registrations_open), "0", "1"),
194 private: bool_to_val(Keyword.get(instance, :public, true), "0", "1"),
195 vapidPublicKey: vapid_public_key,
196 accountActivationRequired:
197 bool_to_val(Keyword.get(instance, :account_activation_required, false)),
198 invitesEnabled: bool_to_val(Keyword.get(instance, :invites_enabled, false)),
199 safeDMMentionsEnabled: bool_to_val(Pleroma.Config.get([:instance, :safe_dm_mentions]))
200 }
201
202 managed_config = Keyword.get(instance, :managed_config)
203
204 data =
205 if managed_config do
206 pleroma_fe = Pleroma.Config.get([:frontend_configurations, :pleroma_fe])
207 Map.put(data, "pleromafe", pleroma_fe)
208 else
209 data
210 end
211
212 json(conn, %{site: data})
213 end
214
215 defp bool_to_val(true), do: "1"
216 defp bool_to_val(_), do: "0"
217 defp bool_to_val(true, val, _), do: val
218 defp bool_to_val(_, _, val), do: val
219
220 def frontend_configurations(conn, _params) do
221 config =
222 Pleroma.Config.get(:frontend_configurations, %{})
223 |> Enum.into(%{})
224
225 json(conn, config)
226 end
227
228 def version(%{assigns: %{format: "xml"}} = conn, _params) do
229 version = Pleroma.Application.named_version()
230
231 conn
232 |> put_resp_content_type("application/xml")
233 |> send_resp(200, "<version>#{version}</version>")
234 end
235
236 def version(conn, _params) do
237 json(conn, Pleroma.Application.named_version())
238 end
239
240 def emoji(conn, _params) do
241 emoji =
242 Emoji.get_all()
243 |> Enum.map(fn {short_code, path, tags} ->
244 {short_code, %{image_url: path, tags: tags}}
245 end)
246 |> Enum.into(%{})
247
248 json(conn, emoji)
249 end
250
251 def update_notificaton_settings(%{assigns: %{user: user}} = conn, params) do
252 with {:ok, _} <- User.update_notification_settings(user, params) do
253 json(conn, %{status: "success"})
254 end
255 end
256
257 def follow_import(conn, %{"list" => %Plug.Upload{} = listfile}) do
258 follow_import(conn, %{"list" => File.read!(listfile.path)})
259 end
260
261 def follow_import(%{assigns: %{user: follower}} = conn, %{"list" => list}) do
262 with lines <- String.split(list, "\n"),
263 followed_identifiers <-
264 Enum.map(lines, fn line ->
265 String.split(line, ",") |> List.first()
266 end)
267 |> List.delete("Account address") do
268 PleromaJobQueue.enqueue(:background, User, [
269 :follow_import,
270 follower,
271 followed_identifiers
272 ])
273
274 json(conn, "job started")
275 end
276 end
277
278 def blocks_import(conn, %{"list" => %Plug.Upload{} = listfile}) do
279 blocks_import(conn, %{"list" => File.read!(listfile.path)})
280 end
281
282 def blocks_import(%{assigns: %{user: blocker}} = conn, %{"list" => list}) do
283 with blocked_identifiers <- String.split(list) do
284 PleromaJobQueue.enqueue(:background, User, [
285 :blocks_import,
286 blocker,
287 blocked_identifiers
288 ])
289
290 json(conn, "job started")
291 end
292 end
293
294 def change_password(%{assigns: %{user: user}} = conn, params) do
295 case CommonAPI.Utils.confirm_current_password(user, params["password"]) do
296 {:ok, user} ->
297 with {:ok, _user} <-
298 User.reset_password(user, %{
299 password: params["new_password"],
300 password_confirmation: params["new_password_confirmation"]
301 }) do
302 json(conn, %{status: "success"})
303 else
304 {:error, changeset} ->
305 {_, {error, _}} = Enum.at(changeset.errors, 0)
306 json(conn, %{error: "New password #{error}."})
307
308 _ ->
309 json(conn, %{error: "Unable to change password."})
310 end
311
312 {:error, msg} ->
313 json(conn, %{error: msg})
314 end
315 end
316
317 def change_email(%{assigns: %{user: user}} = conn, params) do
318 case CommonAPI.Utils.confirm_current_password(user, params["password"]) do
319 {:ok, user} ->
320 with {:ok, _user} <- User.change_email(user, params["email"]) do
321 json(conn, %{status: "success"})
322 else
323 {:error, changeset} ->
324 {_, {error, _}} = Enum.at(changeset.errors, 0)
325 json(conn, %{error: "Email #{error}."})
326
327 _ ->
328 json(conn, %{error: "Unable to change email."})
329 end
330
331 {:error, msg} ->
332 json(conn, %{error: msg})
333 end
334 end
335
336 def delete_account(%{assigns: %{user: user}} = conn, params) do
337 case CommonAPI.Utils.confirm_current_password(user, params["password"]) do
338 {:ok, user} ->
339 User.delete(user)
340 json(conn, %{status: "success"})
341
342 {:error, msg} ->
343 json(conn, %{error: msg})
344 end
345 end
346
347 def disable_account(%{assigns: %{user: user}} = conn, params) do
348 case CommonAPI.Utils.confirm_current_password(user, params["password"]) do
349 {:ok, user} ->
350 User.deactivate_async(user)
351 json(conn, %{status: "success"})
352
353 {:error, msg} ->
354 json(conn, %{error: msg})
355 end
356 end
357
358 def captcha(conn, _params) do
359 json(conn, Pleroma.Captcha.new())
360 end
361
362 def healthcheck(conn, _params) do
363 with true <- Config.get([:instance, :healthcheck]),
364 %{healthy: true} = info <- Healthcheck.system_info() do
365 json(conn, info)
366 else
367 %{healthy: false} = info ->
368 service_unavailable(conn, info)
369
370 _ ->
371 service_unavailable(conn, %{})
372 end
373 end
374
375 defp service_unavailable(conn, info) do
376 conn
377 |> put_status(:service_unavailable)
378 |> json(info)
379 end
380 end