forked from AkkomaGang/akkoma
Fix merge conflict
This commit is contained in:
commit
74346a7035
39 changed files with 559 additions and 81 deletions
|
@ -139,7 +139,8 @@
|
||||||
finmoji_enabled: true,
|
finmoji_enabled: true,
|
||||||
mrf_transparency: true,
|
mrf_transparency: true,
|
||||||
autofollowed_nicknames: [],
|
autofollowed_nicknames: [],
|
||||||
max_pinned_statuses: 1
|
max_pinned_statuses: 1,
|
||||||
|
no_attachment_links: false
|
||||||
|
|
||||||
config :pleroma, :markup,
|
config :pleroma, :markup,
|
||||||
# XXX - unfortunately, inline images must be enabled by default right now, because
|
# XXX - unfortunately, inline images must be enabled by default right now, because
|
||||||
|
|
|
@ -95,6 +95,7 @@ config :pleroma, Pleroma.Mailer,
|
||||||
older software for theses nicknames.
|
older software for theses nicknames.
|
||||||
* `max_pinned_statuses`: The maximum number of pinned statuses. `0` will disable the feature.
|
* `max_pinned_statuses`: The maximum number of pinned statuses. `0` will disable the feature.
|
||||||
* `autofollowed_nicknames`: Set to nicknames of (local) users that every new user should automatically follow.
|
* `autofollowed_nicknames`: Set to nicknames of (local) users that every new user should automatically follow.
|
||||||
|
* `no_attachment_links`: Set to true to disable automatically adding attachment link text to statuses
|
||||||
|
|
||||||
## :logger
|
## :logger
|
||||||
* `backends`: `:console` is used to send logs to stdout, `{ExSyslogger, :ex_syslogger}` to log to syslog
|
* `backends`: `:console` is used to send logs to stdout, `{ExSyslogger, :ex_syslogger}` to log to syslog
|
||||||
|
|
|
@ -79,8 +79,10 @@ server {
|
||||||
proxy_cache_valid 200 206 301 304 1h;
|
proxy_cache_valid 200 206 301 304 1h;
|
||||||
proxy_cache_lock on;
|
proxy_cache_lock on;
|
||||||
proxy_ignore_client_abort on;
|
proxy_ignore_client_abort on;
|
||||||
proxy_buffering off;
|
proxy_buffering on;
|
||||||
chunked_transfer_encoding on;
|
chunked_transfer_encoding on;
|
||||||
|
proxy_ignore_headers Cache-Control;
|
||||||
|
proxy_hide_header Cache-Control;
|
||||||
proxy_pass http://localhost:4000;
|
proxy_pass http://localhost:4000;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,15 +3,23 @@ Description=Pleroma social network
|
||||||
After=network.target postgresql.service
|
After=network.target postgresql.service
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
User=pleroma
|
|
||||||
WorkingDirectory=/home/pleroma/pleroma
|
|
||||||
Environment="HOME=/home/pleroma"
|
|
||||||
Environment="MIX_ENV=prod"
|
|
||||||
ExecStart=/usr/local/bin/mix phx.server
|
|
||||||
ExecReload=/bin/kill $MAINPID
|
ExecReload=/bin/kill $MAINPID
|
||||||
KillMode=process
|
KillMode=process
|
||||||
Restart=on-failure
|
Restart=on-failure
|
||||||
|
|
||||||
|
; Name of the user that runs the Pleroma service.
|
||||||
|
User=pleroma
|
||||||
|
; Declares that Pleroma runs in production mode.
|
||||||
|
Environment="MIX_ENV=prod"
|
||||||
|
|
||||||
|
; Make sure that all paths fit your installation.
|
||||||
|
; Path to the home directory of the user running the Pleroma service.
|
||||||
|
Environment="HOME=/home/pleroma"
|
||||||
|
; Path to the folder containing the Pleroma installation.
|
||||||
|
WorkingDirectory=/home/pleroma/pleroma
|
||||||
|
; Path to the Mix binary.
|
||||||
|
ExecStart=/usr/bin/mix phx.server
|
||||||
|
|
||||||
; Some security directives.
|
; Some security directives.
|
||||||
; Use private /tmp and /var/tmp folders inside a new file system namespace, which are discarded after the process stops.
|
; Use private /tmp and /var/tmp folders inside a new file system namespace, which are discarded after the process stops.
|
||||||
PrivateTmp=true
|
PrivateTmp=true
|
||||||
|
|
|
@ -155,7 +155,9 @@ def add_user_links({subs, text}, mentions) do
|
||||||
short_match = String.split(match, "@") |> tl() |> hd()
|
short_match = String.split(match, "@") |> tl() |> hd()
|
||||||
|
|
||||||
{uuid,
|
{uuid,
|
||||||
"<span><a data-user='#{id}' class='mention' href='#{ap_id}'>@<span>#{short_match}</span></a></span>"}
|
"<span class='h-card'><a data-user='#{id}' class='u-url mention' href='#{ap_id}'>@<span>#{
|
||||||
|
short_match
|
||||||
|
}</span></a></span>"}
|
||||||
end)
|
end)
|
||||||
|
|
||||||
{subs, uuid_text}
|
{subs, uuid_text}
|
||||||
|
@ -178,7 +180,7 @@ def add_hashtag_links({subs, text}, tags) do
|
||||||
subs ++
|
subs ++
|
||||||
Enum.map(tags, fn {tag_text, tag, uuid} ->
|
Enum.map(tags, fn {tag_text, tag, uuid} ->
|
||||||
url =
|
url =
|
||||||
"<a data-tag='#{tag}' href='#{Pleroma.Web.base_url()}/tag/#{tag}' rel='tag'>#{
|
"<a class='hashtag' data-tag='#{tag}' href='#{Pleroma.Web.base_url()}/tag/#{tag}' rel='tag'>#{
|
||||||
tag_text
|
tag_text
|
||||||
}</a>"
|
}</a>"
|
||||||
|
|
||||||
|
|
|
@ -78,14 +78,14 @@ defmodule Pleroma.HTML.Scrubber.TwitterText do
|
||||||
|
|
||||||
# links
|
# links
|
||||||
Meta.allow_tag_with_uri_attributes("a", ["href", "data-user", "data-tag"], @valid_schemes)
|
Meta.allow_tag_with_uri_attributes("a", ["href", "data-user", "data-tag"], @valid_schemes)
|
||||||
Meta.allow_tag_with_these_attributes("a", ["name", "title"])
|
Meta.allow_tag_with_these_attributes("a", ["name", "title", "class"])
|
||||||
|
|
||||||
# paragraphs and linebreaks
|
# paragraphs and linebreaks
|
||||||
Meta.allow_tag_with_these_attributes("br", [])
|
Meta.allow_tag_with_these_attributes("br", [])
|
||||||
Meta.allow_tag_with_these_attributes("p", [])
|
Meta.allow_tag_with_these_attributes("p", [])
|
||||||
|
|
||||||
# microformats
|
# microformats
|
||||||
Meta.allow_tag_with_these_attributes("span", [])
|
Meta.allow_tag_with_these_attributes("span", ["class"])
|
||||||
|
|
||||||
# allow inline images for custom emoji
|
# allow inline images for custom emoji
|
||||||
@allow_inline_images Keyword.get(@markup, :allow_inline_images)
|
@allow_inline_images Keyword.get(@markup, :allow_inline_images)
|
||||||
|
@ -119,7 +119,7 @@ defmodule Pleroma.HTML.Scrubber.Default do
|
||||||
Meta.strip_comments()
|
Meta.strip_comments()
|
||||||
|
|
||||||
Meta.allow_tag_with_uri_attributes("a", ["href", "data-user", "data-tag"], @valid_schemes)
|
Meta.allow_tag_with_uri_attributes("a", ["href", "data-user", "data-tag"], @valid_schemes)
|
||||||
Meta.allow_tag_with_these_attributes("a", ["name", "title"])
|
Meta.allow_tag_with_these_attributes("a", ["name", "title", "class"])
|
||||||
|
|
||||||
Meta.allow_tag_with_these_attributes("abbr", ["title"])
|
Meta.allow_tag_with_these_attributes("abbr", ["title"])
|
||||||
|
|
||||||
|
@ -134,7 +134,7 @@ defmodule Pleroma.HTML.Scrubber.Default do
|
||||||
Meta.allow_tag_with_these_attributes("ol", [])
|
Meta.allow_tag_with_these_attributes("ol", [])
|
||||||
Meta.allow_tag_with_these_attributes("p", [])
|
Meta.allow_tag_with_these_attributes("p", [])
|
||||||
Meta.allow_tag_with_these_attributes("pre", [])
|
Meta.allow_tag_with_these_attributes("pre", [])
|
||||||
Meta.allow_tag_with_these_attributes("span", [])
|
Meta.allow_tag_with_these_attributes("span", ["class"])
|
||||||
Meta.allow_tag_with_these_attributes("strong", [])
|
Meta.allow_tag_with_these_attributes("strong", [])
|
||||||
Meta.allow_tag_with_these_attributes("u", [])
|
Meta.allow_tag_with_these_attributes("u", [])
|
||||||
Meta.allow_tag_with_these_attributes("ul", [])
|
Meta.allow_tag_with_these_attributes("ul", [])
|
||||||
|
|
|
@ -31,12 +31,15 @@ def request(method, url, body \\ "", headers \\ [], options \\ []) do
|
||||||
process_request_options(options)
|
process_request_options(options)
|
||||||
|> process_sni_options(url)
|
|> process_sni_options(url)
|
||||||
|
|
||||||
|
params = Keyword.get(options, :params, [])
|
||||||
|
|
||||||
%{}
|
%{}
|
||||||
|> Builder.method(method)
|
|> Builder.method(method)
|
||||||
|> Builder.headers(headers)
|
|> Builder.headers(headers)
|
||||||
|> Builder.opts(options)
|
|> Builder.opts(options)
|
||||||
|> Builder.url(url)
|
|> Builder.url(url)
|
||||||
|> Builder.add_param(:body, :body, body)
|
|> Builder.add_param(:body, :body, body)
|
||||||
|
|> Builder.add_param(:query, :query, params)
|
||||||
|> Enum.into([])
|
|> Enum.into([])
|
||||||
|> (&Tesla.request(Connection.new(), &1)).()
|
|> (&Tesla.request(Connection.new(), &1)).()
|
||||||
end
|
end
|
||||||
|
|
|
@ -100,6 +100,8 @@ def add_optional_params(request, definitions, [{key, value} | tail]) do
|
||||||
Map
|
Map
|
||||||
"""
|
"""
|
||||||
@spec add_param(map(), atom, atom, any()) :: map()
|
@spec add_param(map(), atom, atom, any()) :: map()
|
||||||
|
def add_param(request, :query, :query, values), do: Map.put(request, :query, values)
|
||||||
|
|
||||||
def add_param(request, :body, :body, value), do: Map.put(request, :body, value)
|
def add_param(request, :body, :body, value), do: Map.put(request, :body, value)
|
||||||
|
|
||||||
def add_param(request, :body, key, value) do
|
def add_param(request, :body, key, value) do
|
||||||
|
@ -107,7 +109,10 @@ def add_param(request, :body, key, value) do
|
||||||
|> Map.put_new_lazy(:body, &Tesla.Multipart.new/0)
|
|> Map.put_new_lazy(:body, &Tesla.Multipart.new/0)
|
||||||
|> Map.update!(
|
|> Map.update!(
|
||||||
:body,
|
:body,
|
||||||
&Tesla.Multipart.add_field(&1, key, Poison.encode!(value),
|
&Tesla.Multipart.add_field(
|
||||||
|
&1,
|
||||||
|
key,
|
||||||
|
Jason.encode!(value),
|
||||||
headers: [{:"Content-Type", "application/json"}]
|
headers: [{:"Content-Type", "application/json"}]
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
|
@ -34,10 +34,11 @@ def update_stats do
|
||||||
peers =
|
peers =
|
||||||
from(
|
from(
|
||||||
u in Pleroma.User,
|
u in Pleroma.User,
|
||||||
select: fragment("distinct ?->'host'", u.info),
|
select: fragment("distinct split_part(?, '@', 2)", u.nickname),
|
||||||
where: u.local != ^true
|
where: u.local != ^true
|
||||||
)
|
)
|
||||||
|> Repo.all()
|
|> Repo.all()
|
||||||
|
|> Enum.filter(& &1)
|
||||||
|
|
||||||
domain_count = Enum.count(peers)
|
domain_count = Enum.count(peers)
|
||||||
|
|
||||||
|
@ -45,7 +46,7 @@ def update_stats do
|
||||||
from(u in User.local_user_query(), select: fragment("sum((?->>'note_count')::int)", u.info))
|
from(u in User.local_user_query(), select: fragment("sum((?->>'note_count')::int)", u.info))
|
||||||
|
|
||||||
status_count = Repo.one(status_query)
|
status_count = Repo.one(status_query)
|
||||||
user_count = Repo.aggregate(User.local_user_query(), :count, :id)
|
user_count = Repo.aggregate(User.active_local_user_query(), :count, :id)
|
||||||
|
|
||||||
Agent.update(__MODULE__, fn _ ->
|
Agent.update(__MODULE__, fn _ ->
|
||||||
{peers, %{domain_count: domain_count, status_count: status_count, user_count: user_count}}
|
{peers, %{domain_count: domain_count, status_count: status_count, user_count: user_count}}
|
||||||
|
|
|
@ -34,8 +34,9 @@ defmodule Pleroma.Upload do
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
@type source ::
|
@type source ::
|
||||||
Plug.Upload.t() | data_uri_string ::
|
Plug.Upload.t()
|
||||||
String.t() | {:from_local, name :: String.t(), id :: String.t(), path :: String.t()}
|
| (data_uri_string :: String.t())
|
||||||
|
| {:from_local, name :: String.t(), id :: String.t(), path :: String.t()}
|
||||||
|
|
||||||
@type option ::
|
@type option ::
|
||||||
{:type, :avatar | :banner | :background}
|
{:type, :avatar | :banner | :background}
|
||||||
|
@ -215,6 +216,12 @@ defp tempfile_for_image(data) do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp url_from_spec(base_url, {:file, path}) do
|
defp url_from_spec(base_url, {:file, path}) do
|
||||||
|
path =
|
||||||
|
path
|
||||||
|
|> URI.encode()
|
||||||
|
|> String.replace("?", "%3F")
|
||||||
|
|> String.replace(":", "%3A")
|
||||||
|
|
||||||
[base_url, "media", path]
|
[base_url, "media", path]
|
||||||
|> Path.join()
|
|> Path.join()
|
||||||
end
|
end
|
||||||
|
|
|
@ -822,7 +822,7 @@ def unblock_domain(user, domain) do
|
||||||
update_and_set_cache(cng)
|
update_and_set_cache(cng)
|
||||||
end
|
end
|
||||||
|
|
||||||
def local_user_query() do
|
def local_user_query do
|
||||||
from(
|
from(
|
||||||
u in User,
|
u in User,
|
||||||
where: u.local == true,
|
where: u.local == true,
|
||||||
|
@ -830,7 +830,14 @@ def local_user_query() do
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
def moderator_user_query() do
|
def active_local_user_query do
|
||||||
|
from(
|
||||||
|
u in local_user_query(),
|
||||||
|
where: fragment("?->'deactivated' @> 'false'", u.info)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def moderator_user_query do
|
||||||
from(
|
from(
|
||||||
u in User,
|
u in User,
|
||||||
where: u.local == true,
|
where: u.local == true,
|
||||||
|
@ -1066,4 +1073,14 @@ defp local_nickname_regex() do
|
||||||
@strict_local_nickname_regex
|
@strict_local_nickname_regex
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def error_user(ap_id) do
|
||||||
|
%User{
|
||||||
|
name: ap_id,
|
||||||
|
ap_id: ap_id,
|
||||||
|
info: %User.Info{},
|
||||||
|
nickname: "erroruser@example.com",
|
||||||
|
inserted_at: NaiveDateTime.utc_now()
|
||||||
|
}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -515,15 +515,6 @@ defp restrict_reblogs(query, %{"exclude_reblogs" => val}) when val == "true" or
|
||||||
|
|
||||||
defp restrict_reblogs(query, _), do: query
|
defp restrict_reblogs(query, _), do: query
|
||||||
|
|
||||||
# Only search through last 100_000 activities by default
|
|
||||||
defp restrict_recent(query, %{"whole_db" => true}), do: query
|
|
||||||
|
|
||||||
defp restrict_recent(query, _) do
|
|
||||||
since = (Repo.aggregate(Activity, :max, :id) || 0) - 100_000
|
|
||||||
|
|
||||||
from(activity in query, where: activity.id > ^since)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp restrict_blocked(query, %{"blocking_user" => %User{info: info}}) do
|
defp restrict_blocked(query, %{"blocking_user" => %User{info: info}}) do
|
||||||
blocks = info.blocks || []
|
blocks = info.blocks || []
|
||||||
domain_blocks = info.domain_blocks || []
|
domain_blocks = info.domain_blocks || []
|
||||||
|
@ -574,7 +565,6 @@ def fetch_activities_query(recipients, opts \\ %{}) do
|
||||||
|> restrict_actor(opts)
|
|> restrict_actor(opts)
|
||||||
|> restrict_type(opts)
|
|> restrict_type(opts)
|
||||||
|> restrict_favorited_by(opts)
|
|> restrict_favorited_by(opts)
|
||||||
|> restrict_recent(opts)
|
|
||||||
|> restrict_blocked(opts)
|
|> restrict_blocked(opts)
|
||||||
|> restrict_media(opts)
|
|> restrict_media(opts)
|
||||||
|> restrict_visibility(opts)
|
|> restrict_visibility(opts)
|
||||||
|
|
|
@ -54,6 +54,36 @@ def object(conn, %{"uuid" => uuid}) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def object_likes(conn, %{"uuid" => uuid, "page" => page}) do
|
||||||
|
with ap_id <- o_status_url(conn, :object, uuid),
|
||||||
|
%Object{} = object <- Object.get_cached_by_ap_id(ap_id),
|
||||||
|
{_, true} <- {:public?, ActivityPub.is_public?(object)},
|
||||||
|
likes <- Utils.get_object_likes(object) do
|
||||||
|
{page, _} = Integer.parse(page)
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> put_resp_header("content-type", "application/activity+json")
|
||||||
|
|> json(ObjectView.render("likes.json", ap_id, likes, page))
|
||||||
|
else
|
||||||
|
{:public?, false} ->
|
||||||
|
{:error, :not_found}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def object_likes(conn, %{"uuid" => uuid}) do
|
||||||
|
with ap_id <- o_status_url(conn, :object, uuid),
|
||||||
|
%Object{} = object <- Object.get_cached_by_ap_id(ap_id),
|
||||||
|
{_, true} <- {:public?, ActivityPub.is_public?(object)},
|
||||||
|
likes <- Utils.get_object_likes(object) do
|
||||||
|
conn
|
||||||
|
|> put_resp_header("content-type", "application/activity+json")
|
||||||
|
|> json(ObjectView.render("likes.json", ap_id, likes))
|
||||||
|
else
|
||||||
|
{:public?, false} ->
|
||||||
|
{:error, :not_found}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def activity(conn, %{"uuid" => uuid}) do
|
def activity(conn, %{"uuid" => uuid}) do
|
||||||
with ap_id <- o_status_url(conn, :activity, uuid),
|
with ap_id <- o_status_url(conn, :activity, uuid),
|
||||||
%Activity{} = activity <- Activity.normalize(ap_id),
|
%Activity{} = activity <- Activity.normalize(ap_id),
|
||||||
|
@ -204,6 +234,15 @@ def handle_user_activity(user, %{"type" => "Delete"} = params) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def handle_user_activity(user, %{"type" => "Like"} = params) do
|
||||||
|
with %Object{} = object <- Object.normalize(params["object"]),
|
||||||
|
{:ok, activity, _object} <- ActivityPub.like(user, object) do
|
||||||
|
{:ok, activity}
|
||||||
|
else
|
||||||
|
_ -> {:error, "Can't like object"}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def handle_user_activity(_, _) do
|
def handle_user_activity(_, _) do
|
||||||
{:error, "Unhandled activity type"}
|
{:error, "Unhandled activity type"}
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.ActivityPub.MRF.NoPlaceholderTextPolicy do
|
||||||
|
@behaviour Pleroma.Web.ActivityPub.MRF
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def filter(
|
||||||
|
%{
|
||||||
|
"type" => "Create",
|
||||||
|
"object" => %{"content" => content, "attachment" => _attachment} = child_object
|
||||||
|
} = object
|
||||||
|
)
|
||||||
|
when content in [".", "<p>.</p>"] do
|
||||||
|
child_object =
|
||||||
|
child_object
|
||||||
|
|> Map.put("content", "")
|
||||||
|
|
||||||
|
object =
|
||||||
|
object
|
||||||
|
|> Map.put("object", child_object)
|
||||||
|
|
||||||
|
{:ok, object}
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def filter(object), do: {:ok, object}
|
||||||
|
end
|
|
@ -629,6 +629,7 @@ def prepare_object(object) do
|
||||||
|> add_mention_tags
|
|> add_mention_tags
|
||||||
|> add_emoji_tags
|
|> add_emoji_tags
|
||||||
|> add_attributed_to
|
|> add_attributed_to
|
||||||
|
|> add_likes
|
||||||
|> prepare_attachments
|
|> prepare_attachments
|
||||||
|> set_conversation
|
|> set_conversation
|
||||||
|> set_reply_to_uri
|
|> set_reply_to_uri
|
||||||
|
@ -641,7 +642,7 @@ def prepare_object(object) do
|
||||||
# internal -> Mastodon
|
# internal -> Mastodon
|
||||||
# """
|
# """
|
||||||
|
|
||||||
def prepare_outgoing(%{"type" => "Create", "object" => %{"type" => "Note"} = object} = data) do
|
def prepare_outgoing(%{"type" => "Create", "object" => object} = data) do
|
||||||
object =
|
object =
|
||||||
object
|
object
|
||||||
|> prepare_object
|
|> prepare_object
|
||||||
|
@ -788,6 +789,22 @@ def add_attributed_to(object) do
|
||||||
|> Map.put("attributedTo", attributedTo)
|
|> Map.put("attributedTo", attributedTo)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def add_likes(%{"id" => id, "like_count" => likes} = object) do
|
||||||
|
likes = %{
|
||||||
|
"id" => "#{id}/likes",
|
||||||
|
"first" => "#{id}/likes?page=1",
|
||||||
|
"type" => "OrderedCollection",
|
||||||
|
"totalItems" => likes
|
||||||
|
}
|
||||||
|
|
||||||
|
object
|
||||||
|
|> Map.put("likes", likes)
|
||||||
|
end
|
||||||
|
|
||||||
|
def add_likes(object) do
|
||||||
|
object
|
||||||
|
end
|
||||||
|
|
||||||
def prepare_attachments(object) do
|
def prepare_attachments(object) do
|
||||||
attachments =
|
attachments =
|
||||||
(object["attachment"] || [])
|
(object["attachment"] || [])
|
||||||
|
@ -803,7 +820,6 @@ def prepare_attachments(object) do
|
||||||
defp strip_internal_fields(object) do
|
defp strip_internal_fields(object) do
|
||||||
object
|
object
|
||||||
|> Map.drop([
|
|> Map.drop([
|
||||||
"likes",
|
|
||||||
"like_count",
|
"like_count",
|
||||||
"announcements",
|
"announcements",
|
||||||
"announcement_count",
|
"announcement_count",
|
||||||
|
|
|
@ -231,6 +231,27 @@ def get_existing_like(actor, %{data: %{"id" => id}}) do
|
||||||
Repo.one(query)
|
Repo.one(query)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Returns like activities targeting an object
|
||||||
|
"""
|
||||||
|
def get_object_likes(%{data: %{"id" => id}}) do
|
||||||
|
query =
|
||||||
|
from(
|
||||||
|
activity in Activity,
|
||||||
|
# this is to use the index
|
||||||
|
where:
|
||||||
|
fragment(
|
||||||
|
"coalesce((?)->'object'->>'id', (?)->>'object') = ?",
|
||||||
|
activity.data,
|
||||||
|
activity.data,
|
||||||
|
^id
|
||||||
|
),
|
||||||
|
where: fragment("(?)->>'type' = 'Like'", activity.data)
|
||||||
|
)
|
||||||
|
|
||||||
|
Repo.all(query)
|
||||||
|
end
|
||||||
|
|
||||||
def make_like_data(%User{ap_id: ap_id} = actor, %{data: %{"id" => id}} = object, activity_id) do
|
def make_like_data(%User{ap_id: ap_id} = actor, %{data: %{"id" => id}} = object, activity_id) do
|
||||||
data = %{
|
data = %{
|
||||||
"type" => "Like",
|
"type" => "Like",
|
||||||
|
|
|
@ -35,4 +35,38 @@ def render("object.json", %{object: %Activity{} = activity}) do
|
||||||
|
|
||||||
Map.merge(base, additional)
|
Map.merge(base, additional)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def render("likes.json", ap_id, likes, page) do
|
||||||
|
collection(likes, "#{ap_id}/likes", page)
|
||||||
|
|> Map.merge(Pleroma.Web.ActivityPub.Utils.make_json_ld_header())
|
||||||
|
end
|
||||||
|
|
||||||
|
def render("likes.json", ap_id, likes) do
|
||||||
|
%{
|
||||||
|
"id" => "#{ap_id}/likes",
|
||||||
|
"type" => "OrderedCollection",
|
||||||
|
"totalItems" => length(likes),
|
||||||
|
"first" => collection(likes, "#{ap_id}/likes", 1)
|
||||||
|
}
|
||||||
|
|> Map.merge(Pleroma.Web.ActivityPub.Utils.make_json_ld_header())
|
||||||
|
end
|
||||||
|
|
||||||
|
def collection(collection, iri, page) do
|
||||||
|
offset = (page - 1) * 10
|
||||||
|
items = Enum.slice(collection, offset, 10)
|
||||||
|
items = Enum.map(items, fn object -> Transmogrifier.prepare_object(object.data) end)
|
||||||
|
total = length(collection)
|
||||||
|
|
||||||
|
map = %{
|
||||||
|
"id" => "#{iri}?page=#{page}",
|
||||||
|
"type" => "OrderedCollectionPage",
|
||||||
|
"partOf" => iri,
|
||||||
|
"totalItems" => total,
|
||||||
|
"orderedItems" => items
|
||||||
|
}
|
||||||
|
|
||||||
|
if offset < total do
|
||||||
|
Map.put(map, "next", "#{iri}?page=#{page + 1}")
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -103,7 +103,14 @@ def post(user, %{"status" => status} = data) do
|
||||||
attachments,
|
attachments,
|
||||||
tags,
|
tags,
|
||||||
get_content_type(data["content_type"]),
|
get_content_type(data["content_type"]),
|
||||||
true
|
Enum.member?(
|
||||||
|
[true, "true"],
|
||||||
|
Map.get(
|
||||||
|
data,
|
||||||
|
"no_attachment_links",
|
||||||
|
Pleroma.Config.get([:instance, :no_attachment_links], false)
|
||||||
|
)
|
||||||
|
)
|
||||||
),
|
),
|
||||||
context <- make_context(inReplyTo),
|
context <- make_context(inReplyTo),
|
||||||
cw <- data["spoiler_text"],
|
cw <- data["spoiler_text"],
|
||||||
|
|
|
@ -341,7 +341,6 @@ def post_status(%{assigns: %{user: user}} = conn, %{"status" => _} = params) do
|
||||||
params =
|
params =
|
||||||
params
|
params
|
||||||
|> Map.put("in_reply_to_status_id", params["in_reply_to_id"])
|
|> Map.put("in_reply_to_status_id", params["in_reply_to_id"])
|
||||||
|> Map.put("no_attachment_links", true)
|
|
||||||
|
|
||||||
idempotency_key =
|
idempotency_key =
|
||||||
case get_req_header(conn, "idempotency-key") do
|
case get_req_header(conn, "idempotency-key") do
|
||||||
|
@ -824,9 +823,9 @@ def account_search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) d
|
||||||
json(conn, res)
|
json(conn, res)
|
||||||
end
|
end
|
||||||
|
|
||||||
def favourites(%{assigns: %{user: user}} = conn, _) do
|
def favourites(%{assigns: %{user: user}} = conn, params) do
|
||||||
params =
|
params =
|
||||||
%{}
|
params
|
||||||
|> Map.put("type", "Create")
|
|> Map.put("type", "Create")
|
||||||
|> Map.put("favorited_by", user.ap_id)
|
|> Map.put("favorited_by", user.ap_id)
|
||||||
|> Map.put("blocking_user", user)
|
|> Map.put("blocking_user", user)
|
||||||
|
@ -836,6 +835,7 @@ def favourites(%{assigns: %{user: user}} = conn, _) do
|
||||||
|> Enum.reverse()
|
|> Enum.reverse()
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|
|> add_link_headers(:favourites, activities)
|
||||||
|> put_view(StatusView)
|
|> put_view(StatusView)
|
||||||
|> render("index.json", %{activities: activities, for: user, as: :activity})
|
|> render("index.json", %{activities: activities, for: user, as: :activity})
|
||||||
end
|
end
|
||||||
|
|
|
@ -32,6 +32,19 @@ defp get_replied_to_activities(activities) do
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp get_user(ap_id) do
|
||||||
|
cond do
|
||||||
|
user = User.get_cached_by_ap_id(ap_id) ->
|
||||||
|
user
|
||||||
|
|
||||||
|
user = User.get_by_guessed_nickname(ap_id) ->
|
||||||
|
user
|
||||||
|
|
||||||
|
true ->
|
||||||
|
User.error_user(ap_id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def render("index.json", opts) do
|
def render("index.json", opts) do
|
||||||
replied_to_activities = get_replied_to_activities(opts.activities)
|
replied_to_activities = get_replied_to_activities(opts.activities)
|
||||||
|
|
||||||
|
@ -48,7 +61,7 @@ def render(
|
||||||
"status.json",
|
"status.json",
|
||||||
%{activity: %{data: %{"type" => "Announce", "object" => object}} = activity} = opts
|
%{activity: %{data: %{"type" => "Announce", "object" => object}} = activity} = opts
|
||||||
) do
|
) do
|
||||||
user = User.get_cached_by_ap_id(activity.data["actor"])
|
user = get_user(activity.data["actor"])
|
||||||
created_at = Utils.to_masto_date(activity.data["published"])
|
created_at = Utils.to_masto_date(activity.data["published"])
|
||||||
|
|
||||||
reblogged = Activity.get_create_activity_by_object_ap_id(object)
|
reblogged = Activity.get_create_activity_by_object_ap_id(object)
|
||||||
|
@ -93,7 +106,7 @@ def render(
|
||||||
end
|
end
|
||||||
|
|
||||||
def render("status.json", %{activity: %{data: %{"object" => object}} = activity} = opts) do
|
def render("status.json", %{activity: %{data: %{"object" => object}} = activity} = opts) do
|
||||||
user = User.get_cached_by_ap_id(activity.data["actor"])
|
user = get_user(activity.data["actor"])
|
||||||
|
|
||||||
like_count = object["like_count"] || 0
|
like_count = object["like_count"] || 0
|
||||||
announcement_count = object["announcement_count"] || 0
|
announcement_count = object["announcement_count"] || 0
|
||||||
|
@ -116,7 +129,7 @@ def render("status.json", %{activity: %{data: %{"object" => object}} = activity}
|
||||||
created_at = Utils.to_masto_date(object["published"])
|
created_at = Utils.to_masto_date(object["published"])
|
||||||
|
|
||||||
reply_to = get_reply_to(activity, opts)
|
reply_to = get_reply_to(activity, opts)
|
||||||
reply_to_user = reply_to && User.get_cached_by_ap_id(reply_to.data["actor"])
|
reply_to_user = reply_to && get_user(reply_to.data["actor"])
|
||||||
|
|
||||||
content =
|
content =
|
||||||
object
|
object
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
defmodule Pleroma.Web.RichMedia.Parser do
|
defmodule Pleroma.Web.RichMedia.Parser do
|
||||||
@parsers [Pleroma.Web.RichMedia.Parsers.OGP, Pleroma.Web.RichMedia.Parsers.TwitterCard]
|
@parsers [
|
||||||
|
Pleroma.Web.RichMedia.Parsers.OGP,
|
||||||
|
Pleroma.Web.RichMedia.Parsers.TwitterCard,
|
||||||
|
Pleroma.Web.RichMedia.Parsers.OEmbed
|
||||||
|
]
|
||||||
|
|
||||||
if Mix.env() == :test do
|
if Mix.env() == :test do
|
||||||
def parse(url), do: parse_url(url)
|
def parse(url), do: parse_url(url)
|
||||||
|
|
27
lib/pleroma/web/rich_media/parsers/oembed_parser.ex
Normal file
27
lib/pleroma/web/rich_media/parsers/oembed_parser.ex
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
defmodule Pleroma.Web.RichMedia.Parsers.OEmbed do
|
||||||
|
def parse(html, _data) do
|
||||||
|
with elements = [_ | _] <- get_discovery_data(html),
|
||||||
|
{:ok, oembed_url} <- get_oembed_url(elements),
|
||||||
|
{:ok, oembed_data} <- get_oembed_data(oembed_url) do
|
||||||
|
{:ok, oembed_data}
|
||||||
|
else
|
||||||
|
_e -> {:error, "No OEmbed data found"}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp get_discovery_data(html) do
|
||||||
|
html |> Floki.find("link[type='application/json+oembed']")
|
||||||
|
end
|
||||||
|
|
||||||
|
defp get_oembed_url(nodes) do
|
||||||
|
{"link", attributes, _children} = nodes |> hd()
|
||||||
|
|
||||||
|
{:ok, Enum.into(attributes, %{})["href"]}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp get_oembed_data(url) do
|
||||||
|
{:ok, %Tesla.Env{body: json}} = Pleroma.HTTP.get(url)
|
||||||
|
|
||||||
|
{:ok, Poison.decode!(json)}
|
||||||
|
end
|
||||||
|
end
|
|
@ -431,6 +431,7 @@ defmodule Pleroma.Web.Router do
|
||||||
get("/users/:nickname/followers", ActivityPubController, :followers)
|
get("/users/:nickname/followers", ActivityPubController, :followers)
|
||||||
get("/users/:nickname/following", ActivityPubController, :following)
|
get("/users/:nickname/following", ActivityPubController, :following)
|
||||||
get("/users/:nickname/outbox", ActivityPubController, :outbox)
|
get("/users/:nickname/outbox", ActivityPubController, :outbox)
|
||||||
|
get("/objects/:uuid/likes", ActivityPubController, :object_likes)
|
||||||
end
|
end
|
||||||
|
|
||||||
pipeline :activitypub_client do
|
pipeline :activitypub_client do
|
||||||
|
|
|
@ -101,20 +101,10 @@ defp get_user(ap_id, opts) do
|
||||||
user
|
user
|
||||||
|
|
||||||
true ->
|
true ->
|
||||||
error_user(ap_id)
|
User.error_user(ap_id)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp error_user(ap_id) do
|
|
||||||
%User{
|
|
||||||
name: ap_id,
|
|
||||||
ap_id: ap_id,
|
|
||||||
info: %User.Info{},
|
|
||||||
nickname: "erroruser@example.com",
|
|
||||||
inserted_at: NaiveDateTime.utc_now()
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
def render("index.json", opts) do
|
def render("index.json", opts) do
|
||||||
context_ids = collect_context_ids(opts.activities)
|
context_ids = collect_context_ids(opts.activities)
|
||||||
users = collect_users(opts.activities)
|
users = collect_users(opts.activities)
|
||||||
|
|
|
@ -121,6 +121,12 @@ def incoming_subscription_request(user, %{"hub.mode" => "subscribe"} = params) d
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def incoming_subscription_request(user, params) do
|
||||||
|
Logger.info("Unhandled WebSub request for #{user.nickname}: #{inspect(params)}")
|
||||||
|
|
||||||
|
{:error, "Invalid WebSub request"}
|
||||||
|
end
|
||||||
|
|
||||||
defp get_subscription(topic, callback) do
|
defp get_subscription(topic, callback) do
|
||||||
Repo.get_by(WebsubServerSubscription, topic: topic, callback: callback) ||
|
Repo.get_by(WebsubServerSubscription, topic: topic, callback: callback) ||
|
||||||
%WebsubServerSubscription{}
|
%WebsubServerSubscription{}
|
||||||
|
|
|
@ -67,6 +67,13 @@ def websub_subscription_confirmation(
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def websub_subscription_confirmation(conn, params) do
|
||||||
|
Logger.info("Invalid WebSub confirmation request: #{inspect(params)}")
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> send_resp(500, "Invalid parameters")
|
||||||
|
end
|
||||||
|
|
||||||
def websub_incoming(conn, %{"id" => id}) do
|
def websub_incoming(conn, %{"id" => id}) do
|
||||||
with "sha1=" <> signature <- hd(get_req_header(conn, "x-hub-signature")),
|
with "sha1=" <> signature <- hd(get_req_header(conn, "x-hub-signature")),
|
||||||
signature <- String.downcase(signature),
|
signature <- String.downcase(signature),
|
||||||
|
|
3
test/fixtures/rich_media/oembed.html
vendored
Normal file
3
test/fixtures/rich_media/oembed.html
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<link rel="alternate" type="application/json+oembed"
|
||||||
|
href="http://example.com/oembed.json"
|
||||||
|
title="Bacon Lollys oEmbed Profile" />
|
1
test/fixtures/rich_media/oembed.json
vendored
Normal file
1
test/fixtures/rich_media/oembed.json
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{"type":"photo","flickr_type":"photo","title":"Bacon Lollys","author_name":"\u202e\u202d\u202cbees\u202c","author_url":"https:\/\/www.flickr.com\/photos\/bees\/","width":"1024","height":"768","url":"https:\/\/farm4.staticflickr.com\/3040\/2362225867_4a87ab8baf_b.jpg","web_page":"https:\/\/www.flickr.com\/photos\/bees\/2362225867\/","thumbnail_url":"https:\/\/farm4.staticflickr.com\/3040\/2362225867_4a87ab8baf_q.jpg","thumbnail_width":150,"thumbnail_height":150,"web_page_short_url":"https:\/\/flic.kr\/p\/4AK2sc","license":"All Rights Reserved","license_id":0,"html":"<a data-flickr-embed=\"true\" href=\"https:\/\/www.flickr.com\/photos\/bees\/2362225867\/\" title=\"Bacon Lollys by \u202e\u202d\u202cbees\u202c, on Flickr\"><img src=\"https:\/\/farm4.staticflickr.com\/3040\/2362225867_4a87ab8baf_b.jpg\" width=\"1024\" height=\"768\" alt=\"Bacon Lollys\"><\/a><script async src=\"https:\/\/embedr.flickr.com\/assets\/client-code.js\" charset=\"utf-8\"><\/script>","version":"1.0","cache_age":3600,"provider_name":"Flickr","provider_url":"https:\/\/www.flickr.com\/"}
|
|
@ -19,7 +19,7 @@ test "turns hashtags into links" do
|
||||||
text = "I love #cofe and #2hu"
|
text = "I love #cofe and #2hu"
|
||||||
|
|
||||||
expected_text =
|
expected_text =
|
||||||
"I love <a data-tag='cofe' href='http://localhost:4001/tag/cofe' rel='tag'>#cofe</a> and <a data-tag='2hu' href='http://localhost:4001/tag/2hu' rel='tag'>#2hu</a>"
|
"I love <a class='hashtag' data-tag='cofe' href='http://localhost:4001/tag/cofe' rel='tag'>#cofe</a> and <a class='hashtag' data-tag='2hu' href='http://localhost:4001/tag/2hu' rel='tag'>#2hu</a>"
|
||||||
|
|
||||||
tags = Formatter.parse_tags(text)
|
tags = Formatter.parse_tags(text)
|
||||||
|
|
||||||
|
@ -31,7 +31,7 @@ test "does not turn html characters to tags" do
|
||||||
text = "Fact #3: pleroma does what mastodon't"
|
text = "Fact #3: pleroma does what mastodon't"
|
||||||
|
|
||||||
expected_text =
|
expected_text =
|
||||||
"Fact <a data-tag='3' href='http://localhost:4001/tag/3' rel='tag'>#3</a>: pleroma does what mastodon't"
|
"Fact <a class='hashtag' data-tag='3' href='http://localhost:4001/tag/3' rel='tag'>#3</a>: pleroma does what mastodon't"
|
||||||
|
|
||||||
tags = Formatter.parse_tags(text)
|
tags = Formatter.parse_tags(text)
|
||||||
|
|
||||||
|
@ -144,11 +144,13 @@ test "gives a replacement for user links" do
|
||||||
Enum.each(subs, fn {uuid, _} -> assert String.contains?(text, uuid) end)
|
Enum.each(subs, fn {uuid, _} -> assert String.contains?(text, uuid) end)
|
||||||
|
|
||||||
expected_text =
|
expected_text =
|
||||||
"<span><a data-user='#{gsimg.id}' class='mention' href='#{gsimg.ap_id}'>@<span>gsimg</span></a></span> According to <span><a data-user='#{
|
"<span class='h-card'><a data-user='#{gsimg.id}' class='u-url mention' href='#{
|
||||||
|
gsimg.ap_id
|
||||||
|
}'>@<span>gsimg</span></a></span> According to <span class='h-card'><a data-user='#{
|
||||||
archaeme.id
|
archaeme.id
|
||||||
}' class='mention' href='#{"https://archeme/@archa_eme_"}'>@<span>archa_eme_</span></a></span>, that is @daggsy. Also hello <span><a data-user='#{
|
}' class='u-url mention' href='#{"https://archeme/@archa_eme_"}'>@<span>archa_eme_</span></a></span>, that is @daggsy. Also hello <span class='h-card'><a data-user='#{
|
||||||
archaeme_remote.id
|
archaeme_remote.id
|
||||||
}' class='mention' href='#{archaeme_remote.ap_id}'>@<span>archaeme</span></a></span>"
|
}' class='u-url mention' href='#{archaeme_remote.ap_id}'>@<span>archaeme</span></a></span>"
|
||||||
|
|
||||||
assert expected_text == Formatter.finalize({subs, text})
|
assert expected_text == Formatter.finalize({subs, text})
|
||||||
end
|
end
|
||||||
|
@ -166,7 +168,7 @@ test "gives a replacement for user links when the user is using Osada" do
|
||||||
Enum.each(subs, fn {uuid, _} -> assert String.contains?(text, uuid) end)
|
Enum.each(subs, fn {uuid, _} -> assert String.contains?(text, uuid) end)
|
||||||
|
|
||||||
expected_text =
|
expected_text =
|
||||||
"<span><a data-user='#{mike.id}' class='mention' href='#{mike.ap_id}'>@<span>mike</span></a></span> test"
|
"<span class='h-card'><a data-user='#{mike.id}' class='u-url mention' href='#{mike.ap_id}'>@<span>mike</span></a></span> test"
|
||||||
|
|
||||||
assert expected_text == Formatter.finalize({subs, text})
|
assert expected_text == Formatter.finalize({subs, text})
|
||||||
end
|
end
|
||||||
|
@ -183,7 +185,7 @@ test "gives a replacement for single-character local nicknames" do
|
||||||
Enum.each(subs, fn {uuid, _} -> assert String.contains?(text, uuid) end)
|
Enum.each(subs, fn {uuid, _} -> assert String.contains?(text, uuid) end)
|
||||||
|
|
||||||
expected_text =
|
expected_text =
|
||||||
"<span><a data-user='#{o.id}' class='mention' href='#{o.ap_id}'>@<span>o</span></a></span> hi"
|
"<span class='h-card'><a data-user='#{o.id}' class='u-url mention' href='#{o.ap_id}'>@<span>o</span></a></span> hi"
|
||||||
|
|
||||||
assert expected_text == Formatter.finalize({subs, text})
|
assert expected_text == Formatter.finalize({subs, text})
|
||||||
end
|
end
|
||||||
|
|
|
@ -57,6 +57,11 @@ def direct_note_factory do
|
||||||
%Pleroma.Object{data: Map.merge(data, %{"to" => [user2.ap_id]})}
|
%Pleroma.Object{data: Map.merge(data, %{"to" => [user2.ap_id]})}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def article_factory do
|
||||||
|
note_factory()
|
||||||
|
|> Map.put("type", "Article")
|
||||||
|
end
|
||||||
|
|
||||||
def tombstone_factory do
|
def tombstone_factory do
|
||||||
data = %{
|
data = %{
|
||||||
"type" => "Tombstone",
|
"type" => "Tombstone",
|
||||||
|
@ -110,6 +115,26 @@ def note_activity_factory do
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def article_activity_factory do
|
||||||
|
article = insert(:article)
|
||||||
|
|
||||||
|
data = %{
|
||||||
|
"id" => Pleroma.Web.ActivityPub.Utils.generate_activity_id(),
|
||||||
|
"type" => "Create",
|
||||||
|
"actor" => article.data["actor"],
|
||||||
|
"to" => article.data["to"],
|
||||||
|
"object" => article.data,
|
||||||
|
"published" => DateTime.utc_now() |> DateTime.to_iso8601(),
|
||||||
|
"context" => article.data["context"]
|
||||||
|
}
|
||||||
|
|
||||||
|
%Pleroma.Activity{
|
||||||
|
data: data,
|
||||||
|
actor: data["actor"],
|
||||||
|
recipients: data["to"]
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
def announce_activity_factory do
|
def announce_activity_factory do
|
||||||
note_activity = insert(:note_activity)
|
note_activity = insert(:note_activity)
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
|
|
|
@ -137,5 +137,35 @@ test "copies the file to the configured folder with anonymizing filename" do
|
||||||
|
|
||||||
refute data["name"] == "an [image.jpg"
|
refute data["name"] == "an [image.jpg"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "escapes invalid characters in url" do
|
||||||
|
File.cp!("test/fixtures/image.jpg", "test/fixtures/image_tmp.jpg")
|
||||||
|
|
||||||
|
file = %Plug.Upload{
|
||||||
|
content_type: "image/jpg",
|
||||||
|
path: Path.absname("test/fixtures/image_tmp.jpg"),
|
||||||
|
filename: "an… image.jpg"
|
||||||
|
}
|
||||||
|
|
||||||
|
{:ok, data} = Upload.store(file)
|
||||||
|
[attachment_url | _] = data["url"]
|
||||||
|
|
||||||
|
assert Path.basename(attachment_url["href"]) == "an%E2%80%A6%20image.jpg"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "replaces : (colon) and ? (question-mark) to %3A and %3F (respectively)" do
|
||||||
|
File.cp!("test/fixtures/image.jpg", "test/fixtures/image_tmp.jpg")
|
||||||
|
|
||||||
|
file = %Plug.Upload{
|
||||||
|
content_type: "image/jpg",
|
||||||
|
path: Path.absname("test/fixtures/image_tmp.jpg"),
|
||||||
|
filename: "is:an?image.jpg"
|
||||||
|
}
|
||||||
|
|
||||||
|
{:ok, data} = Upload.store(file)
|
||||||
|
[attachment_url | _] = data["url"]
|
||||||
|
|
||||||
|
assert Path.basename(attachment_url["href"]) == "is%3Aan%3Fimage.jpg"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -89,6 +89,21 @@ test "it returns 404 for tombstone objects", %{conn: conn} do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "/object/:uuid/likes" do
|
||||||
|
test "it returns the like activities in a collection", %{conn: conn} do
|
||||||
|
like = insert(:like_activity)
|
||||||
|
uuid = String.split(like.data["object"], "/") |> List.last()
|
||||||
|
|
||||||
|
result =
|
||||||
|
conn
|
||||||
|
|> put_req_header("accept", "application/activity+json")
|
||||||
|
|> get("/objects/#{uuid}/likes")
|
||||||
|
|> json_response(200)
|
||||||
|
|
||||||
|
assert List.first(result["first"]["orderedItems"])["id"] == like.data["id"]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "/activities/:uuid" do
|
describe "/activities/:uuid" do
|
||||||
test "it returns a json representation of the activity", %{conn: conn} do
|
test "it returns a json representation of the activity", %{conn: conn} do
|
||||||
activity = insert(:note_activity)
|
activity = insert(:note_activity)
|
||||||
|
@ -292,6 +307,31 @@ test "it rejects delete activity of object from other actor", %{conn: conn} do
|
||||||
|
|
||||||
assert json_response(conn, 400)
|
assert json_response(conn, 400)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "it increases like count when receiving a like action", %{conn: conn} do
|
||||||
|
note_activity = insert(:note_activity)
|
||||||
|
user = User.get_cached_by_ap_id(note_activity.data["actor"])
|
||||||
|
|
||||||
|
data = %{
|
||||||
|
type: "Like",
|
||||||
|
object: %{
|
||||||
|
id: note_activity.data["object"]["id"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> put_req_header("content-type", "application/activity+json")
|
||||||
|
|> post("/users/#{user.nickname}/outbox", data)
|
||||||
|
|
||||||
|
result = json_response(conn, 201)
|
||||||
|
assert Activity.get_by_ap_id(result["id"])
|
||||||
|
|
||||||
|
object = Object.get_by_ap_id(note_activity.data["object"]["id"])
|
||||||
|
assert object
|
||||||
|
assert object.data["like_count"] == 1
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "/users/:nickname/followers" do
|
describe "/users/:nickname/followers" do
|
||||||
|
|
|
@ -829,12 +829,33 @@ test "it strips internal fields" do
|
||||||
assert length(modified["object"]["tag"]) == 2
|
assert length(modified["object"]["tag"]) == 2
|
||||||
|
|
||||||
assert is_nil(modified["object"]["emoji"])
|
assert is_nil(modified["object"]["emoji"])
|
||||||
assert is_nil(modified["object"]["likes"])
|
|
||||||
assert is_nil(modified["object"]["like_count"])
|
assert is_nil(modified["object"]["like_count"])
|
||||||
assert is_nil(modified["object"]["announcements"])
|
assert is_nil(modified["object"]["announcements"])
|
||||||
assert is_nil(modified["object"]["announcement_count"])
|
assert is_nil(modified["object"]["announcement_count"])
|
||||||
assert is_nil(modified["object"]["context_id"])
|
assert is_nil(modified["object"]["context_id"])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "it strips internal fields of article" do
|
||||||
|
activity = insert(:article_activity)
|
||||||
|
|
||||||
|
{:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
|
||||||
|
|
||||||
|
assert length(modified["object"]["tag"]) == 2
|
||||||
|
|
||||||
|
assert is_nil(modified["object"]["emoji"])
|
||||||
|
assert is_nil(modified["object"]["like_count"])
|
||||||
|
assert is_nil(modified["object"]["announcements"])
|
||||||
|
assert is_nil(modified["object"]["announcement_count"])
|
||||||
|
assert is_nil(modified["object"]["context_id"])
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it adds like collection to object" do
|
||||||
|
activity = insert(:note_activity)
|
||||||
|
{:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
|
||||||
|
|
||||||
|
assert modified["object"]["likes"]["type"] == "OrderedCollection"
|
||||||
|
assert modified["object"]["likes"]["totalItems"] == 0
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "user upgrade" do
|
describe "user upgrade" do
|
||||||
|
|
|
@ -386,7 +386,7 @@ test "get a filter", %{conn: conn} do
|
||||||
|> assign(:user, user)
|
|> assign(:user, user)
|
||||||
|> get("/api/v1/filters/#{filter.filter_id}")
|
|> get("/api/v1/filters/#{filter.filter_id}")
|
||||||
|
|
||||||
assert response = json_response(conn, 200)
|
assert _response = json_response(conn, 200)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "update a filter", %{conn: conn} do
|
test "update a filter", %{conn: conn} do
|
||||||
|
@ -600,7 +600,9 @@ test "list of notifications", %{conn: conn} do
|
||||||
|> get("/api/v1/notifications")
|
|> get("/api/v1/notifications")
|
||||||
|
|
||||||
expected_response =
|
expected_response =
|
||||||
"hi <span><a data-user=\"#{user.id}\" href=\"#{user.ap_id}\">@<span>#{user.nickname}</span></a></span>"
|
"hi <span class=\"h-card\"><a data-user=\"#{user.id}\" class=\"u-url mention\" href=\"#{
|
||||||
|
user.ap_id
|
||||||
|
}\">@<span>#{user.nickname}</span></a></span>"
|
||||||
|
|
||||||
assert [%{"status" => %{"content" => response}} | _rest] = json_response(conn, 200)
|
assert [%{"status" => %{"content" => response}} | _rest] = json_response(conn, 200)
|
||||||
assert response == expected_response
|
assert response == expected_response
|
||||||
|
@ -621,7 +623,9 @@ test "getting a single notification", %{conn: conn} do
|
||||||
|> get("/api/v1/notifications/#{notification.id}")
|
|> get("/api/v1/notifications/#{notification.id}")
|
||||||
|
|
||||||
expected_response =
|
expected_response =
|
||||||
"hi <span><a data-user=\"#{user.id}\" href=\"#{user.ap_id}\">@<span>#{user.nickname}</span></a></span>"
|
"hi <span class=\"h-card\"><a data-user=\"#{user.id}\" class=\"u-url mention\" href=\"#{
|
||||||
|
user.ap_id
|
||||||
|
}\">@<span>#{user.nickname}</span></a></span>"
|
||||||
|
|
||||||
assert %{"status" => %{"content" => response}} = json_response(conn, 200)
|
assert %{"status" => %{"content" => response}} = json_response(conn, 200)
|
||||||
assert response == expected_response
|
assert response == expected_response
|
||||||
|
@ -1349,13 +1353,42 @@ test "returns the favorites of a user", %{conn: conn} do
|
||||||
|
|
||||||
{:ok, _, _} = CommonAPI.favorite(activity.id, user)
|
{:ok, _, _} = CommonAPI.favorite(activity.id, user)
|
||||||
|
|
||||||
conn =
|
first_conn =
|
||||||
conn
|
conn
|
||||||
|> assign(:user, user)
|
|> assign(:user, user)
|
||||||
|> get("/api/v1/favourites")
|
|> get("/api/v1/favourites")
|
||||||
|
|
||||||
assert [status] = json_response(conn, 200)
|
assert [status] = json_response(first_conn, 200)
|
||||||
assert status["id"] == to_string(activity.id)
|
assert status["id"] == to_string(activity.id)
|
||||||
|
|
||||||
|
assert [{"link", _link_header}] =
|
||||||
|
Enum.filter(first_conn.resp_headers, fn element -> match?({"link", _}, element) end)
|
||||||
|
|
||||||
|
# Honours query params
|
||||||
|
{:ok, second_activity} =
|
||||||
|
CommonAPI.post(other_user, %{
|
||||||
|
"status" =>
|
||||||
|
"Trees Are Never Sad Look At Them Every Once In Awhile They're Quite Beautiful."
|
||||||
|
})
|
||||||
|
|
||||||
|
{:ok, _, _} = CommonAPI.favorite(second_activity.id, user)
|
||||||
|
|
||||||
|
last_like = status["id"]
|
||||||
|
|
||||||
|
second_conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> get("/api/v1/favourites?since_id=#{last_like}")
|
||||||
|
|
||||||
|
assert [second_status] = json_response(second_conn, 200)
|
||||||
|
assert second_status["id"] == to_string(second_activity.id)
|
||||||
|
|
||||||
|
third_conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> get("/api/v1/favourites?limit=0")
|
||||||
|
|
||||||
|
assert [] = json_response(third_conn, 200)
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "updating credentials" do
|
describe "updating credentials" do
|
||||||
|
@ -1373,9 +1406,9 @@ test "updates the user's bio", %{conn: conn} do
|
||||||
assert user = json_response(conn, 200)
|
assert user = json_response(conn, 200)
|
||||||
|
|
||||||
assert user["note"] ==
|
assert user["note"] ==
|
||||||
"I drink <a data-tag=\"cofe\" href=\"http://localhost:4001/tag/cofe\">#cofe</a> with <span><a data-user=\"#{
|
"I drink <a class=\"hashtag\" data-tag=\"cofe\" href=\"http://localhost:4001/tag/cofe\">#cofe</a> with <span class=\"h-card\"><a data-user=\"#{
|
||||||
user2.id
|
user2.id
|
||||||
}\" href=\"#{user2.ap_id}\">@<span>#{user2.nickname}</span></a></span>"
|
}\" class=\"u-url mention\" href=\"#{user2.ap_id}\">@<span>#{user2.nickname}</span></a></span>"
|
||||||
end
|
end
|
||||||
|
|
||||||
test "updates the user's locking status", %{conn: conn} do
|
test "updates the user's locking status", %{conn: conn} do
|
||||||
|
@ -1440,22 +1473,41 @@ test "updates the user's banner", %{conn: conn} do
|
||||||
end
|
end
|
||||||
|
|
||||||
test "get instance information", %{conn: conn} do
|
test "get instance information", %{conn: conn} do
|
||||||
insert(:user, %{local: true})
|
|
||||||
user = insert(:user, %{local: true})
|
user = insert(:user, %{local: true})
|
||||||
insert(:user, %{local: false})
|
|
||||||
|
user2 = insert(:user, %{local: true})
|
||||||
|
{:ok, _user2} = User.deactivate(user2, !user2.info.deactivated)
|
||||||
|
|
||||||
|
insert(:user, %{local: false, nickname: "u@peer1.com"})
|
||||||
|
insert(:user, %{local: false, nickname: "u@peer2.com"})
|
||||||
|
|
||||||
{:ok, _} = TwitterAPI.create_status(user, %{"status" => "cofe"})
|
{:ok, _} = TwitterAPI.create_status(user, %{"status" => "cofe"})
|
||||||
|
|
||||||
Pleroma.Stats.update_stats()
|
Pleroma.Stats.update_stats()
|
||||||
|
|
||||||
conn =
|
conn = get(conn, "/api/v1/instance")
|
||||||
conn
|
|
||||||
|> get("/api/v1/instance")
|
|
||||||
|
|
||||||
assert result = json_response(conn, 200)
|
assert result = json_response(conn, 200)
|
||||||
|
|
||||||
assert result["stats"]["user_count"] == 2
|
stats = result["stats"]
|
||||||
assert result["stats"]["status_count"] == 1
|
|
||||||
|
assert stats
|
||||||
|
assert stats["user_count"] == 1
|
||||||
|
assert stats["status_count"] == 1
|
||||||
|
assert stats["domain_count"] == 2
|
||||||
|
end
|
||||||
|
|
||||||
|
test "get peers", %{conn: conn} do
|
||||||
|
insert(:user, %{local: false, nickname: "u@peer1.com"})
|
||||||
|
insert(:user, %{local: false, nickname: "u@peer2.com"})
|
||||||
|
|
||||||
|
Pleroma.Stats.update_stats()
|
||||||
|
|
||||||
|
conn = get(conn, "/api/v1/instance/peers")
|
||||||
|
|
||||||
|
assert result = json_response(conn, 200)
|
||||||
|
|
||||||
|
assert ["peer1.com", "peer2.com"] == Enum.sort(result)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "put settings", %{conn: conn} do
|
test "put settings", %{conn: conn} do
|
||||||
|
@ -1466,7 +1518,7 @@ test "put settings", %{conn: conn} do
|
||||||
|> assign(:user, user)
|
|> assign(:user, user)
|
||||||
|> put("/api/web/settings", %{"data" => %{"programming" => "socks"}})
|
|> put("/api/web/settings", %{"data" => %{"programming" => "socks"}})
|
||||||
|
|
||||||
assert result = json_response(conn, 200)
|
assert _result = json_response(conn, 200)
|
||||||
|
|
||||||
user = User.get_cached_by_ap_id(user.ap_id)
|
user = User.get_cached_by_ap_id(user.ap_id)
|
||||||
assert user.info.settings == %{"programming" => "socks"}
|
assert user.info.settings == %{"programming" => "socks"}
|
||||||
|
|
|
@ -19,6 +19,36 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do
|
||||||
:ok
|
:ok
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "returns a temporary ap_id based user for activities missing db users" do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
{:ok, activity} = CommonAPI.post(user, %{"status" => "Hey @shp!", "visibility" => "direct"})
|
||||||
|
|
||||||
|
Repo.delete(user)
|
||||||
|
Cachex.clear(:user_cache)
|
||||||
|
|
||||||
|
%{account: ms_user} = StatusView.render("status.json", activity: activity)
|
||||||
|
|
||||||
|
assert ms_user.acct == "erroruser@example.com"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "tries to get a user by nickname if fetching by ap_id doesn't work" do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
{:ok, activity} = CommonAPI.post(user, %{"status" => "Hey @shp!", "visibility" => "direct"})
|
||||||
|
|
||||||
|
{:ok, user} =
|
||||||
|
user
|
||||||
|
|> Ecto.Changeset.change(%{ap_id: "#{user.ap_id}/extension/#{user.nickname}"})
|
||||||
|
|> Repo.update()
|
||||||
|
|
||||||
|
Cachex.clear(:user_cache)
|
||||||
|
|
||||||
|
result = StatusView.render("status.json", activity: activity)
|
||||||
|
|
||||||
|
assert result[:account][:id] == to_string(user.id)
|
||||||
|
end
|
||||||
|
|
||||||
test "a note with null content" do
|
test "a note with null content" do
|
||||||
note = insert(:note_activity)
|
note = insert(:note_activity)
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,18 @@ defmodule Pleroma.Web.RichMedia.ParserTest do
|
||||||
} ->
|
} ->
|
||||||
%Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/twitter_card.html")}
|
%Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/twitter_card.html")}
|
||||||
|
|
||||||
|
%{
|
||||||
|
method: :get,
|
||||||
|
url: "http://example.com/oembed"
|
||||||
|
} ->
|
||||||
|
%Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/oembed.html")}
|
||||||
|
|
||||||
|
%{
|
||||||
|
method: :get,
|
||||||
|
url: "http://example.com/oembed.json"
|
||||||
|
} ->
|
||||||
|
%Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/oembed.json")}
|
||||||
|
|
||||||
%{method: :get, url: "http://example.com/empty"} ->
|
%{method: :get, url: "http://example.com/empty"} ->
|
||||||
%Tesla.Env{status: 200, body: "hello"}
|
%Tesla.Env{status: 200, body: "hello"}
|
||||||
end)
|
end)
|
||||||
|
@ -48,4 +60,33 @@ test "parses twitter card" do
|
||||||
description: "View the album on Flickr."
|
description: "View the album on Flickr."
|
||||||
}}
|
}}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "parses OEmbed" do
|
||||||
|
assert Pleroma.Web.RichMedia.Parser.parse("http://example.com/oembed") ==
|
||||||
|
{:ok,
|
||||||
|
%{
|
||||||
|
"author_name" => "bees",
|
||||||
|
"author_url" => "https://www.flickr.com/photos/bees/",
|
||||||
|
"cache_age" => 3600,
|
||||||
|
"flickr_type" => "photo",
|
||||||
|
"height" => "768",
|
||||||
|
"html" =>
|
||||||
|
"<a data-flickr-embed=\"true\" href=\"https://www.flickr.com/photos/bees/2362225867/\" title=\"Bacon Lollys by bees, on Flickr\"><img src=\"https://farm4.staticflickr.com/3040/2362225867_4a87ab8baf_b.jpg\" width=\"1024\" height=\"768\" alt=\"Bacon Lollys\"></a><script async src=\"https://embedr.flickr.com/assets/client-code.js\" charset=\"utf-8\"></script>",
|
||||||
|
"license" => "All Rights Reserved",
|
||||||
|
"license_id" => 0,
|
||||||
|
"provider_name" => "Flickr",
|
||||||
|
"provider_url" => "https://www.flickr.com/",
|
||||||
|
"thumbnail_height" => 150,
|
||||||
|
"thumbnail_url" =>
|
||||||
|
"https://farm4.staticflickr.com/3040/2362225867_4a87ab8baf_q.jpg",
|
||||||
|
"thumbnail_width" => 150,
|
||||||
|
"title" => "Bacon Lollys",
|
||||||
|
"type" => "photo",
|
||||||
|
"url" => "https://farm4.staticflickr.com/3040/2362225867_4a87ab8baf_b.jpg",
|
||||||
|
"version" => "1.0",
|
||||||
|
"web_page" => "https://www.flickr.com/photos/bees/2362225867/",
|
||||||
|
"web_page_short_url" => "https://flic.kr/p/4AK2sc",
|
||||||
|
"width" => "1024"
|
||||||
|
}}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1357,9 +1357,9 @@ test "it updates a user's profile", %{conn: conn} do
|
||||||
assert user.name == "new name"
|
assert user.name == "new name"
|
||||||
|
|
||||||
assert user.bio ==
|
assert user.bio ==
|
||||||
"hi <span><a data-user='#{user2.id}' class='mention' href='#{user2.ap_id}'>@<span>#{
|
"hi <span class='h-card'><a data-user='#{user2.id}' class='u-url mention' href='#{
|
||||||
user2.nickname
|
user2.ap_id
|
||||||
}</span></a></span>"
|
}'>@<span>#{user2.nickname}</span></a></span>"
|
||||||
|
|
||||||
assert json_response(conn, 200) == UserView.render("user.json", %{user: user, for: user})
|
assert json_response(conn, 200) == UserView.render("user.json", %{user: user, for: user})
|
||||||
end
|
end
|
||||||
|
|
|
@ -38,7 +38,7 @@ test "create a status" do
|
||||||
{:ok, activity = %Activity{}} = TwitterAPI.create_status(user, input)
|
{:ok, activity = %Activity{}} = TwitterAPI.create_status(user, input)
|
||||||
|
|
||||||
expected_text =
|
expected_text =
|
||||||
"Hello again, <span><a data-user='#{mentioned_user.id}' class='mention' href='shp'>@<span>shp</span></a></span>.<script></script><br>This is on another :moominmamma: line. <a data-tag='2hu' href='http://localhost:4001/tag/2hu' rel='tag'>#2hu</a> <a data-tag='epic' href='http://localhost:4001/tag/epic' rel='tag'>#epic</a> <a data-tag='phantasmagoric' href='http://localhost:4001/tag/phantasmagoric' rel='tag'>#phantasmagoric</a><br><a href=\"http://example.org/image.jpg\" class='attachment'>image.jpg</a>"
|
"Hello again, <span class='h-card'><a data-user='#{mentioned_user.id}' class='u-url mention' href='shp'>@<span>shp</span></a></span>.<script></script><br>This is on another :moominmamma: line. <a class='hashtag' data-tag='2hu' href='http://localhost:4001/tag/2hu' rel='tag'>#2hu</a> <a class='hashtag' data-tag='epic' href='http://localhost:4001/tag/epic' rel='tag'>#epic</a> <a class='hashtag' data-tag='phantasmagoric' 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", "content"]) == expected_text
|
||||||
assert get_in(activity.data, ["object", "type"]) == "Note"
|
assert get_in(activity.data, ["object", "type"]) == "Note"
|
||||||
|
@ -328,7 +328,7 @@ test "it registers a new user and parses mentions in the bio" do
|
||||||
{:ok, user2} = TwitterAPI.register_user(data2)
|
{:ok, user2} = TwitterAPI.register_user(data2)
|
||||||
|
|
||||||
expected_text =
|
expected_text =
|
||||||
"<span><a data-user='#{user1.id}' class='mention' href='#{user1.ap_id}'>@<span>john</span></a></span> test"
|
"<span class='h-card'><a data-user='#{user1.id}' class='u-url mention' href='#{user1.ap_id}'>@<span>john</span></a></span> test"
|
||||||
|
|
||||||
assert user2.bio == expected_text
|
assert user2.bio == expected_text
|
||||||
end
|
end
|
||||||
|
|
|
@ -66,7 +66,7 @@ test "a create activity with a html status" do
|
||||||
result = ActivityView.render("activity.json", activity: activity)
|
result = ActivityView.render("activity.json", activity: activity)
|
||||||
|
|
||||||
assert result["statusnet_html"] ==
|
assert result["statusnet_html"] ==
|
||||||
"<a data-tag=\"bike\" href=\"http://localhost:4001/tag/bike\">#Bike</a> log - Commute Tuesday<br /><a href=\"https://pla.bike/posts/20181211/\">https://pla.bike/posts/20181211/</a><br /><a data-tag=\"cycling\" href=\"http://localhost:4001/tag/cycling\">#cycling</a> <a data-tag=\"chscycling\" href=\"http://localhost:4001/tag/chscycling\">#CHScycling</a> <a data-tag=\"commute\" href=\"http://localhost:4001/tag/commute\">#commute</a><br />MVIMG_20181211_054020.jpg"
|
"<a class=\"hashtag\" data-tag=\"bike\" href=\"http://localhost:4001/tag/bike\">#Bike</a> log - Commute Tuesday<br /><a href=\"https://pla.bike/posts/20181211/\">https://pla.bike/posts/20181211/</a><br /><a class=\"hashtag\" data-tag=\"cycling\" href=\"http://localhost:4001/tag/cycling\">#cycling</a> <a class=\"hashtag\" data-tag=\"chscycling\" href=\"http://localhost:4001/tag/chscycling\">#CHScycling</a> <a class=\"hashtag\" data-tag=\"commute\" href=\"http://localhost:4001/tag/commute\">#commute</a><br />MVIMG_20181211_054020.jpg"
|
||||||
|
|
||||||
assert result["text"] ==
|
assert result["text"] ==
|
||||||
"#Bike log - Commute Tuesday\nhttps://pla.bike/posts/20181211/\n#cycling #CHScycling #commute\nMVIMG_20181211_054020.jpg"
|
"#Bike log - Commute Tuesday\nhttps://pla.bike/posts/20181211/\n#cycling #CHScycling #commute\nMVIMG_20181211_054020.jpg"
|
||||||
|
@ -141,7 +141,9 @@ test "a create activity with a note" do
|
||||||
"summary" => "",
|
"summary" => "",
|
||||||
"summary_html" => "",
|
"summary_html" => "",
|
||||||
"statusnet_html" =>
|
"statusnet_html" =>
|
||||||
"Hey <span><a data-user=\"#{other_user.id}\" href=\"#{other_user.ap_id}\">@<span>shp</span></a></span>!",
|
"Hey <span class=\"h-card\"><a data-user=\"#{other_user.id}\" class=\"u-url mention\" href=\"#{
|
||||||
|
other_user.ap_id
|
||||||
|
}\">@<span>shp</span></a></span>!",
|
||||||
"tags" => [],
|
"tags" => [],
|
||||||
"text" => "Hey @shp!",
|
"text" => "Hey @shp!",
|
||||||
"uri" => activity.data["object"]["id"],
|
"uri" => activity.data["object"]["id"],
|
||||||
|
|
Loading…
Reference in a new issue