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

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

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

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

lain's avatar
lain committed
28 29
  require Logger

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

lain's avatar
lain committed
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})
lain's avatar
lain committed
176 177 178
  end

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    conn
327 328
    |> put_status(status)
    |> json(response_body)
329 330
  end

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

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

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

342 343 344 345
    conn
    |> json_reply(200, response)
  end

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    json_reply(conn, 201, "")
  end

571 572 573 574 575 576 577 578
  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

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

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

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

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

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

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

631 632 633 634
  def raw_empty_array(conn, _params) do
    json(conn, [])
  end

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

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

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

Maxim Filippov's avatar
Maxim Filippov committed
656
  defp parse_profile_bio(user, params) do
lain's avatar
lain committed
657
    if bio = params["description"] do
658 659 660 661 662 663
      emojis_text = (params["description"] || "") <> " " <> (params["name"] || "")

      emojis =
        ((user.info.emoji || []) ++ Formatter.get_emoji_map(emojis_text))
        |> Enum.dedup()

Haelwenn's avatar
Haelwenn committed
664 665 666
      user_info =
        user.info
        |> Map.put(
667 668
          "emoji",
          emojis
Haelwenn's avatar
Haelwenn committed
669 670 671 672 673
        )

      params
      |> Map.put("bio", User.parse_bio(bio, user))
      |> Map.put("info", user_info)
lain's avatar
lain committed
674 675 676 677 678 679
    else
      params
    end
  end

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

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

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

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

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

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

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

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

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

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

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

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

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