Page MenuHomePhorge

No OneTemporary

Size
49 KB
Referenced Files
None
Subscribers
None
diff --git a/lib/pleroma/web/twitter_api/representers/activity_representer.ex b/lib/pleroma/web/twitter_api/representers/activity_representer.ex
index c216c606e..9a4954de8 100644
--- a/lib/pleroma/web/twitter_api/representers/activity_representer.ex
+++ b/lib/pleroma/web/twitter_api/representers/activity_representer.ex
@@ -1,221 +1,223 @@
+# 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
alias Pleroma.{Activity, User}
alias Pleroma.Web.TwitterAPI.{TwitterAPI, UserView}
alias Pleroma.Web.CommonAPI.Utils
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)
end
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()
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]}),
"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" => conversation_id(announced_activity),
"external_url" => activity.data["id"],
"activity_type" => "repeat"
}
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()
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 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]}),
"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
# 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()
text = "#{user.nickname} undid the action at #{undid_activity}"
%{
"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" => "undo"
}
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()
%{
"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 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"] || [])
repeated = opts[:for] && opts[:for].ap_id in (object["announcements"] || [])
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)
conversation_id = conversation_id(activity)
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 =
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" => to_boolean(favorited),
"repeated" => to_boolean(repeated),
"external_url" => object["external_url"] || object["id"],
"tags" => tags,
"activity_type" => "post",
"possibly_sensitive" => possibly_sensitive
}
end
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
end
end
defp to_boolean(false) do
false
end
defp to_boolean(nil) do
false
end
defp to_boolean(_) do
true
end
end
diff --git a/lib/pleroma/web/twitter_api/twitter_api.ex b/lib/pleroma/web/twitter_api/twitter_api.ex
index b3b505be2..30362ef70 100644
--- a/lib/pleroma/web/twitter_api/twitter_api.ex
+++ b/lib/pleroma/web/twitter_api/twitter_api.ex
@@ -1,387 +1,259 @@
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
@httpoison Application.get_env(:pleroma, :httpoison)
def create_status(%User{} = user, %{"status" => _} = data) 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, follower, followed, activity}
else
err -> err
end
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,
# get latest Follow for these users
"object" => follow_activity.data["id"],
"published" => make_date()
}) do
{:ok, follower, unfollowed}
else
err -> err
end
end
def block(%User{} = blocker, params) do
with {:ok, %User{} = blocked} <- get_user(params),
{:ok, blocker} <- User.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) 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_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
def upload(%Plug.Upload{} = file, format \\ "xml") do
{:ok, object} = ActivityPub.upload(file)
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) do
params = %{
nickname: params["nickname"],
name: params["fullname"],
bio: params["bio"],
email: params["email"],
password: params["password"],
password_confirmation: params["confirm"]
}
changeset = User.register_changeset(%User{}, params)
with {:ok, user} <- Repo.insert(changeset) do
{:ok, user}
else
{:error, changeset} ->
errors =
Ecto.Changeset.traverse_errors(changeset, fn {msg, _opts} -> msg end)
|> Jason.encode!()
{:error, %{error: errors}}
end
end
def get_by_id_or_nickname(id_or_nickname) do
if !is_integer(id_or_nickname) && :error == Integer.parse(id_or_nickname) do
Repo.get_by(User, nickname: id_or_nickname)
else
Repo.get(User, id_or_nickname)
end
end
def get_user(user \\ nil, params) do
case params do
%{"user_id" => user_id} ->
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}
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
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,
# this one isn't indexed so psql won't take the wrong index.
order_by: [desc: :inserted_at]
)
activities = Repo.all(q)
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})
- )
- end
-
defp make_date do
DateTime.utc_now() |> DateTime.to_iso8601()
end
def context_to_conversation_id(context) do
with %Object{id: id} <- Object.get_cached_by_ap_id(context) do
id
else
_e ->
changeset = Object.context_mapping(context)
case Repo.insert(changeset) do
{: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
end
end
end
def conversation_id_to_context(id) do
with %Object{data: %{"id" => context}} <- Repo.get(Object, id) do
context
else
_e ->
{:error, "No such conversation"}
end
end
def get_external_profile(for_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
OStatus.handle_incoming(body)
end
end)
{: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 fb09deada..157e4826b 100644
--- a/lib/pleroma/web/twitter_api/twitter_api_controller.ex
+++ b/lib/pleroma/web/twitter_api/twitter_api_controller.ex
@@ -1,389 +1,385 @@
defmodule Pleroma.Web.TwitterAPI.Controller do
use Pleroma.Web, :controller
alias Pleroma.Web.TwitterAPI.{TwitterAPI, UserView, ActivityView}
- alias Pleroma.Web.TwitterAPI.Representers.ActivityRepresenter
alias Pleroma.Web.CommonAPI
alias Pleroma.{Repo, Activity, User}
alias Pleroma.Web.ActivityPub.ActivityPub
alias Ecto.Changeset
require Logger
def verify_credentials(%{assigns: %{user: user}} = conn, _params) do
token = Phoenix.Token.sign(conn, "user socket", user.id)
render(conn, UserView, "show.json", %{user: user, token: token})
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", "Follow"])
|> Map.put("blocking_user", user)
activities = ActivityPub.fetch_public_activities(params)
conn
|> render(ActivityView, "index.json", %{activities: activities, for: user})
end
def public_timeline(%{assigns: %{user: user}} = conn, params) do
params =
params
|> Map.put("type", ["Create", "Announce", "Follow"])
|> Map.put("local_only", true)
|> Map.put("blocking_user", user)
activities = ActivityPub.fetch_public_activities(params)
conn
|> render(ActivityView, "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
|> 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})
else
render(conn, UserView, "show.json", %{user: shown})
end
else
{:error, msg} ->
bad_request_reply(conn, msg)
end
end
def user_timeline(%{assigns: %{user: user}} = conn, params) do
case TwitterAPI.get_user(user, params) do
{:ok, target_user} ->
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
|> 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
activities = ActivityPub.fetch_activities([user.ap_id], params)
conn
|> render(ActivityView, "index.json", %{activities: activities, 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)
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)
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)
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
def unfollow(%{assigns: %{user: user}} = conn, params) 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)
end
end
def fetch_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
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)
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)
end
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
Activity.get_create_activity_by_object_ap_id(activity.data["object"])
end
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
def register(conn, params) do
with {:ok, user} <- TwitterAPI.register_user(params) do
render(conn, UserView, "show.json", %{user: user})
else
{:error, errors} ->
conn
|> json_reply(400, Jason.encode!(errors))
end
end
def update_avatar(%{assigns: %{user: user}} = conn, params) do
{:ok, object} = ActivityPub.upload(params)
change = Changeset.change(user, %{avatar: object.data})
{:ok, user} = User.update_and_set_cache(change)
CommonAPI.update(user)
render(conn, UserView, "show.json", %{user: user, for: user})
end
def update_banner(%{assigns: %{user: user}} = conn, params) do
with {:ok, object} <- ActivityPub.upload(%{"img" => params["banner"]}),
new_info <- Map.put(user.info, "banner", object.data),
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} |> Jason.encode!()
conn
|> json_reply(200, response)
end
end
def update_background(%{assigns: %{user: user}} = conn, params) do
with {:ok, object} <- ActivityPub.upload(params),
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} |> 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 update_most_recent_notification(%{assigns: %{user: user}} = conn, %{"id" => id}) do
with id when is_number(id) <- String.to_integer(id),
info <- user.info,
mrn <- max(id, user.info["most_recent_notification"] || 0),
updated_info <- Map.put(info, "most_recent_notification", mrn),
changeset <- User.info_changeset(user, %{info: updated_info}),
{:ok, _user} <- User.update_and_set_cache(changeset) do
conn
|> json_reply(200, Jason.encode!(mrn))
else
_e -> bad_request_reply(conn, "Can't update.")
end
end
def followers(conn, params) do
with {:ok, user} <- TwitterAPI.get_user(conn.assigns.user, params),
{:ok, followers} <- User.get_followers(user) do
render(conn, UserView, "index.json", %{users: followers, for: user})
else
_e -> bad_request_reply(conn, "Can't get followers")
end
end
def friends(conn, params) do
with {:ok, user} <- TwitterAPI.get_user(conn.assigns.user, params),
{:ok, friends} <- User.get_friends(user) do
render(conn, UserView, "index.json", %{users: friends, for: user})
else
_e -> bad_request_reply(conn, "Can't get friends")
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 update_profile(%{assigns: %{user: user}} = conn, params) do
params =
if bio = params["description"] do
Map.put(params, "bio", bio)
else
params
end
with changeset <- User.update_changeset(user, params),
{:ok, user} <- User.update_and_set_cache(changeset) do
CommonAPI.update(user)
render(conn, UserView, "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)
conn
|> render(ActivityView, "index.json", %{activities: activities, for: user})
end
defp bad_request_reply(conn, error_message) do
json = error_json(conn, error_message)
json_reply(conn, 400, json)
end
defp json_reply(conn, status, json) do
conn
|> put_resp_content_type("application/json")
|> 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
defp error_json(conn, error_message) do
%{"error" => error_message, "request" => conn.request_path} |> Jason.encode!()
end
end
diff --git a/test/web/twitter_api/twitter_api_test.exs b/test/web/twitter_api/twitter_api_test.exs
index d4341a867..af565c0e5 100644
--- a/test/web/twitter_api/twitter_api_test.exs
+++ b/test/web/twitter_api/twitter_api_test.exs
@@ -1,441 +1,306 @@
defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do
use Pleroma.DataCase
- alias Pleroma.Builders.{UserBuilder, ActivityBuilder}
+ alias Pleroma.Builders.UserBuilder
alias Pleroma.Web.TwitterAPI.{TwitterAPI, UserView}
- alias Pleroma.Web.CommonAPI.Utils
alias Pleroma.{Activity, User, Object, Repo}
- alias Pleroma.Web.TwitterAPI.Representers.ActivityRepresenter
alias Pleroma.Web.ActivityPub.ActivityPub
+ alias Pleroma.Web.TwitterAPI.ActivityView
import Pleroma.Factory
test "create a status" do
user = insert(:user)
_mentioned_user = UserBuilder.insert(%{nickname: "shp", ap_id: "shp"})
object_data = %{
"type" => "Image",
"url" => [
%{
"type" => "Link",
"mediaType" => "image/jpg",
"href" => "http://example.org/image.jpg"
}
],
"uuid" => 1
}
object = Repo.insert!(%Object{data: object_data})
input = %{
"status" =>
"Hello again, @shp.<script></script>\nThis is on another :moominmamma: line. #2hu #epic #phantasmagoric",
"media_ids" => [object.id]
}
{:ok, activity = %Activity{}} = TwitterAPI.create_status(user, input)
expected_text =
"Hello again, <span><a href='shp'>@<span>shp</span></a></span>.&lt;script&gt;&lt;/script&gt;<br>This is on another :moominmamma: line. <a href='http://localhost:4001/tag/2hu' rel='tag'>#2hu</a> <a href='http://localhost:4001/tag/epic' rel='tag'>#epic</a> <a href='http://localhost:4001/tag/phantasmagoric' rel='tag'>#phantasmagoric</a><br><a href=\"http://example.org/image.jpg\" class='attachment'>image.jpg</a>"
assert get_in(activity.data, ["object", "content"]) == expected_text
assert get_in(activity.data, ["object", "type"]) == "Note"
assert get_in(activity.data, ["object", "actor"]) == user.ap_id
assert get_in(activity.data, ["actor"]) == user.ap_id
assert Enum.member?(get_in(activity.data, ["cc"]), User.ap_followers(user))
assert Enum.member?(
get_in(activity.data, ["to"]),
"https://www.w3.org/ns/activitystreams#Public"
)
assert Enum.member?(get_in(activity.data, ["cc"]), "shp")
assert activity.local == true
assert %{"moominmamma" => "http://localhost:4001/finmoji/128px/moominmamma-128.png"} =
activity.data["object"]["emoji"]
# hashtags
assert activity.data["object"]["tag"] == ["2hu", "epic", "phantasmagoric"]
# Add a context
assert is_binary(get_in(activity.data, ["context"]))
assert is_binary(get_in(activity.data, ["object", "context"]))
assert is_list(activity.data["object"]["attachment"])
assert activity.data["object"] == Object.get_by_ap_id(activity.data["object"]["id"]).data
user = User.get_by_ap_id(user.ap_id)
assert user.info["note_count"] == 1
end
test "create a status that is a reply" do
user = insert(:user)
input = %{
"status" => "Hello again."
}
{:ok, activity = %Activity{}} = TwitterAPI.create_status(user, input)
input = %{
"status" => "Here's your (you).",
"in_reply_to_status_id" => activity.id
}
{:ok, reply = %Activity{}} = TwitterAPI.create_status(user, input)
assert get_in(reply.data, ["context"]) == get_in(activity.data, ["context"])
assert get_in(reply.data, ["object", "context"]) ==
get_in(activity.data, ["object", "context"])
assert get_in(reply.data, ["object", "inReplyTo"]) == get_in(activity.data, ["object", "id"])
assert get_in(reply.data, ["object", "inReplyToStatusId"]) == activity.id
end
- test "fetch public statuses, excluding remote ones." do
- %{public: activity, user: user} = ActivityBuilder.public_and_non_public()
- insert(:note_activity, %{local: false})
-
- follower = insert(:user, following: [User.ap_followers(user)])
-
- statuses = TwitterAPI.fetch_public_statuses(follower)
-
- assert length(statuses) == 1
-
- assert Enum.at(statuses, 0) ==
- ActivityRepresenter.to_map(activity, %{user: user, for: follower})
- end
-
- test "fetch whole known network statuses" do
- %{public: activity, user: user} = ActivityBuilder.public_and_non_public()
- insert(:note_activity, %{local: false})
-
- follower = insert(:user, following: [user.follower_address])
-
- statuses = TwitterAPI.fetch_public_and_external_statuses(follower)
-
- assert length(statuses) == 2
-
- assert Enum.at(statuses, 0) ==
- ActivityRepresenter.to_map(activity, %{user: user, for: follower})
- end
-
- test "fetch friends' statuses" do
- user = insert(:user, %{following: ["someguy/followers"]})
- {:ok, activity} = ActivityBuilder.insert(%{"to" => ["someguy/followers"]})
- {:ok, direct_activity} = ActivityBuilder.insert(%{"to" => [user.ap_id]})
-
- statuses = TwitterAPI.fetch_friend_statuses(user)
-
- activity_user = Repo.get_by(User, ap_id: activity.data["actor"])
- direct_activity_user = Repo.get_by(User, ap_id: direct_activity.data["actor"])
-
- assert length(statuses) == 2
- assert Enum.at(statuses, 0) == ActivityRepresenter.to_map(activity, %{user: activity_user})
-
- assert Enum.at(statuses, 1) ==
- ActivityRepresenter.to_map(direct_activity, %{
- user: direct_activity_user,
- mentioned: [user]
- })
- end
-
- test "fetch user's mentions" do
- user = insert(:user)
- {:ok, activity} = ActivityBuilder.insert(%{"to" => [user.ap_id]})
- activity_user = Repo.get_by(User, ap_id: activity.data["actor"])
-
- statuses = TwitterAPI.fetch_mentions(user)
-
- assert length(statuses) == 1
-
- assert Enum.at(statuses, 0) ==
- ActivityRepresenter.to_map(activity, %{user: activity_user, mentioned: [user]})
- end
-
- test "get a user by params" do
- user1_result = {:ok, user1} = UserBuilder.insert(%{ap_id: "some id", email: "test@pleroma"})
-
- {:ok, user2} =
- UserBuilder.insert(%{ap_id: "some other id", nickname: "testname2", email: "test2@pleroma"})
-
- assert {:error, "You need to specify screen_name or user_id"} == TwitterAPI.get_user(nil, nil)
- assert user1_result == TwitterAPI.get_user(nil, %{"user_id" => user1.id})
- assert user1_result == TwitterAPI.get_user(nil, %{"user_id" => user1.nickname})
- assert user1_result == TwitterAPI.get_user(nil, %{"screen_name" => user1.nickname})
- assert user1_result == TwitterAPI.get_user(user1, nil)
- assert user1_result == TwitterAPI.get_user(user2, %{"user_id" => user1.id})
- assert user1_result == TwitterAPI.get_user(user2, %{"screen_name" => user1.nickname})
-
- assert {:error, "No user with such screen_name"} ==
- TwitterAPI.get_user(nil, %{"screen_name" => "Satan"})
-
- assert {:error, "No user with such user_id"} == TwitterAPI.get_user(nil, %{"user_id" => 666})
- end
-
- test "fetch user's statuses" do
- {:ok, user1} = UserBuilder.insert(%{ap_id: "some id", email: "test@pleroma"})
-
- {:ok, user2} =
- UserBuilder.insert(%{ap_id: "some other id", nickname: "testname2", email: "test2@pleroma"})
-
- {:ok, status1} = ActivityBuilder.insert(%{"id" => 1}, %{user: user1})
- {:ok, status2} = ActivityBuilder.insert(%{"id" => 2}, %{user: user2})
-
- user1_statuses = TwitterAPI.fetch_user_statuses(user1, %{"actor_id" => user1.ap_id})
-
- assert length(user1_statuses) == 1
- assert Enum.at(user1_statuses, 0) == ActivityRepresenter.to_map(status1, %{user: user1})
-
- user2_statuses = TwitterAPI.fetch_user_statuses(user1, %{"actor_id" => user2.ap_id})
-
- assert length(user2_statuses) == 1
- assert Enum.at(user2_statuses, 0) == ActivityRepresenter.to_map(status2, %{user: user2})
- end
-
- test "fetch a single status" do
- {:ok, activity} = ActivityBuilder.insert()
- {:ok, user} = UserBuilder.insert()
- actor = Repo.get_by!(User, ap_id: activity.data["actor"])
-
- status = TwitterAPI.fetch_status(user, activity.id)
-
- assert status == ActivityRepresenter.to_map(activity, %{for: user, user: actor})
- end
-
test "Follow another user using user_id" do
user = insert(:user)
followed = insert(:user)
{:ok, user, followed, _activity} = TwitterAPI.follow(user, %{"user_id" => followed.id})
assert User.ap_followers(followed) in user.following
{:error, msg} = TwitterAPI.follow(user, %{"user_id" => followed.id})
assert msg == "Could not follow user: #{followed.nickname} is already on your list."
end
test "Follow another user using screen_name" do
user = insert(:user)
followed = insert(:user)
{:ok, user, followed, _activity} =
TwitterAPI.follow(user, %{"screen_name" => followed.nickname})
assert User.ap_followers(followed) in user.following
followed = User.get_by_ap_id(followed.ap_id)
assert followed.info["follower_count"] == 1
{:error, msg} = TwitterAPI.follow(user, %{"screen_name" => followed.nickname})
assert msg == "Could not follow user: #{followed.nickname} is already on your list."
end
test "Unfollow another user using user_id" do
unfollowed = insert(:user)
user = insert(:user, %{following: [User.ap_followers(unfollowed)]})
ActivityPub.follow(user, unfollowed)
{:ok, user, unfollowed} = TwitterAPI.unfollow(user, %{"user_id" => unfollowed.id})
assert user.following == []
{:error, msg} = TwitterAPI.unfollow(user, %{"user_id" => unfollowed.id})
assert msg == "Not subscribed!"
end
test "Unfollow another user using screen_name" do
unfollowed = insert(:user)
user = insert(:user, %{following: [User.ap_followers(unfollowed)]})
ActivityPub.follow(user, unfollowed)
{:ok, user, unfollowed} = TwitterAPI.unfollow(user, %{"screen_name" => unfollowed.nickname})
assert user.following == []
{:error, msg} = TwitterAPI.unfollow(user, %{"screen_name" => unfollowed.nickname})
assert msg == "Not subscribed!"
end
test "Block another user using user_id" do
user = insert(:user)
blocked = insert(:user)
{:ok, user, blocked} = TwitterAPI.block(user, %{"user_id" => blocked.id})
assert User.blocks?(user, blocked)
end
test "Block another user using screen_name" do
user = insert(:user)
blocked = insert(:user)
{:ok, user, blocked} = TwitterAPI.block(user, %{"screen_name" => blocked.nickname})
assert User.blocks?(user, blocked)
end
test "Unblock another user using user_id" do
unblocked = insert(:user)
user = insert(:user)
User.block(user, unblocked)
{:ok, user, _unblocked} = TwitterAPI.unblock(user, %{"user_id" => unblocked.id})
assert user.info["blocks"] == []
end
test "Unblock another user using screen_name" do
unblocked = insert(:user)
user = insert(:user)
User.block(user, unblocked)
{:ok, user, _unblocked} = TwitterAPI.unblock(user, %{"screen_name" => unblocked.nickname})
assert user.info["blocks"] == []
end
- test "fetch statuses in a context using the conversation id" do
- {:ok, user} = UserBuilder.insert()
- {:ok, activity} = ActivityBuilder.insert(%{"type" => "Create", "context" => "2hu"})
- {:ok, activity_two} = ActivityBuilder.insert(%{"type" => "Create", "context" => "2hu"})
- {:ok, _activity_three} = ActivityBuilder.insert(%{"type" => "Create", "context" => "3hu"})
-
- {:ok, object} = Object.context_mapping("2hu") |> Repo.insert()
-
- statuses = TwitterAPI.fetch_conversation(user, object.id)
-
- assert length(statuses) == 2
- assert Enum.at(statuses, 1)["id"] == activity.id
- assert Enum.at(statuses, 0)["id"] == activity_two.id
- end
-
test "upload a file" do
file = %Plug.Upload{
content_type: "image/jpg",
path: Path.absname("test/fixtures/image.jpg"),
filename: "an_image.jpg"
}
response = TwitterAPI.upload(file)
assert is_binary(response)
end
- test "it favorites a status, returns the updated status" do
+ test "it favorites a status, returns the updated activity" do
user = insert(:user)
note_activity = insert(:note_activity)
- activity_user = Repo.get_by!(User, ap_id: note_activity.data["actor"])
{:ok, status} = TwitterAPI.fav(user, note_activity.id)
updated_activity = Activity.get_by_ap_id(note_activity.data["id"])
- assert status ==
- ActivityRepresenter.to_map(updated_activity, %{user: activity_user, for: user})
+ assert status == updated_activity
end
- test "it unfavorites a status, returns the updated status" do
+ test "it unfavorites a status, returns the updated activity" do
user = insert(:user)
note_activity = insert(:note_activity)
- activity_user = Repo.get_by!(User, ap_id: note_activity.data["actor"])
object = Object.get_by_ap_id(note_activity.data["object"]["id"])
{:ok, _like_activity, _object} = ActivityPub.like(user, object)
updated_activity = Activity.get_by_ap_id(note_activity.data["id"])
- assert ActivityRepresenter.to_map(updated_activity, %{user: activity_user, for: user})[
- "fave_num"
- ] == 1
+ assert ActivityView.render("activity.json", activity: updated_activity)["fave_num"] == 1
- {:ok, status} = TwitterAPI.unfav(user, note_activity.id)
+ {:ok, activity} = TwitterAPI.unfav(user, note_activity.id)
- assert status["fave_num"] == 0
+ assert ActivityView.render("activity.json", activity: activity)["fave_num"] == 0
end
test "it retweets a status and returns the retweet" do
user = insert(:user)
note_activity = insert(:note_activity)
- activity_user = Repo.get_by!(User, ap_id: note_activity.data["actor"])
{:ok, status} = TwitterAPI.repeat(user, note_activity.id)
updated_activity = Activity.get_by_ap_id(note_activity.data["id"])
- assert status ==
- ActivityRepresenter.to_map(updated_activity, %{user: activity_user, for: user})
+ assert status == updated_activity
end
test "it registers a new user and returns the user." do
data = %{
"nickname" => "lain",
"email" => "lain@wired.jp",
"fullname" => "lain iwakura",
"bio" => "close the world.",
"password" => "bear",
"confirm" => "bear"
}
{:ok, user} = TwitterAPI.register_user(data)
fetched_user = Repo.get_by(User, nickname: "lain")
assert UserView.render("show.json", %{user: user}) ==
UserView.render("show.json", %{user: fetched_user})
end
test "it returns the error on registration problems" do
data = %{
"nickname" => "lain",
"email" => "lain@wired.jp",
"fullname" => "lain iwakura",
"bio" => "close the world.",
"password" => "bear"
}
{:error, error_object} = TwitterAPI.register_user(data)
assert is_binary(error_object[:error])
refute Repo.get_by(User, nickname: "lain")
end
test "it assigns an integer conversation_id" do
note_activity = insert(:note_activity)
- user = User.get_cached_by_ap_id(note_activity.data["actor"])
- status = ActivityRepresenter.to_map(note_activity, %{user: user})
+ status = ActivityView.render("activity.json", activity: note_activity)
assert is_number(status["statusnet_conversation_id"])
end
setup do
Supervisor.terminate_child(Pleroma.Supervisor, Cachex)
Supervisor.restart_child(Pleroma.Supervisor, Cachex)
:ok
end
describe "context_to_conversation_id" do
test "creates a mapping object" do
conversation_id = TwitterAPI.context_to_conversation_id("random context")
object = Object.get_by_ap_id("random context")
assert conversation_id == object.id
end
test "returns an existing mapping for an existing object" do
{:ok, object} = Object.context_mapping("random context") |> Repo.insert()
conversation_id = TwitterAPI.context_to_conversation_id("random context")
assert conversation_id == object.id
end
end
describe "fetching a user by uri" do
test "fetches a user by uri" do
id = "https://mastodon.social/users/lambadalambda"
user = insert(:user)
{:ok, represented} = TwitterAPI.get_external_profile(user, id)
remote = User.get_by_ap_id(id)
assert represented["id"] == UserView.render("show.json", %{user: remote, for: user})["id"]
# Also fetches the feed.
# assert Activity.get_create_activity_by_object_ap_id("tag:mastodon.social,2017-04-05:objectId=1641750:objectType=Status")
end
end
end

File Metadata

Mime Type
text/x-diff
Expires
Sat, Sep 19, 12:19 PM (1 d, 23 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1767131
Default Alt Text
(49 KB)

Event Timeline