alias Pleroma.Activity
alias Pleroma.HTML
+ alias Pleroma.Object
alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.MastodonAPI.StatusView
alias Pleroma.Web.MediaProxy
+ import Pleroma.Web.ActivityPub.Visibility, only: [get_visibility: 1]
+
# TODO: Add cached version.
+ defp get_replied_to_activities([]), do: %{}
+
defp get_replied_to_activities(activities) do
activities
|> Enum.map(fn
- %{data: %{"type" => "Create", "object" => %{"inReplyTo" => in_reply_to}}} ->
- in_reply_to != "" && in_reply_to
+ %{data: %{"type" => "Create", "object" => object}} ->
+ object = Object.normalize(object)
+ object.data["inReplyTo"] != "" && object.data["inReplyTo"]
_ ->
nil
|> Activity.create_by_object_ap_id()
|> Repo.all()
|> Enum.reduce(%{}, fn activity, acc ->
- Map.put(acc, activity.data["object"]["id"], activity)
+ object = Object.normalize(activity)
+ Map.put(acc, object.data["id"], activity)
end)
end
defp get_context_id(_), do: nil
+ defp reblogged?(activity, user) do
+ object = Object.normalize(activity) || %{}
+ present?(user && user.ap_id in (object.data["announcements"] || []))
+ end
+
def render("index.json", opts) do
replied_to_activities = get_replied_to_activities(opts.activities)
def render(
"status.json",
- %{activity: %{data: %{"type" => "Announce", "object" => object}} = activity} = opts
+ %{activity: %{data: %{"type" => "Announce", "object" => _object}} = activity} = opts
) do
user = get_user(activity.data["actor"])
created_at = Utils.to_masto_date(activity.data["published"])
+ activity_object = Object.normalize(activity)
+
+ reblogged_activity =
+ Activity.create_by_object_ap_id(activity_object.data["id"])
+ |> Activity.with_preloaded_bookmark(opts[:for])
+ |> Repo.one()
- reblogged = Activity.get_create_by_object_ap_id(object)
- reblogged = render("status.json", Map.put(opts, :activity, reblogged))
+ reblogged = render("status.json", Map.put(opts, :activity, reblogged_activity))
+
+ favorited = opts[:for] && opts[:for].ap_id in (activity_object.data["likes"] || [])
+
+ bookmarked = Activity.get_bookmark(reblogged_activity, opts[:for]) != nil
mentions =
activity.recipients
%{
id: to_string(activity.id),
- uri: object,
- url: object,
- account: AccountView.render("account.json", %{user: user}),
+ uri: activity_object.data["id"],
+ url: activity_object.data["id"],
+ account: AccountView.render("account.json", %{user: user, for: opts[:for]}),
in_reply_to_id: nil,
in_reply_to_account_id: nil,
reblog: reblogged,
reblogs_count: 0,
replies_count: 0,
favourites_count: 0,
- reblogged: false,
- favourited: false,
- bookmarked: false,
+ reblogged: reblogged?(reblogged_activity, opts[:for]),
+ favourited: present?(favorited),
+ bookmarked: present?(bookmarked),
muted: false,
pinned: pinned?(activity, user),
sensitive: false,
}
end
- def render("status.json", %{activity: %{data: %{"object" => object}} = activity} = opts) do
+ def render("status.json", %{activity: %{data: %{"object" => _object}} = activity} = opts) do
+ object = Object.normalize(activity)
+
user = get_user(activity.data["actor"])
- like_count = object["like_count"] || 0
- announcement_count = object["announcement_count"] || 0
+ like_count = object.data["like_count"] || 0
+ announcement_count = object.data["announcement_count"] || 0
+
+ tags = object.data["tag"] || []
+ sensitive = object.data["sensitive"] || Enum.member?(tags, "nsfw")
- tags = object["tag"] || []
- sensitive = object["sensitive"] || Enum.member?(tags, "nsfw")
+ tag_mentions =
+ tags
+ |> Enum.filter(fn tag -> is_map(tag) and tag["type"] == "Mention" end)
+ |> Enum.map(fn tag -> tag["href"] end)
mentions =
- activity.recipients
+ (object.data["to"] ++ tag_mentions)
+ |> Enum.uniq()
|> Enum.map(fn ap_id -> User.get_cached_by_ap_id(ap_id) end)
|> Enum.filter(& &1)
|> Enum.map(fn user -> AccountView.render("mention.json", %{user: user}) end)
- repeated = opts[:for] && opts[:for].ap_id in (object["announcements"] || [])
- favorited = opts[:for] && opts[:for].ap_id in (object["likes"] || [])
- bookmarked = opts[:for] && object["id"] in opts[:for].bookmarks
+ favorited = opts[:for] && opts[:for].ap_id in (object.data["likes"] || [])
+
+ bookmarked = Activity.get_bookmark(activity, opts[:for]) != nil
- attachment_data = object["attachment"] || []
+ thread_muted? =
+ case activity.thread_muted? do
+ thread_muted? when is_boolean(thread_muted?) -> thread_muted?
+ nil -> CommonAPI.thread_muted?(user, activity)
+ end
+
+ attachment_data = object.data["attachment"] || []
attachments = render_many(attachment_data, StatusView, "attachment.json", as: :attachment)
- created_at = Utils.to_masto_date(object["published"])
+ created_at = Utils.to_masto_date(object.data["published"])
reply_to = get_reply_to(activity, opts)
+
reply_to_user = reply_to && get_user(reply_to.data["actor"])
content =
"mastoapi:content"
)
- summary = object["summary"] || ""
+ summary = object.data["summary"] || ""
summary_html =
summary
if user.local do
Pleroma.Web.Router.Helpers.o_status_url(Pleroma.Web.Endpoint, :notice, activity)
else
- object["external_url"] || object["id"]
+ object.data["external_url"] || object.data["id"]
end
%{
id: to_string(activity.id),
- uri: object["id"],
+ uri: object.data["id"],
url: url,
- account: AccountView.render("account.json", %{user: user}),
+ account: AccountView.render("account.json", %{user: user, for: opts[:for]}),
in_reply_to_id: reply_to && to_string(reply_to.id),
in_reply_to_account_id: reply_to_user && to_string(reply_to_user.id),
reblog: nil,
content: content_html,
created_at: created_at,
reblogs_count: announcement_count,
- replies_count: object["repliesCount"] || 0,
+ replies_count: object.data["repliesCount"] || 0,
favourites_count: like_count,
- reblogged: present?(repeated),
+ reblogged: reblogged?(activity, opts[:for]),
favourited: present?(favorited),
bookmarked: present?(bookmarked),
- muted: CommonAPI.thread_muted?(user, activity) || User.mutes?(opts[:for], user),
+ muted: thread_muted? || User.mutes?(opts[:for], user),
pinned: pinned?(activity, user),
sensitive: sensitive,
spoiler_text: summary_html,
visibility: get_visibility(object),
media_attachments: attachments,
+ poll: render("poll.json", %{object: object, for: opts[:for]}),
mentions: mentions,
tags: build_tags(tags),
application: %{
website: nil
},
language: nil,
- emojis: build_emojis(activity.data["object"]["emoji"]),
+ emojis: build_emojis(object.data["emoji"]),
pleroma: %{
local: activity.local,
conversation_id: get_context_id(activity),
+ in_reply_to_account_acct: reply_to_user && reply_to_user.nickname,
content: %{"text/plain" => content_plaintext},
spoiler_text: %{"text/plain" => summary_plaintext}
}
provider_url: page_url_data.scheme <> "://" <> page_url_data.host,
url: page_url,
image: image_url |> MediaProxy.url(),
- title: rich_media[:title],
- description: rich_media[:description],
+ title: rich_media[:title] || "",
+ description: rich_media[:description] || "",
pleroma: %{
opengraph: rich_media
}
}
end
- def get_reply_to(activity, %{replied_to_activities: replied_to_activities}) do
- with nil <- replied_to_activities[activity.data["object"]["inReplyTo"]] do
- # If user didn't participate in the thread
- Activity.get_in_reply_to_activity(activity)
- end
- end
+ def render("poll.json", %{object: object} = opts) do
+ {multiple, options} =
+ case object.data do
+ %{"anyOf" => options} when is_list(options) -> {true, options}
+ %{"oneOf" => options} when is_list(options) -> {false, options}
+ _ -> {nil, nil}
+ end
- def get_reply_to(%{data: %{"object" => object}}, _) do
- if object["inReplyTo"] && object["inReplyTo"] != "" do
- Activity.get_create_by_object_ap_id(object["inReplyTo"])
+ if options do
+ end_time =
+ (object.data["closed"] || object.data["endTime"])
+ |> NaiveDateTime.from_iso8601!()
+
+ expired =
+ end_time
+ |> NaiveDateTime.compare(NaiveDateTime.utc_now())
+ |> case do
+ :lt -> true
+ _ -> false
+ end
+
+ voted =
+ if opts[:for] do
+ existing_votes =
+ Pleroma.Web.ActivityPub.Utils.get_existing_votes(opts[:for].ap_id, object)
+
+ existing_votes != [] or opts[:for].ap_id == object.data["actor"]
+ else
+ false
+ end
+
+ {options, votes_count} =
+ Enum.map_reduce(options, 0, fn %{"name" => name} = option, count ->
+ current_count = option["replies"]["totalItems"] || 0
+
+ {%{
+ title: HTML.strip_tags(name),
+ votes_count: current_count
+ }, current_count + count}
+ end)
+
+ %{
+ # Mastodon uses separate ids for polls, but an object can't have
+ # more than one poll embedded so object id is fine
+ id: to_string(object.id),
+ expires_at: Utils.to_masto_date(end_time),
+ expired: expired,
+ multiple: multiple,
+ votes_count: votes_count,
+ options: options,
+ voted: voted,
+ emojis: build_emojis(object.data["emoji"])
+ }
else
nil
end
end
- def get_visibility(object) do
- public = "https://www.w3.org/ns/activitystreams#Public"
- to = object["to"] || []
- cc = object["cc"] || []
-
- cond do
- public in to ->
- "public"
-
- public in cc ->
- "unlisted"
+ def get_reply_to(activity, %{replied_to_activities: replied_to_activities}) do
+ object = Object.normalize(activity)
- # this should use the sql for the object's activity
- Enum.any?(to, &String.contains?(&1, "/followers")) ->
- "private"
+ with nil <- replied_to_activities[object.data["inReplyTo"]] do
+ # If user didn't participate in the thread
+ Activity.get_in_reply_to_activity(activity)
+ end
+ end
- length(cc) > 0 ->
- "private"
+ def get_reply_to(%{data: %{"object" => _object}} = activity, _) do
+ object = Object.normalize(activity)
- true ->
- "direct"
+ if object.data["inReplyTo"] && object.data["inReplyTo"] != "" do
+ Activity.get_create_by_object_ap_id(object.data["inReplyTo"])
+ else
+ nil
end
end
- def render_content(%{"type" => "Video"} = object) do
- with name when not is_nil(name) and name != "" <- object["name"] do
- "<p><a href=\"#{object["id"]}\">#{name}</a></p>#{object["content"]}"
+ def render_content(%{data: %{"type" => "Video"}} = object) do
+ with name when not is_nil(name) and name != "" <- object.data["name"] do
+ "<p><a href=\"#{object.data["id"]}\">#{name}</a></p>#{object.data["content"]}"
else
- _ -> object["content"] || ""
+ _ -> object.data["content"] || ""
end
end
- def render_content(%{"type" => object_type} = object)
+ def render_content(%{data: %{"type" => object_type}} = object)
when object_type in ["Article", "Page"] do
- with summary when not is_nil(summary) and summary != "" <- object["name"],
- url when is_bitstring(url) <- object["url"] do
- "<p><a href=\"#{url}\">#{summary}</a></p>#{object["content"]}"
+ with summary when not is_nil(summary) and summary != "" <- object.data["name"],
+ url when is_bitstring(url) <- object.data["url"] do
+ "<p><a href=\"#{url}\">#{summary}</a></p>#{object.data["content"]}"
else
- _ -> object["content"] || ""
+ _ -> object.data["content"] || ""
end
end
- def render_content(object), do: object["content"] || ""
+ def render_content(object), do: object.data["content"] || ""
@doc """
Builds a dictionary tags.