aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorIvan Tashkinov <ivantashkinov@gmail.com>2019-09-06 13:26:05 +0300
committerIvan Tashkinov <ivantashkinov@gmail.com>2019-09-06 13:26:05 +0300
commitca1ba1e272fd51ee1d9524a39da07b4b2f9e0b6e (patch)
tree7a9d31d44f362ff903813925171a4c31942b8261 /lib
parent79cf629e1a3610a93f5b9349c18a4464eb859d3d (diff)
parent896ffabe37406e85c2a3c2f30d2e882c68b5831e (diff)
downloadpleroma-ca1ba1e272fd51ee1d9524a39da07b4b2f9e0b6e.tar.gz
[#1149] Merge remote-tracking branch 'remotes/upstream/develop' into 1149-oban-job-queue
# Conflicts: # test/web/twitter_api/twitter_api_controller_test.exs
Diffstat (limited to 'lib')
-rw-r--r--lib/pleroma/plugs/trailing_format_plug.ex41
-rw-r--r--lib/pleroma/user.ex18
-rw-r--r--lib/pleroma/web/endpoint.ex2
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex4
-rw-r--r--lib/pleroma/web/mastodon_api/views/status_view.ex33
-rw-r--r--lib/pleroma/web/router.ex100
-rw-r--r--lib/pleroma/web/twitter_api/representers/base_representer.ex38
-rw-r--r--lib/pleroma/web/twitter_api/representers/object_representer.ex39
-rw-r--r--lib/pleroma/web/twitter_api/twitter_api.ex195
-rw-r--r--lib/pleroma/web/twitter_api/twitter_api_controller.ex766
-rw-r--r--lib/pleroma/web/twitter_api/views/activity_view.ex366
-rw-r--r--lib/pleroma/web/twitter_api/views/notification_view.ex71
-rw-r--r--lib/pleroma/web/twitter_api/views/user_view.ex191
13 files changed, 102 insertions, 1762 deletions
diff --git a/lib/pleroma/plugs/trailing_format_plug.ex b/lib/pleroma/plugs/trailing_format_plug.ex
new file mode 100644
index 000000000..ce366b218
--- /dev/null
+++ b/lib/pleroma/plugs/trailing_format_plug.ex
@@ -0,0 +1,41 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Plugs.TrailingFormatPlug do
+ @moduledoc "Calls TrailingFormatPlug for specific paths. Ideally we would just do this in the router, but TrailingFormatPlug needs to be called before Plug.Parsers."
+
+ @behaviour Plug
+ @paths [
+ "/api/statusnet",
+ "/api/statuses",
+ "/api/qvitter",
+ "/api/search",
+ "/api/account",
+ "/api/friends",
+ "/api/mutes",
+ "/api/media",
+ "/api/favorites",
+ "/api/blocks",
+ "/api/friendships",
+ "/api/users",
+ "/users",
+ "/nodeinfo",
+ "/api/help",
+ "/api/externalprofile",
+ "/notice",
+ "/api/pleroma/emoji"
+ ]
+
+ def init(opts) do
+ TrailingFormatPlug.init(opts)
+ end
+
+ for path <- @paths do
+ def call(%{request_path: unquote(path) <> _} = conn, opts) do
+ TrailingFormatPlug.call(conn, opts)
+ end
+ end
+
+ def call(conn, _opts), do: conn
+end
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index 2fe7e1748..0d0fbe3a8 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -570,8 +570,22 @@ defmodule Pleroma.User do
end)
end
- def get_cached_by_nickname_or_id(nickname_or_id) do
- get_cached_by_id(nickname_or_id) || get_cached_by_nickname(nickname_or_id)
+ def get_cached_by_nickname_or_id(nickname_or_id, opts \\ []) do
+ restrict_to_local = Pleroma.Config.get([:instance, :limit_to_local_content])
+
+ cond do
+ is_integer(nickname_or_id) or Pleroma.FlakeId.is_flake_id?(nickname_or_id) ->
+ get_cached_by_id(nickname_or_id) || get_cached_by_nickname(nickname_or_id)
+
+ restrict_to_local == false ->
+ get_cached_by_nickname(nickname_or_id)
+
+ restrict_to_local == :unauthenticated and match?(%User{}, opts[:for]) ->
+ get_cached_by_nickname(nickname_or_id)
+
+ true ->
+ nil
+ end
end
def get_by_nickname(nickname) do
diff --git a/lib/pleroma/web/endpoint.ex b/lib/pleroma/web/endpoint.ex
index c123530dc..eb805e853 100644
--- a/lib/pleroma/web/endpoint.ex
+++ b/lib/pleroma/web/endpoint.ex
@@ -57,7 +57,7 @@ defmodule Pleroma.Web.Endpoint do
plug(Phoenix.CodeReloader)
end
- plug(TrailingFormatPlug)
+ plug(Pleroma.Plugs.TrailingFormatPlug)
plug(Plug.RequestId)
plug(Plug.Logger)
diff --git a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex
index 83e877c0e..8dfad7a54 100644
--- a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex
@@ -290,7 +290,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
end
def user(%{assigns: %{user: for_user}} = conn, %{"id" => nickname_or_id}) do
- with %User{} = user <- User.get_cached_by_nickname_or_id(nickname_or_id),
+ with %User{} = user <- User.get_cached_by_nickname_or_id(nickname_or_id, for: for_user),
true <- User.auth_active?(user) || user.id == for_user.id || User.superuser?(for_user) do
account = AccountView.render("account.json", %{user: user, for: for_user})
json(conn, account)
@@ -390,7 +390,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
end
def user_statuses(%{assigns: %{user: reading_user}} = conn, params) do
- with %User{} = user <- User.get_cached_by_nickname_or_id(params["id"]) do
+ with %User{} = user <- User.get_cached_by_nickname_or_id(params["id"], for: reading_user) do
params =
params
|> Map.put("tag", params["tagged"])
diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex
index 4c3c8c564..e71083b91 100644
--- a/lib/pleroma/web/mastodon_api/views/status_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/status_view.ex
@@ -385,16 +385,27 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
end
if options do
- end_time =
- (object.data["closed"] || object.data["endTime"])
- |> NaiveDateTime.from_iso8601!()
-
- expired =
- end_time
- |> NaiveDateTime.compare(NaiveDateTime.utc_now())
- |> case do
- :lt -> true
- _ -> false
+ {end_time, expired} =
+ case object.data["closed"] || object.data["endTime"] do
+ end_time when is_binary(end_time) ->
+ end_time =
+ (object.data["closed"] || object.data["endTime"])
+ |> NaiveDateTime.from_iso8601!()
+
+ expired =
+ end_time
+ |> NaiveDateTime.compare(NaiveDateTime.utc_now())
+ |> case do
+ :lt -> true
+ _ -> false
+ end
+
+ end_time = Utils.to_masto_date(end_time)
+
+ {end_time, expired}
+
+ _ ->
+ {nil, false}
end
voted =
@@ -421,7 +432,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
# Mastodon uses separate ids for polls, but an object can't have
# more than one poll embedded so object id is fine
id: to_string(object.id),
- expires_at: Utils.to_masto_date(end_time),
+ expires_at: end_time,
expired: expired,
multiple: multiple,
votes_count: votes_count,
diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex
index 44a4279f7..cfb973f53 100644
--- a/lib/pleroma/web/router.ex
+++ b/lib/pleroma/web/router.ex
@@ -477,53 +477,12 @@ defmodule Pleroma.Web.Router do
scope "/api", Pleroma.Web do
pipe_through(:api)
- post("/account/register", TwitterAPI.Controller, :register)
- post("/account/password_reset", TwitterAPI.Controller, :password_reset)
-
- post("/account/resend_confirmation_email", TwitterAPI.Controller, :resend_confirmation_email)
-
get(
"/account/confirm_email/:user_id/:token",
TwitterAPI.Controller,
:confirm_email,
as: :confirm_email
)
-
- scope [] do
- pipe_through(:oauth_read_or_public)
-
- get("/statuses/user_timeline", TwitterAPI.Controller, :user_timeline)
- get("/qvitter/statuses/user_timeline", TwitterAPI.Controller, :user_timeline)
- get("/users/show", TwitterAPI.Controller, :show_user)
-
- get("/statuses/followers", TwitterAPI.Controller, :followers)
- get("/statuses/friends", TwitterAPI.Controller, :friends)
- get("/statuses/blocks", TwitterAPI.Controller, :blocks)
- get("/statuses/show/:id", TwitterAPI.Controller, :fetch_status)
- get("/statusnet/conversation/:id", TwitterAPI.Controller, :fetch_conversation)
-
- get("/search", TwitterAPI.Controller, :search)
- get("/statusnet/tags/timeline/:tag", TwitterAPI.Controller, :public_and_external_timeline)
- end
- end
-
- scope "/api", Pleroma.Web do
- pipe_through([:api, :oauth_read_or_public])
-
- get("/statuses/public_timeline", TwitterAPI.Controller, :public_timeline)
-
- get(
- "/statuses/public_and_external_timeline",
- TwitterAPI.Controller,
- :public_and_external_timeline
- )
-
- get("/statuses/networkpublic_timeline", TwitterAPI.Controller, :public_and_external_timeline)
- end
-
- scope "/api", Pleroma.Web, as: :twitter_api_search do
- pipe_through([:api, :oauth_read_or_public])
- get("/pleroma/search_user", TwitterAPI.Controller, :search_user)
end
scope "/api", Pleroma.Web, as: :authenticated_twitter_api do
@@ -535,67 +494,8 @@ defmodule Pleroma.Web.Router do
scope [] do
pipe_through(:oauth_read)
- get("/account/verify_credentials", TwitterAPI.Controller, :verify_credentials)
- post("/account/verify_credentials", TwitterAPI.Controller, :verify_credentials)
-
- get("/statuses/home_timeline", TwitterAPI.Controller, :friends_timeline)
- get("/statuses/friends_timeline", TwitterAPI.Controller, :friends_timeline)
- get("/statuses/mentions", TwitterAPI.Controller, :mentions_timeline)
- get("/statuses/mentions_timeline", TwitterAPI.Controller, :mentions_timeline)
- get("/statuses/dm_timeline", TwitterAPI.Controller, :dm_timeline)
- get("/qvitter/statuses/notifications", TwitterAPI.Controller, :notifications)
-
- get("/pleroma/friend_requests", TwitterAPI.Controller, :friend_requests)
-
- get("/friends/ids", TwitterAPI.Controller, :friends_ids)
- get("/friendships/no_retweets/ids", TwitterAPI.Controller, :empty_array)
-
- get("/mutes/users/ids", TwitterAPI.Controller, :empty_array)
- get("/qvitter/mutes", TwitterAPI.Controller, :raw_empty_array)
-
- get("/externalprofile/show", TwitterAPI.Controller, :external_profile)
-
post("/qvitter/statuses/notifications/read", TwitterAPI.Controller, :notifications_read)
end
-
- scope [] do
- pipe_through(:oauth_write)
-
- post("/account/update_profile", TwitterAPI.Controller, :update_profile)
- post("/account/update_profile_banner", TwitterAPI.Controller, :update_banner)
- post("/qvitter/update_background_image", TwitterAPI.Controller, :update_background)
-
- post("/statuses/update", TwitterAPI.Controller, :status_update)
- post("/statuses/retweet/:id", TwitterAPI.Controller, :retweet)
- post("/statuses/unretweet/:id", TwitterAPI.Controller, :unretweet)
- post("/statuses/destroy/:id", TwitterAPI.Controller, :delete_post)
-
- post("/statuses/pin/:id", TwitterAPI.Controller, :pin)
- post("/statuses/unpin/:id", TwitterAPI.Controller, :unpin)
-
- post("/statusnet/media/upload", TwitterAPI.Controller, :upload)
- post("/media/upload", TwitterAPI.Controller, :upload_json)
- post("/media/metadata/create", TwitterAPI.Controller, :update_media)
-
- post("/favorites/create/:id", TwitterAPI.Controller, :favorite)
- post("/favorites/create", TwitterAPI.Controller, :favorite)
- post("/favorites/destroy/:id", TwitterAPI.Controller, :unfavorite)
-
- post("/qvitter/update_avatar", TwitterAPI.Controller, :update_avatar)
- end
-
- scope [] do
- pipe_through(:oauth_follow)
-
- post("/pleroma/friendships/approve", TwitterAPI.Controller, :approve_friend_request)
- post("/pleroma/friendships/deny", TwitterAPI.Controller, :deny_friend_request)
-
- post("/friendships/create", TwitterAPI.Controller, :follow)
- post("/friendships/destroy", TwitterAPI.Controller, :unfollow)
-
- post("/blocks/create", TwitterAPI.Controller, :block)
- post("/blocks/destroy", TwitterAPI.Controller, :unblock)
- end
end
pipeline :ap_service_actor do
diff --git a/lib/pleroma/web/twitter_api/representers/base_representer.ex b/lib/pleroma/web/twitter_api/representers/base_representer.ex
deleted file mode 100644
index 3d31e6079..000000000
--- a/lib/pleroma/web/twitter_api/representers/base_representer.ex
+++ /dev/null
@@ -1,38 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.TwitterAPI.Representers.BaseRepresenter do
- defmacro __using__(_opts) do
- quote do
- def to_json(object) do
- to_json(object, %{})
- end
-
- def to_json(object, options) do
- object
- |> to_map(options)
- |> Jason.encode!()
- end
-
- def enum_to_list(enum, options) do
- mapping = fn el -> to_map(el, options) end
- Enum.map(enum, mapping)
- end
-
- def to_map(object) do
- to_map(object, %{})
- end
-
- def enum_to_json(enum) do
- enum_to_json(enum, %{})
- end
-
- def enum_to_json(enum, options) do
- enum
- |> enum_to_list(options)
- |> Jason.encode!()
- end
- end
- end
-end
diff --git a/lib/pleroma/web/twitter_api/representers/object_representer.ex b/lib/pleroma/web/twitter_api/representers/object_representer.ex
deleted file mode 100644
index 47130ba06..000000000
--- a/lib/pleroma/web/twitter_api/representers/object_representer.ex
+++ /dev/null
@@ -1,39 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.TwitterAPI.Representers.ObjectRepresenter do
- use Pleroma.Web.TwitterAPI.Representers.BaseRepresenter
- alias Pleroma.Object
-
- def to_map(%Object{data: %{"url" => [url | _]}} = object, _opts) do
- data = object.data
-
- %{
- url: url["href"] |> Pleroma.Web.MediaProxy.url(),
- mimetype: url["mediaType"] || url["mimeType"],
- id: data["uuid"],
- oembed: false,
- description: data["name"]
- }
- end
-
- def to_map(%Object{data: %{"url" => url} = data}, _opts) when is_binary(url) do
- %{
- url: url |> Pleroma.Web.MediaProxy.url(),
- mimetype: data["mediaType"] || data["mimeType"],
- id: data["uuid"],
- oembed: false,
- description: data["name"]
- }
- end
-
- def to_map(%Object{}, _opts) do
- %{}
- end
-
- # If we only get the naked data, wrap in an object
- def to_map(%{} = data, opts) do
- to_map(%Object{data: data}, opts)
- end
-end
diff --git a/lib/pleroma/web/twitter_api/twitter_api.ex b/lib/pleroma/web/twitter_api/twitter_api.ex
index 80082ea84..8eda762c7 100644
--- a/lib/pleroma/web/twitter_api/twitter_api.ex
+++ b/lib/pleroma/web/twitter_api/twitter_api.ex
@@ -3,133 +3,14 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
- alias Pleroma.Activity
alias Pleroma.Emails.Mailer
alias Pleroma.Emails.UserEmail
alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.UserInviteToken
- alias Pleroma.Web.ActivityPub.ActivityPub
- alias Pleroma.Web.CommonAPI
- alias Pleroma.Web.TwitterAPI.UserView
-
- import Ecto.Query
require Pleroma.Constants
- def create_status(%User{} = user, %{"status" => _} = data) do
- CommonAPI.post(user, data)
- end
-
- def delete(%User{} = user, id) do
- with %Activity{data: %{"type" => _type}} <- Activity.get_by_id(id),
- {:ok, activity} <- CommonAPI.delete(id, user) do
- {:ok, activity}
- end
- end
-
- def follow(%User{} = follower, params) do
- with {:ok, %User{} = followed} <- get_user(params) do
- CommonAPI.follow(follower, followed)
- end
- end
-
- def unfollow(%User{} = follower, params) do
- with {:ok, %User{} = unfollowed} <- get_user(params),
- {:ok, follower} <- CommonAPI.unfollow(follower, unfollowed) do
- {:ok, follower, unfollowed}
- end
- end
-
- def block(%User{} = blocker, params) do
- with {:ok, %User{} = blocked} <- get_user(params),
- {:ok, blocker} <- User.block(blocker, blocked),
- {:ok, _activity} <- ActivityPub.block(blocker, blocked) do
- {:ok, blocker, blocked}
- else
- err -> err
- end
- end
-
- def unblock(%User{} = blocker, params) do
- with {:ok, %User{} = blocked} <- get_user(params),
- {:ok, blocker} <- User.unblock(blocker, blocked),
- {:ok, _activity} <- ActivityPub.unblock(blocker, blocked) do
- {:ok, blocker, blocked}
- else
- err -> err
- end
- end
-
- def repeat(%User{} = user, ap_id_or_id) do
- with {:ok, _announce, %{data: %{"id" => id}}} <- CommonAPI.repeat(ap_id_or_id, user),
- %Activity{} = activity <- Activity.get_create_by_object_ap_id(id) do
- {:ok, activity}
- end
- end
-
- def unrepeat(%User{} = user, ap_id_or_id) do
- with {:ok, _unannounce, %{data: %{"id" => id}}} <- CommonAPI.unrepeat(ap_id_or_id, user),
- %Activity{} = activity <- Activity.get_create_by_object_ap_id(id) do
- {:ok, activity}
- end
- end
-
- def pin(%User{} = user, ap_id_or_id) do
- CommonAPI.pin(ap_id_or_id, user)
- end
-
- def unpin(%User{} = user, ap_id_or_id) do
- CommonAPI.unpin(ap_id_or_id, user)
- end
-
- def fav(%User{} = user, ap_id_or_id) do
- with {:ok, _fav, %{data: %{"id" => id}}} <- CommonAPI.favorite(ap_id_or_id, user),
- %Activity{} = activity <- Activity.get_create_by_object_ap_id(id) do
- {:ok, activity}
- end
- end
-
- def unfav(%User{} = user, ap_id_or_id) do
- with {:ok, _unfav, _fav, %{data: %{"id" => id}}} <- CommonAPI.unfavorite(ap_id_or_id, user),
- %Activity{} = activity <- Activity.get_create_by_object_ap_id(id) do
- {:ok, activity}
- end
- end
-
- def upload(%Plug.Upload{} = file, %User{} = user, format \\ "xml") do
- {:ok, object} = ActivityPub.upload(file, actor: User.ap_id(user))
-
- url = List.first(object.data["url"])
- href = url["href"]
- type = url["mediaType"]
-
- case format do
- "xml" ->
- # Fake this as good as possible...
- """
- <?xml version="1.0" encoding="UTF-8"?>
- <rsp stat="ok" xmlns:atom="http://www.w3.org/2005/Atom">
- <mediaid>#{object.id}</mediaid>
- <media_id>#{object.id}</media_id>
- <media_id_string>#{object.id}</media_id_string>
- <media_url>#{href}</media_url>
- <mediaurl>#{href}</mediaurl>
- <atom:link rel="enclosure" href="#{href}" type="#{type}"></atom:link>
- </rsp>
- """
-
- "json" ->
- %{
- media_id: object.id,
- media_id_string: "#{object.id}}",
- media_url: href,
- size: 0
- }
- |> Jason.encode!()
- end
- end
-
def register_user(params, opts \\ []) do
token = params["token"]
@@ -236,80 +117,4 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
{:error, "unknown user"}
end
end
-
- def get_user(user \\ nil, params) do
- case params do
- %{"user_id" => user_id} ->
- case User.get_cached_by_nickname_or_id(user_id) do
- nil ->
- {:error, "No user with such user_id"}
-
- %User{info: %{deactivated: true}} ->
- {:error, "User has been disabled"}
-
- user ->
- {:ok, user}
- end
-
- %{"screen_name" => nickname} ->
- case User.get_cached_by_nickname(nickname) do
- nil -> {:error, "No user with such screen_name"}
- target -> {:ok, target}
- end
-
- _ ->
- if user do
- {:ok, user}
- else
- {:error, "You need to specify screen_name or user_id"}
- end
- end
- end
-
- defp parse_int(string, default)
-
- defp parse_int(string, default) when is_binary(string) do
- with {n, _} <- Integer.parse(string) do
- n
- else
- _e -> default
- end
- end
-
- defp parse_int(_, default), do: default
-
- # TODO: unify the search query with MastoAPI one and do only pagination here
- def search(_user, %{"q" => query} = params) do
- limit = parse_int(params["rpp"], 20)
- page = parse_int(params["page"], 1)
- offset = (page - 1) * limit
-
- q =
- from(
- [a, o] in Activity.with_preloaded_object(Activity),
- where: fragment("?->>'type' = 'Create'", a.data),
- where: ^Pleroma.Constants.as_public() in a.recipients,
- where:
- fragment(
- "to_tsvector('english', ?->>'content') @@ plainto_tsquery('english', ?)",
- o.data,
- ^query
- ),
- limit: ^limit,
- offset: ^offset,
- # this one isn't indexed so psql won't take the wrong index.
- order_by: [desc: :inserted_at]
- )
-
- _activities = Repo.all(q)
- end
-
- def get_external_profile(for_user, uri) do
- with {:ok, %User{} = user} <- User.get_or_fetch(uri) do
- {:ok, UserView.render("show.json", %{user: user, for: for_user})}
- else
- _e ->
- {:error, "Couldn't find user"}
- end
- end
end
diff --git a/lib/pleroma/web/twitter_api/twitter_api_controller.ex b/lib/pleroma/web/twitter_api/twitter_api_controller.ex
index 5dfab6a6c..42234ae09 100644
--- a/lib/pleroma/web/twitter_api/twitter_api_controller.ex
+++ b/lib/pleroma/web/twitter_api/twitter_api_controller.ex
@@ -5,448 +5,16 @@
defmodule Pleroma.Web.TwitterAPI.Controller do
use Pleroma.Web, :controller
- import Pleroma.Web.ControllerHelper, only: [json_response: 3]
-
alias Ecto.Changeset
- alias Pleroma.Activity
- alias Pleroma.Formatter
alias Pleroma.Notification
- alias Pleroma.Object
- alias Pleroma.Repo
alias Pleroma.User
- alias Pleroma.Web.ActivityPub.ActivityPub
- alias Pleroma.Web.ActivityPub.Visibility
- alias Pleroma.Web.CommonAPI
- alias Pleroma.Web.CommonAPI.Utils
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
require Logger
- plug(Pleroma.Plugs.RateLimiter, :password_reset when action == :password_reset)
- plug(:only_if_public_instance when action in [:public_timeline, :public_and_external_timeline])
action_fallback(:errors)
- def verify_credentials(%{assigns: %{user: user}} = conn, _params) do
- token = Phoenix.Token.sign(conn, "user socket", user.id)
-
- conn
- |> put_view(UserView)
- |> render("show.json", %{user: user, token: token, for: user})
- end
-
- def status_update(%{assigns: %{user: user}} = conn, %{"status" => _} = status_data) do
- with media_ids <- extract_media_ids(status_data),
- {:ok, activity} <-
- TwitterAPI.create_status(user, Map.put(status_data, "media_ids", media_ids)) do
- conn
- |> json(ActivityView.render("activity.json", activity: activity, for: user))
- else
- _ -> empty_status_reply(conn)
- end
- end
-
- 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
-
- 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, ","),
- clean_ids <- Enum.reject(split_ids, fn id -> String.length(id) == 0 end) do
- clean_ids
- else
- _e -> []
- end
- end
-
- def public_and_external_timeline(%{assigns: %{user: user}} = conn, params) do
- params =
- params
- |> Map.put("type", ["Create", "Announce"])
- |> Map.put("blocking_user", user)
-
- activities = ActivityPub.fetch_public_activities(params)
-
- conn
- |> put_view(ActivityView)
- |> render("index.json", %{activities: activities, for: user})
- end
-
- def public_timeline(%{assigns: %{user: user}} = conn, params) do
- params =
- params
- |> Map.put("type", ["Create", "Announce"])
- |> Map.put("local_only", true)
- |> Map.put("blocking_user", user)
-
- activities = ActivityPub.fetch_public_activities(params)
-
- conn
- |> put_view(ActivityView)
- |> render("index.json", %{activities: activities, for: user})
- end
-
- def friends_timeline(%{assigns: %{user: user}} = conn, params) do
- params =
- params
- |> Map.put("type", ["Create", "Announce", "Follow", "Like"])
- |> Map.put("blocking_user", user)
- |> Map.put("user", user)
-
- activities = ActivityPub.fetch_activities([user.ap_id | user.following], params)
-
- conn
- |> put_view(ActivityView)
- |> render("index.json", %{activities: activities, for: user})
- end
-
- def show_user(conn, params) do
- for_user = conn.assigns.user
-
- with {:ok, shown} <- TwitterAPI.get_user(params),
- true <-
- User.auth_active?(shown) ||
- (for_user && (for_user.id == shown.id || User.superuser?(for_user))) do
- params =
- if for_user do
- %{user: shown, for: for_user}
- else
- %{user: shown}
- end
-
- conn
- |> put_view(UserView)
- |> render("show.json", params)
- else
- {:error, msg} ->
- bad_request_reply(conn, msg)
-
- false ->
- conn
- |> put_status(404)
- |> json(%{error: "Unconfirmed user"})
- end
- end
-
- def user_timeline(%{assigns: %{user: user}} = conn, params) do
- case TwitterAPI.get_user(user, params) do
- {:ok, target_user} ->
- # 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
-
- activities = ActivityPub.fetch_user_activities(target_user, user, params)
-
- conn
- |> put_view(ActivityView)
- |> render("index.json", %{activities: activities, for: user})
-
- {:error, msg} ->
- bad_request_reply(conn, msg)
- end
- end
-
- def mentions_timeline(%{assigns: %{user: user}} = conn, params) do
- params =
- params
- |> Map.put("type", ["Create", "Announce", "Follow", "Like"])
- |> Map.put("blocking_user", user)
- |> Map.put(:visibility, ~w[unlisted public private])
-
- activities = ActivityPub.fetch_activities([user.ap_id], params)
-
- conn
- |> put_view(ActivityView)
- |> render("index.json", %{activities: activities, for: user})
- end
-
- def dm_timeline(%{assigns: %{user: user}} = conn, params) do
- params =
- params
- |> Map.put("type", "Create")
- |> Map.put("blocking_user", user)
- |> Map.put("user", user)
- |> Map.put(:visibility, "direct")
- |> Map.put(:order, :desc)
-
- activities =
- ActivityPub.fetch_activities_query([user.ap_id], params)
- |> Repo.all()
-
- conn
- |> put_view(ActivityView)
- |> render("index.json", %{activities: activities, for: user})
- end
-
- def notifications(%{assigns: %{user: user}} = conn, params) do
- params =
- if Map.has_key?(params, "with_muted") do
- Map.put(params, :with_muted, params["with_muted"] in [true, "True", "true", "1"])
- else
- params
- end
-
- notifications = Notification.for_user(user, params)
-
- conn
- |> put_view(NotificationView)
- |> render("notification.json", %{notifications: notifications, for: user})
- end
-
- 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
- |> put_view(NotificationView)
- |> render("notification.json", %{notifications: notifications, for: user})
- end
-
- def notifications_read(%{assigns: %{user: _user}} = conn, _) do
- bad_request_reply(conn, "You need to specify latest_id")
- end
-
- def follow(%{assigns: %{user: user}} = conn, params) do
- case TwitterAPI.follow(user, params) do
- {:ok, user, followed, _activity} ->
- conn
- |> put_view(UserView)
- |> render("show.json", %{user: followed, for: user})
-
- {:error, msg} ->
- forbidden_json_reply(conn, msg)
- end
- end
-
- def block(%{assigns: %{user: user}} = conn, params) do
- case TwitterAPI.block(user, params) do
- {:ok, user, blocked} ->
- conn
- |> put_view(UserView)
- |> render("show.json", %{user: blocked, for: user})
-
- {:error, msg} ->
- forbidden_json_reply(conn, msg)
- end
- end
-
- def unblock(%{assigns: %{user: user}} = conn, params) do
- case TwitterAPI.unblock(user, params) do
- {:ok, user, blocked} ->
- conn
- |> put_view(UserView)
- |> render("show.json", %{user: blocked, for: user})
-
- {:error, msg} ->
- forbidden_json_reply(conn, msg)
- end
- end
-
- def delete_post(%{assigns: %{user: user}} = conn, %{"id" => id}) do
- with {:ok, activity} <- TwitterAPI.delete(user, id) do
- conn
- |> put_view(ActivityView)
- |> render("activity.json", %{activity: activity, for: user})
- end
- end
-
- def unfollow(%{assigns: %{user: user}} = conn, params) do
- case TwitterAPI.unfollow(user, params) do
- {:ok, user, unfollowed} ->
- conn
- |> put_view(UserView)
- |> render("show.json", %{user: unfollowed, for: user})
-
- {:error, msg} ->
- forbidden_json_reply(conn, msg)
- end
- end
-
- def fetch_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
- with %Activity{} = activity <- Activity.get_by_id(id),
- true <- Visibility.visible_for_user?(activity, user) do
- conn
- |> put_view(ActivityView)
- |> render("activity.json", %{activity: activity, for: user})
- end
- end
-
- def fetch_conversation(%{assigns: %{user: user}} = conn, %{"id" => id}) do
- with context when is_binary(context) <- Utils.conversation_id_to_context(id),
- activities <-
- ActivityPub.fetch_activities_for_context(context, %{
- "blocking_user" => user,
- "user" => user
- }) do
- conn
- |> put_view(ActivityView)
- |> render("index.json", %{activities: activities, for: user})
- end
- end
-
- @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).
- """
- def update_media(%{assigns: %{user: user}} = conn, %{"media_id" => id} = data) do
- object = Repo.get(Object, id)
- description = get_in(data, ["alt_text", "text"]) || data["name"] || data["description"]
-
- {conn, status, response_body} =
- cond do
- !object ->
- {halt(conn), :not_found, ""}
-
- !Object.authorize_mutation(object, user) ->
- {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
-
- conn
- |> put_status(status)
- |> json(response_body)
- end
-
- def upload(%{assigns: %{user: user}} = conn, %{"media" => media}) do
- response = TwitterAPI.upload(media, user)
-
- conn
- |> put_resp_content_type("application/atom+xml")
- |> send_resp(200, response)
- end
-
- def upload_json(%{assigns: %{user: user}} = conn, %{"media" => media}) do
- response = TwitterAPI.upload(media, user, "json")
-
- conn
- |> json_reply(200, response)
- end
-
- def get_by_id_or_ap_id(id) do
- activity = Activity.get_by_id(id) || Activity.get_create_by_object_ap_id(id)
-
- if activity.data["type"] == "Create" do
- activity
- else
- Activity.get_create_by_object_ap_id(activity.data["object"])
- end
- end
-
- def favorite(%{assigns: %{user: user}} = conn, %{"id" => id}) do
- with {:ok, activity} <- TwitterAPI.fav(user, id) do
- conn
- |> put_view(ActivityView)
- |> render("activity.json", %{activity: activity, for: user})
- else
- _ -> json_reply(conn, 400, Jason.encode!(%{}))
- end
- end
-
- def unfavorite(%{assigns: %{user: user}} = conn, %{"id" => id}) do
- with {:ok, activity} <- TwitterAPI.unfav(user, id) do
- conn
- |> put_view(ActivityView)
- |> render("activity.json", %{activity: activity, for: user})
- else
- _ -> json_reply(conn, 400, Jason.encode!(%{}))
- end
- end
-
- def retweet(%{assigns: %{user: user}} = conn, %{"id" => id}) do
- with {:ok, activity} <- TwitterAPI.repeat(user, id) do
- conn
- |> put_view(ActivityView)
- |> render("activity.json", %{activity: activity, for: user})
- else
- _ -> json_reply(conn, 400, Jason.encode!(%{}))
- end
- end
-
- def unretweet(%{assigns: %{user: user}} = conn, %{"id" => id}) do
- with {:ok, activity} <- TwitterAPI.unrepeat(user, id) do
- conn
- |> put_view(ActivityView)
- |> render("activity.json", %{activity: activity, for: user})
- else
- _ -> json_reply(conn, 400, Jason.encode!(%{}))
- end
- end
-
- def pin(%{assigns: %{user: user}} = conn, %{"id" => id}) do
- with {: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, 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
-
- def register(conn, params) do
- with {:ok, user} <- TwitterAPI.register_user(params) do
- conn
- |> put_view(UserView)
- |> render("show.json", %{user: user})
- else
- {:error, errors} ->
- conn
- |> json_reply(400, Jason.encode!(errors))
- end
- end
-
- def password_reset(conn, params) do
- nickname_or_email = params["email"] || params["nickname"]
-
- with {:ok, _} <- TwitterAPI.password_reset(nickname_or_email) do
- json_response(conn, :no_content, "")
- else
- {:error, "unknown user"} ->
- send_resp(conn, :not_found, "")
-
- {:error, _} ->
- send_resp(conn, :bad_request, "")
- end
- end
-
def confirm_email(conn, %{"user_id" => uid, "token" => token}) do
with %User{} = user <- User.get_cached_by_id(uid),
true <- user.local,
@@ -460,147 +28,6 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
end
end
- 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
-
- def update_avatar(%{assigns: %{user: user}} = conn, %{"img" => ""}) do
- change = Changeset.change(user, %{avatar: nil})
- {:ok, user} = User.update_and_set_cache(change)
- CommonAPI.update(user)
-
- conn
- |> put_view(UserView)
- |> render("show.json", %{user: user, for: user})
- end
-
- def update_avatar(%{assigns: %{user: user}} = conn, params) do
- {:ok, object} = ActivityPub.upload(params, type: :avatar)
- change = Changeset.change(user, %{avatar: object.data})
- {:ok, user} = User.update_and_set_cache(change)
- CommonAPI.update(user)
-
- conn
- |> put_view(UserView)
- |> render("show.json", %{user: user, for: user})
- end
-
- def update_banner(%{assigns: %{user: user}} = conn, %{"banner" => ""}) do
- with new_info <- %{"banner" => %{}},
- 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
- CommonAPI.update(user)
- response = %{url: nil} |> Jason.encode!()
-
- conn
- |> json_reply(200, response)
- end
- end
-
- def update_banner(%{assigns: %{user: user}} = conn, params) do
- with {:ok, object} <- ActivityPub.upload(%{"img" => params["banner"]}, type: :banner),
- 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
- CommonAPI.update(user)
- %{"url" => [%{"href" => href} | _]} = object.data
- response = %{url: href} |> Jason.encode!()
-
- conn
- |> json_reply(200, response)
- end
- end
-
- def update_background(%{assigns: %{user: user}} = conn, %{"img" => ""}) do
- with new_info <- %{"background" => %{}},
- 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
- response = %{url: nil} |> Jason.encode!()
-
- conn
- |> json_reply(200, response)
- end
- end
-
- def update_background(%{assigns: %{user: user}} = conn, params) do
- with {:ok, object} <- ActivityPub.upload(params, type: :background),
- 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
- %{"url" => [%{"href" => href} | _]} = object.data
- response = %{url: href} |> Jason.encode!()
-
- conn
- |> json_reply(200, response)
- end
- end
-
- def external_profile(%{assigns: %{user: current_user}} = conn, %{"profileurl" => uri}) do
- with {:ok, user_map} <- TwitterAPI.get_external_profile(current_user, uri),
- response <- Jason.encode!(user_map) do
- conn
- |> json_reply(200, response)
- else
- _e ->
- conn
- |> put_status(404)
- |> json(%{error: "Can't find user"})
- end
- end
-
- def followers(%{assigns: %{user: for_user}} = conn, params) do
- {:ok, page} = Ecto.Type.cast(:integer, params["page"] || 1)
-
- with {:ok, user} <- TwitterAPI.get_user(for_user, params),
- {:ok, followers} <- User.get_followers(user, page) do
- followers =
- cond do
- for_user && user.id == for_user.id -> followers
- user.info.hide_followers -> []
- true -> followers
- end
-
- conn
- |> put_view(UserView)
- |> render("index.json", %{users: followers, for: conn.assigns[:user]})
- else
- _e -> bad_request_reply(conn, "Can't get followers")
- end
- end
-
- def friends(%{assigns: %{user: for_user}} = conn, params) do
- {:ok, page} = Ecto.Type.cast(:integer, params["page"] || 1)
- {:ok, export} = Ecto.Type.cast(:boolean, params["all"] || false)
-
- page = if export, do: nil, else: page
-
- with {:ok, user} <- TwitterAPI.get_user(conn.assigns[:user], params),
- {:ok, friends} <- User.get_friends(user, page) do
- friends =
- cond do
- for_user && user.id == for_user.id -> friends
- user.info.hide_follows -> []
- true -> friends
- end
-
- conn
- |> put_view(UserView)
- |> render("index.json", %{users: friends, for: conn.assigns[:user]})
- else
- _e -> bad_request_reply(conn, "Can't get friends")
- end
- end
-
def oauth_tokens(%{assigns: %{user: user}} = conn, _params) do
with oauth_tokens <- Token.get_user_tokens(user) do
conn
@@ -615,160 +42,16 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
json_reply(conn, 201, "")
end
- 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
-
- def friend_requests(conn, params) do
- with {:ok, user} <- TwitterAPI.get_user(conn.assigns[:user], params),
- {:ok, friend_requests} <- User.get_follow_requests(user) do
- conn
- |> put_view(UserView)
- |> render("index.json", %{users: friend_requests, for: conn.assigns[:user]})
- else
- _e -> bad_request_reply(conn, "Can't get friend requests")
- end
- end
-
- def approve_friend_request(conn, %{"user_id" => uid} = _params) do
- with followed <- conn.assigns[:user],
- %User{} = follower <- User.get_cached_by_id(uid),
- {:ok, follower} <- CommonAPI.accept_follow_request(follower, followed) do
- conn
- |> put_view(UserView)
- |> render("show.json", %{user: follower, for: followed})
- else
- e -> bad_request_reply(conn, "Can't approve user: #{inspect(e)}")
- end
- end
-
- def deny_friend_request(conn, %{"user_id" => uid} = _params) do
- with followed <- conn.assigns[:user],
- %User{} = follower <- User.get_cached_by_id(uid),
- {:ok, follower} <- CommonAPI.reject_follow_request(follower, followed) do
- conn
- |> put_view(UserView)
- |> render("show.json", %{user: follower, for: followed})
- else
- e -> bad_request_reply(conn, "Can't deny user: #{inspect(e)}")
- end
- end
-
- def friends_ids(%{assigns: %{user: user}} = conn, _params) do
- with {:ok, friends} <- User.get_friends(user) do
- ids =
- friends
- |> Enum.map(fn x -> x.id end)
- |> Jason.encode!()
-
- json(conn, ids)
- else
- _e -> bad_request_reply(conn, "Can't get friends")
- end
- end
-
- def empty_array(conn, _params) do
- json(conn, Jason.encode!([]))
- end
-
- def raw_empty_array(conn, _params) do
- json(conn, [])
- end
-
- defp build_info_cng(user, params) do
- info_params =
- [
- "no_rich_text",
- "locked",
- "hide_followers",
- "hide_follows",
- "hide_favorites",
- "show_role",
- "skip_thread_containment"
- ]
- |> Enum.reduce(%{}, fn key, res ->
- if value = params[key] do
- Map.put(res, key, value == "true")
- else
- res
- end
- end)
-
- info_params =
- if value = params["default_scope"] do
- Map.put(info_params, "default_scope", value)
- else
- info_params
- end
-
- User.Info.profile_update(user.info, info_params)
- end
-
- defp parse_profile_bio(user, params) do
- if bio = params["description"] do
- emojis_text = (params["description"] || "") <> " " <> (params["name"] || "")
-
- emojis =
- ((user.info.emoji || []) ++ Formatter.get_emoji_map(emojis_text))
- |> Enum.dedup()
-
- user_info =
- user.info
- |> Map.put(
- "emoji",
- emojis
- )
-
- params
- |> Map.put("bio", User.parse_bio(bio, user))
- |> Map.put("info", user_info)
- else
- params
- end
- end
-
- def update_profile(%{assigns: %{user: user}} = conn, params) do
- params = parse_profile_bio(user, params)
- info_cng = build_info_cng(user, params)
-
- with changeset <- User.update_changeset(user, params),
- changeset <- Ecto.Changeset.put_embed(changeset, :info, info_cng),
- {:ok, user} <- User.update_and_set_cache(changeset) do
- CommonAPI.update(user)
-
- conn
- |> put_view(UserView)
- |> render("user.json", %{user: user, for: user})
- else
- error ->
- Logger.debug("Can't update user: #{inspect(error)}")
- bad_request_reply(conn, "Can't update user")
- end
- end
-
- def search(%{assigns: %{user: user}} = conn, %{"q" => _query} = params) do
- activities = TwitterAPI.search(user, params)
-
+ def errors(conn, {:param_cast, _}) do
conn
- |> put_view(ActivityView)
- |> render("index.json", %{activities: activities, for: user})
+ |> put_status(400)
+ |> json("Invalid parameters")
end
- def search_user(%{assigns: %{user: user}} = conn, %{"query" => query}) do
- users = User.search(query, resolve: true, for_user: user)
-
+ def errors(conn, _) do
conn
- |> put_view(UserView)
- |> render("index.json", %{users: users, for: user})
- end
-
- defp bad_request_reply(conn, error_message) do
- json = error_json(conn, error_message)
- json_reply(conn, 400, json)
+ |> put_status(500)
+ |> json("Something went wrong")
end
defp json_reply(conn, status, json) do
@@ -777,36 +60,27 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
|> send_resp(status, json)
end
- defp forbidden_json_reply(conn, error_message) do
- json = error_json(conn, error_message)
- json_reply(conn, 403, json)
- end
+ def notifications_read(%{assigns: %{user: user}} = conn, %{"latest_id" => latest_id} = params) do
+ Notification.set_read_up_to(user, latest_id)
- def only_if_public_instance(%{assigns: %{user: %User{}}} = conn, _), do: conn
+ notifications = Notification.for_user(user, params)
- def only_if_public_instance(conn, _) do
- if Pleroma.Config.get([:instance, :public]) do
- conn
- else
- conn
- |> forbidden_json_reply("Invalid credentials.")
- |> halt()
- end
+ conn
+ # XXX: This is a hack because pleroma-fe still uses that API.
+ |> put_view(Pleroma.Web.MastodonAPI.NotificationView)
+ |> render("index.json", %{notifications: notifications, for: user})
end
- defp error_json(conn, error_message) do
- %{"error" => error_message, "request" => conn.request_path} |> Jason.encode!()
+ def notifications_read(%{assigns: %{user: _user}} = conn, _) do
+ bad_request_reply(conn, "You need to specify latest_id")
end
- def errors(conn, {:param_cast, _}) do
- conn
- |> put_status(400)
- |> json("Invalid parameters")
+ defp bad_request_reply(conn, error_message) do
+ json = error_json(conn, error_message)
+ json_reply(conn, 400, json)
end
- def errors(conn, _) do
- conn
- |> put_status(500)
- |> json("Something went wrong")
+ defp error_json(conn, error_message) do
+ %{"error" => error_message, "request" => conn.request_path} |> Jason.encode!()
end
end
diff --git a/lib/pleroma/web/twitter_api/views/activity_view.ex b/lib/pleroma/web/twitter_api/views/activity_view.ex
deleted file mode 100644
index abae63877..000000000
--- a/lib/pleroma/web/twitter_api/views/activity_view.ex
+++ /dev/null
@@ -1,366 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.TwitterAPI.ActivityView do
- use Pleroma.Web, :view
- alias Pleroma.Activity
- alias Pleroma.Formatter
- alias Pleroma.HTML
- alias Pleroma.Object
- alias Pleroma.Repo
- alias Pleroma.User
- alias Pleroma.Web.CommonAPI
- alias Pleroma.Web.CommonAPI.Utils
- alias Pleroma.Web.MastodonAPI.StatusView
- alias Pleroma.Web.TwitterAPI.ActivityView
- alias Pleroma.Web.TwitterAPI.Representers.ObjectRepresenter
- alias Pleroma.Web.TwitterAPI.UserView
-
- import Ecto.Query
- require Logger
- require Pleroma.Constants
-
- defp query_context_ids([]), do: []
-
- defp query_context_ids(contexts) do
- query = from(o in Object, where: fragment("(?)->>'id' = ANY(?)", o.data, ^contexts))
-
- Repo.all(query)
- end
-
- defp query_users([]), do: []
-
- defp query_users(user_ids) do
- query = from(user in User, where: user.ap_id in ^user_ids)
-
- Repo.all(query)
- end
-
- defp collect_context_ids(activities) do
- _contexts =
- activities
- |> Enum.reject(& &1.data["context_id"])
- |> Enum.map(fn %{data: data} ->
- data["context"]
- end)
- |> Enum.filter(& &1)
- |> query_context_ids()
- |> Enum.reduce(%{}, fn %{data: %{"id" => ap_id}, id: id}, acc ->
- Map.put(acc, ap_id, id)
- end)
- end
-
- defp collect_users(activities) do
- activities
- |> Enum.map(fn activity ->
- case activity.data do
- data = %{"type" => "Follow"} ->
- [data["actor"], data["object"]]
-
- data ->
- [data["actor"]]
- end ++ activity.recipients
- end)
- |> List.flatten()
- |> Enum.uniq()
- |> query_users()
- |> Enum.reduce(%{}, fn user, acc ->
- Map.put(acc, user.ap_id, user)
- end)
- end
-
- defp get_context_id(%{data: %{"context_id" => context_id}}, _) when not is_nil(context_id),
- do: context_id
-
- defp get_context_id(%{data: %{"context" => nil}}, _), do: nil
-
- defp get_context_id(%{data: %{"context" => context}}, options) do
- cond do
- id = options[:context_ids][context] -> id
- true -> Utils.context_to_conversation_id(context)
- end
- end
-
- defp get_context_id(_, _), do: nil
-
- defp get_user(ap_id, opts) do
- cond do
- user = opts[:users][ap_id] ->
- user
-
- String.ends_with?(ap_id, "/followers") ->
- nil
-
- ap_id == Pleroma.Constants.as_public() ->
- nil
-
- user = User.get_cached_by_ap_id(ap_id) ->
- user
-
- user = User.get_by_guessed_nickname(ap_id) ->
- user
-
- true ->
- User.error_user(ap_id)
- end
- end
-
- def render("index.json", opts) do
- context_ids = collect_context_ids(opts.activities)
- users = collect_users(opts.activities)
-
- opts =
- opts
- |> Map.put(:context_ids, context_ids)
- |> Map.put(:users, users)
-
- safe_render_many(
- opts.activities,
- ActivityView,
- "activity.json",
- opts
- )
- end
-
- def render("activity.json", %{activity: %{data: %{"type" => "Delete"}} = activity} = opts) do
- user = get_user(activity.data["actor"], opts)
- created_at = activity.data["published"] |> Utils.date_to_asctime()
-
- %{
- "id" => activity.id,
- "uri" => activity.data["object"],
- "user" => UserView.render("show.json", %{user: user, for: opts[:for]}),
- "attentions" => [],
- "statusnet_html" => "deleted notice {{tag",
- "text" => "deleted notice {{tag",
- "is_local" => activity.local,
- "is_post_verb" => false,
- "created_at" => created_at,
- "in_reply_to_status_id" => nil,
- "external_url" => activity.data["id"],
- "activity_type" => "delete"
- }
- end
-
- def render("activity.json", %{activity: %{data: %{"type" => "Follow"}} = activity} = opts) do
- user = get_user(activity.data["actor"], opts)
- created_at = activity.data["published"] || DateTime.to_iso8601(activity.inserted_at)
- created_at = created_at |> Utils.date_to_asctime()
-
- followed = get_user(activity.data["object"], opts)
- text = "#{user.nickname} started following #{followed.nickname}"
-
- %{
- "id" => activity.id,
- "user" => UserView.render("show.json", %{user: user, for: opts[:for]}),
- "attentions" => [],
- "statusnet_html" => text,
- "text" => text,
- "is_local" => activity.local,
- "is_post_verb" => false,
- "created_at" => created_at,
- "in_reply_to_status_id" => nil,
- "external_url" => activity.data["id"],
- "activity_type" => "follow"
- }
- end
-
- def render("activity.json", %{activity: %{data: %{"type" => "Announce"}} = activity} = opts) do
- user = get_user(activity.data["actor"], opts)
- created_at = activity.data["published"] |> Utils.date_to_asctime()
- announced_activity = Activity.get_create_by_object_ap_id(activity.data["object"])
-
- text = "#{user.nickname} repeated a status."
-
- retweeted_status = render("activity.json", Map.merge(opts, %{activity: announced_activity}))
-
- %{
- "id" => activity.id,
- "user" => UserView.render("show.json", %{user: user, for: opts[:for]}),
- "statusnet_html" => text,
- "text" => text,
- "is_local" => activity.local,
- "is_post_verb" => false,
- "uri" => "tag:#{activity.data["id"]}:objectType=note",
- "created_at" => created_at,
- "retweeted_status" => retweeted_status,
- "statusnet_conversation_id" => get_context_id(announced_activity, opts),
- "external_url" => activity.data["id"],
- "activity_type" => "repeat"
- }
- end
-
- def render("activity.json", %{activity: %{data: %{"type" => "Like"}} = activity} = opts) do
- user = get_user(activity.data["actor"], opts)
- liked_activity = Activity.get_create_by_object_ap_id(activity.data["object"])
- liked_activity_id = if liked_activity, do: liked_activity.id, else: nil
-
- created_at =
- activity.data["published"]
- |> Utils.date_to_asctime()
-
- text = "#{user.nickname} favorited a status."
-
- favorited_status =
- if liked_activity,
- do: render("activity.json", Map.merge(opts, %{activity: liked_activity})),
- else: nil
-
- %{
- "id" => activity.id,
- "user" => UserView.render("show.json", %{user: user, for: opts[:for]}),
- "statusnet_html" => text,
- "text" => text,
- "is_local" => activity.local,
- "is_post_verb" => false,
- "uri" => "tag:#{activity.data["id"]}:objectType=Favourite",
- "created_at" => created_at,
- "favorited_status" => favorited_status,
- "in_reply_to_status_id" => liked_activity_id,
- "external_url" => activity.data["id"],
- "activity_type" => "like"
- }
- end
-
- def render(
- "activity.json",
- %{activity: %{data: %{"type" => "Create", "object" => object_id}} = activity} = opts
- ) do
- user = get_user(activity.data["actor"], opts)
-
- object = Object.normalize(object_id)
-
- created_at = object.data["published"] |> Utils.date_to_asctime()
- like_count = object.data["like_count"] || 0
- announcement_count = object.data["announcement_count"] || 0
- favorited = opts[:for] && opts[:for].ap_id in (object.data["likes"] || [])
- repeated = opts[:for] && opts[:for].ap_id in (object.data["announcements"] || [])
- pinned = activity.id in user.info.pinned_activities
-
- attentions =
- []
- |> Utils.maybe_notify_to_recipients(activity)
- |> Utils.maybe_notify_mentioned_recipients(activity)
- |> Enum.map(fn ap_id -> get_user(ap_id, opts) end)
- |> Enum.filter(& &1)
- |> Enum.map(fn user -> UserView.render("show.json", %{user: user, for: opts[:for]}) end)
-
- conversation_id = get_context_id(activity, opts)
-
- tags = object.data["tag"] || []
- possibly_sensitive = object.data["sensitive"] || Enum.member?(tags, "nsfw")
-
- tags = if possibly_sensitive, do: Enum.uniq(["nsfw" | tags]), else: tags
-
- {summary, content} = render_content(object.data)
-
- html =
- content
- |> HTML.get_cached_scrubbed_html_for_activity(
- User.html_filter_policy(opts[:for]),
- activity,
- "twitterapi:content"
- )
- |> Formatter.emojify(object.data["emoji"])
-
- text =
- if content do
- content
- |> String.replace(~r/<br\s?\/?>/, "\n")
- |> HTML.get_cached_stripped_html_for_activity(activity, "twitterapi:content")
- else
- ""
- end
-
- reply_parent = Activity.get_in_reply_to_activity(activity)
-
- reply_user = reply_parent && User.get_cached_by_ap_id(reply_parent.actor)
-
- summary = HTML.strip_tags(summary)
-
- card =
- StatusView.render(
- "card.json",
- Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity)
- )
-
- thread_muted? =
- case activity.thread_muted? do
- thread_muted? when is_boolean(thread_muted?) -> thread_muted?
- nil -> CommonAPI.thread_muted?(user, activity)
- end
-
- %{
- "id" => activity.id,
- "uri" => object.data["id"],
- "user" => UserView.render("show.json", %{user: user, for: opts[:for]}),
- "statusnet_html" => html,
- "text" => text,
- "is_local" => activity.local,
- "is_post_verb" => true,
- "created_at" => created_at,
- "in_reply_to_status_id" => reply_parent && reply_parent.id,
- "in_reply_to_screen_name" => reply_user && reply_user.nickname,
- "in_reply_to_profileurl" => User.profile_url(reply_user),
- "in_reply_to_ostatus_uri" => reply_user && reply_user.ap_id,
- "in_reply_to_user_id" => reply_user && reply_user.id,
- "statusnet_conversation_id" => conversation_id,
- "attachments" => (object.data["attachment"] || []) |> ObjectRepresenter.enum_to_list(opts),
- "attentions" => attentions,
- "fave_num" => like_count,
- "repeat_num" => announcement_count,
- "favorited" => !!favorited,
- "repeated" => !!repeated,
- "pinned" => pinned,
- "external_url" => object.data["external_url"] || object.data["id"],
- "tags" => tags,
- "activity_type" => "post",
- "possibly_sensitive" => possibly_sensitive,
- "visibility" => Pleroma.Web.ActivityPub.Visibility.get_visibility(object),
- "summary" => summary,
- "summary_html" => summary |> Formatter.emojify(object.data["emoji"]),
- "card" => card,
- "muted" => thread_muted? || User.mutes?(opts[:for], user)
- }
- end
-
- def render("activity.json", %{activity: unhandled_activity}) do
- Logger.warn("#{__MODULE__} unhandled activity: #{inspect(unhandled_activity)}")
- nil
- end
-
- def render_content(%{"type" => "Note"} = object) do
- summary = object["summary"]
-
- content =
- if !!summary and summary != "" do
- "<p>#{summary}</p>#{object["content"]}"
- else
- object["content"]
- end
-
- {summary, content}
- end
-
- def render_content(%{"type" => object_type} = object)
- when object_type in ["Article", "Page", "Video"] do
- summary = object["name"] || object["summary"]
-
- content =
- if !!summary and summary != "" and is_bitstring(object["url"]) do
- "<p><a href=\"#{object["url"]}\">#{summary}</a></p>#{object["content"]}"
- else
- object["content"]
- end
-
- {summary, content}
- end
-
- def render_content(object) do
- summary = object["summary"] || "Unhandled activity type: #{object["type"]}"
- content = "<p>#{summary}</p>#{object["content"]}"
-
- {summary, content}
- end
-end
diff --git a/lib/pleroma/web/twitter_api/views/notification_view.ex b/lib/pleroma/web/twitter_api/views/notification_view.ex
deleted file mode 100644
index 085cd5aa3..000000000
--- a/lib/pleroma/web/twitter_api/views/notification_view.ex
+++ /dev/null
@@ -1,71 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.TwitterAPI.NotificationView do
- use Pleroma.Web, :view
- alias Pleroma.Notification
- alias Pleroma.User
- alias Pleroma.Web.CommonAPI.Utils
- alias Pleroma.Web.TwitterAPI.ActivityView
- alias Pleroma.Web.TwitterAPI.UserView
-
- require Pleroma.Constants
-
- defp get_user(ap_id, opts) do
- cond do
- user = opts[:users][ap_id] ->
- user
-
- String.ends_with?(ap_id, "/followers") ->
- nil
-
- ap_id == Pleroma.Constants.as_public() ->
- nil
-
- true ->
- User.get_cached_by_ap_id(ap_id)
- end
- end
-
- def render("notification.json", %{notifications: notifications, for: user}) do
- render_many(
- notifications,
- Pleroma.Web.TwitterAPI.NotificationView,
- "notification.json",
- for: user
- )
- end
-
- def render(
- "notification.json",
- %{
- notification: %Notification{
- id: id,
- seen: seen,
- activity: activity,
- inserted_at: created_at
- },
- for: user
- } = opts
- ) do
- ntype =
- case activity.data["type"] do
- "Create" -> "mention"
- "Like" -> "like"
- "Announce" -> "repeat"
- "Follow" -> "follow"
- end
-
- from = get_user(activity.data["actor"], opts)
-
- %{
- "id" => id,
- "ntype" => ntype,
- "notice" => ActivityView.render("activity.json", %{activity: activity, for: user}),
- "from_profile" => UserView.render("show.json", %{user: from, for: user}),
- "is_seen" => if(seen, do: 1, else: 0),
- "created_at" => created_at |> Utils.format_naive_asctime()
- }
- end
-end
diff --git a/lib/pleroma/web/twitter_api/views/user_view.ex b/lib/pleroma/web/twitter_api/views/user_view.ex
deleted file mode 100644
index 8a7d2fc72..000000000
--- a/lib/pleroma/web/twitter_api/views/user_view.ex
+++ /dev/null
@@ -1,191 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.TwitterAPI.UserView do
- use Pleroma.Web, :view
- alias Pleroma.Formatter
- alias Pleroma.HTML
- alias Pleroma.User
- alias Pleroma.Web.CommonAPI.Utils
- alias Pleroma.Web.MediaProxy
-
- def render("show.json", %{user: user = %User{}} = assigns) do
- render_one(user, Pleroma.Web.TwitterAPI.UserView, "user.json", assigns)
- end
-
- def render("index.json", %{users: users, for: user}) do
- users
- |> render_many(Pleroma.Web.TwitterAPI.UserView, "user.json", for: user)
- |> Enum.filter(&Enum.any?/1)
- end
-
- def render("user.json", %{user: user = %User{}} = assigns) do
- if User.visible_for?(user, assigns[:for]),
- do: do_render("user.json", assigns),
- else: %{}
- end
-
- def render("short.json", %{
- user: %User{
- nickname: nickname,
- id: id,
- ap_id: ap_id,
- name: name
- }
- }) do
- %{
- "fullname" => name,
- "id" => id,
- "ostatus_uri" => ap_id,
- "profile_url" => ap_id,
- "screen_name" => nickname
- }
- end
-
- defp do_render("user.json", %{user: user = %User{}} = assigns) do
- for_user = assigns[:for]
- image = User.avatar_url(user) |> MediaProxy.url()
-
- {following, follows_you, statusnet_blocking} =
- if for_user do
- {
- User.following?(for_user, user),
- User.following?(user, for_user),
- User.blocks?(for_user, user)
- }
- else
- {false, false, false}
- end
-
- user_info = User.get_cached_user_info(user)
-
- emoji =
- (user.info.source_data["tag"] || [])
- |> Enum.filter(fn %{"type" => t} -> t == "Emoji" end)
- |> Enum.map(fn %{"icon" => %{"url" => url}, "name" => name} ->
- {String.trim(name, ":"), url}
- end)
-
- emoji = Enum.dedup(emoji ++ user.info.emoji)
-
- description_html =
- (user.bio || "")
- |> HTML.filter_tags(User.html_filter_policy(for_user))
- |> Formatter.emojify(emoji)
-
- fields =
- user.info
- |> User.Info.fields()
- |> Enum.map(fn %{"name" => name, "value" => value} ->
- %{
- "name" => Pleroma.HTML.strip_tags(name),
- "value" => Pleroma.HTML.filter_tags(value, Pleroma.HTML.Scrubber.LinksOnly)
- }
- end)
-
- data =
- %{
- "created_at" => user.inserted_at |> Utils.format_naive_asctime(),
- "description" => HTML.strip_tags((user.bio || "") |> String.replace("<br>", "\n")),
- "description_html" => description_html,
- "favourites_count" => 0,
- "followers_count" => user_info[:follower_count],
- "following" => following,
- "follows_you" => follows_you,
- "statusnet_blocking" => statusnet_blocking,
- "friends_count" => user_info[:following_count],
- "id" => user.id,
- "name" => user.name || user.nickname,
- "name_html" =>
- if(user.name,
- do: HTML.strip_tags(user.name) |> Formatter.emojify(emoji),
- else: user.nickname
- ),
- "profile_image_url" => image,
- "profile_image_url_https" => image,
- "profile_image_url_profile_size" => image,
- "profile_image_url_original" => image,
- "screen_name" => user.nickname,
- "statuses_count" => user_info[:note_count],
- "statusnet_profile_url" => user.ap_id,
- "cover_photo" => User.banner_url(user) |> MediaProxy.url(),
- "background_image" => image_url(user.info.background) |> MediaProxy.url(),
- "is_local" => user.local,
- "locked" => user.info.locked,
- "hide_followers" => user.info.hide_followers,
- "hide_follows" => user.info.hide_follows,
- "fields" => fields,
-
- # Pleroma extension
- "pleroma" =>
- %{
- "confirmation_pending" => user_info.confirmation_pending,
- "tags" => user.tags,
- "skip_thread_containment" => user.info.skip_thread_containment
- }
- |> maybe_with_activation_status(user, for_user)
- |> with_notification_settings(user, for_user)
- }
- |> maybe_with_user_settings(user, for_user)
- |> maybe_with_role(user, for_user)
-
- if assigns[:token] do
- Map.put(data, "token", token_string(assigns[:token]))
- else
- data
- end
- end
-
- defp with_notification_settings(data, %User{id: user_id} = user, %User{id: user_id}) do
- Map.put(data, "notification_settings", user.info.notification_settings)
- end
-
- defp with_notification_settings(data, _, _), do: data
-
- defp maybe_with_activation_status(data, user, %User{info: %{is_admin: true}}) do
- Map.put(data, "deactivated", user.info.deactivated)
- end
-
- defp maybe_with_activation_status(data, _, _), do: data
-
- defp maybe_with_role(data, %User{id: id} = user, %User{id: id}) do
- Map.merge(data, %{
- "role" => role(user),
- "show_role" => user.info.show_role,
- "rights" => %{
- "delete_others_notice" => !!user.info.is_moderator,
- "admin" => !!user.info.is_admin
- }
- })
- end
-
- defp maybe_with_role(data, %User{info: %{show_role: true}} = user, _user) do
- Map.merge(data, %{
- "role" => role(user),
- "rights" => %{
- "delete_others_notice" => !!user.info.is_moderator,
- "admin" => !!user.info.is_admin
- }
- })
- end
-
- defp maybe_with_role(data, _, _), do: data
-
- defp maybe_with_user_settings(data, %User{info: info, id: id} = _user, %User{id: id}) do
- data
- |> Kernel.put_in(["default_scope"], info.default_scope)
- |> Kernel.put_in(["no_rich_text"], info.no_rich_text)
- end
-
- defp maybe_with_user_settings(data, _, _), do: data
- defp role(%User{info: %{:is_admin => true}}), do: "admin"
- defp role(%User{info: %{:is_moderator => true}}), do: "moderator"
- defp role(_), do: "member"
-
- defp image_url(%{"url" => [%{"href" => href} | _]}), do: href
- defp image_url(_), do: nil
-
- defp token_string(%Pleroma.Web.OAuth.Token{token: token_str}), do: token_str
- defp token_string(token), do: token
-end