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
12
  alias Pleroma.Web.ActivityPub.ActivityPub
  alias Pleroma.Web.ActivityPub.Utils
13
  alias Pleroma.Web.CommonAPI
Haelwenn's avatar
Haelwenn committed
14
15
16
17
18
19
20
21
22
  alias Pleroma.Web.TwitterAPI.ActivityView
  alias Pleroma.Web.TwitterAPI.NotificationView
  alias Pleroma.Web.TwitterAPI.TwitterAPI
  alias Pleroma.Web.TwitterAPI.UserView
  alias Pleroma.Activity
  alias Pleroma.Object
  alias Pleroma.Notification
  alias Pleroma.Repo
  alias Pleroma.User
23

lain's avatar
lain committed
24
25
  require Logger

href's avatar
href committed
26
  plug(:only_if_public_instance when action in [:public_timeline, :public_and_external_timeline])
27
28
  action_fallback(:errors)

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

    conn
    |> put_view(UserView)
34
    |> render("show.json", %{user: user, token: token, for: user})
35
36
  end

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

dtluna's avatar
dtluna committed
48
49
50
51
52
53
54
55
  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
56
57
58
  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
59
60
61
62
         clean_ids <- Enum.reject(split_ids, fn id -> String.length(id) == 0 end) do
      clean_ids
    else
      _e -> []
lain's avatar
lain committed
63
64
65
    end
  end

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

    activities = ActivityPub.fetch_public_activities(params)
lain's avatar
lain committed
73
74

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

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

    activities = ActivityPub.fetch_public_activities(params)
lain's avatar
lain committed
87
88

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

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

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

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

eal's avatar
eal committed
109
  def show_user(conn, params) do
110
111
112
    for_user = conn.assigns.user

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

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

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

dtluna's avatar
dtluna committed
137
  def user_timeline(%{assigns: %{user: user}} = conn, params) do
138
139
    case TwitterAPI.get_user(user, params) do
      {:ok, target_user} ->
140
141
142
143
144
145
146
147
148
        # 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

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

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

155
156
157
      {:error, msg} ->
        bad_request_reply(conn, msg)
    end
dtluna's avatar
dtluna committed
158
159
  end

dtluna's avatar
dtluna committed
160
  def mentions_timeline(%{assigns: %{user: user}} = conn, params) do
161
162
163
164
165
    params =
      params
      |> Map.put("type", ["Create", "Announce", "Follow", "Like"])
      |> Map.put("blocking_user", user)

lain's avatar
lain committed
166
    activities = ActivityPub.fetch_activities([user.ap_id], params)
dtluna's avatar
dtluna committed
167
168

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

  def dm_timeline(%{assigns: %{user: user}} = conn, params) do
    query =
      ActivityPub.fetch_activities_query(
        [user.ap_id],
177
        Map.merge(params, %{"type" => "Create", "user" => user, visibility: "direct"})
lain's avatar
lain committed
178
179
180
181
182
      )

    activities = Repo.all(query)

    conn
href's avatar
href committed
183
184
    |> put_view(ActivityView)
    |> render("index.json", %{activities: activities, for: user})
dtluna's avatar
dtluna committed
185
186
  end

187
188
189
190
  def notifications(%{assigns: %{user: user}} = conn, params) do
    notifications = Notification.for_user(user, params)

    conn
href's avatar
href committed
191
192
    |> put_view(NotificationView)
    |> render("notification.json", %{notifications: notifications, for: user})
193
194
  end

195
196
197
198
199
200
  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
201
202
    |> put_view(NotificationView)
    |> render("notification.json", %{notifications: notifications, for: user})
203
204
  end

Maksim's avatar
Maksim committed
205
  def notifications_read(%{assigns: %{user: _user}} = conn, _) do
206
207
208
    bad_request_reply(conn, "You need to specify latest_id")
  end

209
210
  def follow(%{assigns: %{user: user}} = conn, params) do
    case TwitterAPI.follow(user, params) do
211
      {:ok, user, followed, _activity} ->
href's avatar
href committed
212
213
214
        conn
        |> put_view(UserView)
        |> render("show.json", %{user: followed, for: user})
lain's avatar
lain committed
215
216
217

      {:error, msg} ->
        forbidden_json_reply(conn, msg)
218
    end
lain's avatar
lain committed
219
220
  end

eal's avatar
eal committed
221
222
223
  def block(%{assigns: %{user: user}} = conn, params) do
    case TwitterAPI.block(user, params) do
      {:ok, user, blocked} ->
href's avatar
href committed
224
225
226
        conn
        |> put_view(UserView)
        |> render("show.json", %{user: blocked, for: user})
lain's avatar
lain committed
227
228
229

      {:error, msg} ->
        forbidden_json_reply(conn, msg)
eal's avatar
eal committed
230
231
232
233
234
235
    end
  end

  def unblock(%{assigns: %{user: user}} = conn, params) do
    case TwitterAPI.unblock(user, params) do
      {:ok, user, blocked} ->
href's avatar
href committed
236
237
238
        conn
        |> put_view(UserView)
        |> render("show.json", %{user: blocked, for: user})
lain's avatar
lain committed
239
240
241

      {:error, msg} ->
        forbidden_json_reply(conn, msg)
eal's avatar
eal committed
242
243
244
    end
  end

lain's avatar
lain committed
245
  def delete_post(%{assigns: %{user: user}} = conn, %{"id" => id}) do
normandy's avatar
normandy committed
246
    with {:ok, activity} <- TwitterAPI.delete(user, id) do
href's avatar
href committed
247
248
249
      conn
      |> put_view(ActivityView)
      |> render("activity.json", %{activity: activity, for: user})
lain's avatar
lain committed
250
251
252
    end
  end

253
  def unfollow(%{assigns: %{user: user}} = conn, params) do
254
    case TwitterAPI.unfollow(user, params) do
255
      {:ok, user, unfollowed} ->
href's avatar
href committed
256
257
258
        conn
        |> put_view(UserView)
        |> render("show.json", %{user: unfollowed, for: user})
lain's avatar
lain committed
259
260
261

      {:error, msg} ->
        forbidden_json_reply(conn, msg)
262
    end
lain's avatar
lain committed
263
264
  end

265
  def fetch_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
lain's avatar
lain committed
266
267
    with %Activity{} = activity <- Repo.get(Activity, id),
         true <- ActivityPub.visible_for_user?(activity, user) do
href's avatar
href committed
268
269
270
      conn
      |> put_view(ActivityView)
      |> render("activity.json", %{activity: activity, for: user})
lain's avatar
lain committed
271
    end
lain's avatar
lain committed
272
273
  end

274
  def fetch_conversation(%{assigns: %{user: user}} = conn, %{"id" => id}) do
lain's avatar
lain committed
275
276
277
278
279
280
281
    with context when is_binary(context) <- TwitterAPI.conversation_id_to_context(id),
         activities <-
           ActivityPub.fetch_activities_for_context(context, %{
             "blocking_user" => user,
             "user" => user
           }) do
      conn
href's avatar
href committed
282
283
      |> put_view(ActivityView)
      |> render("index.json", %{activities: activities, for: user})
lain's avatar
lain committed
284
    end
285
286
  end

Ivan Tashkinov's avatar
Ivan Tashkinov committed
287
288
289
290
  @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).
  """
291
292
  def update_media(%{assigns: %{user: user}} = conn, %{"media_id" => id} = data) do
    object = Repo.get(Object, id)
293
294
    description = get_in(data, ["alt_text", "text"]) || data["name"] || data["description"]

295
296
297
298
    {conn, status, response_body} =
      cond do
        !object ->
          {halt(conn), :not_found, ""}
Ivan Tashkinov's avatar
Ivan Tashkinov committed
299

300
        !Object.authorize_mutation(object, user) ->
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
          {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
316
317

    conn
318
319
    |> put_status(status)
    |> json(response_body)
320
321
  end

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

lain's avatar
lain committed
325
326
327
328
    conn
    |> put_resp_content_type("application/atom+xml")
    |> send_resp(200, response)
  end
329

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

333
334
335
336
    conn
    |> json_reply(200, response)
  end

lain's avatar
lain committed
337
  def get_by_id_or_ap_id(id) do
338
    activity = Repo.get(Activity, id) || Activity.get_create_by_object_ap_id(id)
lain's avatar
lain committed
339

340
341
342
    if activity.data["type"] == "Create" do
      activity
    else
343
      Activity.get_create_by_object_ap_id(activity.data["object"])
344
    end
lain's avatar
lain committed
345
346
  end

lain's avatar
lain committed
347
  def favorite(%{assigns: %{user: user}} = conn, %{"id" => id}) do
href's avatar
href committed
348
    with {:ok, activity} <- TwitterAPI.fav(user, id) do
href's avatar
href committed
349
350
351
      conn
      |> put_view(ActivityView)
      |> render("activity.json", %{activity: activity, for: user})
href's avatar
href committed
352
353
    else
      _ -> json_reply(conn, 400, Jason.encode!(%{}))
lain's avatar
lain committed
354
    end
lain's avatar
lain committed
355
356
  end

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

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

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

387
  def pin(%{assigns: %{user: user}} = conn, %{"id" => id}) do
href's avatar
href committed
388
    with {:ok, activity} <- TwitterAPI.pin(user, id) do
389
390
391
392
393
394
395
396
397
398
      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
399
    with {:ok, activity} <- TwitterAPI.unpin(user, id) do
400
401
402
403
404
405
406
407
408
      conn
      |> put_view(ActivityView)
      |> render("activity.json", %{activity: activity, for: user})
    else
      {:error, message} -> bad_request_reply(conn, message)
      err -> err
    end
  end

409
410
  def register(conn, params) do
    with {:ok, user} <- TwitterAPI.register_user(params) do
href's avatar
href committed
411
412
413
      conn
      |> put_view(UserView)
      |> render("show.json", %{user: user})
414
415
    else
      {:error, errors} ->
lain's avatar
lain committed
416
417
        conn
        |> json_reply(400, Jason.encode!(errors))
418
419
420
    end
  end

421
422
423
  def password_reset(conn, params) do
    nickname_or_email = params["email"] || params["nickname"]

424
    with {:ok, _} <- TwitterAPI.password_reset(nickname_or_email) do
425
426
427
428
      json_response(conn, :no_content, "")
    end
  end

429
430
  def confirm_email(conn, %{"user_id" => uid, "token" => token}) do
    with %User{} = user <- Repo.get(User, uid),
431
         true <- user.local,
432
433
         true <- user.info.confirmation_pending,
         true <- user.info.confirmation_token == token,
434
         info_change <- User.Info.confirmation_changeset(user.info, :confirmed),
435
436
437
438
439
440
441
         changeset <- Changeset.change(user) |> Changeset.put_embed(:info, info_change),
         {:ok, _} <- User.update_and_set_cache(changeset) do
      conn
      |> redirect(to: "/")
    end
  end

442
443
444
445
446
447
448
449
450
451
  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
452
  def update_avatar(%{assigns: %{user: user}} = conn, params) do
href's avatar
href committed
453
    {:ok, object} = ActivityPub.upload(params, type: :avatar)
454
    change = Changeset.change(user, %{avatar: object.data})
lain's avatar
lain committed
455
    {:ok, user} = User.update_and_set_cache(change)
lain's avatar
lain committed
456
    CommonAPI.update(user)
lain's avatar
lain committed
457

href's avatar
href committed
458
459
460
    conn
    |> put_view(UserView)
    |> render("show.json", %{user: user, for: user})
lain's avatar
lain committed
461
462
  end

lain's avatar
lain committed
463
  def update_banner(%{assigns: %{user: user}} = conn, params) do
href's avatar
href committed
464
    with {:ok, object} <- ActivityPub.upload(%{"img" => params["banner"]}, type: :banner),
465
466
467
468
         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
469
      CommonAPI.update(user)
lain's avatar
lain committed
470
471
472
      %{"url" => [%{"href" => href} | _]} = object.data
      response = %{url: href} |> Jason.encode!()

lain's avatar
lain committed
473
474
475
476
477
478
      conn
      |> json_reply(200, response)
    end
  end

  def update_background(%{assigns: %{user: user}} = conn, params) do
href's avatar
href committed
479
    with {:ok, object} <- ActivityPub.upload(params, type: :background),
480
481
482
483
         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
484
485
486
      %{"url" => [%{"href" => href} | _]} = object.data
      response = %{url: href} |> Jason.encode!()

lain's avatar
lain committed
487
488
489
490
491
      conn
      |> json_reply(200, response)
    end
  end

lain's avatar
lain committed
492
493
  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
494
         response <- Jason.encode!(user_map) do
lain's avatar
lain committed
495
496
      conn
      |> json_reply(200, response)
lain's avatar
lain committed
497
498
499
500
501
    else
      _e ->
        conn
        |> put_status(404)
        |> json(%{error: "Can't find user"})
lain's avatar
lain committed
502
503
504
    end
  end

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

508
    with {:ok, user} <- TwitterAPI.get_user(for_user, params),
509
         {:ok, followers} <- User.get_followers(user, page) do
510
511
512
      followers =
        cond do
          for_user && user.id == for_user.id -> followers
513
          user.info.hide_followers -> []
514
515
516
          true -> followers
        end

href's avatar
href committed
517
518
519
      conn
      |> put_view(UserView)
      |> render("index.json", %{users: followers, for: conn.assigns[:user]})
lain's avatar
lain committed
520
521
522
523
524
    else
      _e -> bad_request_reply(conn, "Can't get followers")
    end
  end

525
  def friends(%{assigns: %{user: for_user}} = conn, params) do
lain's avatar
lain committed
526
    {:ok, page} = Ecto.Type.cast(:integer, params["page"] || 1)
527
528
529
    {:ok, export} = Ecto.Type.cast(:boolean, params["all"] || false)

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

531
    with {:ok, user} <- TwitterAPI.get_user(conn.assigns[:user], params),
532
         {:ok, friends} <- User.get_friends(user, page) do
533
534
535
      friends =
        cond do
          for_user && user.id == for_user.id -> friends
536
          user.info.hide_follows -> []
537
538
539
          true -> friends
        end

href's avatar
href committed
540
541
542
      conn
      |> put_view(UserView)
      |> render("index.json", %{users: friends, for: conn.assigns[:user]})
lain's avatar
lain committed
543
544
545
546
547
    else
      _e -> bad_request_reply(conn, "Can't get friends")
    end
  end

548
549
550
551
552
553
554
555
  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

556
  def friend_requests(conn, params) do
557
    with {:ok, user} <- TwitterAPI.get_user(conn.assigns[:user], params),
558
         {:ok, friend_requests} <- User.get_follow_requests(user) do
href's avatar
href committed
559
560
561
      conn
      |> put_view(UserView)
      |> render("index.json", %{users: friend_requests, for: conn.assigns[:user]})
562
563
564
565
566
    else
      _e -> bad_request_reply(conn, "Can't get friend requests")
    end
  end

Maksim's avatar
Maksim committed
567
  def approve_friend_request(conn, %{"user_id" => uid} = _params) do
568
569
570
571
572
573
574
575
576
577
578
579
    with followed <- conn.assigns[:user],
         %User{} = follower <- Repo.get(User, uid),
         {:ok, follower} <- User.maybe_follow(follower, followed),
         %Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed),
         {:ok, follow_activity} <- Utils.update_follow_state(follow_activity, "accept"),
         {:ok, _activity} <-
           ActivityPub.accept(%{
             to: [follower.ap_id],
             actor: followed.ap_id,
             object: follow_activity.data["id"],
             type: "Accept"
           }) do
href's avatar
href committed
580
581
582
      conn
      |> put_view(UserView)
      |> render("show.json", %{user: follower, for: followed})
583
584
585
586
587
    else
      e -> bad_request_reply(conn, "Can't approve user: #{inspect(e)}")
    end
  end

Maksim's avatar
Maksim committed
588
  def deny_friend_request(conn, %{"user_id" => uid} = _params) do
589
590
591
592
593
594
595
596
597
598
599
    with followed <- conn.assigns[:user],
         %User{} = follower <- Repo.get(User, uid),
         %Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed),
         {:ok, follow_activity} <- Utils.update_follow_state(follow_activity, "reject"),
         {:ok, _activity} <-
           ActivityPub.reject(%{
             to: [follower.ap_id],
             actor: followed.ap_id,
             object: follow_activity.data["id"],
             type: "Reject"
           }) do
href's avatar
href committed
600
601
602
      conn
      |> put_view(UserView)
      |> render("show.json", %{user: follower, for: followed})
603
604
605
606
607
    else
      e -> bad_request_reply(conn, "Can't deny user: #{inspect(e)}")
    end
  end

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

eal's avatar
eal committed
615
616
617
618
619
620
      json(conn, ids)
    else
      _e -> bad_request_reply(conn, "Can't get friends")
    end
  end

621
  def empty_array(conn, _params) do
lain's avatar
lain committed
622
    json(conn, Jason.encode!([]))
623
  end
eal's avatar
eal committed
624

625
626
627
628
  def raw_empty_array(conn, _params) do
    json(conn, [])
  end

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

lain's avatar
lain committed
640
641
642
    info_params =
      if value = params["default_scope"] do
        Map.put(info_params, "default_scope", value)
643
      else
lain's avatar
lain committed
644
        info_params
645
646
      end

lain's avatar
lain committed
647
648
649
    User.Info.profile_update(user.info, info_params)
  end

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

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

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

      conn
      |> put_view(UserView)
      |> render("user.json", %{user: user, for: user})
lain's avatar
lain committed
670
671
672
673
674
675
676
    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
677
  def search(%{assigns: %{user: user}} = conn, %{"q" => _query} = params) do
lain's avatar
lain committed
678
679
    activities = TwitterAPI.search(user, params)

lain's avatar
lain committed
680
    conn
href's avatar
href committed
681
682
    |> put_view(ActivityView)
    |> render("index.json", %{activities: activities, for: user})
lain's avatar
lain committed
683
684
  end

685
  def search_user(%{assigns: %{user: user}} = conn, %{"query" => query}) do
686
    users = User.search(query, true, user)
687
688

    conn
href's avatar
href committed
689
690
    |> put_view(UserView)
    |> render("index.json", %{users: users, for: user})
691
692
  end

693
  defp bad_request_reply(conn, error_message) do
dtluna's avatar
dtluna committed
694
    json = error_json(conn, error_message)
695
696
697
    json_reply(conn, 400, json)
  end

698
699
700
701
702
  defp json_reply(conn, status, json) do
    conn
    |> put_resp_content_type("application/json")
    |> send_resp(status, json)
  end
703
704

  defp forbidden_json_reply(conn, error_message) do
dtluna's avatar
dtluna committed
705
    json = error_json(conn, error_message)
706
707
    json_reply(conn, 403, json)
  end
dtluna's avatar
dtluna committed
708

709
  def only_if_public_instance(%{assigns: %{user: %User{}}} = conn, _), do: conn
href's avatar
href committed
710
711
712
713
714
715
716

  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
717
      |> halt()
href's avatar
href committed
718
719
720
    end
  end

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

  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
736
end