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 Pleroma.Web.TwitterAPI.{TwitterAPI, UserView, ActivityView, NotificationView}
lain's avatar
lain committed
11
  alias Pleroma.Web.CommonAPI
12
  alias Pleroma.{Repo, Activity, Object, User, Notification}
lain's avatar
lain committed
13
  alias Pleroma.Web.ActivityPub.ActivityPub
14
  alias Pleroma.Web.ActivityPub.Utils
15
  alias Ecto.Changeset
16

lain's avatar
lain committed
17 18
  require Logger

href's avatar
href committed
19
  plug(:only_if_public_instance when action in [:public_timeline, :public_and_external_timeline])
20 21
  action_fallback(:errors)

22
  def verify_credentials(%{assigns: %{user: user}} = conn, _params) do
lain's avatar
lain committed
23
    token = Phoenix.Token.sign(conn, "user socket", user.id)
href's avatar
href committed
24 25 26 27

    conn
    |> put_view(UserView)
    |> render("show.json", %{user: user, token: token})
28 29
  end

Thog's avatar
Thog committed
30
  def status_update(%{assigns: %{user: user}} = conn, %{"status" => _} = status_data) do
31
    with media_ids <- extract_media_ids(status_data),
lain's avatar
lain committed
32 33
         {:ok, activity} <-
           TwitterAPI.create_status(user, Map.put(status_data, "media_ids", media_ids)) do
dtluna's avatar
dtluna committed
34
      conn
lain's avatar
lain committed
35
      |> json(ActivityView.render("activity.json", activity: activity, for: user))
dtluna's avatar
dtluna committed
36
    else
37
      _ -> empty_status_reply(conn)
dtluna's avatar
dtluna committed
38
    end
lain's avatar
lain committed
39 40
  end

dtluna's avatar
dtluna committed
41 42 43 44 45 46 47 48
  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
49 50 51
  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
52 53 54 55
         clean_ids <- Enum.reject(split_ids, fn id -> String.length(id) == 0 end) do
      clean_ids
    else
      _e -> []
lain's avatar
lain committed
56 57 58
    end
  end

lain's avatar
lain committed
59
  def public_and_external_timeline(%{assigns: %{user: user}} = conn, params) do
lain's avatar
lain committed
60 61
    params =
      params
62
      |> Map.put("type", ["Create", "Announce"])
lain's avatar
lain committed
63 64 65
      |> Map.put("blocking_user", user)

    activities = ActivityPub.fetch_public_activities(params)
lain's avatar
lain committed
66 67

    conn
href's avatar
href committed
68 69
    |> put_view(ActivityView)
    |> render("index.json", %{activities: activities, for: user})
lain's avatar
lain committed
70 71
  end

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

    activities = ActivityPub.fetch_public_activities(params)
lain's avatar
lain committed
80 81

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

lain's avatar
lain committed
86
  def friends_timeline(%{assigns: %{user: user}} = conn, params) do
lain's avatar
lain committed
87 88 89 90 91 92
    params =
      params
      |> Map.put("type", ["Create", "Announce", "Follow", "Like"])
      |> Map.put("blocking_user", user)
      |> Map.put("user", user)

93 94 95
    activities =
      ActivityPub.fetch_activities([user.ap_id | user.following], params)
      |> ActivityPub.contain_timeline(user)
lain's avatar
lain committed
96 97

    conn
href's avatar
href committed
98 99
    |> put_view(ActivityView)
    |> render("index.json", %{activities: activities, for: user})
lain's avatar
lain committed
100 101
  end

eal's avatar
eal committed
102
  def show_user(conn, params) do
103 104 105
    for_user = conn.assigns.user

    with {:ok, shown} <- TwitterAPI.get_user(params),
Ivan Tashkinov's avatar
Ivan Tashkinov committed
106 107 108
         true <-
           User.auth_active?(shown) ||
             (for_user && (for_user.id == shown.id || User.superuser?(for_user))) do
href's avatar
href committed
109
      params =
110 111
        if for_user do
          %{user: shown, for: for_user}
href's avatar
href committed
112 113 114 115 116 117 118
        else
          %{user: shown}
        end

      conn
      |> put_view(UserView)
      |> render("show.json", params)
eal's avatar
eal committed
119 120 121
    else
      {:error, msg} ->
        bad_request_reply(conn, msg)
122 123 124 125 126

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

dtluna's avatar
dtluna committed
130
  def user_timeline(%{assigns: %{user: user}} = conn, params) do
131 132
    case TwitterAPI.get_user(user, params) do
      {:ok, target_user} ->
133 134 135 136 137 138 139 140 141
        # 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

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

144
        conn
href's avatar
href committed
145 146
        |> put_view(ActivityView)
        |> render("index.json", %{activities: activities, for: user})
lain's avatar
lain committed
147

148 149 150
      {:error, msg} ->
        bad_request_reply(conn, msg)
    end
dtluna's avatar
dtluna committed
151 152
  end

dtluna's avatar
dtluna committed
153
  def mentions_timeline(%{assigns: %{user: user}} = conn, params) do
154 155 156 157 158
    params =
      params
      |> Map.put("type", ["Create", "Announce", "Follow", "Like"])
      |> Map.put("blocking_user", user)

lain's avatar
lain committed
159
    activities = ActivityPub.fetch_activities([user.ap_id], params)
dtluna's avatar
dtluna committed
160 161

    conn
href's avatar
href committed
162 163
    |> put_view(ActivityView)
    |> render("index.json", %{activities: activities, for: user})
lain's avatar
lain committed
164 165 166 167 168 169
  end

  def dm_timeline(%{assigns: %{user: user}} = conn, params) do
    query =
      ActivityPub.fetch_activities_query(
        [user.ap_id],
170
        Map.merge(params, %{"type" => "Create", "user" => user, visibility: "direct"})
lain's avatar
lain committed
171 172 173 174 175
      )

    activities = Repo.all(query)

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

180 181 182 183
  def notifications(%{assigns: %{user: user}} = conn, params) do
    notifications = Notification.for_user(user, params)

    conn
href's avatar
href committed
184 185
    |> put_view(NotificationView)
    |> render("notification.json", %{notifications: notifications, for: user})
186 187
  end

188 189 190 191 192 193
  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
194 195
    |> put_view(NotificationView)
    |> render("notification.json", %{notifications: notifications, for: user})
196 197
  end

Maksim's avatar
Maksim committed
198
  def notifications_read(%{assigns: %{user: _user}} = conn, _) do
199 200 201
    bad_request_reply(conn, "You need to specify latest_id")
  end

202 203
  def follow(%{assigns: %{user: user}} = conn, params) do
    case TwitterAPI.follow(user, params) do
204
      {:ok, user, followed, _activity} ->
href's avatar
href committed
205 206 207
        conn
        |> put_view(UserView)
        |> render("show.json", %{user: followed, for: user})
lain's avatar
lain committed
208 209 210

      {:error, msg} ->
        forbidden_json_reply(conn, msg)
211
    end
lain's avatar
lain committed
212 213
  end

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

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

  def unblock(%{assigns: %{user: user}} = conn, params) do
    case TwitterAPI.unblock(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
    end
  end

lain's avatar
lain committed
238
  def delete_post(%{assigns: %{user: user}} = conn, %{"id" => id}) do
normandy's avatar
normandy committed
239
    with {:ok, activity} <- TwitterAPI.delete(user, id) do
href's avatar
href committed
240 241 242
      conn
      |> put_view(ActivityView)
      |> render("activity.json", %{activity: activity, for: user})
lain's avatar
lain committed
243 244 245
    end
  end

246
  def unfollow(%{assigns: %{user: user}} = conn, params) do
247
    case TwitterAPI.unfollow(user, params) do
248
      {:ok, user, unfollowed} ->
href's avatar
href committed
249 250 251
        conn
        |> put_view(UserView)
        |> render("show.json", %{user: unfollowed, for: user})
lain's avatar
lain committed
252 253 254

      {:error, msg} ->
        forbidden_json_reply(conn, msg)
255
    end
lain's avatar
lain committed
256 257
  end

258
  def fetch_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
lain's avatar
lain committed
259 260
    with %Activity{} = activity <- Repo.get(Activity, id),
         true <- ActivityPub.visible_for_user?(activity, user) do
href's avatar
href committed
261 262 263
      conn
      |> put_view(ActivityView)
      |> render("activity.json", %{activity: activity, for: user})
lain's avatar
lain committed
264
    end
lain's avatar
lain committed
265 266
  end

267
  def fetch_conversation(%{assigns: %{user: user}} = conn, %{"id" => id}) do
268 269
    id = String.to_integer(id)

lain's avatar
lain committed
270 271 272 273 274 275 276
    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
277 278
      |> put_view(ActivityView)
      |> render("index.json", %{activities: activities, for: user})
lain's avatar
lain committed
279
    end
280 281
  end

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

290 291 292 293
    {conn, status, response_body} =
      cond do
        !object ->
          {halt(conn), :not_found, ""}
Ivan Tashkinov's avatar
Ivan Tashkinov committed
294

295
        !Object.authorize_mutation(object, user) ->
296 297 298 299 300 301 302 303 304 305 306 307 308 309 310
          {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
311 312

    conn
313 314
    |> put_status(status)
    |> json(response_body)
315 316
  end

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

lain's avatar
lain committed
320 321 322 323
    conn
    |> put_resp_content_type("application/atom+xml")
    |> send_resp(200, response)
  end
324

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

328 329 330 331
    conn
    |> json_reply(200, response)
  end

lain's avatar
lain committed
332
  def get_by_id_or_ap_id(id) do
333
    activity = Repo.get(Activity, id) || Activity.get_create_activity_by_object_ap_id(id)
lain's avatar
lain committed
334

335 336 337 338 339
    if activity.data["type"] == "Create" do
      activity
    else
      Activity.get_create_activity_by_object_ap_id(activity.data["object"])
    end
lain's avatar
lain committed
340 341
  end

lain's avatar
lain committed
342
  def favorite(%{assigns: %{user: user}} = conn, %{"id" => id}) do
343 344
    with {_, {:ok, id}} <- {:param_cast, Ecto.Type.cast(:integer, id)},
         {:ok, activity} <- TwitterAPI.fav(user, id) do
href's avatar
href committed
345 346 347
      conn
      |> put_view(ActivityView)
      |> render("activity.json", %{activity: activity, for: user})
lain's avatar
lain committed
348
    end
lain's avatar
lain committed
349 350
  end

lain's avatar
lain committed
351
  def unfavorite(%{assigns: %{user: user}} = conn, %{"id" => id}) do
352 353
    with {_, {:ok, id}} <- {:param_cast, Ecto.Type.cast(:integer, id)},
         {:ok, activity} <- TwitterAPI.unfav(user, id) do
href's avatar
href committed
354 355 356
      conn
      |> put_view(ActivityView)
      |> render("activity.json", %{activity: activity, for: user})
lain's avatar
lain committed
357
    end
lain's avatar
lain committed
358 359
  end

lain's avatar
lain committed
360
  def retweet(%{assigns: %{user: user}} = conn, %{"id" => id}) do
361 362
    with {_, {:ok, id}} <- {:param_cast, Ecto.Type.cast(:integer, id)},
         {:ok, activity} <- TwitterAPI.repeat(user, id) do
href's avatar
href committed
363 364 365
      conn
      |> put_view(ActivityView)
      |> render("activity.json", %{activity: activity, for: user})
dtluna's avatar
dtluna committed
366
    end
lain's avatar
lain committed
367 368
  end

369 370 371
  def unretweet(%{assigns: %{user: user}} = conn, %{"id" => id}) do
    with {_, {:ok, id}} <- {:param_cast, Ecto.Type.cast(:integer, id)},
         {:ok, activity} <- TwitterAPI.unrepeat(user, id) do
href's avatar
href committed
372 373 374
      conn
      |> put_view(ActivityView)
      |> render("activity.json", %{activity: activity, for: user})
375 376 377
    end
  end

378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401
  def pin(%{assigns: %{user: user}} = conn, %{"id" => id}) do
    with {_, {:ok, id}} <- {:param_cast, Ecto.Type.cast(:integer, id)},
         {:ok, activity} <- TwitterAPI.pin(user, id) do
      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
    with {_, {:ok, id}} <- {:param_cast, Ecto.Type.cast(:integer, id)},
         {:ok, activity} <- TwitterAPI.unpin(user, id) do
      conn
      |> put_view(ActivityView)
      |> render("activity.json", %{activity: activity, for: user})
    else
      {:error, message} -> bad_request_reply(conn, message)
      err -> err
    end
  end

402 403
  def register(conn, params) do
    with {:ok, user} <- TwitterAPI.register_user(params) do
href's avatar
href committed
404 405 406
      conn
      |> put_view(UserView)
      |> render("show.json", %{user: user})
407 408
    else
      {:error, errors} ->
lain's avatar
lain committed
409 410
        conn
        |> json_reply(400, Jason.encode!(errors))
411 412 413
    end
  end

414 415 416
  def password_reset(conn, params) do
    nickname_or_email = params["email"] || params["nickname"]

417
    with {:ok, _} <- TwitterAPI.password_reset(nickname_or_email) do
418 419 420 421
      json_response(conn, :no_content, "")
    end
  end

422 423
  def confirm_email(conn, %{"user_id" => uid, "token" => token}) do
    with %User{} = user <- Repo.get(User, uid),
424
         true <- user.local,
425 426
         true <- user.info.confirmation_pending,
         true <- user.info.confirmation_token == token,
427
         info_change <- User.Info.confirmation_changeset(user.info, :confirmed),
428 429 430 431 432 433 434
         changeset <- Changeset.change(user) |> Changeset.put_embed(:info, info_change),
         {:ok, _} <- User.update_and_set_cache(changeset) do
      conn
      |> redirect(to: "/")
    end
  end

435 436 437 438 439 440 441 442 443 444
  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
445
  def update_avatar(%{assigns: %{user: user}} = conn, params) do
href's avatar
href committed
446
    {:ok, object} = ActivityPub.upload(params, type: :avatar)
447
    change = Changeset.change(user, %{avatar: object.data})
lain's avatar
lain committed
448
    {:ok, user} = User.update_and_set_cache(change)
lain's avatar
lain committed
449
    CommonAPI.update(user)
lain's avatar
lain committed
450

href's avatar
href committed
451 452 453
    conn
    |> put_view(UserView)
    |> render("show.json", %{user: user, for: user})
lain's avatar
lain committed
454 455
  end

lain's avatar
lain committed
456
  def update_banner(%{assigns: %{user: user}} = conn, params) do
href's avatar
href committed
457
    with {:ok, object} <- ActivityPub.upload(%{"img" => params["banner"]}, type: :banner),
458 459 460 461
         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
462
      CommonAPI.update(user)
lain's avatar
lain committed
463 464 465
      %{"url" => [%{"href" => href} | _]} = object.data
      response = %{url: href} |> Jason.encode!()

lain's avatar
lain committed
466 467 468 469 470 471
      conn
      |> json_reply(200, response)
    end
  end

  def update_background(%{assigns: %{user: user}} = conn, params) do
href's avatar
href committed
472
    with {:ok, object} <- ActivityPub.upload(params, type: :background),
473 474 475 476
         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
477 478 479
      %{"url" => [%{"href" => href} | _]} = object.data
      response = %{url: href} |> Jason.encode!()

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

lain's avatar
lain committed
485 486
  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
487
         response <- Jason.encode!(user_map) do
lain's avatar
lain committed
488 489
      conn
      |> json_reply(200, response)
lain's avatar
lain committed
490 491 492 493 494
    else
      _e ->
        conn
        |> put_status(404)
        |> json(%{error: "Can't find user"})
lain's avatar
lain committed
495 496 497
    end
  end

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

501
    with {:ok, user} <- TwitterAPI.get_user(for_user, params),
502
         {:ok, followers} <- User.get_followers(user, page) do
503 504 505 506 507 508 509
      followers =
        cond do
          for_user && user.id == for_user.id -> followers
          user.info.hide_network -> []
          true -> followers
        end

href's avatar
href committed
510 511 512
      conn
      |> put_view(UserView)
      |> render("index.json", %{users: followers, for: conn.assigns[:user]})
lain's avatar
lain committed
513 514 515 516 517
    else
      _e -> bad_request_reply(conn, "Can't get followers")
    end
  end

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

521
    with {:ok, user} <- TwitterAPI.get_user(conn.assigns[:user], params),
522
         {:ok, friends} <- User.get_friends(user, page) do
523 524 525 526 527 528 529
      friends =
        cond do
          for_user && user.id == for_user.id -> friends
          user.info.hide_network -> []
          true -> friends
        end

href's avatar
href committed
530 531 532
      conn
      |> put_view(UserView)
      |> render("index.json", %{users: friends, for: conn.assigns[:user]})
lain's avatar
lain committed
533 534 535 536 537
    else
      _e -> bad_request_reply(conn, "Can't get friends")
    end
  end

538 539 540 541 542 543 544 545
  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

546
  def friend_requests(conn, params) do
547
    with {:ok, user} <- TwitterAPI.get_user(conn.assigns[:user], params),
548
         {:ok, friend_requests} <- User.get_follow_requests(user) do
href's avatar
href committed
549 550 551
      conn
      |> put_view(UserView)
      |> render("index.json", %{users: friend_requests, for: conn.assigns[:user]})
552 553 554 555 556
    else
      _e -> bad_request_reply(conn, "Can't get friend requests")
    end
  end

Maksim's avatar
Maksim committed
557
  def approve_friend_request(conn, %{"user_id" => uid} = _params) do
558 559 560 561 562 563 564 565 566 567 568 569 570
    with followed <- conn.assigns[:user],
         uid when is_number(uid) <- String.to_integer(uid),
         %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
571 572 573
      conn
      |> put_view(UserView)
      |> render("show.json", %{user: follower, for: followed})
574 575 576 577 578
    else
      e -> bad_request_reply(conn, "Can't approve user: #{inspect(e)}")
    end
  end

Maksim's avatar
Maksim committed
579
  def deny_friend_request(conn, %{"user_id" => uid} = _params) do
580 581 582 583 584 585 586 587 588 589 590 591
    with followed <- conn.assigns[:user],
         uid when is_number(uid) <- String.to_integer(uid),
         %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
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 deny user: #{inspect(e)}")
    end
  end

eal's avatar
eal committed
600 601
  def friends_ids(%{assigns: %{user: user}} = conn, _params) do
    with {:ok, friends} <- User.get_friends(user) do
lain's avatar
lain committed
602 603 604 605
      ids =
        friends
        |> Enum.map(fn x -> x.id end)
        |> Jason.encode!()
606

eal's avatar
eal committed
607 608 609 610 611 612
      json(conn, ids)
    else
      _e -> bad_request_reply(conn, "Can't get friends")
    end
  end

613
  def empty_array(conn, _params) do
lain's avatar
lain committed
614
    json(conn, Jason.encode!([]))
615
  end
eal's avatar
eal committed
616

617 618 619 620
  def raw_empty_array(conn, _params) do
    json(conn, [])
  end

lain's avatar
lain committed
621 622
  defp build_info_cng(user, params) do
    info_params =
lain's avatar
lain committed
623
      ["no_rich_text", "locked", "hide_network"]
lain's avatar
lain committed
624 625 626
      |> Enum.reduce(%{}, fn key, res ->
        if value = params[key] do
          Map.put(res, key, value == "true")
627
        else
lain's avatar
lain committed
628
          res
629
        end
lain's avatar
lain committed
630
      end)
631

lain's avatar
lain committed
632 633 634
    info_params =
      if value = params["default_scope"] do
        Map.put(info_params, "default_scope", value)
635
      else
lain's avatar
lain committed
636
        info_params
637 638
      end

lain's avatar
lain committed
639 640 641
    User.Info.profile_update(user.info, info_params)
  end

Maxim Filippov's avatar
Maxim Filippov committed
642
  defp parse_profile_bio(user, params) do
lain's avatar
lain committed
643
    if bio = params["description"] do
Maxim Filippov's avatar
Maxim Filippov committed
644
      Map.put(params, "bio", User.parse_bio(bio, user))
lain's avatar
lain committed
645 646 647 648 649 650
    else
      params
    end
  end

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

lain's avatar
lain committed
654
    with changeset <- User.update_changeset(user, params),
lain's avatar
lain committed
655
         changeset <- Ecto.Changeset.put_embed(changeset, :info, info_cng),
lain's avatar
lain committed
656
         {:ok, user} <- User.update_and_set_cache(changeset) do
lain's avatar
lain committed
657
      CommonAPI.update(user)
href's avatar
href committed
658 659 660 661

      conn
      |> put_view(UserView)
      |> render("user.json", %{user: user, for: user})
lain's avatar
lain committed
662 663 664 665 666 667 668
    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
669
  def search(%{assigns: %{user: user}} = conn, %{"q" => _query} = params) do
lain's avatar
lain committed
670 671
    activities = TwitterAPI.search(user, params)

lain's avatar
lain committed
672
    conn
href's avatar
href committed
673 674
    |> put_view(ActivityView)
    |> render("index.json", %{activities: activities, for: user})
lain's avatar
lain committed
675 676
  end

677
  def search_user(%{assigns: %{user: user}} = conn, %{"query" => query}) do
678
    users = User.search(query, true, user)
679 680

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

685
  defp bad_request_reply(conn, error_message) do
dtluna's avatar
dtluna committed
686
    json = error_json(conn, error_message)
687 688 689
    json_reply(conn, 400, json)
  end

690 691 692 693 694
  defp json_reply(conn, status, json) do
    conn
    |> put_resp_content_type("application/json")
    |> send_resp(status, json)
  end
695 696

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

701
  def only_if_public_instance(%{assigns: %{user: %User{}}} = conn, _), do: conn
href's avatar
href committed
702 703 704 705 706 707 708

  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
709
      |> halt()
href's avatar
href committed
710 711 712
    end
  end

dtluna's avatar
dtluna committed
713
  defp error_json(conn, error_message) do
lain's avatar
lain committed
714
    %{"error" => error_message, "request" => conn.request_path} |> Jason.encode!()
dtluna's avatar
dtluna committed
715
  end
716 717 718 719 720 721 722 723 724 725 726 727

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