Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F21967441
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.ActivityExpiration
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
]
# 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
def
get_user
(
ap_id
,
fake_record_fallback
\\
true
)
do
cond
do
user
=
User
.
get_cached_by_ap_id
(
ap_id
)
->
user
user
=
User
.
get_by_guessed_nickname
(
ap_id
)
->
user
fake_record_fallback
->
# TODO: refactor (fake records is never a good idea)
User
.
error_user
(
ap_id
)
true
->
nil
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
)
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
(
&
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
=
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
=
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
,
%
ActivityExpiration
{
scheduled_at
:
scheduled_at
}
<-
ActivityExpiration
.
get_by_activity_id
(
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
&&
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
]
->
%{
name
:
emoji
,
count
:
length
(
users
),
me
:
!!
(
opts
[
:for
]
&&
opts
[
:for
]
.
ap_id
in
users
)
}
end
)
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
()
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
,
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
:
%{
"type"
=>
object_type
}}
=
object
)
when
object_type
in
[
"Video"
,
"Event"
,
"Audio"
]
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
.
data
[
"content"
]
||
""
end
end
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
.
data
[
"name"
],
url
when
is_bitstring
(
url
)
<-
object
.
data
[
"url"
]
do
"<p><a href=\"
#{
url
}
\">
#{
summary
}
</a></p>
#{
object
.
data
[
"content"
]
}
"
else
_
->
object
.
data
[
"content"
]
||
""
end
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
end
File Metadata
Details
Attached
Mime Type
text/x-ruby
Expires
Sun, Dec 28, 3:23 AM (13 h, 21 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
726042
Default Alt Text
status_view.ex (16 KB)
Attached To
Mode
rPUBE pleroma-upstream
Attached
Detach File
Event Timeline
Log In to Comment