Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F85711129
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Size
32 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index a38f63b03..b2508de68 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -1,178 +1,178 @@
defmodule Pleroma.Web.ActivityPub.ActivityPub do
alias Pleroma.Repo
alias Pleroma.{Activity, Object, Upload, User}
import Ecto.Query
def insert(map) when is_map(map) do
map = map
|> Map.put_new_lazy("id", &generate_activity_id/0)
|> Map.put_new_lazy("published", &make_date/0)
map = if is_map(map["object"]) do
object = Map.put_new_lazy(map["object"], "id", &generate_object_id/0)
Repo.insert!(%Object{data: object})
Map.put(map, "object", object)
else
map
end
Repo.insert(%Activity{data: map})
end
def like(%User{ap_id: ap_id} = user, %Object{data: %{ "id" => id}} = object) do
cond do
# There's already a like here, so return the original activity.
ap_id in (object.data["likes"] || []) ->
query = from activity in Activity,
where: fragment("? @> ?", activity.data, ^%{actor: ap_id, object: id, type: "Like"})
activity = Repo.one(query)
{:ok, activity, object}
true ->
data = %{
"type" => "Like",
"actor" => ap_id,
"object" => id,
"to" => [User.ap_followers(user)]
}
{:ok, activity} = insert(data)
likes = [ap_id | (object.data["likes"] || [])] |> Enum.uniq
new_data = object.data
|> Map.put("like_count", length(likes))
|> Map.put("likes", likes)
changeset = Ecto.Changeset.change(object, data: new_data)
{:ok, object} = Repo.update(changeset)
update_object_in_activities(object)
{:ok, activity, object}
end
end
defp update_object_in_activities(%{data: %{"id" => id}} = object) do
# Update activities that already had this. Could be done in a seperate process.
relevant_activities = Activity.all_by_object_ap_id(id)
Enum.map(relevant_activities, fn (activity) ->
new_activity_data = activity.data |> Map.put("object", object.data)
changeset = Ecto.Changeset.change(activity, data: new_activity_data)
Repo.update(changeset)
end)
end
- def unlike(%User{ap_id: ap_id} = user, %Object{data: %{ "id" => id}} = object) do
+ def unlike(%User{ap_id: ap_id}, %Object{data: %{ "id" => id}} = object) do
query = from activity in Activity,
where: fragment("? @> ?", activity.data, ^%{actor: ap_id, object: id, type: "Like"})
activity = Repo.one(query)
if activity do
# just delete for now...
{:ok, _activity} = Repo.delete(activity)
likes = (object.data["likes"] || []) |> List.delete(ap_id)
new_data = object.data
|> Map.put("like_count", length(likes))
|> Map.put("likes", likes)
changeset = Ecto.Changeset.change(object, data: new_data)
{:ok, object} = Repo.update(changeset)
update_object_in_activities(object)
{:ok, object}
else
{:ok, object}
end
end
def generate_activity_id do
generate_id("activities")
end
def generate_context_id do
generate_id("contexts")
end
def generate_object_id do
generate_id("objects")
end
def generate_id(type) do
"#{Pleroma.Web.base_url()}/#{type}/#{Ecto.UUID.generate}"
end
def fetch_public_activities(opts \\ %{}) do
public = ["https://www.w3.org/ns/activitystreams#Public"]
fetch_activities(public, opts)
end
def fetch_activities(recipients, opts \\ %{}) do
since_id = opts["since_id"] || 0
query = from activity in Activity,
limit: 20,
order_by: [desc: :inserted_at]
query = Enum.reduce(recipients, query, fn (recipient, q) ->
map = %{ to: [recipient] }
from activity in q,
or_where: fragment(~s(? @> ?), activity.data, ^map)
end)
query = from activity in query,
where: activity.id > ^since_id
query = if opts["max_id"] do
from activity in query, where: activity.id < ^opts["max_id"]
else
query
end
Repo.all(query)
|> Enum.reverse
end
def announce(%User{ap_id: ap_id} = user, %Object{data: %{"id" => id}} = object) do
data = %{
"type" => "Announce",
"actor" => ap_id,
"object" => id,
"to" => [User.ap_followers(user)]
}
{:ok, activity} = insert(data)
announcements = [ap_id | (object.data["announcements"] || [])] |> Enum.uniq
new_data = object.data
|> Map.put("announcement_count", length(announcements))
|> Map.put("announcements", announcements)
changeset = Ecto.Changeset.change(object, data: new_data)
{:ok, object} = Repo.update(changeset)
update_object_in_activities(object)
{:ok, activity, object}
end
def fetch_activities_for_context(context) do
query = from activity in Activity,
where: fragment("? @> ?", activity.data, ^%{ context: context })
Repo.all(query)
end
def upload(%Plug.Upload{} = file) do
data = Upload.store(file)
Repo.insert(%Object{data: data})
end
defp make_date do
DateTime.utc_now() |> DateTime.to_iso8601
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 e69a63e1d..f2bf93abb 100644
--- a/lib/pleroma/web/twitter_api/representers/activity_representer.ex
+++ b/lib/pleroma/web/twitter_api/representers/activity_representer.ex
@@ -1,82 +1,112 @@
defmodule Pleroma.Web.TwitterAPI.Representers.ActivityRepresenter do
use Pleroma.Web.TwitterAPI.Representers.BaseRepresenter
alias Pleroma.Web.TwitterAPI.Representers.{UserRepresenter, ObjectRepresenter}
alias Pleroma.Activity
+ 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}} = activity, %{users: users, announced_activity: announced_activity} = opts) do
+ user = user_by_ap_id(users, actor)
+ created_at = get_in(activity.data, ["published"])
+ |> 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" => UserRepresenter.to_map(user, opts),
+ "statusnet_html" => text,
+ "text" => text,
+ "is_local" => true,
+ "is_post_verb" => false,
+ "uri" => "tag:#{activity.data["id"]}:objectType=note",
+ "created_at" => created_at,
+ "retweeted_status" => retweeted_status
+ }
+ end
+
def to_map(%Activity{data: %{"type" => "Like"}} = activity, %{user: user, liked_activity: liked_activity} = opts) do
created_at = get_in(activity.data, ["published"])
|> date_to_asctime
text = "#{user.nickname} favorited a status."
%{
"id" => activity.id,
"user" => UserRepresenter.to_map(user, opts),
"statusnet_html" => text, # TODO: add summary
"text" => text,
"is_local" => true,
"is_post_verb" => false,
"uri" => "tag:#{activity.data["id"]}:objectType=Favourite",
"created_at" => created_at,
"in_reply_to_status_id" => liked_activity.id,
}
end
def to_map(%Activity{data: %{"type" => "Follow"}} = activity, %{user: user} = opts) do
created_at = get_in(activity.data, ["published"])
|> date_to_asctime
%{
"id" => activity.id,
"user" => UserRepresenter.to_map(user, opts),
"attentions" => [],
"statusnet_html" => "", # TODO: add summary
"text" => "",
"is_local" => true,
"is_post_verb" => false,
"created_at" => created_at,
"in_reply_to_status_id" => nil,
}
end
def to_map(%Activity{} = activity, %{user: user} = opts) do
content = get_in(activity.data, ["object", "content"])
created_at = get_in(activity.data, ["object", "published"])
|> date_to_asctime
like_count = get_in(activity.data, ["object", "like_count"]) || 0
+ announcement_count = get_in(activity.data, ["object", "announcement_count"]) || 0
favorited = opts[:for] && opts[:for].ap_id in (activity.data["object"]["likes"] || [])
+ repeated = opts[:for] && opts[:for].ap_id in (activity.data["object"]["announcements"] || [])
mentions = opts[:mentioned] || []
attentions = activity.data["to"]
|> Enum.map(fn (ap_id) -> Enum.find(mentions, fn(user) -> ap_id == user.ap_id end) end)
|> Enum.filter(&(&1))
|> Enum.map(fn (user) -> UserRepresenter.to_map(user, opts) end)
%{
"id" => activity.id,
"user" => UserRepresenter.to_map(user, opts),
"attentions" => [],
"statusnet_html" => content,
"text" => HtmlSanitizeEx.strip_tags(content),
"is_local" => true,
"is_post_verb" => true,
"created_at" => created_at,
"in_reply_to_status_id" => activity.data["object"]["inReplyToStatusId"],
"statusnet_conversation_id" => activity.data["object"]["statusnetConversationId"],
"attachments" => (activity.data["object"]["attachment"] || []) |> ObjectRepresenter.enum_to_list(opts),
"attentions" => attentions,
"fave_num" => like_count,
- "favorited" => !!favorited
+ "repeat_num" => announcement_count,
+ "favorited" => !!favorited,
+ "repeated" => !!repeated,
}
end
defp date_to_asctime(date) do
with {:ok, date, _offset} <- date |> DateTime.from_iso8601 do
Calendar.Strftime.strftime!(date, "%a %b %d %H:%M:%S %z %Y")
else _e ->
""
end
end
end
diff --git a/lib/pleroma/web/twitter_api/twitter_api.ex b/lib/pleroma/web/twitter_api/twitter_api.ex
index 76dbc77a1..58f8bc1d3 100644
--- a/lib/pleroma/web/twitter_api/twitter_api.ex
+++ b/lib/pleroma/web/twitter_api/twitter_api.ex
@@ -1,247 +1,270 @@
defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
alias Pleroma.{User, Activity, Repo, Object}
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.TwitterAPI.Representers.ActivityRepresenter
import Ecto.Query
def create_status(user = %User{}, data = %{}) do
attachments = Enum.map(data["media_ids"] || [], fn (media_id) ->
Repo.get(Object, media_id).data
end)
context = ActivityPub.generate_context_id
content = HtmlSanitizeEx.strip_tags(data["status"])
mentions = parse_mentions(content)
default_to = [
User.ap_followers(user),
"https://www.w3.org/ns/activitystreams#Public"
]
to = default_to ++ Enum.map(mentions, fn ({_, %{ap_id: ap_id}}) -> ap_id end)
content_html = add_user_links(content, mentions)
date = make_date()
activity = %{
"type" => "Create",
"to" => to,
"actor" => user.ap_id,
"object" => %{
"type" => "Note",
"to" => to,
"content" => content_html,
"published" => date,
"context" => context,
"attachment" => attachments,
"actor" => user.ap_id
},
"published" => date,
"context" => context
}
# Wire up reply info.
activity = with inReplyToId when not is_nil(inReplyToId) <- data["in_reply_to_status_id"],
inReplyTo <- Repo.get(Activity, inReplyToId),
context <- inReplyTo.data["context"]
do
to = activity["to"] ++ [inReplyTo.data["actor"]]
activity
|> put_in(["to"], to)
|> put_in(["context"], context)
|> put_in(["object", "context"], context)
|> put_in(["object", "inReplyTo"], inReplyTo.data["object"]["id"])
|> put_in(["object", "inReplyToStatusId"], inReplyToId)
|> put_in(["statusnetConversationId"], inReplyTo.data["statusnetConversationId"])
|> put_in(["object", "statusnetConversationId"], inReplyTo.data["statusnetConversationId"])
else _e ->
activity
end
with {:ok, activity} <- ActivityPub.insert(activity) do
add_conversation_id(activity)
end
end
def fetch_friend_statuses(user, opts \\ %{}) do
ActivityPub.fetch_activities([user.ap_id | user.following], opts)
|> activities_to_statuses(%{for: user})
end
def fetch_public_statuses(user, opts \\ %{}) do
ActivityPub.fetch_public_activities(opts)
|> activities_to_statuses(%{for: user})
end
def fetch_conversation(user, id) do
query = from activity in Activity,
where: fragment("? @> ?", activity.data, ^%{ statusnetConversationId: id}),
limit: 1
with %Activity{} = activity <- Repo.one(query),
context <- activity.data["context"],
activities <- ActivityPub.fetch_activities_for_context(context),
statuses <- activities |> activities_to_statuses(%{for: user})
do
statuses
else e ->
IO.inspect(e)
[]
end
end
def fetch_status(user, id) do
with %Activity{} = activity <- Repo.get(Activity, id) do
activity_to_status(activity, %{for: user})
end
end
def follow(%User{} = follower, followed_id) do
with %User{} = followed <- Repo.get(User, followed_id),
{ :ok, follower } <- User.follow(follower, followed),
{ :ok, activity } <- ActivityPub.insert(%{
"type" => "Follow",
"actor" => follower.ap_id,
"object" => followed.ap_id,
"published" => make_date()
})
do
{ :ok, follower, followed, activity }
end
end
def unfollow(%User{} = follower, followed_id) do
with %User{} = followed <- Repo.get(User, followed_id),
{ :ok, follower } <- User.unfollow(follower, followed)
do
{ :ok, follower, followed }
end
end
def favorite(%User{} = user, %Activity{data: %{"object" => object}} = activity) do
object = Object.get_by_ap_id(object["id"])
{:ok, _like_activity, object} = ActivityPub.like(user, object)
new_data = activity.data
|> Map.put("object", object.data)
status = %{activity | data: new_data}
|> activity_to_status(%{for: user})
{:ok, status}
end
def unfavorite(%User{} = user, %Activity{data: %{"object" => object}} = activity) do
object = Object.get_by_ap_id(object["id"])
{:ok, object} = ActivityPub.unlike(user, object)
new_data = activity.data
|> Map.put("object", object.data)
status = %{activity | data: new_data}
|> activity_to_status(%{for: user})
{:ok, status}
end
+ def retweet(%User{} = user, %Activity{data: %{"object" => object}} = activity) do
+ object = Object.get_by_ap_id(object["id"])
+
+ {:ok, _announce_activity, object} = ActivityPub.announce(user, object)
+ new_data = activity.data
+ |> Map.put("object", object.data)
+
+ status = %{activity | data: new_data}
+ |> activity_to_status(%{for: user})
+
+ {:ok, status}
+ 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
} |> Poison.encode!
end
end
def parse_mentions(text) do
# Modified from https://www.w3.org/TR/html5/forms.html#valid-e-mail-address
regex = ~r/@[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@?[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*/
Regex.scan(regex, text)
|> List.flatten
|> Enum.uniq
|> Enum.map(fn ("@" <> match = full_match) -> {full_match, User.get_cached_by_nickname(match)} end)
|> Enum.filter(fn ({_match, user}) -> user end)
end
def add_user_links(text, mentions) do
Enum.reduce(mentions, text, fn ({match, %User{ap_id: ap_id}}, text) -> String.replace(text, match, "<a href='#{ap_id}'>#{match}</a>") end)
end
defp add_conversation_id(activity) do
if is_integer(activity.data["statusnetConversationId"]) do
{:ok, activity}
else
data = activity.data
|> put_in(["object", "statusnetConversationId"], activity.id)
|> put_in(["statusnetConversationId"], activity.id)
object = Object.get_by_ap_id(activity.data["object"]["id"])
changeset = Ecto.Changeset.change(object, data: data["object"])
Repo.update(changeset)
changeset = Ecto.Changeset.change(activity, data: data)
Repo.update(changeset)
end
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, 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.data["to"], fn (ap_id) ->
User.get_cached_by_ap_id(ap_id)
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
end
diff --git a/test/web/twitter_api/representers/activity_representer_test.exs b/test/web/twitter_api/representers/activity_representer_test.exs
index d1f9e00c8..a9129bccc 100644
--- a/test/web/twitter_api/representers/activity_representer_test.exs
+++ b/test/web/twitter_api/representers/activity_representer_test.exs
@@ -1,98 +1,121 @@
defmodule Pleroma.Web.TwitterAPI.Representers.ActivityRepresenterTest do
use Pleroma.DataCase
alias Pleroma.{User, Activity, Object}
alias Pleroma.Web.TwitterAPI.Representers.{UserRepresenter, ActivityRepresenter, ObjectRepresenter}
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Builders.UserBuilder
import Pleroma.Factory
+ test "an announce activity" do
+ user = insert(:user)
+ note_activity = insert(:note_activity)
+ activity_actor = Repo.get_by(User, ap_id: note_activity.data["actor"])
+ object = Object.get_by_ap_id(note_activity.data["object"]["id"])
+
+ {:ok, announce_activity, _object} = ActivityPub.announce(user, object)
+ note_activity = Activity.get_by_ap_id(note_activity.data["id"])
+
+ status = ActivityRepresenter.to_map(announce_activity, %{users: [user, activity_actor], announced_activity: note_activity, for: user})
+
+ assert status["id"] == announce_activity.id
+ assert status["user"] == UserRepresenter.to_map(user, %{for: user})
+
+ retweeted_status = ActivityRepresenter.to_map(note_activity, %{user: activity_actor, for: user})
+ assert retweeted_status["repeated"] == true
+
+ assert status["retweeted_status"] == retweeted_status
+ end
+
test "a like activity" do
user = insert(:user)
note_activity = insert(:note_activity)
object = Object.get_by_ap_id(note_activity.data["object"]["id"])
{:ok, like_activity, _object} = ActivityPub.like(user, object)
status = ActivityRepresenter.to_map(like_activity, %{user: user, liked_activity: note_activity})
assert status["id"] == like_activity.id
assert status["in_reply_to_status_id"] == note_activity.id
note_activity = Activity.get_by_ap_id(note_activity.data["id"])
activity_actor = Repo.get_by(User, ap_id: note_activity.data["actor"])
liked_status = ActivityRepresenter.to_map(note_activity, %{user: activity_actor, for: user})
assert liked_status["favorited"] == true
end
test "an activity" do
{:ok, user} = UserBuilder.insert
{:ok, mentioned_user } = UserBuilder.insert(%{nickname: "shp", ap_id: "shp"})
{:ok, follower} = UserBuilder.insert(%{following: [User.ap_followers(user)]})
object = %Object{
data: %{
"type" => "Image",
"url" => [
%{
"type" => "Link",
"mediaType" => "image/jpg",
"href" => "http://example.org/image.jpg"
}
],
"uuid" => 1
}
}
content_html = "Some content mentioning <a href='shp'>@shp</shp>"
content = HtmlSanitizeEx.strip_tags(content_html)
date = DateTime.from_naive!(~N[2016-05-24 13:26:08.003], "Etc/UTC") |> DateTime.to_iso8601
activity = %Activity{
id: 1,
data: %{
"type" => "Create",
"to" => [
User.ap_followers(user),
"https://www.w3.org/ns/activitystreams#Public",
mentioned_user.ap_id
],
"actor" => User.ap_id(user),
"object" => %{
"published" => date,
"type" => "Note",
"content" => content_html,
"inReplyToStatusId" => 213123,
"statusnetConversationId" => 4711,
"attachment" => [
object
],
- "like_count" => 5
+ "like_count" => 5,
+ "announcement_count" => 3
},
"published" => date
}
}
expected_status = %{
"id" => activity.id,
"user" => UserRepresenter.to_map(user, %{for: follower}),
"is_local" => true,
"attentions" => [],
"statusnet_html" => content_html,
"text" => content,
"is_post_verb" => true,
"created_at" => "Tue May 24 13:26:08 +0000 2016",
"in_reply_to_status_id" => 213123,
"statusnet_conversation_id" => 4711,
"attachments" => [
ObjectRepresenter.to_map(object)
],
"attentions" => [
UserRepresenter.to_map(mentioned_user, %{for: follower})
],
"fave_num" => 5,
- "favorited" => false
+ "repeat_num" => 3,
+ "favorited" => false,
+ "repeated" => false
}
assert ActivityRepresenter.to_map(activity, %{user: user, for: follower, mentioned: [mentioned_user]}) == expected_status
end
end
diff --git a/test/web/twitter_api/twitter_api_test.exs b/test/web/twitter_api/twitter_api_test.exs
index fd9908b9f..71b0d8b12 100644
--- a/test/web/twitter_api/twitter_api_test.exs
+++ b/test/web/twitter_api/twitter_api_test.exs
@@ -1,217 +1,228 @@
defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do
use Pleroma.DataCase
alias Pleroma.Builders.{UserBuilder, ActivityBuilder}
alias Pleroma.Web.TwitterAPI.TwitterAPI
alias Pleroma.{Activity, User, Object, Repo}
alias Pleroma.Web.TwitterAPI.Representers.ActivityRepresenter
alias Pleroma.Web.ActivityPub.ActivityPub
import Pleroma.Factory
test "create a status" do
user = UserBuilder.build(%{ap_id: "142344"})
_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>",
"media_ids" => [object.id]
}
{ :ok, activity = %Activity{} } = TwitterAPI.create_status(user, input)
assert get_in(activity.data, ["object", "content"]) == "Hello again, <a href='shp'>@shp</a>."
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, ["to"]), 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, ["to"]), "shp")
# Add a context + 'statusnet_conversation_id'
assert is_binary(get_in(activity.data, ["context"]))
assert is_binary(get_in(activity.data, ["object", "context"]))
assert get_in(activity.data, ["object", "statusnetConversationId"]) == activity.id
assert get_in(activity.data, ["statusnetConversationId"]) == activity.id
assert is_list(activity.data["object"]["attachment"])
assert activity.data["object"] == Object.get_by_ap_id(activity.data["object"]["id"]).data
end
test "create a status that is a reply" do
user = UserBuilder.build(%{ap_id: "some_cool_id"})
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, ["statusnetConversationId"]) == get_in(activity.data, ["statusnetConversationId"])
assert get_in(reply.data, ["object", "statusnetConversationId"]) == get_in(activity.data, ["object", "statusnetConversationId"])
assert get_in(reply.data, ["object", "inReplyTo"]) == get_in(activity.data, ["object", "id"])
assert get_in(reply.data, ["object", "inReplyToStatusId"]) == activity.id
assert Enum.member?(get_in(reply.data, ["to"]), "some_cool_id")
end
test "fetch public statuses" do
%{ public: activity, user: user } = ActivityBuilder.public_and_non_public
{:ok, follower } = UserBuilder.insert(%{name: "dude", ap_id: "idididid", 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 friends' statuses" do
ActivityBuilder.public_and_non_public
{:ok, activity} = ActivityBuilder.insert(%{"to" => ["someguy/followers"]})
{:ok, direct_activity} = ActivityBuilder.insert(%{"to" => ["some other id"]})
{:ok, user} = UserBuilder.insert(%{ap_id: "some other id", following: ["someguy/followers"]})
statuses = TwitterAPI.fetch_friend_statuses(user)
activity_user = Repo.get_by(User, ap_id: 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: activity_user, mentioned: [user]})
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" do
{ :ok, user } = UserBuilder.insert
{ :ok, following } = UserBuilder.insert(%{nickname: "guy"})
{:ok, user, following, activity } = TwitterAPI.follow(user, following.id)
user = Repo.get(User, user.id)
follow = Repo.get(Activity, activity.id)
assert user.following == [User.ap_followers(following)]
assert follow == activity
end
test "Unfollow another user" do
{ :ok, following } = UserBuilder.insert(%{nickname: "guy"})
{ :ok, user } = UserBuilder.insert(%{following: [User.ap_followers(following)]})
{:ok, user, _following } = TwitterAPI.unfollow(user, following.id)
user = Repo.get(User, user.id)
assert user.following == []
end
test "fetch statuses in a context using the conversation id" do
{:ok, user} = UserBuilder.insert()
{:ok, activity} = ActivityBuilder.insert(%{"statusnetConversationId" => 1, "context" => "2hu"})
{:ok, activity_two} = ActivityBuilder.insert(%{"statusnetConversationId" => 1,"context" => "2hu"})
{:ok, _activity_three} = ActivityBuilder.insert(%{"context" => "3hu"})
statuses = TwitterAPI.fetch_conversation(user, 1)
assert length(statuses) == 2
assert Enum.at(statuses, 0)["id"] == activity.id
assert Enum.at(statuses, 1)["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 can parse mentions and return the relevant users" do
text = "@gsimg According to @archaeme , that is @daggsy."
{:ok, gsimg} = UserBuilder.insert(%{nickname: "gsimg"})
{:ok, archaeme} = UserBuilder.insert(%{nickname: "archaeme"})
expected_result = [
{"@gsimg", gsimg},
{"@archaeme", archaeme}
]
assert TwitterAPI.parse_mentions(text) == expected_result
end
test "it adds user links to an existing text" do
text = "@gsimg According to @archaeme , that is @daggsy."
{:ok, _gsimg} = UserBuilder.insert(%{nickname: "gsimg", ap_id: "first_link" })
{:ok, _archaeme} = UserBuilder.insert(%{nickname: "archaeme", ap_id: "second_link"})
mentions = TwitterAPI.parse_mentions(text)
expected_text = "<a href='first_link'>@gsimg</a> According to <a href='second_link'>@archaeme</a> , that is @daggsy."
assert TwitterAPI.add_user_links(text, mentions) == expected_text
end
test "it favorites a status, returns the updated status" do
user = insert(:user)
note_activity = insert(:note_activity)
activity_user = Repo.get_by!(User, ap_id: note_activity.data["actor"])
{:ok, status} = TwitterAPI.favorite(user, note_activity)
updated_activity = Activity.get_by_ap_id(note_activity.data["id"])
assert status == ActivityRepresenter.to_map(updated_activity, %{user: activity_user, for: user})
end
test "it unfavorites a status, returns the updated status" 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
{:ok, status} = TwitterAPI.unfavorite(user, note_activity)
assert status["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.retweet(user, note_activity)
+ updated_activity = Activity.get_by_ap_id(note_activity.data["id"])
+
+ assert status == ActivityRepresenter.to_map(updated_activity, %{user: activity_user, for: user})
+ end
+
setup do
Supervisor.terminate_child(Pleroma.Supervisor, ConCache)
Supervisor.restart_child(Pleroma.Supervisor, ConCache)
:ok
end
end
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sat, Sep 19, 4:10 AM (23 h, 16 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1768805
Default Alt Text
(32 KB)
Attached To
Mode
rPUBE pleroma-upstream
Attached
Detach File
Event Timeline
Log In to Comment