twitter_api_controller.ex 21.8 KB
Newer Older
1
# Pleroma: A lightweight social networking server
kaniini's avatar
kaniini committed
2
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
3
4
# SPDX-License-Identifier: AGPL-3.0-only

5
6
defmodule Pleroma.Web.TwitterAPI.Controller do
  use Pleroma.Web, :controller
7
8
9

  import Pleroma.Web.ControllerHelper, only: [json_response: 3]

10
  alias Ecto.Changeset
Haelwenn's avatar
Haelwenn committed
11
  alias Pleroma.Activity
12
  alias Pleroma.Notification
Haelwenn's avatar
Haelwenn committed
13
  alias Pleroma.Object
14
  alias Pleroma.Repo
Haelwenn's avatar
Haelwenn committed
15
  alias Pleroma.User
Haelwenn's avatar
Haelwenn committed
16
  alias Pleroma.Web.ActivityPub.ActivityPub
17
18
  alias Pleroma.Web.ActivityPub.Visibility
  alias Pleroma.Web.CommonAPI
19
  alias Pleroma.Web.CommonAPI.Utils
20
21
22
23
24
25
  alias Pleroma.Web.OAuth.Token
  alias Pleroma.Web.TwitterAPI.ActivityView
  alias Pleroma.Web.TwitterAPI.NotificationView
  alias Pleroma.Web.TwitterAPI.TokenView
  alias Pleroma.Web.TwitterAPI.TwitterAPI
  alias Pleroma.Web.TwitterAPI.UserView
26

lain's avatar
lain committed
27
28
  require Logger

href's avatar
href committed
29
  plug(:only_if_public_instance when action in [:public_timeline, :public_and_external_timeline])
30
31
  action_fallback(:errors)

32
  def verify_credentials(%{assigns: %{user: user}} = conn, _params) do
lain's avatar
lain committed
33
    token = Phoenix.Token.sign(conn, "user socket", user.id)
href's avatar
href committed
34
35
36

    conn
    |> put_view(UserView)
37
    |> render("show.json", %{user: user, token: token, for: user})
38
39
  end

Thog's avatar
Thog committed
40
  def status_update(%{assigns: %{user: user}} = conn, %{"status" => _} = status_data) do
41
    with media_ids <- extract_media_ids(status_data),
lain's avatar
lain committed
42
43
         {:ok, activity} <-
           TwitterAPI.create_status(user, Map.put(status_data, "media_ids", media_ids)) do
dtluna's avatar
dtluna committed
44
      conn
lain's avatar
lain committed
45
      |> json(ActivityView.render("activity.json", activity: activity, for: user))
dtluna's avatar
dtluna committed
46
    else
47
      _ -> empty_status_reply(conn)
dtluna's avatar
dtluna committed
48
    end
lain's avatar
lain committed
49
50
  end

dtluna's avatar
dtluna committed
51
52
53
54
55
56
57
58
  def status_update(conn, _status_data) do
    empty_status_reply(conn)
  end

  defp empty_status_reply(conn) do
    bad_request_reply(conn, "Client must provide a 'status' parameter with a value.")
  end

lain's avatar
lain committed
59
60
61
  defp extract_media_ids(status_data) do
    with media_ids when not is_nil(media_ids) <- status_data["media_ids"],
         split_ids <- String.split(media_ids, ","),
lain's avatar
lain committed
62
63
64
65
         clean_ids <- Enum.reject(split_ids, fn id -> String.length(id) == 0 end) do
      clean_ids
    else
      _e -> []
lain's avatar
lain committed
66
67
68
    end
  end

lain's avatar
lain committed
69
  def public_and_external_timeline(%{assigns: %{user: user}} = conn, params) do
lain's avatar
lain committed
70
71
    params =
      params
72
      |> Map.put("type", ["Create", "Announce"])
lain's avatar
lain committed
73
74
75
      |> Map.put("blocking_user", user)

    activities = ActivityPub.fetch_public_activities(params)
lain's avatar
lain committed
76
77

    conn
href's avatar
href committed
78
79
    |> put_view(ActivityView)
    |> render("index.json", %{activities: activities, for: user})
lain's avatar
lain committed
80
81
  end

82
  def public_timeline(%{assigns: %{user: user}} = conn, params) do
lain's avatar
lain committed
83
84
    params =
      params
85
      |> Map.put("type", ["Create", "Announce"])
lain's avatar
lain committed
86
87
88
89
      |> Map.put("local_only", true)
      |> Map.put("blocking_user", user)

    activities = ActivityPub.fetch_public_activities(params)
lain's avatar
lain committed
90
91

    conn
href's avatar
href committed
92
93
    |> put_view(ActivityView)
    |> render("index.json", %{activities: activities, for: user})
lain's avatar
lain committed
94
95
  end

lain's avatar
lain committed
96
  def friends_timeline(%{assigns: %{user: user}} = conn, params) do
lain's avatar
lain committed
97
98
99
100
101
102
    params =
      params
      |> Map.put("type", ["Create", "Announce", "Follow", "Like"])
      |> Map.put("blocking_user", user)
      |> Map.put("user", user)

103
104
105
    activities =
      ActivityPub.fetch_activities([user.ap_id | user.following], params)
      |> ActivityPub.contain_timeline(user)
lain's avatar
lain committed
106
107

    conn
href's avatar
href committed
108
109
    |> put_view(ActivityView)
    |> render("index.json", %{activities: activities, for: user})
lain's avatar
lain committed
110
111
  end

eal's avatar
eal committed
112
  def show_user(conn, params) do
113
114
115
    for_user = conn.assigns.user

    with {:ok, shown} <- TwitterAPI.get_user(params),
Ivan Tashkinov's avatar
Ivan Tashkinov committed
116
117
118
         true <-
           User.auth_active?(shown) ||
             (for_user && (for_user.id == shown.id || User.superuser?(for_user))) do
href's avatar
href committed
119
      params =
120
121
        if for_user do
          %{user: shown, for: for_user}
href's avatar
href committed
122
123
124
125
126
127
128
        else
          %{user: shown}
        end

      conn
      |> put_view(UserView)
      |> render("show.json", params)
eal's avatar
eal committed
129
130
131
    else
      {:error, msg} ->
        bad_request_reply(conn, msg)
132
133
134
135
136

      false ->
        conn
        |> put_status(404)
        |> json(%{error: "Unconfirmed user"})
eal's avatar
eal committed
137
138
139
    end
  end

dtluna's avatar
dtluna committed
140
  def user_timeline(%{assigns: %{user: user}} = conn, params) do
141
142
    case TwitterAPI.get_user(user, params) do
      {:ok, target_user} ->
143
144
145
146
147
148
149
150
151
        # Twitter and ActivityPub use a different name and sense for this parameter.
        {include_rts, params} = Map.pop(params, "include_rts")

        params =
          case include_rts do
            x when x == "false" or x == "0" -> Map.put(params, "exclude_reblogs", "true")
            _ -> params
          end

152
        activities = ActivityPub.fetch_user_activities(target_user, user, params)
lain's avatar
lain committed
153

154
        conn
href's avatar
href committed
155
156
        |> put_view(ActivityView)
        |> render("index.json", %{activities: activities, for: user})
lain's avatar
lain committed
157

158
159
160
      {:error, msg} ->
        bad_request_reply(conn, msg)
    end
dtluna's avatar
dtluna committed
161
162
  end

dtluna's avatar
dtluna committed
163
  def mentions_timeline(%{assigns: %{user: user}} = conn, params) do
164
165
166
167
    params =
      params
      |> Map.put("type", ["Create", "Announce", "Follow", "Like"])
      |> Map.put("blocking_user", user)
168
      |> Map.put(:visibility, ~w[unlisted public private])
169

lain's avatar
lain committed
170
    activities = ActivityPub.fetch_activities([user.ap_id], params)
dtluna's avatar
dtluna committed
171
172

    conn
href's avatar
href committed
173
174
    |> put_view(ActivityView)
    |> render("index.json", %{activities: activities, for: user})
lain's avatar
lain committed
175
176
177
  end

  def dm_timeline(%{assigns: %{user: user}} = conn, params) do
Eugenij's avatar
Eugenij committed
178
179
180
181
182
183
    params =
      params
      |> Map.put("type", "Create")
      |> Map.put("blocking_user", user)
      |> Map.put("user", user)
      |> Map.put(:visibility, "direct")
lain's avatar
lain committed
184

Eugenij's avatar
Eugenij committed
185
186
187
    activities =
      ActivityPub.fetch_activities_query([user.ap_id], params)
      |> Repo.all()
lain's avatar
lain committed
188
189

    conn
href's avatar
href committed
190
191
    |> put_view(ActivityView)
    |> render("index.json", %{activities: activities, for: user})
dtluna's avatar
dtluna committed
192
193
  end

194
195
196
197
  def notifications(%{assigns: %{user: user}} = conn, params) do
    notifications = Notification.for_user(user, params)

    conn
href's avatar
href committed
198
199
    |> put_view(NotificationView)
    |> render("notification.json", %{notifications: notifications, for: user})
200
201
  end

202
203
204
205
206
207
  def notifications_read(%{assigns: %{user: user}} = conn, %{"latest_id" => latest_id} = params) do
    Notification.set_read_up_to(user, latest_id)

    notifications = Notification.for_user(user, params)

    conn
href's avatar
href committed
208
209
    |> put_view(NotificationView)
    |> render("notification.json", %{notifications: notifications, for: user})
210
211
  end

Maksim's avatar
Maksim committed
212
  def notifications_read(%{assigns: %{user: _user}} = conn, _) do
213
214
215
    bad_request_reply(conn, "You need to specify latest_id")
  end

216
217
  def follow(%{assigns: %{user: user}} = conn, params) do
    case TwitterAPI.follow(user, params) do
218
      {:ok, user, followed, _activity} ->
href's avatar
href committed
219
220
221
        conn
        |> put_view(UserView)
        |> render("show.json", %{user: followed, for: user})
lain's avatar
lain committed
222
223
224

      {:error, msg} ->
        forbidden_json_reply(conn, msg)
225
    end
lain's avatar
lain committed
226
227
  end

eal's avatar
eal committed
228
229
230
  def block(%{assigns: %{user: user}} = conn, params) do
    case TwitterAPI.block(user, params) do
      {:ok, user, blocked} ->
href's avatar
href committed
231
232
233
        conn
        |> put_view(UserView)
        |> render("show.json", %{user: blocked, for: user})
lain's avatar
lain committed
234
235
236

      {:error, msg} ->
        forbidden_json_reply(conn, msg)
eal's avatar
eal committed
237
238
239
240
241
242
    end
  end

  def unblock(%{assigns: %{user: user}} = conn, params) do
    case TwitterAPI.unblock(user, params) do
      {:ok, user, blocked} ->
href's avatar
href committed
243
244
245
        conn
        |> put_view(UserView)
        |> render("show.json", %{user: blocked, for: user})
lain's avatar
lain committed
246
247
248

      {:error, msg} ->
        forbidden_json_reply(conn, msg)
eal's avatar
eal committed
249
250
251
    end
  end

lain's avatar
lain committed
252
  def delete_post(%{assigns: %{user: user}} = conn, %{"id" => id}) do
normandy's avatar
normandy committed
253
    with {:ok, activity} <- TwitterAPI.delete(user, id) do
href's avatar
href committed
254
255
256
      conn
      |> put_view(ActivityView)
      |> render("activity.json", %{activity: activity, for: user})
lain's avatar
lain committed
257
258
259
    end
  end

260
  def unfollow(%{assigns: %{user: user}} = conn, params) do
261
    case TwitterAPI.unfollow(user, params) do
262
      {:ok, user, unfollowed} ->
href's avatar
href committed
263
264
265
        conn
        |> put_view(UserView)
        |> render("show.json", %{user: unfollowed, for: user})
lain's avatar
lain committed
266
267
268

      {:error, msg} ->
        forbidden_json_reply(conn, msg)
269
    end
lain's avatar
lain committed
270
271
  end

272
  def fetch_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
273
    with %Activity{} = activity <- Activity.get_by_id(id),
lain's avatar
lain committed
274
         true <- Visibility.visible_for_user?(activity, user) do
href's avatar
href committed
275
276
277
      conn
      |> put_view(ActivityView)
      |> render("activity.json", %{activity: activity, for: user})
lain's avatar
lain committed
278
    end
lain's avatar
lain committed
279
280
  end

281
  def fetch_conversation(%{assigns: %{user: user}} = conn, %{"id" => id}) do
282
    with context when is_binary(context) <- Utils.conversation_id_to_context(id),
lain's avatar
lain committed
283
284
285
286
287
288
         activities <-
           ActivityPub.fetch_activities_for_context(context, %{
             "blocking_user" => user,
             "user" => user
           }) do
      conn
href's avatar
href committed
289
290
      |> put_view(ActivityView)
      |> render("index.json", %{activities: activities, for: user})
lain's avatar
lain committed
291
    end
292
293
  end

Ivan Tashkinov's avatar
Ivan Tashkinov committed
294
295
296
297
  @doc """
  Updates metadata of uploaded media object.
  Derived from [Twitter API endpoint](https://developer.twitter.com/en/docs/media/upload-media/api-reference/post-media-metadata-create).
  """
298
299
  def update_media(%{assigns: %{user: user}} = conn, %{"media_id" => id} = data) do
    object = Repo.get(Object, id)
300
301
    description = get_in(data, ["alt_text", "text"]) || data["name"] || data["description"]

302
303
304
305
    {conn, status, response_body} =
      cond do
        !object ->
          {halt(conn), :not_found, ""}
Ivan Tashkinov's avatar
Ivan Tashkinov committed
306

307
        !Object.authorize_mutation(object, user) ->
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
          {halt(conn), :forbidden, "You can only update your own uploads."}

        !is_binary(description) ->
          {conn, :not_modified, ""}

        true ->
          new_data = Map.put(object.data, "name", description)

          {:ok, _} =
            object
            |> Object.change(%{data: new_data})
            |> Repo.update()

          {conn, :no_content, ""}
      end
323
324

    conn
325
326
    |> put_status(status)
    |> json(response_body)
327
328
  end

329
330
  def upload(%{assigns: %{user: user}} = conn, %{"media" => media}) do
    response = TwitterAPI.upload(media, user)
lain's avatar
lain committed
331

lain's avatar
lain committed
332
333
334
335
    conn
    |> put_resp_content_type("application/atom+xml")
    |> send_resp(200, response)
  end
336

337
338
  def upload_json(%{assigns: %{user: user}} = conn, %{"media" => media}) do
    response = TwitterAPI.upload(media, user, "json")
lain's avatar
lain committed
339

340
341
342
343
    conn
    |> json_reply(200, response)
  end

lain's avatar
lain committed
344
  def get_by_id_or_ap_id(id) do
345
    activity = Activity.get_by_id(id) || Activity.get_create_by_object_ap_id(id)
lain's avatar
lain committed
346

347
348
349
    if activity.data["type"] == "Create" do
      activity
    else
350
      Activity.get_create_by_object_ap_id(activity.data["object"])
351
    end
lain's avatar
lain committed
352
353
  end

lain's avatar
lain committed
354
  def favorite(%{assigns: %{user: user}} = conn, %{"id" => id}) do
href's avatar
href committed
355
    with {:ok, activity} <- TwitterAPI.fav(user, id) do
href's avatar
href committed
356
357
358
      conn
      |> put_view(ActivityView)
      |> render("activity.json", %{activity: activity, for: user})
href's avatar
href committed
359
360
    else
      _ -> json_reply(conn, 400, Jason.encode!(%{}))
lain's avatar
lain committed
361
    end
lain's avatar
lain committed
362
363
  end

lain's avatar
lain committed
364
  def unfavorite(%{assigns: %{user: user}} = conn, %{"id" => id}) do
href's avatar
href committed
365
    with {:ok, activity} <- TwitterAPI.unfav(user, id) do
href's avatar
href committed
366
367
368
      conn
      |> put_view(ActivityView)
      |> render("activity.json", %{activity: activity, for: user})
href's avatar
href committed
369
370
    else
      _ -> json_reply(conn, 400, Jason.encode!(%{}))
lain's avatar
lain committed
371
    end
lain's avatar
lain committed
372
373
  end

lain's avatar
lain committed
374
  def retweet(%{assigns: %{user: user}} = conn, %{"id" => id}) do
href's avatar
href committed
375
    with {:ok, activity} <- TwitterAPI.repeat(user, id) do
href's avatar
href committed
376
377
378
      conn
      |> put_view(ActivityView)
      |> render("activity.json", %{activity: activity, for: user})
href's avatar
href committed
379
380
    else
      _ -> json_reply(conn, 400, Jason.encode!(%{}))
dtluna's avatar
dtluna committed
381
    end
lain's avatar
lain committed
382
383
  end

384
  def unretweet(%{assigns: %{user: user}} = conn, %{"id" => id}) do
href's avatar
href committed
385
    with {:ok, activity} <- TwitterAPI.unrepeat(user, id) do
href's avatar
href committed
386
387
388
      conn
      |> put_view(ActivityView)
      |> render("activity.json", %{activity: activity, for: user})
href's avatar
href committed
389
390
    else
      _ -> json_reply(conn, 400, Jason.encode!(%{}))
391
392
393
    end
  end

394
  def pin(%{assigns: %{user: user}} = conn, %{"id" => id}) do
href's avatar
href committed
395
    with {:ok, activity} <- TwitterAPI.pin(user, id) do
396
397
398
399
400
401
402
403
404
405
      conn
      |> put_view(ActivityView)
      |> render("activity.json", %{activity: activity, for: user})
    else
      {:error, message} -> bad_request_reply(conn, message)
      err -> err
    end
  end

  def unpin(%{assigns: %{user: user}} = conn, %{"id" => id}) do
href's avatar
href committed
406
    with {:ok, activity} <- TwitterAPI.unpin(user, id) do
407
408
409
410
411
412
413
414
415
      conn
      |> put_view(ActivityView)
      |> render("activity.json", %{activity: activity, for: user})
    else
      {:error, message} -> bad_request_reply(conn, message)
      err -> err
    end
  end

416
417
  def register(conn, params) do
    with {:ok, user} <- TwitterAPI.register_user(params) do
href's avatar
href committed
418
419
420
      conn
      |> put_view(UserView)
      |> render("show.json", %{user: user})
421
422
    else
      {:error, errors} ->
lain's avatar
lain committed
423
424
        conn
        |> json_reply(400, Jason.encode!(errors))
425
426
427
    end
  end

428
429
430
  def password_reset(conn, params) do
    nickname_or_email = params["email"] || params["nickname"]

431
    with {:ok, _} <- TwitterAPI.password_reset(nickname_or_email) do
432
433
434
435
      json_response(conn, :no_content, "")
    end
  end

436
  def confirm_email(conn, %{"user_id" => uid, "token" => token}) do
minibikini's avatar
minibikini committed
437
    with %User{} = user <- User.get_cached_by_id(uid),
438
         true <- user.local,
439
440
         true <- user.info.confirmation_pending,
         true <- user.info.confirmation_token == token,
441
         info_change <- User.Info.confirmation_changeset(user.info, :confirmed),
442
443
444
445
446
447
448
         changeset <- Changeset.change(user) |> Changeset.put_embed(:info, info_change),
         {:ok, _} <- User.update_and_set_cache(changeset) do
      conn
      |> redirect(to: "/")
    end
  end

449
450
451
452
453
454
455
456
457
458
  def resend_confirmation_email(conn, params) do
    nickname_or_email = params["email"] || params["nickname"]

    with %User{} = user <- User.get_by_nickname_or_email(nickname_or_email),
         {:ok, _} <- User.try_send_confirmation_email(user) do
      conn
      |> json_response(:no_content, "")
    end
  end

lain's avatar
lain committed
459
  def update_avatar(%{assigns: %{user: user}} = conn, params) do
href's avatar
href committed
460
    {:ok, object} = ActivityPub.upload(params, type: :avatar)
461
    change = Changeset.change(user, %{avatar: object.data})
lain's avatar
lain committed
462
    {:ok, user} = User.update_and_set_cache(change)
lain's avatar
lain committed
463
    CommonAPI.update(user)
lain's avatar
lain committed
464

href's avatar
href committed
465
466
467
    conn
    |> put_view(UserView)
    |> render("show.json", %{user: user, for: user})
lain's avatar
lain committed
468
469
  end

lain's avatar
lain committed
470
  def update_banner(%{assigns: %{user: user}} = conn, params) do
href's avatar
href committed
471
    with {:ok, object} <- ActivityPub.upload(%{"img" => params["banner"]}, type: :banner),
472
473
474
475
         new_info <- %{"banner" => object.data},
         info_cng <- User.Info.profile_update(user.info, new_info),
         changeset <- Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_cng),
         {:ok, user} <- User.update_and_set_cache(changeset) do
lain's avatar
lain committed
476
      CommonAPI.update(user)
lain's avatar
lain committed
477
478
479
      %{"url" => [%{"href" => href} | _]} = object.data
      response = %{url: href} |> Jason.encode!()

lain's avatar
lain committed
480
481
482
483
484
485
      conn
      |> json_reply(200, response)
    end
  end

  def update_background(%{assigns: %{user: user}} = conn, params) do
href's avatar
href committed
486
    with {:ok, object} <- ActivityPub.upload(params, type: :background),
487
488
489
490
         new_info <- %{"background" => object.data},
         info_cng <- User.Info.profile_update(user.info, new_info),
         changeset <- Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_cng),
         {:ok, _user} <- User.update_and_set_cache(changeset) do
lain's avatar
lain committed
491
492
493
      %{"url" => [%{"href" => href} | _]} = object.data
      response = %{url: href} |> Jason.encode!()

lain's avatar
lain committed
494
495
496
497
498
      conn
      |> json_reply(200, response)
    end
  end

lain's avatar
lain committed
499
500
  def external_profile(%{assigns: %{user: current_user}} = conn, %{"profileurl" => uri}) do
    with {:ok, user_map} <- TwitterAPI.get_external_profile(current_user, uri),
lain's avatar
lain committed
501
         response <- Jason.encode!(user_map) do
lain's avatar
lain committed
502
503
      conn
      |> json_reply(200, response)
lain's avatar
lain committed
504
505
506
507
508
    else
      _e ->
        conn
        |> put_status(404)
        |> json(%{error: "Can't find user"})
lain's avatar
lain committed
509
510
511
    end
  end

512
  def followers(%{assigns: %{user: for_user}} = conn, params) do
lain's avatar
lain committed
513
    {:ok, page} = Ecto.Type.cast(:integer, params["page"] || 1)
514

515
    with {:ok, user} <- TwitterAPI.get_user(for_user, params),
516
         {:ok, followers} <- User.get_followers(user, page) do
517
518
519
      followers =
        cond do
          for_user && user.id == for_user.id -> followers
520
          user.info.hide_followers -> []
521
522
523
          true -> followers
        end

href's avatar
href committed
524
525
526
      conn
      |> put_view(UserView)
      |> render("index.json", %{users: followers, for: conn.assigns[:user]})
lain's avatar
lain committed
527
528
529
530
531
    else
      _e -> bad_request_reply(conn, "Can't get followers")
    end
  end

532
  def friends(%{assigns: %{user: for_user}} = conn, params) do
lain's avatar
lain committed
533
    {:ok, page} = Ecto.Type.cast(:integer, params["page"] || 1)
534
535
536
    {:ok, export} = Ecto.Type.cast(:boolean, params["all"] || false)

    page = if export, do: nil, else: page
537

538
    with {:ok, user} <- TwitterAPI.get_user(conn.assigns[:user], params),
539
         {:ok, friends} <- User.get_friends(user, page) do
540
541
542
      friends =
        cond do
          for_user && user.id == for_user.id -> friends
543
          user.info.hide_follows -> []
544
545
546
          true -> friends
        end

href's avatar
href committed
547
548
549
      conn
      |> put_view(UserView)
      |> render("index.json", %{users: friends, for: conn.assigns[:user]})
lain's avatar
lain committed
550
551
552
553
554
    else
      _e -> bad_request_reply(conn, "Can't get friends")
    end
  end

Maxim Filippov's avatar
Maxim Filippov committed
555
556
557
558
559
560
561
562
  def oauth_tokens(%{assigns: %{user: user}} = conn, _params) do
    with oauth_tokens <- Token.get_user_tokens(user) do
      conn
      |> put_view(TokenView)
      |> render("index.json", %{tokens: oauth_tokens})
    end
  end

Maxim Filippov's avatar
Maxim Filippov committed
563
564
565
566
567
568
  def revoke_token(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do
    Token.delete_user_token(user, id)

    json_reply(conn, 201, "")
  end

569
570
571
572
573
574
575
576
  def blocks(%{assigns: %{user: user}} = conn, _params) do
    with blocked_users <- User.blocked_users(user) do
      conn
      |> put_view(UserView)
      |> render("index.json", %{users: blocked_users, for: user})
    end
  end

577
  def friend_requests(conn, params) do
578
    with {:ok, user} <- TwitterAPI.get_user(conn.assigns[:user], params),
579
         {:ok, friend_requests} <- User.get_follow_requests(user) do
href's avatar
href committed
580
581
582
      conn
      |> put_view(UserView)
      |> render("index.json", %{users: friend_requests, for: conn.assigns[:user]})
583
584
585
586
587
    else
      _e -> bad_request_reply(conn, "Can't get friend requests")
    end
  end

Maksim's avatar
Maksim committed
588
  def approve_friend_request(conn, %{"user_id" => uid} = _params) do
589
    with followed <- conn.assigns[:user],
minibikini's avatar
minibikini committed
590
         %User{} = follower <- User.get_cached_by_id(uid),
591
         {:ok, follower} <- CommonAPI.accept_follow_request(follower, followed) do
href's avatar
href committed
592
593
594
      conn
      |> put_view(UserView)
      |> render("show.json", %{user: follower, for: followed})
595
596
597
598
599
    else
      e -> bad_request_reply(conn, "Can't approve user: #{inspect(e)}")
    end
  end

Maksim's avatar
Maksim committed
600
  def deny_friend_request(conn, %{"user_id" => uid} = _params) do
601
    with followed <- conn.assigns[:user],
minibikini's avatar
minibikini committed
602
         %User{} = follower <- User.get_cached_by_id(uid),
603
         {:ok, follower} <- CommonAPI.reject_follow_request(follower, followed) do
href's avatar
href committed
604
605
606
      conn
      |> put_view(UserView)
      |> render("show.json", %{user: follower, for: followed})
607
608
609
610
611
    else
      e -> bad_request_reply(conn, "Can't deny user: #{inspect(e)}")
    end
  end

eal's avatar
eal committed
612
613
  def friends_ids(%{assigns: %{user: user}} = conn, _params) do
    with {:ok, friends} <- User.get_friends(user) do
lain's avatar
lain committed
614
615
616
617
      ids =
        friends
        |> Enum.map(fn x -> x.id end)
        |> Jason.encode!()
618

eal's avatar
eal committed
619
620
621
622
623
624
      json(conn, ids)
    else
      _e -> bad_request_reply(conn, "Can't get friends")
    end
  end

625
  def empty_array(conn, _params) do
lain's avatar
lain committed
626
    json(conn, Jason.encode!([]))
627
  end
eal's avatar
eal committed
628

629
630
631
632
  def raw_empty_array(conn, _params) do
    json(conn, [])
  end

lain's avatar
lain committed
633
634
  defp build_info_cng(user, params) do
    info_params =
635
      ["no_rich_text", "locked", "hide_followers", "hide_follows", "hide_favorites", "show_role"]
lain's avatar
lain committed
636
637
638
      |> Enum.reduce(%{}, fn key, res ->
        if value = params[key] do
          Map.put(res, key, value == "true")
639
        else
lain's avatar
lain committed
640
          res
641
        end
lain's avatar
lain committed
642
      end)
643

lain's avatar
lain committed
644
645
646
    info_params =
      if value = params["default_scope"] do
        Map.put(info_params, "default_scope", value)
647
      else
lain's avatar
lain committed
648
        info_params
649
650
      end

lain's avatar
lain committed
651
652
653
    User.Info.profile_update(user.info, info_params)
  end

Maxim Filippov's avatar
Maxim Filippov committed
654
  defp parse_profile_bio(user, params) do
lain's avatar
lain committed
655
    if bio = params["description"] do
Maxim Filippov's avatar
Maxim Filippov committed
656
      Map.put(params, "bio", User.parse_bio(bio, user))
lain's avatar
lain committed
657
658
659
660
661
662
    else
      params
    end
  end

  def update_profile(%{assigns: %{user: user}} = conn, params) do
Maxim Filippov's avatar
Maxim Filippov committed
663
    params = parse_profile_bio(user, params)
lain's avatar
lain committed
664
    info_cng = build_info_cng(user, params)
665

lain's avatar
lain committed
666
    with changeset <- User.update_changeset(user, params),
lain's avatar
lain committed
667
         changeset <- Ecto.Changeset.put_embed(changeset, :info, info_cng),
lain's avatar
lain committed
668
         {:ok, user} <- User.update_and_set_cache(changeset) do
lain's avatar
lain committed
669
      CommonAPI.update(user)
href's avatar
href committed
670
671
672
673

      conn
      |> put_view(UserView)
      |> render("user.json", %{user: user, for: user})
lain's avatar
lain committed
674
675
676
677
678
679
680
    else
      error ->
        Logger.debug("Can't update user: #{inspect(error)}")
        bad_request_reply(conn, "Can't update user")
    end
  end

Thog's avatar
Thog committed
681
  def search(%{assigns: %{user: user}} = conn, %{"q" => _query} = params) do
lain's avatar
lain committed
682
683
    activities = TwitterAPI.search(user, params)

lain's avatar
lain committed
684
    conn
href's avatar
href committed
685
686
    |> put_view(ActivityView)
    |> render("index.json", %{activities: activities, for: user})
lain's avatar
lain committed
687
688
  end

689
  def search_user(%{assigns: %{user: user}} = conn, %{"query" => query}) do
690
    users = User.search(query, resolve: true, for_user: user)
691
692

    conn
href's avatar
href committed
693
694
    |> put_view(UserView)
    |> render("index.json", %{users: users, for: user})
695
696
  end

697
  defp bad_request_reply(conn, error_message) do
dtluna's avatar
dtluna committed
698
    json = error_json(conn, error_message)
699
700
701
    json_reply(conn, 400, json)
  end

702
703
704
705
706
  defp json_reply(conn, status, json) do
    conn
    |> put_resp_content_type("application/json")
    |> send_resp(status, json)
  end
707
708

  defp forbidden_json_reply(conn, error_message) do
dtluna's avatar
dtluna committed
709
    json = error_json(conn, error_message)
710
711
    json_reply(conn, 403, json)
  end
dtluna's avatar
dtluna committed
712

713
  def only_if_public_instance(%{assigns: %{user: %User{}}} = conn, _), do: conn
href's avatar
href committed
714
715
716
717
718
719
720

  def only_if_public_instance(conn, _) do
    if Keyword.get(Application.get_env(:pleroma, :instance), :public) do
      conn
    else
      conn
      |> forbidden_json_reply("Invalid credentials.")
href's avatar
href committed
721
      |> halt()
href's avatar
href committed
722
723
724
    end
  end

dtluna's avatar
dtluna committed
725
  defp error_json(conn, error_message) do
lain's avatar
lain committed
726
    %{"error" => error_message, "request" => conn.request_path} |> Jason.encode!()
dtluna's avatar
dtluna committed
727
  end
728
729
730
731
732
733
734
735
736
737
738
739

  def errors(conn, {:param_cast, _}) do
    conn
    |> put_status(400)
    |> json("Invalid parameters")
  end

  def errors(conn, _) do
    conn
    |> put_status(500)
    |> json("Something went wrong")
  end
740
end