Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F55939612
status_view.ex
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Size
16 KB
Referenced Files
None
Subscribers
None
status_view.ex
View Options
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule
Pleroma.Web.MastodonAPI.StatusView
do
use
Pleroma.Web
,
:view
require
Pleroma.Constants
alias
Pleroma.Activity
alias
Pleroma.HTML
alias
Pleroma.Object
alias
Pleroma.Repo
alias
Pleroma.User
alias
Pleroma.UserRelationship
alias
Pleroma.Web.CommonAPI
alias
Pleroma.Web.CommonAPI.Utils
alias
Pleroma.Web.MastodonAPI.AccountView
alias
Pleroma.Web.MastodonAPI.PollView
alias
Pleroma.Web.MastodonAPI.StatusView
alias
Pleroma.Web.MediaProxy
import
Pleroma.Web.ActivityPub.Visibility
,
only
:
[
get_visibility
:
1
,
visible_for_user?
:
2
]
# This is a naive way to do this, just spawning a process per activity
# to fetch the preview. However it should be fine considering
# pagination is restricted to 40 activities at a time
defp
fetch_rich_media_for_activities
(
activities
)
do
Enum
.
each
(
activities
,
fn
activity
->
spawn
(
fn
->
Pleroma.Web.RichMedia.Helpers
.
fetch_data_for_activity
(
activity
)
end
)
end
)
end
# TODO: Add cached version.
defp
get_replied_to_activities
([]),
do
:
%{}
defp
get_replied_to_activities
(
activities
)
do
activities
|>
Enum
.
map
(
fn
%{
data
:
%{
"type"
=>
"Create"
}}
=
activity
->
object
=
Object
.
normalize
(
activity
)
object
&&
object
.
data
[
"inReplyTo"
]
!=
""
&&
object
.
data
[
"inReplyTo"
]
_
->
nil
end
)
|>
Enum
.
filter
(
&
&1
)
|>
Activity
.
create_by_object_ap_id_with_object
()
|>
Repo
.
all
()
|>
Enum
.
reduce
(%{},
fn
activity
,
acc
->
object
=
Object
.
normalize
(
activity
)
if
object
,
do
:
Map
.
put
(
acc
,
object
.
data
[
"id"
],
activity
),
else
:
acc
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"
=>
context
}})
when
is_binary
(
context
),
do
:
Utils
.
context_to_conversation_id
(
context
)
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
reading_user
=
opts
[
:for
]
# To do: check AdminAPIControllerTest on the reasons behind nil activities in the list
activities
=
Enum
.
filter
(
opts
.
activities
,
&
&1
)
# Start fetching rich media before doing anything else, so that later calls to get the cards
# only block for timeout in the worst case, as opposed to
# length(activities_with_links) * timeout
fetch_rich_media_for_activities
(
activities
)
replied_to_activities
=
get_replied_to_activities
(
activities
)
parent_activities
=
activities
|>
Enum
.
filter
(
&
(
&1
.
data
[
"type"
]
==
"Announce"
&&
&1
.
data
[
"object"
]))
|>
Enum
.
map
(
&
Object
.
normalize
(
&1
)
.
data
[
"id"
])
|>
Activity
.
create_by_object_ap_id
()
|>
Activity
.
with_preloaded_object
(
:left
)
|>
Activity
.
with_preloaded_bookmark
(
reading_user
)
|>
Activity
.
with_set_thread_muted_field
(
reading_user
)
|>
Repo
.
all
()
relationships_opt
=
cond
do
Map
.
has_key?
(
opts
,
:relationships
)
->
opts
[
:relationships
]
is_nil
(
reading_user
)
->
UserRelationship
.
view_relationships_option
(
nil
,
[])
true
->
# Note: unresolved users are filtered out
actors
=
(
activities
++
parent_activities
)
|>
Enum
.
map
(
&
CommonAPI
.
get_user
(
&1
.
data
[
"actor"
],
false
))
|>
Enum
.
filter
(
&
&1
)
UserRelationship
.
view_relationships_option
(
reading_user
,
actors
,
subset
:
:source_mutes
)
end
opts
=
opts
|>
Map
.
put
(
:replied_to_activities
,
replied_to_activities
)
|>
Map
.
put
(
:parent_activities
,
parent_activities
)
|>
Map
.
put
(
:relationships
,
relationships_opt
)
safe_render_many
(
activities
,
StatusView
,
"show.json"
,
opts
)
end
def
render
(
"show.json"
,
%{
activity
:
%{
data
:
%{
"type"
=>
"Announce"
,
"object"
=>
_object
}}
=
activity
}
=
opts
)
do
user
=
CommonAPI
.
get_user
(
activity
.
data
[
"actor"
])
created_at
=
Utils
.
to_masto_date
(
activity
.
data
[
"published"
])
activity_object
=
Object
.
normalize
(
activity
)
reblogged_parent_activity
=
if
opts
[
:parent_activities
]
do
Activity.Queries
.
find_by_object_ap_id
(
opts
[
:parent_activities
],
activity_object
.
data
[
"id"
]
)
else
Activity
.
create_by_object_ap_id
(
activity_object
.
data
[
"id"
])
|>
Activity
.
with_preloaded_bookmark
(
opts
[
:for
])
|>
Activity
.
with_set_thread_muted_field
(
opts
[
:for
])
|>
Repo
.
one
()
end
reblog_rendering_opts
=
Map
.
put
(
opts
,
:activity
,
reblogged_parent_activity
)
reblogged
=
render
(
"show.json"
,
reblog_rendering_opts
)
favorited
=
opts
[
:for
]
&&
opts
[
:for
]
.
ap_id
in
(
activity_object
.
data
[
"likes"
]
||
[])
bookmarked
=
Activity
.
get_bookmark
(
reblogged_parent_activity
,
opts
[
:for
])
!=
nil
mentions
=
activity
.
recipients
|>
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
)
%{
id
:
to_string
(
activity
.
id
),
uri
:
activity_object
.
data
[
"id"
],
url
:
activity_object
.
data
[
"id"
],
account
:
AccountView
.
render
(
"show.json"
,
%{
user
:
user
,
for
:
opts
[
:for
]
}),
in_reply_to_id
:
nil
,
in_reply_to_account_id
:
nil
,
reblog
:
reblogged
,
content
:
reblogged
[
:content
]
||
""
,
created_at
:
created_at
,
reblogs_count
:
0
,
replies_count
:
0
,
favourites_count
:
0
,
reblogged
:
reblogged?
(
reblogged_parent_activity
,
opts
[
:for
]),
favourited
:
present?
(
favorited
),
bookmarked
:
present?
(
bookmarked
),
muted
:
false
,
pinned
:
pinned?
(
activity
,
user
),
sensitive
:
false
,
spoiler_text
:
""
,
visibility
:
get_visibility
(
activity
),
media_attachments
:
reblogged
[
:media_attachments
]
||
[],
mentions
:
mentions
,
tags
:
reblogged
[
:tags
]
||
[],
application
:
%{
name
:
"Web"
,
website
:
nil
},
language
:
nil
,
emojis
:
[],
pleroma
:
%{
local
:
activity
.
local
}
}
end
def
render
(
"show.json"
,
%{
activity
:
%{
data
:
%{
"object"
=>
_object
}}
=
activity
}
=
opts
)
do
object
=
Object
.
normalize
(
activity
)
user
=
CommonAPI
.
get_user
(
activity
.
data
[
"actor"
])
user_follower_address
=
user
.
follower_address
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"
)
tag_mentions
=
tags
|>
Enum
.
filter
(
fn
tag
->
is_map
(
tag
)
and
tag
[
"type"
]
==
"Mention"
end
)
|>
Enum
.
map
(
fn
tag
->
tag
[
"href"
]
end
)
mentions
=
(
object
.
data
[
"to"
]
++
tag_mentions
)
|>
Enum
.
uniq
()
|>
Enum
.
map
(
fn
Pleroma.Constants
.
as_public
()
->
nil
^
user_follower_address
->
nil
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
)
favorited
=
opts
[
:for
]
&&
opts
[
:for
]
.
ap_id
in
(
object
.
data
[
"likes"
]
||
[])
bookmarked
=
Activity
.
get_bookmark
(
activity
,
opts
[
:for
])
!=
nil
client_posted_this_activity
=
opts
[
:for
]
&&
user
.
id
==
opts
[
:for
]
.
id
expires_at
=
with
true
<-
client_posted_this_activity
,
%
Oban.Job
{
scheduled_at
:
scheduled_at
}
<-
Pleroma.Workers.PurgeExpiredActivity
.
get_expiration
(
activity
.
id
)
do
scheduled_at
else
_
->
nil
end
thread_muted?
=
cond
do
is_nil
(
opts
[
:for
])
->
false
is_boolean
(
activity
.
thread_muted?
)
->
activity
.
thread_muted?
true
->
CommonAPI
.
thread_muted?
(
opts
[
:for
],
activity
)
end
attachment_data
=
object
.
data
[
"attachment"
]
||
[]
attachments
=
render_many
(
attachment_data
,
StatusView
,
"attachment.json"
,
as
:
:attachment
)
created_at
=
Utils
.
to_masto_date
(
object
.
data
[
"published"
])
reply_to
=
get_reply_to
(
activity
,
opts
)
reply_to_user
=
reply_to
&&
CommonAPI
.
get_user
(
reply_to
.
data
[
"actor"
])
content
=
object
|>
render_content
()
content_html
=
content
|>
HTML
.
get_cached_scrubbed_html_for_activity
(
User
.
html_filter_policy
(
opts
[
:for
]),
activity
,
"mastoapi:content"
)
content_plaintext
=
content
|>
HTML
.
get_cached_stripped_html_for_activity
(
activity
,
"mastoapi:content"
)
summary
=
object
.
data
[
"summary"
]
||
""
card
=
render
(
"card.json"
,
Pleroma.Web.RichMedia.Helpers
.
fetch_data_for_activity
(
activity
))
url
=
if
user
.
local
do
Pleroma.Web.Router.Helpers
.
o_status_url
(
Pleroma.Web.Endpoint
,
:notice
,
activity
)
else
object
.
data
[
"url"
]
||
object
.
data
[
"external_url"
]
||
object
.
data
[
"id"
]
end
direct_conversation_id
=
with
{
_
,
nil
}
<-
{
:direct_conversation_id
,
opts
[
:direct_conversation_id
]},
{
_
,
true
}
<-
{
:include_id
,
opts
[
:with_direct_conversation_id
]},
{
_
,
%
User
{}
=
for_user
}
<-
{
:for_user
,
opts
[
:for
]}
do
Activity
.
direct_conversation_id
(
activity
,
for_user
)
else
{
:direct_conversation_id
,
participation_id
}
when
is_integer
(
participation_id
)
->
participation_id
_e
->
nil
end
emoji_reactions
=
with
%{
data
:
%{
"reactions"
=>
emoji_reactions
}}
<-
object
do
Enum
.
map
(
emoji_reactions
,
fn
[
emoji
,
users
]
when
is_list
(
users
)
->
build_emoji_map
(
emoji
,
users
,
opts
[
:for
])
{
emoji
,
users
}
when
is_list
(
users
)
->
build_emoji_map
(
emoji
,
users
,
opts
[
:for
])
_
->
nil
end
)
|>
Enum
.
reject
(
&
is_nil
/
1
)
else
_
->
[]
end
# Status muted state (would do 1 request per status unless user mutes are preloaded)
muted
=
thread_muted?
||
UserRelationship
.
exists?
(
get_in
(
opts
,
[
:relationships
,
:user_relationships
]),
:mute
,
opts
[
:for
],
user
,
fn
for_user
,
user
->
User
.
mutes?
(
for_user
,
user
)
end
)
%{
id
:
to_string
(
activity
.
id
),
uri
:
object
.
data
[
"id"
],
url
:
url
,
account
:
AccountView
.
render
(
"show.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
,
card
:
card
,
content
:
content_html
,
text
:
opts
[
:with_source
]
&&
object
.
data
[
"source"
],
created_at
:
created_at
,
reblogs_count
:
announcement_count
,
replies_count
:
object
.
data
[
"repliesCount"
]
||
0
,
favourites_count
:
like_count
,
reblogged
:
reblogged?
(
activity
,
opts
[
:for
]),
favourited
:
present?
(
favorited
),
bookmarked
:
present?
(
bookmarked
),
muted
:
muted
,
pinned
:
pinned?
(
activity
,
user
),
sensitive
:
sensitive
,
spoiler_text
:
summary
,
visibility
:
get_visibility
(
object
),
media_attachments
:
attachments
,
poll
:
render
(
PollView
,
"show.json"
,
object
:
object
,
for
:
opts
[
:for
]),
mentions
:
mentions
,
tags
:
build_tags
(
tags
),
application
:
%{
name
:
"Web"
,
website
:
nil
},
language
:
nil
,
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
},
expires_at
:
expires_at
,
direct_conversation_id
:
direct_conversation_id
,
thread_muted
:
thread_muted?
,
emoji_reactions
:
emoji_reactions
,
parent_visible
:
visible_for_user?
(
reply_to
,
opts
[
:for
])
}
}
end
def
render
(
"show.json"
,
_
)
do
nil
end
def
render
(
"card.json"
,
%{
rich_media
:
rich_media
,
page_url
:
page_url
})
do
page_url_data
=
URI
.
parse
(
page_url
)
page_url_data
=
if
is_binary
(
rich_media
[
"url"
])
do
URI
.
merge
(
page_url_data
,
URI
.
parse
(
rich_media
[
"url"
]))
else
page_url_data
end
page_url
=
page_url_data
|>
to_string
image_url
=
if
is_binary
(
rich_media
[
"image"
])
do
URI
.
merge
(
page_url_data
,
URI
.
parse
(
rich_media
[
"image"
]))
|>
to_string
end
%{
type
:
"link"
,
provider_name
:
page_url_data
.
host
,
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"
]
||
""
,
pleroma
:
%{
opengraph
:
rich_media
}
}
end
def
render
(
"card.json"
,
_
),
do
:
nil
def
render
(
"attachment.json"
,
%{
attachment
:
attachment
})
do
[
attachment_url
|
_
]
=
attachment
[
"url"
]
media_type
=
attachment_url
[
"mediaType"
]
||
attachment_url
[
"mimeType"
]
||
"image"
href
=
attachment_url
[
"href"
]
|>
MediaProxy
.
url
()
href_preview
=
attachment_url
[
"href"
]
|>
MediaProxy
.
preview_url
()
type
=
cond
do
String
.
contains?
(
media_type
,
"image"
)
->
"image"
String
.
contains?
(
media_type
,
"video"
)
->
"video"
String
.
contains?
(
media_type
,
"audio"
)
->
"audio"
true
->
"unknown"
end
<<
hash_id
::
signed
-
32
,
_rest
::
binary
>>
=
:crypto
.
hash
(
:md5
,
href
)
%{
id
:
to_string
(
attachment
[
"id"
]
||
hash_id
),
url
:
href
,
remote_url
:
href
,
preview_url
:
href_preview
,
text_url
:
href
,
type
:
type
,
description
:
attachment
[
"name"
],
pleroma
:
%{
mime_type
:
media_type
}
}
end
def
render
(
"context.json"
,
%{
activity
:
activity
,
activities
:
activities
,
user
:
user
})
do
%{
ancestors
:
ancestors
,
descendants
:
descendants
}
=
activities
|>
Enum
.
reverse
()
|>
Enum
.
group_by
(
fn
%{
id
:
id
}
->
if
id
<
activity
.
id
,
do
:
:ancestors
,
else
:
:descendants
end
)
|>
Map
.
put_new
(
:ancestors
,
[])
|>
Map
.
put_new
(
:descendants
,
[])
%{
ancestors
:
render
(
"index.json"
,
for
:
user
,
activities
:
ancestors
,
as
:
:activity
),
descendants
:
render
(
"index.json"
,
for
:
user
,
activities
:
descendants
,
as
:
:activity
)
}
end
def
get_reply_to
(
activity
,
%{
replied_to_activities
:
replied_to_activities
})
do
object
=
Object
.
normalize
(
activity
)
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
def
get_reply_to
(%{
data
:
%{
"object"
=>
_object
}}
=
activity
,
_
)
do
object
=
Object
.
normalize
(
activity
)
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
(%{
data
:
%{
"name"
=>
name
}}
=
object
)
when
not
is_nil
(
name
)
and
name
!=
""
do
url
=
object
.
data
[
"url"
]
||
object
.
data
[
"id"
]
"<p><a href=\"
#{
url
}
\">
#{
name
}
</a></p>
#{
object
.
data
[
"content"
]
}
"
end
def
render_content
(
object
),
do
:
object
.
data
[
"content"
]
||
""
@doc
"""
Builds a dictionary tags.
##
Examples
iex> Pleroma.Web.MastodonAPI.StatusView.build_tags(["fediverse", "nextcloud"])
[{"name": "fediverse", "url": "/tag/fediverse"},
{"name": "nextcloud", "url": "/tag/nextcloud"}]
"""
@spec
build_tags
(
list
(
any
()))
::
list
(
map
())
def
build_tags
(
object_tags
)
when
is_list
(
object_tags
)
do
object_tags
|>
Enum
.
filter
(
&
is_binary
/
1
)
|>
Enum
.
map
(
&
%{
name
:
&1
,
url
:
"/tag/
#{
URI
.
encode
(
&1
)
}
"
})
end
def
build_tags
(
_
),
do
:
[]
@doc
"""
Builds list emojis.
Arguments: `nil` or list tuple of name and url.
Returns list emojis.
##
Examples
iex> Pleroma.Web.MastodonAPI.StatusView.build_emojis([{"2hu", "corndog.png"}])
[%{shortcode: "2hu", static_url: "corndog.png", url: "corndog.png", visible_in_picker: false}]
"""
@spec
build_emojis
(
nil
|
list
(
tuple
()))
::
list
(
map
())
def
build_emojis
(
nil
),
do
:
[]
def
build_emojis
(
emojis
)
do
emojis
|>
Enum
.
map
(
fn
{
name
,
url
}
->
name
=
HTML
.
strip_tags
(
name
)
url
=
url
|>
HTML
.
strip_tags
()
|>
MediaProxy
.
url
()
%{
shortcode
:
name
,
url
:
url
,
static_url
:
url
,
visible_in_picker
:
false
}
end
)
end
defp
present?
(
nil
),
do
:
false
defp
present?
(
false
),
do
:
false
defp
present?
(
_
),
do
:
true
defp
pinned?
(%
Activity
{
id
:
id
},
%
User
{
pinned_activities
:
pinned_activities
}),
do
:
id
in
pinned_activities
defp
build_emoji_map
(
emoji
,
users
,
current_user
)
do
%{
name
:
emoji
,
count
:
length
(
users
),
me
:
!!
(
current_user
&&
current_user
.
ap_id
in
users
)
}
end
end
File Metadata
Details
Attached
Mime Type
text/x-ruby
Expires
Fri, Apr 3, 9:46 AM (1 d, 11 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1264724
Default Alt Text
status_view.ex (16 KB)
Attached To
Mode
rPUBE pleroma-upstream
Attached
Detach File
Event Timeline
Log In to Comment