diff options
Diffstat (limited to 'lib/pleroma/web/twitter_api')
9 files changed, 677 insertions, 302 deletions
diff --git a/lib/pleroma/web/twitter_api/controllers/util_controller.ex b/lib/pleroma/web/twitter_api/controllers/util_controller.ex index 503719dbf..ea540b34c 100644 --- a/lib/pleroma/web/twitter_api/controllers/util_controller.ex +++ b/lib/pleroma/web/twitter_api/controllers/util_controller.ex @@ -11,21 +11,21 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do def show_password_reset(conn, %{"token" => token}) do with %{used: false} = token <- Repo.get_by(PasswordResetToken, %{token: token}), - %User{} = user <- Repo.get(User, token.user_id) do - render conn, "password_reset.html", %{ + %User{} = user <- Repo.get(User, token.user_id) do + render(conn, "password_reset.html", %{ token: token, user: user - } + }) else - _e -> render conn, "invalid_token.html" + _e -> render(conn, "invalid_token.html") end end def password_reset(conn, %{"data" => data}) do with {:ok, _} <- PasswordResetToken.reset_password(data["token"], data) do - render conn, "password_reset_success.html" + render(conn, "password_reset_success.html") else - _e -> render conn, "password_reset_failed.html" + _e -> render(conn, "password_reset_failed.html") end end @@ -34,14 +34,19 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do end def remote_subscribe(conn, %{"nickname" => nick, "profile" => _}) do - with %User{} = user <- User.get_cached_by_nickname(nick), - avatar = User.avatar_url(user) do + with %User{} = user <- User.get_cached_by_nickname(nick), avatar = User.avatar_url(user) do conn |> render("subscribe.html", %{nickname: nick, avatar: avatar, error: false}) else - _e -> render(conn, "subscribe.html", %{nickname: nick, avatar: nil, error: "Could not find user"}) + _e -> + render(conn, "subscribe.html", %{ + nickname: nick, + avatar: nil, + error: "Could not find user" + }) end end + def remote_subscribe(conn, %{"user" => %{"nickname" => nick, "profile" => profile}}) do with {:ok, %{"subscribe_address" => template}} <- WebFinger.finger(profile), %User{ap_id: ap_id} <- User.get_cached_by_nickname(nick) do @@ -49,7 +54,11 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do |> Phoenix.Controller.redirect(external: String.replace(template, "{uri}", ap_id)) else _e -> - render(conn, "subscribe.html", %{nickname: nick, avatar: nil, error: "Something went wrong."}) + render(conn, "subscribe.html", %{ + nickname: nick, + avatar: nil, + error: "Something went wrong." + }) end end @@ -64,17 +73,26 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do |> render("follow.html", %{error: err, acct: acct, avatar: avatar, name: name, id: id}) else conn - |> render("follow_login.html", %{error: false, acct: acct, avatar: avatar, name: name, id: id}) + |> render("follow_login.html", %{ + error: false, + acct: acct, + avatar: avatar, + name: name, + id: id + }) end end - def do_remote_follow(conn, %{"authorization" => %{"name" => username, "password" => password, "id" => id}}) do + def do_remote_follow(conn, %{ + "authorization" => %{"name" => username, "password" => password, "id" => id} + }) do followee = Repo.get(User, id) avatar = User.avatar_url(followee) name = followee.nickname + with %User{} = user <- User.get_cached_by_nickname(username), true <- Pbkdf2.checkpw(password, user.password_hash), - %User{} = followed <- Repo.get(User, id), + %User{} = _followed <- Repo.get(User, id), {:ok, follower} <- User.follow(user, followee), {:ok, _activity} <- ActivityPub.follow(follower, followee) do conn @@ -82,9 +100,15 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do else _e -> conn - |> render("follow_login.html", %{error: "Wrong username or password", id: id, name: name, avatar: avatar}) + |> render("follow_login.html", %{ + error: "Wrong username or password", + id: id, + name: name, + avatar: avatar + }) end end + def do_remote_follow(%{assigns: %{user: user}} = conn, %{"user" => %{"id" => id}}) do with %User{} = followee <- Repo.get(User, id), {:ok, follower} <- User.follow(user, followee), @@ -93,9 +117,10 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do |> render("followed.html", %{error: false}) else e -> - Logger.debug("Remote follow failed with error #{inspect e}") - conn - |> render("followed.html", %{error: inspect(e)}) + Logger.debug("Remote follow failed with error #{inspect(e)}") + + conn + |> render("followed.html", %{error: inspect(e)}) end end @@ -107,60 +132,67 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do <config> <site> <name>#{Keyword.get(@instance, :name)}</name> - <site>#{Web.base_url}</site> + <site>#{Web.base_url()}</site> <textlimit>#{Keyword.get(@instance, :limit)}</textlimit> <closed>#{!Keyword.get(@instance, :registrations_open)}</closed> </site> </config> """ + conn |> put_resp_content_type("application/xml") |> send_resp(200, response) + _ -> json(conn, %{ - site: %{ - name: Keyword.get(@instance, :name), - server: Web.base_url, - textlimit: to_string(Keyword.get(@instance, :limit)), - closed: if(Keyword.get(@instance, :registrations_open), do: "0", else: "1") - } - }) + site: %{ + name: Keyword.get(@instance, :name), + server: Web.base_url(), + textlimit: to_string(Keyword.get(@instance, :limit)), + closed: if(Keyword.get(@instance, :registrations_open), do: "0", else: "1") + } + }) end end def version(conn, _params) do version = Keyword.get(@instance, :version) + case get_format(conn) do "xml" -> response = "<version>#{version}</version>" + conn |> put_resp_content_type("application/xml") |> send_resp(200, response) - _ -> json(conn, version) + + _ -> + json(conn, version) end end def emoji(conn, _params) do - json conn, Enum.into(Formatter.get_custom_emoji(), %{}) + json(conn, Enum.into(Formatter.get_custom_emoji(), %{})) end def follow_import(conn, %{"list" => %Plug.Upload{} = listfile}) do follow_import(conn, %{"list" => File.read!(listfile.path)}) end + def follow_import(%{assigns: %{user: user}} = conn, %{"list" => list}) do Task.start(fn -> - String.split(list) - |> Enum.map(fn nick -> + String.split(list) + |> Enum.map(fn account -> with %User{} = follower <- User.get_cached_by_ap_id(user.ap_id), - %User{} = followed <- User.get_or_fetch_by_nickname(nick), - {:ok, follower} <- User.follow(follower, followed) do + %User{} = followed <- User.get_or_fetch(account), + {:ok, follower} <- User.follow(follower, followed) do ActivityPub.follow(follower, followed) else - _e -> Logger.debug "follow_import: following #{nick} failed" + _e -> Logger.debug("follow_import: following #{account} failed") end end) end) - json conn, "job started" + json(conn, "job started") end end diff --git a/lib/pleroma/web/twitter_api/representers/activity_representer.ex b/lib/pleroma/web/twitter_api/representers/activity_representer.ex index 5199cef8e..9a4954de8 100644 --- a/lib/pleroma/web/twitter_api/representers/activity_representer.ex +++ b/lib/pleroma/web/twitter_api/representers/activity_representer.ex @@ -1,3 +1,5 @@ +# THIS MODULE IS DEPRECATED! DON'T USE IT! +# USE THE Pleroma.Web.TwitterAPI.Views.ActivityView MODULE! defmodule Pleroma.Web.TwitterAPI.Representers.ActivityRepresenter do use Pleroma.Web.TwitterAPI.Representers.BaseRepresenter alias Pleroma.Web.TwitterAPI.Representers.ObjectRepresenter @@ -7,18 +9,22 @@ defmodule Pleroma.Web.TwitterAPI.Representers.ActivityRepresenter do alias Pleroma.Formatter defp user_by_ap_id(user_list, ap_id) do - Enum.find(user_list, fn (%{ap_id: user_id}) -> ap_id == user_id end) + Enum.find(user_list, fn %{ap_id: user_id} -> ap_id == user_id end) end - def to_map(%Activity{data: %{"type" => "Announce", "actor" => actor, "published" => created_at}} = activity, - %{users: users, announced_activity: announced_activity} = opts) do + def to_map( + %Activity{data: %{"type" => "Announce", "actor" => actor, "published" => created_at}} = + activity, + %{users: users, announced_activity: announced_activity} = opts + ) do user = user_by_ap_id(users, actor) - created_at = created_at |> Utils.date_to_asctime + created_at = created_at |> Utils.date_to_asctime() text = "#{user.nickname} retweeted a status." announced_user = user_by_ap_id(users, announced_activity.data["actor"]) retweeted_status = to_map(announced_activity, Map.merge(%{user: announced_user}, opts)) + %{ "id" => activity.id, "user" => UserView.render("show.json", %{user: user, for: opts[:for]}), @@ -35,9 +41,11 @@ defmodule Pleroma.Web.TwitterAPI.Representers.ActivityRepresenter do } end - def to_map(%Activity{data: %{"type" => "Like", "published" => created_at}} = activity, - %{user: user, liked_activity: liked_activity} = opts) do - created_at = created_at |> Utils.date_to_asctime + def to_map( + %Activity{data: %{"type" => "Like", "published" => created_at}} = activity, + %{user: user, liked_activity: liked_activity} = opts + ) do + created_at = created_at |> Utils.date_to_asctime() text = "#{user.nickname} favorited a status." @@ -56,12 +64,16 @@ defmodule Pleroma.Web.TwitterAPI.Representers.ActivityRepresenter do } end - def to_map(%Activity{data: %{"type" => "Follow", "object" => followed_id}} = activity, %{user: user} = opts) do - created_at = activity.data["published"] || (DateTime.to_iso8601(activity.inserted_at)) - created_at = created_at |> Utils.date_to_asctime + def to_map( + %Activity{data: %{"type" => "Follow", "object" => followed_id}} = activity, + %{user: user} = opts + ) do + created_at = activity.data["published"] || DateTime.to_iso8601(activity.inserted_at) + created_at = created_at |> Utils.date_to_asctime() followed = User.get_cached_by_ap_id(followed_id) text = "#{user.nickname} started following #{followed.nickname}" + %{ "id" => activity.id, "user" => UserView.render("show.json", %{user: user, for: opts[:for]}), @@ -79,10 +91,16 @@ defmodule Pleroma.Web.TwitterAPI.Representers.ActivityRepresenter do # TODO: # Make this more proper. Just a placeholder to not break the frontend. - def to_map(%Activity{data: %{"type" => "Undo", "published" => created_at, "object" => undid_activity }} = activity, %{user: user} = opts) do - created_at = created_at |> Utils.date_to_asctime + def to_map( + %Activity{ + data: %{"type" => "Undo", "published" => created_at, "object" => undid_activity} + } = activity, + %{user: user} = opts + ) do + created_at = created_at |> Utils.date_to_asctime() text = "#{user.nickname} undid the action at #{undid_activity}" + %{ "id" => activity.id, "user" => UserView.render("show.json", %{user: user, for: opts[:for]}), @@ -98,8 +116,12 @@ defmodule Pleroma.Web.TwitterAPI.Representers.ActivityRepresenter do } end - def to_map(%Activity{data: %{"type" => "Delete", "published" => created_at, "object" => _ }} = activity, %{user: user} = opts) do - created_at = created_at |> Utils.date_to_asctime + def to_map( + %Activity{data: %{"type" => "Delete", "published" => created_at, "object" => _}} = + activity, + %{user: user} = opts + ) do + created_at = created_at |> Utils.date_to_asctime() %{ "id" => activity.id, @@ -107,7 +129,7 @@ defmodule Pleroma.Web.TwitterAPI.Representers.ActivityRepresenter do "user" => UserView.render("show.json", %{user: user, for: opts[:for]}), "attentions" => [], "statusnet_html" => "deleted notice {{tag", - "text" => "deleted notice {{tag" , + "text" => "deleted notice {{tag", "is_local" => activity.local, "is_post_verb" => false, "created_at" => created_at, @@ -117,8 +139,11 @@ defmodule Pleroma.Web.TwitterAPI.Representers.ActivityRepresenter do } end - def to_map(%Activity{data: %{"object" => %{"content" => content} = object}} = activity, %{user: user} = opts) do - created_at = object["published"] |> Utils.date_to_asctime + def to_map( + %Activity{data: %{"object" => %{"content" => content} = object}} = activity, + %{user: user} = opts + ) do + created_at = object["published"] |> Utils.date_to_asctime() like_count = object["like_count"] || 0 announcement_count = object["announcement_count"] || 0 favorited = opts[:for] && opts[:for].ap_id in (object["likes"] || []) @@ -126,10 +151,11 @@ defmodule Pleroma.Web.TwitterAPI.Representers.ActivityRepresenter do mentions = opts[:mentioned] || [] - attentions = activity.recipients - |> Enum.map(fn (ap_id) -> Enum.find(mentions, fn(user) -> ap_id == user.ap_id end) end) - |> Enum.filter(&(&1)) - |> Enum.map(fn (user) -> UserView.render("show.json", %{user: user, for: opts[:for]}) end) + attentions = + activity.recipients + |> Enum.map(fn ap_id -> Enum.find(mentions, fn user -> ap_id == user.ap_id end) end) + |> Enum.filter(& &1) + |> Enum.map(fn user -> UserView.render("show.json", %{user: user, for: opts[:for]}) end) conversation_id = conversation_id(activity) @@ -139,13 +165,17 @@ defmodule Pleroma.Web.TwitterAPI.Representers.ActivityRepresenter do tags = if possibly_sensitive, do: Enum.uniq(["nsfw" | tags]), else: tags summary = activity.data["object"]["summary"] - content = if !!summary and summary != "" do - "<span>#{activity.data["object"]["summary"]}</span><br />#{content}</span>" - else - content - end - html = HtmlSanitizeEx.basic_html(content) |> Formatter.emojify(object["emoji"]) + content = + if !!summary and summary != "" do + "<span>#{activity.data["object"]["summary"]}</span><br />#{content}</span>" + else + content + end + + html = + HtmlSanitizeEx.basic_html(content) + |> Formatter.emojify(object["emoji"]) %{ "id" => activity.id, @@ -174,7 +204,8 @@ defmodule Pleroma.Web.TwitterAPI.Representers.ActivityRepresenter do def conversation_id(activity) do with context when not is_nil(context) <- activity.data["context"] do TwitterAPI.context_to_conversation_id(context) - else _e -> nil + else + _e -> nil end end diff --git a/lib/pleroma/web/twitter_api/representers/base_representer.ex b/lib/pleroma/web/twitter_api/representers/base_representer.ex index a4ef245fc..f32a21d47 100644 --- a/lib/pleroma/web/twitter_api/representers/base_representer.ex +++ b/lib/pleroma/web/twitter_api/representers/base_representer.ex @@ -1,15 +1,18 @@ 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) do + to_json(object, %{}) + end + def to_json(object, options) do object |> to_map(options) - |> Poison.encode! + |> Jason.encode!() end def enum_to_list(enum, options) do - mapping = fn (el) -> to_map(el, options) end + mapping = fn el -> to_map(el, options) end Enum.map(enum, mapping) end @@ -17,11 +20,14 @@ defmodule Pleroma.Web.TwitterAPI.Representers.BaseRepresenter do to_map(object, %{}) end - def enum_to_json(enum) do enum_to_json(enum, %{}) end + def enum_to_json(enum) do + enum_to_json(enum, %{}) + end + def enum_to_json(enum, options) do enum |> enum_to_list(options) - |> Poison.encode! + |> Jason.encode!() 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 index e2d653ba8..9af8a1691 100644 --- a/lib/pleroma/web/twitter_api/representers/object_representer.ex +++ b/lib/pleroma/web/twitter_api/representers/object_representer.ex @@ -4,6 +4,7 @@ defmodule Pleroma.Web.TwitterAPI.Representers.ObjectRepresenter do def to_map(%Object{data: %{"url" => [url | _]}} = object, _opts) do data = object.data + %{ url: url["href"] |> Pleroma.Web.MediaProxy.url(), mimetype: url["mediaType"], diff --git a/lib/pleroma/web/twitter_api/twitter_api.ex b/lib/pleroma/web/twitter_api/twitter_api.ex index 987a960bb..44ea40a4e 100644 --- a/lib/pleroma/web/twitter_api/twitter_api.ex +++ b/lib/pleroma/web/twitter_api/twitter_api.ex @@ -1,7 +1,6 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do alias Pleroma.{User, Activity, Repo, Object} alias Pleroma.Web.ActivityPub.ActivityPub - alias Pleroma.Web.TwitterAPI.Representers.ActivityRepresenter alias Pleroma.Web.TwitterAPI.UserView alias Pleroma.Web.{OStatus, CommonAPI} import Ecto.Query @@ -12,70 +11,10 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do CommonAPI.post(user, data) end - def fetch_friend_statuses(user, opts \\ %{}) do - opts = opts - |> Map.put("blocking_user", user) - |> Map.put("user", user) - |> Map.put("type", ["Create", "Announce", "Follow", "Like"]) - - ActivityPub.fetch_activities([user.ap_id | user.following], opts) - |> activities_to_statuses(%{for: user}) - end - - def fetch_public_statuses(user, opts \\ %{}) do - opts = opts - |> Map.put("local_only", true) - |> Map.put("blocking_user", user) - |> Map.put("type", ["Create", "Announce", "Follow"]) - - ActivityPub.fetch_public_activities(opts) - |> activities_to_statuses(%{for: user}) - end - - def fetch_public_and_external_statuses(user, opts \\ %{}) do - opts = opts - |> Map.put("blocking_user", user) - |> Map.put("type", ["Create", "Announce", "Follow"]) - - ActivityPub.fetch_public_activities(opts) - |> activities_to_statuses(%{for: user}) - end - - def fetch_user_statuses(user, opts \\ %{}) do - opts = opts - |> Map.put("type", ["Create"]) - ActivityPub.fetch_public_activities(opts) - |> activities_to_statuses(%{for: user}) - end - - def fetch_mentions(user, opts \\ %{}) do - ActivityPub.fetch_activities([user.ap_id], opts) - |> activities_to_statuses(%{for: user}) - end - - def fetch_conversation(user, id) do - with context when is_binary(context) <- conversation_id_to_context(id), - activities <- ActivityPub.fetch_activities_for_context(context, %{"blocking_user" => user, "user" => user}), - statuses <- activities |> activities_to_statuses(%{for: user}) - do - statuses - else _e -> - [] - end - end - - def fetch_status(user, id) do - with %Activity{} = activity <- Repo.get(Activity, id), - true <- ActivityPub.visible_for_user?(activity, user) do - activity_to_status(activity, %{for: user}) - end - end - def follow(%User{} = follower, params) do with {:ok, %User{} = followed} <- get_user(params), {:ok, follower} <- User.follow(follower, followed), - {:ok, activity} <- ActivityPub.follow(follower, followed) - do + {:ok, activity} <- ActivityPub.follow(follower, followed) do {:ok, follower, followed, activity} else err -> err @@ -83,16 +22,17 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do end def unfollow(%User{} = follower, params) do - with { :ok, %User{} = unfollowed } <- get_user(params), - { :ok, follower, follow_activity } <- User.unfollow(follower, unfollowed), - { :ok, _activity } <- ActivityPub.insert(%{ - "type" => "Undo", - "actor" => follower.ap_id, - "object" => follow_activity.data["id"], # get latest Follow for these users - "published" => make_date() - }) - do - { :ok, follower, unfollowed } + with {:ok, %User{} = unfollowed} <- get_user(params), + {:ok, follower, follow_activity} <- User.unfollow(follower, unfollowed), + {:ok, _activity} <- + ActivityPub.insert(%{ + "type" => "Undo", + "actor" => follower.ap_id, + # get latest Follow for these users + "object" => follow_activity.data["id"], + "published" => make_date() + }) do + {:ok, follower, unfollowed} else err -> err end @@ -100,8 +40,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do def block(%User{} = blocker, params) do with {:ok, %User{} = blocked} <- get_user(params), - {:ok, blocker} <- User.block(blocker, blocked) - do + {:ok, blocker} <- User.block(blocker, blocked) do {:ok, blocker, blocked} else err -> err @@ -110,8 +49,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do def unblock(%User{} = blocker, params) do with {:ok, %User{} = blocked} <- get_user(params), - {:ok, blocker} <- User.unblock(blocker, blocked) - do + {:ok, blocker} <- User.unblock(blocker, blocked) do {:ok, blocker, blocked} else err -> err @@ -120,25 +58,22 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do 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_activity_by_object_ap_id(id), - status <- activity_to_status(activity, %{for: user}) do - {:ok, status} + %Activity{} = activity <- Activity.get_create_activity_by_object_ap_id(id) do + {:ok, activity} end end def fav(%User{} = user, ap_id_or_id) do with {:ok, _announce, %{data: %{"id" => id}}} = CommonAPI.favorite(ap_id_or_id, user), - %Activity{} = activity <- Activity.get_create_activity_by_object_ap_id(id), - status <- activity_to_status(activity, %{for: user}) do - {:ok, status} + %Activity{} = activity <- Activity.get_create_activity_by_object_ap_id(id) do + {:ok, activity} end end def unfav(%User{} = user, ap_id_or_id) do with {:ok, %{data: %{"id" => id}}} = CommonAPI.unfavorite(ap_id_or_id, user), - %Activity{} = activity <- Activity.get_create_activity_by_object_ap_id(id), - status <- activity_to_status(activity, %{for: user}) do - {:ok, status} + %Activity{} = activity <- Activity.get_create_activity_by_object_ap_id(id) do + {:ok, activity} end end @@ -163,13 +98,15 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do <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 - } |> Poison.encode! + } + |> Jason.encode!() end end @@ -189,9 +126,11 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do {:ok, user} else {:error, changeset} -> - errors = Ecto.Changeset.traverse_errors(changeset, fn {msg, _opts} -> msg end) - |> Poison.encode! - {:error, %{error: errors}} + errors = + Ecto.Changeset.traverse_errors(changeset, fn {msg, _opts} -> msg end) + |> Jason.encode!() + + {:error, %{error: errors}} end end @@ -209,16 +148,20 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do case target = get_by_id_or_nickname(user_id) do nil -> {:error, "No user with such user_id"} + _ -> {:ok, target} end + %{"screen_name" => nickname} -> case target = Repo.get_by(User, nickname: nickname) do nil -> {:error, "No user with such screen_name"} + _ -> {:ok, target} end + _ -> if user do {:ok, user} @@ -229,6 +172,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do end defp parse_int(string, default) + defp parse_int(string, default) when is_binary(string) do with {n, _} <- Integer.parse(string) do n @@ -236,85 +180,54 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do _e -> default end end + defp parse_int(_, default), do: default - def search(user, %{"q" => query} = params) do + 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 in Activity, - where: fragment("?->>'type' = 'Create'", a.data), - where: fragment("to_tsvector('english', ?->'object'->>'content') @@ plainto_tsquery('english', ?)", a.data, ^query), - limit: ^limit, - offset: ^offset, - order_by: [desc: :inserted_at] # this one isn't indexed so psql won't take the wrong index. - - activities = Repo.all(q) - activities_to_statuses(activities, %{for: user}) - end - - defp activities_to_statuses(activities, opts) do - Enum.map(activities, fn(activity) -> - activity_to_status(activity, opts) - end) - end - - # For likes, fetch the liked activity, too. - defp activity_to_status(%Activity{data: %{"type" => "Like"}} = activity, opts) do - actor = get_in(activity.data, ["actor"]) - user = User.get_cached_by_ap_id(actor) - [liked_activity] = Activity.all_by_object_ap_id(activity.data["object"]) - - ActivityRepresenter.to_map(activity, Map.merge(opts, %{user: user, liked_activity: liked_activity})) - end - - # For announces, fetch the announced activity and the user. - defp activity_to_status(%Activity{data: %{"type" => "Announce"}} = activity, opts) do - actor = get_in(activity.data, ["actor"]) - user = User.get_cached_by_ap_id(actor) - [announced_activity] = Activity.all_by_object_ap_id(activity.data["object"]) - announced_actor = User.get_cached_by_ap_id(announced_activity.data["actor"]) - - ActivityRepresenter.to_map(activity, Map.merge(opts, %{users: [user, announced_actor], announced_activity: announced_activity})) - end - - defp activity_to_status(%Activity{data: %{"type" => "Delete"}} = activity, opts) do - actor = get_in(activity.data, ["actor"]) - user = User.get_cached_by_ap_id(actor) - ActivityRepresenter.to_map(activity, Map.merge(opts, %{user: user})) - end - - defp activity_to_status(activity, opts) do - actor = get_in(activity.data, ["actor"]) - user = User.get_cached_by_ap_id(actor) - # mentioned_users = Repo.all(from user in User, where: user.ap_id in ^activity.data["to"]) - mentioned_users = Enum.map(activity.recipients || [], fn (ap_id) -> - if ap_id do - User.get_cached_by_ap_id(ap_id) - else - nil - end - end) - |> Enum.filter(&(&1)) - - ActivityRepresenter.to_map(activity, Map.merge(opts, %{user: user, mentioned: mentioned_users})) + q = + from( + a in Activity, + where: fragment("?->>'type' = 'Create'", a.data), + where: "https://www.w3.org/ns/activitystreams#Public" in a.recipients, + where: + fragment( + "to_tsvector('english', ?->'object'->>'content') @@ plainto_tsquery('english', ?)", + a.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 defp make_date do - DateTime.utc_now() |> DateTime.to_iso8601 + DateTime.utc_now() |> DateTime.to_iso8601() end + # DEPRECATED mostly, context objects are now created at insertion time. def context_to_conversation_id(context) do with %Object{id: id} <- Object.get_cached_by_ap_id(context) do id - else _e -> + else + _e -> changeset = Object.context_mapping(context) + case Repo.insert(changeset) do - {:ok, %{id: id}} -> id + {:ok, %{id: id}} -> + id + # This should be solved by an upsert, but it seems ecto # has problems accessing the constraint inside the jsonb. - {:error, _} -> Object.get_cached_by_ap_id(context).id + {:error, _} -> + Object.get_cached_by_ap_id(context).id end end end @@ -322,21 +235,25 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do def conversation_id_to_context(id) do with %Object{data: %{"id" => context}} <- Repo.get(Object, id) do context - else _e -> - {:error, "No such conversation"} + else + _e -> + {:error, "No such conversation"} end end def get_external_profile(for_user, uri) do - with {:ok, %User{} = user} <- OStatus.find_or_make_user(uri) do + with %User{} = user <- User.get_or_fetch(uri) do spawn(fn -> with url <- user.info["topic"], - {:ok, %{body: body}} <- @httpoison.get(url, [], follow_redirect: true, timeout: 10000, recv_timeout: 20000) do + {:ok, %{body: body}} <- + @httpoison.get(url, [], follow_redirect: true, timeout: 10000, recv_timeout: 20000) do OStatus.handle_incoming(body) end end) + {:ok, UserView.render("show.json", %{user: user, for: for_user})} - else _e -> + else + _e -> {:error, "Couldn't find user"} 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 848ec218f..960925f42 100644 --- a/lib/pleroma/web/twitter_api/twitter_api_controller.ex +++ b/lib/pleroma/web/twitter_api/twitter_api_controller.ex @@ -1,9 +1,8 @@ defmodule Pleroma.Web.TwitterAPI.Controller do use Pleroma.Web, :controller - alias Pleroma.Web.TwitterAPI.{TwitterAPI, UserView} - alias Pleroma.Web.TwitterAPI.Representers.ActivityRepresenter + alias Pleroma.Web.TwitterAPI.{TwitterAPI, UserView, ActivityView, NotificationView} alias Pleroma.Web.CommonAPI - alias Pleroma.{Repo, Activity, User} + alias Pleroma.{Repo, Activity, User, Notification} alias Pleroma.Web.ActivityPub.ActivityPub alias Ecto.Changeset @@ -16,9 +15,10 @@ defmodule Pleroma.Web.TwitterAPI.Controller do 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 + {:ok, activity} <- + TwitterAPI.create_status(user, Map.put(status_data, "media_ids", media_ids)) do conn - |> json(ActivityRepresenter.to_map(activity, %{user: user})) + |> json(ActivityView.render("activity.json", activity: activity, for: user)) else _ -> empty_status_reply(conn) end @@ -35,43 +35,57 @@ defmodule Pleroma.Web.TwitterAPI.Controller do 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 -> [] + 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 - statuses = TwitterAPI.fetch_public_and_external_statuses(user, params) - {:ok, json} = Poison.encode(statuses) + params = + params + |> Map.put("type", ["Create", "Announce"]) + |> Map.put("blocking_user", user) + + activities = ActivityPub.fetch_public_activities(params) conn - |> json_reply(200, json) + |> render(ActivityView, "index.json", %{activities: activities, for: user}) end def public_timeline(%{assigns: %{user: user}} = conn, params) do - statuses = TwitterAPI.fetch_public_statuses(user, params) - {:ok, json} = Poison.encode(statuses) + params = + params + |> Map.put("type", ["Create", "Announce"]) + |> Map.put("local_only", true) + |> Map.put("blocking_user", user) + + activities = ActivityPub.fetch_public_activities(params) conn - |> json_reply(200, json) + |> render(ActivityView, "index.json", %{activities: activities, for: user}) end def friends_timeline(%{assigns: %{user: user}} = conn, params) do - statuses = TwitterAPI.fetch_friend_statuses(user, params) - {:ok, json} = Poison.encode(statuses) + 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 - |> json_reply(200, json) + |> render(ActivityView, "index.json", %{activities: activities, for: user}) end def show_user(conn, params) do with {:ok, shown} <- TwitterAPI.get_user(params) do if user = conn.assigns.user do - render conn, UserView, "show.json", %{user: shown, for: user} + render(conn, UserView, "show.json", %{user: shown, for: user}) else - render conn, UserView, "show.json", %{user: shown} + render(conn, UserView, "show.json", %{user: shown}) end else {:error, msg} -> @@ -82,52 +96,69 @@ defmodule Pleroma.Web.TwitterAPI.Controller do def user_timeline(%{assigns: %{user: user}} = conn, params) do case TwitterAPI.get_user(user, params) do {:ok, target_user} -> - params = Map.merge(params, %{"actor_id" => target_user.ap_id, "whole_db" => true}) - statuses = TwitterAPI.fetch_user_statuses(user, params) + params = + params + |> Map.put("type", ["Create", "Announce"]) + |> Map.put("actor_id", target_user.ap_id) + |> Map.put("whole_db", true) + + activities = ActivityPub.fetch_public_activities(params) + conn - |> json_reply(200, statuses |> Poison.encode!) + |> render(ActivityView, "index.json", %{activities: activities, for: user}) + {:error, msg} -> bad_request_reply(conn, msg) end end def mentions_timeline(%{assigns: %{user: user}} = conn, params) do - statuses = TwitterAPI.fetch_mentions(user, params) - {:ok, json} = Poison.encode(statuses) + activities = ActivityPub.fetch_activities([user.ap_id], params) + + conn + |> render(ActivityView, "index.json", %{activities: activities, for: user}) + end + + def notifications(%{assigns: %{user: user}} = conn, params) do + notifications = Notification.for_user(user, params) conn - |> json_reply(200, json) + |> render(NotificationView, "notification.json", %{notifications: notifications, for: user}) end def follow(%{assigns: %{user: user}} = conn, params) do case TwitterAPI.follow(user, params) do {:ok, user, followed, _activity} -> render(conn, UserView, "show.json", %{user: followed, for: user}) - {:error, msg} -> forbidden_json_reply(conn, msg) + + {: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} -> - render conn, UserView, "show.json", %{user: blocked, for: user} - {:error, msg} -> forbidden_json_reply(conn, msg) + render(conn, UserView, "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} -> - render conn, UserView, "show.json", %{user: blocked, for: user} - {:error, msg} -> forbidden_json_reply(conn, msg) + render(conn, UserView, "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, delete} <- CommonAPI.delete(id, user) do - json = ActivityRepresenter.to_json(delete, %{user: user, for: user}) - conn - |> json_reply(200, json) + render(conn, ActivityView, "activity.json", %{activity: delete, for: user}) end end @@ -135,27 +166,36 @@ defmodule Pleroma.Web.TwitterAPI.Controller do case TwitterAPI.unfollow(user, params) do {:ok, user, unfollowed} -> render(conn, UserView, "show.json", %{user: unfollowed, for: user}) - {:error, msg} -> forbidden_json_reply(conn, msg) + + {:error, msg} -> + forbidden_json_reply(conn, msg) end end def fetch_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do - response = Poison.encode!(TwitterAPI.fetch_status(user, id)) - - conn - |> json_reply(200, response) + with %Activity{} = activity <- Repo.get(Activity, id), + true <- ActivityPub.visible_for_user?(activity, user) do + render(conn, ActivityView, "activity.json", %{activity: activity, for: user}) + end end def fetch_conversation(%{assigns: %{user: user}} = conn, %{"id" => id}) do id = String.to_integer(id) - response = Poison.encode!(TwitterAPI.fetch_conversation(user, id)) - conn - |> json_reply(200, response) + with context when is_binary(context) <- TwitterAPI.conversation_id_to_context(id), + activities <- + ActivityPub.fetch_activities_for_context(context, %{ + "blocking_user" => user, + "user" => user + }) do + conn + |> render(ActivityView, "index.json", %{activities: activities, for: user}) + end end def upload(conn, %{"media" => media}) do response = TwitterAPI.upload(media) + conn |> put_resp_content_type("application/atom+xml") |> send_resp(200, response) @@ -163,12 +203,14 @@ defmodule Pleroma.Web.TwitterAPI.Controller do def upload_json(conn, %{"media" => media}) do response = TwitterAPI.upload(media, "json") + conn |> json_reply(200, response) end def get_by_id_or_ap_id(id) do activity = Repo.get(Activity, id) || Activity.get_create_activity_by_object_ap_id(id) + if activity.data["type"] == "Create" do activity else @@ -177,20 +219,20 @@ defmodule Pleroma.Web.TwitterAPI.Controller do end def favorite(%{assigns: %{user: user}} = conn, %{"id" => id}) do - with {:ok, status} <- TwitterAPI.fav(user, id) do - json(conn, status) + with {:ok, activity} <- TwitterAPI.fav(user, id) do + render(conn, ActivityView, "activity.json", %{activity: activity, for: user}) end end def unfavorite(%{assigns: %{user: user}} = conn, %{"id" => id}) do - with {:ok, status} <- TwitterAPI.unfav(user, id) do - json(conn, status) + with {:ok, activity} <- TwitterAPI.unfav(user, id) do + render(conn, ActivityView, "activity.json", %{activity: activity, for: user}) end end def retweet(%{assigns: %{user: user}} = conn, %{"id" => id}) do - with {:ok, status} <- TwitterAPI.repeat(user, id) do - json(conn, status) + with {:ok, activity} <- TwitterAPI.repeat(user, id) do + render(conn, ActivityView, "activity.json", %{activity: activity, for: user}) end end @@ -199,8 +241,8 @@ defmodule Pleroma.Web.TwitterAPI.Controller do render(conn, UserView, "show.json", %{user: user}) else {:error, errors} -> - conn - |> json_reply(400, Poison.encode!(errors)) + conn + |> json_reply(400, Jason.encode!(errors)) end end @@ -219,8 +261,9 @@ defmodule Pleroma.Web.TwitterAPI.Controller do change <- User.info_changeset(user, %{info: new_info}), {:ok, user} <- User.update_and_set_cache(change) do CommonAPI.update(user) - %{"url" => [ %{ "href" => href } | _ ]} = object.data - response = %{ url: href } |> Poison.encode! + %{"url" => [%{"href" => href} | _]} = object.data + response = %{url: href} |> Jason.encode!() + conn |> json_reply(200, response) end @@ -231,8 +274,9 @@ defmodule Pleroma.Web.TwitterAPI.Controller do new_info <- Map.put(user.info, "background", object.data), change <- User.info_changeset(user, %{info: new_info}), {:ok, _user} <- User.update_and_set_cache(change) do - %{"url" => [ %{ "href" => href } | _ ]} = object.data - response = %{ url: href } |> Poison.encode! + %{"url" => [%{"href" => href} | _]} = object.data + response = %{url: href} |> Jason.encode!() + conn |> json_reply(200, response) end @@ -240,7 +284,7 @@ defmodule Pleroma.Web.TwitterAPI.Controller do def external_profile(%{assigns: %{user: current_user}} = conn, %{"profileurl" => uri}) do with {:ok, user_map} <- TwitterAPI.get_external_profile(current_user, uri), - response <- Poison.encode!(user_map) do + response <- Jason.encode!(user_map) do conn |> json_reply(200, response) else @@ -259,7 +303,7 @@ defmodule Pleroma.Web.TwitterAPI.Controller do changeset <- User.info_changeset(user, %{info: updated_info}), {:ok, _user} <- User.update_and_set_cache(changeset) do conn - |> json_reply(200, Poison.encode!(mrn)) + |> json_reply(200, Jason.encode!(mrn)) else _e -> bad_request_reply(conn, "Can't update.") end @@ -285,9 +329,10 @@ defmodule Pleroma.Web.TwitterAPI.Controller do 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) - |> Poison.encode! + ids = + friends + |> Enum.map(fn x -> x.id end) + |> Jason.encode!() json(conn, ids) else @@ -296,15 +341,17 @@ defmodule Pleroma.Web.TwitterAPI.Controller do end def empty_array(conn, _params) do - json(conn, Poison.encode!([])) + json(conn, Jason.encode!([])) end def update_profile(%{assigns: %{user: user}} = conn, params) do - params = if bio = params["description"] do - Map.put(params, "bio", bio) - else - params - end + params = + if bio = params["description"] do + bio_brs = Regex.replace(~r/\r?\n/, bio, "<br>") + Map.put(params, "bio", bio_brs) + else + params + end with changeset <- User.update_changeset(user, params), {:ok, user} <- User.update_and_set_cache(changeset) do @@ -318,8 +365,10 @@ defmodule Pleroma.Web.TwitterAPI.Controller do end def search(%{assigns: %{user: user}} = conn, %{"q" => _query} = params) do + activities = TwitterAPI.search(user, params) + conn - |> json(TwitterAPI.search(user, params)) + |> render(ActivityView, "index.json", %{activities: activities, for: user}) end defp bad_request_reply(conn, error_message) do @@ -339,6 +388,6 @@ defmodule Pleroma.Web.TwitterAPI.Controller do end defp error_json(conn, error_message) do - %{"error" => error_message, "request" => conn.request_path} |> Poison.encode! + %{"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 new file mode 100644 index 000000000..580d4648c --- /dev/null +++ b/lib/pleroma/web/twitter_api/views/activity_view.ex @@ -0,0 +1,268 @@ +defmodule Pleroma.Web.TwitterAPI.ActivityView do + use Pleroma.Web, :view + alias Pleroma.Web.CommonAPI.Utils + alias Pleroma.User + alias Pleroma.Web.TwitterAPI.UserView + alias Pleroma.Web.TwitterAPI.ActivityView + alias Pleroma.Web.TwitterAPI.TwitterAPI + alias Pleroma.Web.TwitterAPI.Representers.ObjectRepresenter + alias Pleroma.Activity + alias Pleroma.Object + alias Pleroma.User + alias Pleroma.Repo + alias Pleroma.Formatter + + import Ecto.Query + + 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 -> TwitterAPI.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 == "https://www.w3.org/ns/activitystreams#Public" -> + nil + + true -> + User.get_cached_by_ap_id(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) + + 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_activity_by_object_ap_id(activity.data["object"]) + + text = "#{user.nickname} retweeted 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_activity_by_object_ap_id(activity.data["object"]) + + created_at = + activity.data["published"] + |> Utils.date_to_asctime() + + text = "#{user.nickname} favorited a status." + + %{ + "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, + "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}} = activity} = opts + ) do + user = get_user(activity.data["actor"], opts) + + created_at = object["published"] |> Utils.date_to_asctime() + like_count = object["like_count"] || 0 + announcement_count = object["announcement_count"] || 0 + favorited = opts[:for] && opts[:for].ap_id in (object["likes"] || []) + repeated = opts[:for] && opts[:for].ap_id in (object["announcements"] || []) + + attentions = + activity.recipients + |> 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 = activity.data["object"]["tag"] || [] + possibly_sensitive = activity.data["object"]["sensitive"] || Enum.member?(tags, "nsfw") + + tags = if possibly_sensitive, do: Enum.uniq(["nsfw" | tags]), else: tags + + summary = activity.data["object"]["summary"] + content = object["content"] + + content = + if !!summary and summary != "" do + "<span>#{activity.data["object"]["summary"]}</span><br />#{content}</span>" + else + content + end + + html = + HtmlSanitizeEx.basic_html(content) + |> Formatter.emojify(object["emoji"]) + + %{ + "id" => activity.id, + "uri" => activity.data["object"]["id"], + "user" => UserView.render("show.json", %{user: user, for: opts[:for]}), + "statusnet_html" => html, + "text" => HtmlSanitizeEx.strip_tags(content), + "is_local" => activity.local, + "is_post_verb" => true, + "created_at" => created_at, + "in_reply_to_status_id" => object["inReplyToStatusId"], + "statusnet_conversation_id" => conversation_id, + "attachments" => (object["attachment"] || []) |> ObjectRepresenter.enum_to_list(opts), + "attentions" => attentions, + "fave_num" => like_count, + "repeat_num" => announcement_count, + "favorited" => !!favorited, + "repeated" => !!repeated, + "external_url" => object["external_url"] || object["id"], + "tags" => tags, + "activity_type" => "post", + "possibly_sensitive" => possibly_sensitive + } + end +end diff --git a/lib/pleroma/web/twitter_api/views/notification_view.ex b/lib/pleroma/web/twitter_api/views/notification_view.ex new file mode 100644 index 000000000..9eeb3afdc --- /dev/null +++ b/lib/pleroma/web/twitter_api/views/notification_view.ex @@ -0,0 +1,64 @@ +defmodule Pleroma.Web.TwitterAPI.NotificationView do + use Pleroma.Web, :view + alias Pleroma.{Notification, User} + alias Pleroma.Web.CommonAPI.Utils + alias Pleroma.Web.TwitterAPI.UserView + alias Pleroma.Web.TwitterAPI.ActivityView + + defp get_user(ap_id, opts) do + cond do + user = opts[:users][ap_id] -> + user + + String.ends_with?(ap_id, "/followers") -> + nil + + ap_id == "https://www.w3.org/ns/activitystreams#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 index 6fb07f052..31527caae 100644 --- a/lib/pleroma/web/twitter_api/views/user_view.ex +++ b/lib/pleroma/web/twitter_api/views/user_view.ex @@ -14,20 +14,22 @@ defmodule Pleroma.Web.TwitterAPI.UserView do def render("user.json", %{user: user = %User{}} = assigns) do image = User.avatar_url(user) |> MediaProxy.url() - {following, follows_you, statusnet_blocking} = if assigns[:for] do - { - User.following?(assigns[:for], user), - User.following?(user, assigns[:for]), - User.blocks?(assigns[:for], user) - } - else - {false, false, false} - end + + {following, follows_you, statusnet_blocking} = + if assigns[:for] do + { + User.following?(assigns[:for], user), + User.following?(user, assigns[:for]), + User.blocks?(assigns[:for], user) + } + else + {false, false, false} + end user_info = User.get_cached_user_info(user) data = %{ - "created_at" => user.inserted_at |> Utils.format_naive_asctime, + "created_at" => user.inserted_at |> Utils.format_naive_asctime(), "description" => HtmlSanitizeEx.strip_tags(user.bio), "favourites_count" => 0, "followers_count" => user_info[:follower_count], @@ -59,9 +61,14 @@ defmodule Pleroma.Web.TwitterAPI.UserView do end end - def render("short.json", %{user: %User{ - nickname: nickname, id: id, ap_id: ap_id, name: name - }}) do + def render("short.json", %{ + user: %User{ + nickname: nickname, + id: id, + ap_id: ap_id, + name: name + } + }) do %{ "fullname" => name, "id" => id, @@ -71,6 +78,6 @@ defmodule Pleroma.Web.TwitterAPI.UserView do } end - defp image_url(%{"url" => [ %{ "href" => href } | _ ]}), do: href + defp image_url(%{"url" => [%{"href" => href} | _]}), do: href defp image_url(_), do: nil end |