twitter_api_controller.ex 22.4 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
Maxim Filippov's avatar
Maxim Filippov committed
11 12 13 14
  alias Pleroma.Web.TwitterAPI.{TwitterAPI, UserView, ActivityView, NotificationView, TokenView}
  alias Pleroma.Web.CommonAPI
  alias Pleroma.{Repo, Activity, Object, User, Notification}
  alias Pleroma.Web.OAuth.Token
Haelwenn's avatar
Haelwenn committed
15
  alias Pleroma.Web.ActivityPub.ActivityPub
lain's avatar
lain committed
16
  alias Pleroma.Web.ActivityPub.Visibility
Haelwenn's avatar
Haelwenn committed
17
  alias Pleroma.Web.ActivityPub.Utils
18
  alias Pleroma.Web.CommonAPI
Haelwenn's avatar
Haelwenn committed
19 20 21 22 23 24 25 26 27
  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
28

lain's avatar
lain committed
29 30
  require Logger

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

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

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

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

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

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

    activities = ActivityPub.fetch_public_activities(params)
lain's avatar
lain committed
78 79

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

84
  def public_timeline(%{assigns: %{user: user}} = conn, params) do
85 86
    params =
      params
87
      |> Map.put("type", ["Create", "Announce"])
88 89 90 91
      |> Map.put("local_only", true)
      |> Map.put("blocking_user", user)

    activities = ActivityPub.fetch_public_activities(params)
lain's avatar
lain committed
92 93

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  def dm_timeline(%{assigns: %{user: user}} = conn, params) do
    query =
      ActivityPub.fetch_activities_query(
        [user.ap_id],
182
        Map.merge(params, %{"type" => "Create", "user" => user, visibility: "direct"})
183 184 185 186 187
      )

    activities = Repo.all(query)

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

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

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

200 201 202 203 204 205
  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
206 207
    |> put_view(NotificationView)
    |> render("notification.json", %{notifications: notifications, for: user})
208 209
  end

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

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

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

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

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

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

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

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

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

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

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

279
  def fetch_conversation(%{assigns: %{user: user}} = conn, %{"id" => id}) do
280 281 282 283 284 285 286
    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
287 288
      |> put_view(ActivityView)
      |> render("index.json", %{activities: activities, for: user})
289
    end
290 291
  end

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

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

305
        !Object.authorize_mutation(object, user) ->
306 307 308 309 310 311 312 313 314 315 316 317 318 319 320
          {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
321 322

    conn
323 324
    |> put_status(status)
    |> json(response_body)
325 326
  end

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

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

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

338 339 340 341
    conn
    |> json_reply(200, response)
  end

lain's avatar
lain committed
342
  def get_by_id_or_ap_id(id) do
343
    activity = Repo.get(Activity, id) || Activity.get_create_by_object_ap_id(id)
lain's avatar
lain committed
344

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Maxim Filippov's avatar
Maxim Filippov committed
553 554 555 556 557 558 559 560
  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
561 562 563 564 565 566
  def revoke_token(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do
    Token.delete_user_token(user, id)

    json_reply(conn, 201, "")
  end

567 568 569 570 571 572 573 574
  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

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

Maksim's avatar
Maksim committed
586
  def approve_friend_request(conn, %{"user_id" => uid} = _params) do
587 588 589 590 591 592 593 594
    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],
595
             actor: followed,
596 597 598
             object: follow_activity.data["id"],
             type: "Accept"
           }) do
href's avatar
href committed
599 600 601
      conn
      |> put_view(UserView)
      |> render("show.json", %{user: follower, for: followed})
602 603 604 605 606
    else
      e -> bad_request_reply(conn, "Can't approve user: #{inspect(e)}")
    end
  end

Maksim's avatar
Maksim committed
607
  def deny_friend_request(conn, %{"user_id" => uid} = _params) do
608 609 610 611 612 613 614
    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],
615
             actor: followed,
616 617 618
             object: follow_activity.data["id"],
             type: "Reject"
           }) do
href's avatar
href committed
619 620 621
      conn
      |> put_view(UserView)
      |> render("show.json", %{user: follower, for: followed})
622 623 624 625 626
    else
      e -> bad_request_reply(conn, "Can't deny user: #{inspect(e)}")
    end
  end

eal's avatar
eal committed
627 628
  def friends_ids(%{assigns: %{user: user}} = conn, _params) do
    with {:ok, friends} <- User.get_friends(user) do
lain's avatar
lain committed
629 630 631 632
      ids =
        friends
        |> Enum.map(fn x -> x.id end)
        |> Jason.encode!()
633

eal's avatar
eal committed
634 635 636 637 638 639
      json(conn, ids)
    else
      _e -> bad_request_reply(conn, "Can't get friends")
    end
  end

640
  def empty_array(conn, _params) do
lain's avatar
lain committed
641
    json(conn, Jason.encode!([]))
642
  end
eal's avatar
eal committed
643

644 645 646 647
  def raw_empty_array(conn, _params) do
    json(conn, [])
  end

lain's avatar
lain committed
648 649
  defp build_info_cng(user, params) do
    info_params =
650
      ["no_rich_text", "locked", "hide_followers", "hide_follows", "show_role"]
lain's avatar
lain committed
651 652 653
      |> Enum.reduce(%{}, fn key, res ->
        if value = params[key] do
          Map.put(res, key, value == "true")
654
        else
lain's avatar
lain committed
655
          res
656
        end
lain's avatar
lain committed
657
      end)
658

lain's avatar
lain committed
659 660 661
    info_params =
      if value = params["default_scope"] do
        Map.put(info_params, "default_scope", value)
662
      else
lain's avatar
lain committed
663
        info_params
664 665
      end

lain's avatar
lain committed
666 667 668
    User.Info.profile_update(user.info, info_params)
  end

669
  defp parse_profile_bio(user, params) do
lain's avatar
lain committed
670
    if bio = params["description"] do
671
      Map.put(params, "bio", User.parse_bio(bio, user))
lain's avatar
lain committed
672 673 674 675 676 677
    else
      params
    end
  end

  def update_profile(%{assigns: %{user: user}} = conn, params) do
678
    params = parse_profile_bio(user, params)
lain's avatar
lain committed
679
    info_cng = build_info_cng(user, params)
680

lain's avatar
lain committed
681
    with changeset <- User.update_changeset(user, params),
lain's avatar
lain committed
682
         changeset <- Ecto.Changeset.put_embed(changeset, :info, info_cng),
lain's avatar
lain committed
683
         {:ok, user} <- User.update_and_set_cache(changeset) do
lain's avatar
lain committed
684
      CommonAPI.update(user)
href's avatar
href committed
685 686 687 688

      conn
      |> put_view(UserView)
      |> render("user.json", %{user: user, for: user})
lain's avatar
lain committed
689 690 691 692 693 694 695
    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
696
  def search(%{assigns: %{user: user}} = conn, %{"q" => _query} = params) do
697 698
    activities = TwitterAPI.search(user, params)

lain's avatar
lain committed
699
    conn
href's avatar
href committed
700 701
    |> put_view(ActivityView)
    |> render("index.json", %{activities: activities, for: user})
lain's avatar
lain committed
702 703
  end

704
  def search_user(%{assigns: %{user: user}} = conn, %{"query" => query}) do
705
    users = User.search(query, true, user)
706 707

    conn
href's avatar
href committed
708 709
    |> put_view(UserView)
    |> render("index.json", %{users: users, for: user})
710 711
  end

712
  defp bad_request_reply(conn, error_message) do
dtluna's avatar
dtluna committed
713
    json = error_json(conn, error_message)
714 715 716
    json_reply(conn, 400, json)
  end

717 718 719 720 721
  defp json_reply(conn, status, json) do
    conn
    |> put_resp_content_type("application/json")
    |> send_resp(status, json)
  end
722 723

  defp forbidden_json_reply(conn, error_message) do
dtluna's avatar
dtluna committed
724
    json = error_json(conn, error_message)
725 726
    json_reply(conn, 403, json)
  end
dtluna's avatar
dtluna committed
727

728
  def only_if_public_instance(%{assigns: %{user: %User{}}} = conn, _), do: conn
href's avatar
href committed
729 730 731 732 733 734 735

  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
736
      |> halt()
href's avatar
href committed
737 738 739
    end
  end

dtluna's avatar
dtluna committed
740
  defp error_json(conn, error_message) do
lain's avatar
lain committed
741
    %{"error" => error_message, "request" => conn.request_path} |> Jason.encode!()
dtluna's avatar
dtluna committed
742
  end
743 744 745 746 747 748 749 750 751 752 753 754

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