Merge remote-tracking branch 'upstream/develop' into neckbeard
This commit is contained in:
commit
e715a6ff40
|
@ -0,0 +1 @@
|
|||
Implement quotes
|
|
@ -0,0 +1 @@
|
|||
Add unified streaming endpoint
|
|
@ -434,6 +434,8 @@ config :pleroma, :mrf_object_age,
|
|||
|
||||
config :pleroma, :mrf_follow_bot, follower_nickname: nil
|
||||
|
||||
config :pleroma, :mrf_inline_quote, template: "<bdi>RT:</bdi> {url}"
|
||||
|
||||
config :pleroma, :rich_media,
|
||||
enabled: true,
|
||||
ignore_hosts: [],
|
||||
|
@ -858,7 +860,11 @@ config :pleroma, :restrict_unauthenticated,
|
|||
config :pleroma, Pleroma.Web.ApiSpec.CastAndValidate, strict: false
|
||||
|
||||
config :pleroma, :mrf,
|
||||
policies: [Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy, Pleroma.Web.ActivityPub.MRF.TagPolicy],
|
||||
policies: [
|
||||
Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy,
|
||||
Pleroma.Web.ActivityPub.MRF.TagPolicy,
|
||||
Pleroma.Web.ActivityPub.MRF.InlineQuotePolicy
|
||||
],
|
||||
transparency: true,
|
||||
transparency_exclusions: []
|
||||
|
||||
|
|
|
@ -160,6 +160,8 @@ To add configuration to your config file, you can copy it from the base config.
|
|||
* `Pleroma.Web.ActivityPub.MRF.AntiFollowbotPolicy`: Drops follow requests from followbots. Users can still allow bots to follow them by first following the bot.
|
||||
* `Pleroma.Web.ActivityPub.MRF.KeywordPolicy`: Rejects or removes from the federated timeline or replaces keywords. (See [`:mrf_keyword`](#mrf_keyword)).
|
||||
* `Pleroma.Web.ActivityPub.MRF.ForceMentionsInContent`: Forces every mentioned user to be reflected in the post content.
|
||||
* `Pleroma.Web.ActivityPub.MRF.InlineQuotePolicy`: Forces quote post URLs to be reflected in the message content inline.
|
||||
* `Pleroma.Web.ActivityPub.MRF.QuoteToLinkTagPolicy`: Force a Link tag for posts quoting another post. (may break outgoing federation of quote posts with older Pleroma versions)
|
||||
* `transparency`: Make the content of your Message Rewrite Facility settings public (via nodeinfo).
|
||||
* `transparency_exclusions`: Exclude specific instance names from MRF transparency. The use of the exclusions feature will be disclosed in nodeinfo as a boolean value.
|
||||
|
||||
|
@ -267,6 +269,9 @@ Notes:
|
|||
* `federated_timeline_removal_url`: A list of patterns which result in message with emojis whose URLs match being removed from federated timelines (a.k.a unlisted). This will apply only to statuses. Each pattern can be a string or a [regular expression](https://hexdocs.pm/elixir/Regex.html).
|
||||
* `federated_timeline_removal_shortcode`: A list of patterns which result in message with emojis whose shortcodes match being removed from federated timelines (a.k.a unlisted). This will apply only to statuses. Each pattern can be a string or a [regular expression](https://hexdocs.pm/elixir/Regex.html).
|
||||
|
||||
#### :mrf_inline_quote
|
||||
* `template`: The template to append to the post. `{url}` will be replaced with the actual link to the quoted post. Default: `<bdi>RT:</bdi> {url}`
|
||||
|
||||
### :activitypub
|
||||
* `unfollow_blocked`: Whether blocks result in people getting unfollowed
|
||||
* `outgoing_blocks`: Whether to federate blocks to other instances
|
||||
|
|
|
@ -357,6 +357,122 @@ The message payload consist of:
|
|||
- `follower_count`: follower count
|
||||
- `following_count`: following count
|
||||
|
||||
### Authenticating via `sec-websocket-protocol` header
|
||||
|
||||
Pleroma allows to authenticate via the `sec-websocket-protocol` header, for example, if your access token is `your-access-token`, you can authenticate using the following:
|
||||
|
||||
```
|
||||
sec-websocket-protocol: your-access-token
|
||||
```
|
||||
|
||||
### Authenticating after connection via `pleroma:authenticate` event
|
||||
|
||||
Pleroma allows to authenticate after connection is established, via the `pleroma:authenticate` event. For example, if your access token is `your-access-token`, you can send the following after the connection is established:
|
||||
|
||||
```
|
||||
{"type": "pleroma:authenticate", "token": "your-access-token"}
|
||||
```
|
||||
|
||||
### Response to client-sent events
|
||||
|
||||
Pleroma will respond to client-sent events that it recognizes. Supported event types are:
|
||||
|
||||
- `subscribe`
|
||||
- `unsubscribe`
|
||||
- `pleroma:authenticate`
|
||||
|
||||
The reply will be in the following format:
|
||||
|
||||
```
|
||||
{
|
||||
"event": "pleroma:respond",
|
||||
"payload": "{\"type\": \"<type of the client-sent event>\", \"result\": \"<result of the action>\", \"error\": \"<error code>\"}"
|
||||
}
|
||||
```
|
||||
|
||||
Result of the action can be either `success`, `ignored` or `error`. If it is `error`, the `error` property will contain the error code. Otherwise, the `error` property will not be present. Below are some examples:
|
||||
|
||||
```
|
||||
{
|
||||
"event": "pleroma:respond",
|
||||
"payload": "{\"type\": \"pleroma:authenticate\", \"result\": \"success\"}"
|
||||
}
|
||||
|
||||
{
|
||||
"event": "pleroma:respond",
|
||||
"payload": "{\"type\": \"subscribe\", \"result\": \"ignored\"}"
|
||||
}
|
||||
|
||||
{
|
||||
"event": "pleroma:respond",
|
||||
"payload": "{\"type\": \"unsubscribe\", \"result\": \"error\", \"error\": \"bad_topic\"}"
|
||||
}
|
||||
```
|
||||
|
||||
If the sent event is not of a type that Pleroma supports, it will not reply.
|
||||
|
||||
### The `stream` attribute of a server-sent event
|
||||
|
||||
Technically, this is in Mastodon, but its documentation does nothing to specify its format.
|
||||
|
||||
This attribute appears on every event type except `pleroma:respond` and `delete`. It helps clients determine where they should display the new statuses.
|
||||
|
||||
The value of the attribute is an array containing one or two elements. The first element is the type of the stream. The second is the identifier related to that specific stream, if applicable.
|
||||
|
||||
For the following stream types, there is a second element in the array:
|
||||
|
||||
- `list`: The second element is the id of the list, as a string.
|
||||
- `hashtag`: The second element is the name of the hashtag.
|
||||
- `public:remote:media` and `public:remote`: The second element is the domain of the corresponding instance.
|
||||
|
||||
For all other stream types, there is no second element.
|
||||
|
||||
Some examples of valid `stream` values:
|
||||
|
||||
- `["list", "1"]`: List of id 1.
|
||||
- `["hashtag", "mew"]`: The hashtag #mew.
|
||||
- `["user:notifications"]`: Notifications for the current user.
|
||||
- `["user"]`: Home timeline.
|
||||
- `["public:remote", "mew.moe"]`: Public posts from the instance mew.moe .
|
||||
|
||||
### The unified streaming endpoint
|
||||
|
||||
If you do not specify a stream to connect to when requesting `/api/v1/streaming`, you will enter a connection that subscribes to no streams. After the connection is established, you can authenticate and then subscribe to different streams.
|
||||
|
||||
### List of supported streams
|
||||
|
||||
Below is a list of supported streams by Pleroma. To make a single-stream WebSocket connection, append the string specified in "Query style" to the streaming endpoint url.
|
||||
To subscribe to a stream after the connection is established, merge the JSON object specified in "Subscribe style" with `{"type": "subscribe"}`. To unsubscribe, merge it with `{"type": "unsubscribe"}`.
|
||||
|
||||
For example, to receive updates on the list 1, you can connect to `/api/v1/streaming/?stream=list&list=1`, or send
|
||||
|
||||
```
|
||||
{"type": "subscribe", "stream": "list", "list": "1"}
|
||||
```
|
||||
|
||||
upon establishing the websocket connection.
|
||||
|
||||
To unsubscribe to list 1, send
|
||||
|
||||
```
|
||||
{"type": "unsubscribe", "stream": "list", "list": "1"}
|
||||
```
|
||||
|
||||
Note that if you specify a stream that requires a logged-in user in the query string (for example, `user` or `list`), you have to specify the access token when you are trying to establish the connection, i.e. in the query string or via the `sec-websocket-protocol` header.
|
||||
|
||||
- `list`
|
||||
- Query style: `?stream=list&list=<id>`
|
||||
- Subscribe style: `{"stream": "list", "list": "<id>"}`
|
||||
- `public`, `public:local`, `public:media`, `public:local:media`, `user`, `user:pleroma_chat`, `user:notifications`, `direct`
|
||||
- Query style: `?stream=<stream name>`
|
||||
- Subscribe style: `{"stream": "<stream name>"}`
|
||||
- `hashtag`
|
||||
- Query style: `?stream=hashtag&tag=<name>`
|
||||
- Subscribe style: `{"stream": "hashtag", "tag": "<name>"}`
|
||||
- `public:remote`, `public:remote:media`
|
||||
- Query style: `?stream=<stream name>&instance=<instance domain>`
|
||||
- Subscribe style: `{"stream": "<stream name>", "instance": "<instance domain>"}`
|
||||
|
||||
## User muting and thread muting
|
||||
|
||||
Both user muting and thread muting can be done for only a certain time by adding an `expires_in` parameter to the API calls and giving the expiration time in seconds.
|
||||
|
|
|
@ -83,4 +83,19 @@ defmodule Pleroma.Constants do
|
|||
)
|
||||
|
||||
const(upload_object_types, do: ["Document", "Image"])
|
||||
|
||||
const(activity_json_canonical_mime_type,
|
||||
do: "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
|
||||
)
|
||||
|
||||
const(activity_json_mime_types,
|
||||
do: [
|
||||
"application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"",
|
||||
"application/activity+json"
|
||||
]
|
||||
)
|
||||
|
||||
const(public_streams,
|
||||
do: ["public", "public:local", "public:media", "public:local:media"]
|
||||
)
|
||||
end
|
||||
|
|
|
@ -217,6 +217,7 @@ defmodule Pleroma.Web.ActivityPub.Builder do
|
|||
"tag" => Keyword.values(draft.tags) |> Enum.uniq()
|
||||
}
|
||||
|> add_in_reply_to(draft.in_reply_to)
|
||||
|> add_quote(draft.quote_post)
|
||||
|> Map.merge(draft.extra)
|
||||
|
||||
{:ok, data, []}
|
||||
|
@ -232,6 +233,16 @@ defmodule Pleroma.Web.ActivityPub.Builder do
|
|||
end
|
||||
end
|
||||
|
||||
defp add_quote(object, nil), do: object
|
||||
|
||||
defp add_quote(object, quote_post) do
|
||||
with %Object{} = quote_object <- Object.normalize(quote_post, fetch: false) do
|
||||
Map.put(object, "quoteUrl", quote_object.data["id"])
|
||||
else
|
||||
_ -> object
|
||||
end
|
||||
end
|
||||
|
||||
def chat_message(actor, recipient, content, opts \\ []) do
|
||||
basic = %{
|
||||
"id" => Utils.generate_object_id(),
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.ActivityPub.MRF.InlineQuotePolicy do
|
||||
@moduledoc "Force a quote line into the message content."
|
||||
@behaviour Pleroma.Web.ActivityPub.MRF.Policy
|
||||
|
||||
defp build_inline_quote(template, url) do
|
||||
quote_line = String.replace(template, "{url}", "<a href=\"#{url}\">#{url}</a>")
|
||||
|
||||
"<span class=\"quote-inline\"><br/><br/>#{quote_line}</span>"
|
||||
end
|
||||
|
||||
defp has_inline_quote?(content, quote_url) do
|
||||
cond do
|
||||
# Does the quote URL exist in the content?
|
||||
content =~ quote_url -> true
|
||||
# Does the content already have a .quote-inline span?
|
||||
content =~ "<span class=\"quote-inline\">" -> true
|
||||
# No inline quote found
|
||||
true -> false
|
||||
end
|
||||
end
|
||||
|
||||
defp filter_object(%{"quoteUrl" => quote_url} = object) do
|
||||
content = object["content"] || ""
|
||||
|
||||
if has_inline_quote?(content, quote_url) do
|
||||
object
|
||||
else
|
||||
template = Pleroma.Config.get([:mrf_inline_quote, :template])
|
||||
|
||||
content =
|
||||
if String.ends_with?(content, "</p>"),
|
||||
do:
|
||||
String.trim_trailing(content, "</p>") <>
|
||||
build_inline_quote(template, quote_url) <> "</p>",
|
||||
else: content <> build_inline_quote(template, quote_url)
|
||||
|
||||
Map.put(object, "content", content)
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
def filter(%{"object" => %{"quoteUrl" => _} = object} = activity) do
|
||||
{:ok, Map.put(activity, "object", filter_object(object))}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def filter(object), do: {:ok, object}
|
||||
|
||||
@impl true
|
||||
def describe, do: {:ok, %{}}
|
||||
|
||||
@impl Pleroma.Web.ActivityPub.MRF.Policy
|
||||
def history_awareness, do: :auto
|
||||
|
||||
@impl true
|
||||
def config_description do
|
||||
%{
|
||||
key: :mrf_inline_quote,
|
||||
related_policy: "Pleroma.Web.ActivityPub.MRF.InlineQuotePolicy",
|
||||
label: "MRF Inline Quote Policy",
|
||||
type: :group,
|
||||
description: "Force quote url to appear in post content.",
|
||||
children: [
|
||||
%{
|
||||
key: :template,
|
||||
type: :string,
|
||||
description:
|
||||
"The template to append to the post. `{url}` will be replaced with the actual link to the quoted post.",
|
||||
suggestions: ["<bdi>RT:</bdi> {url}"]
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
end
|
|
@ -0,0 +1,49 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2023 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.ActivityPub.MRF.QuoteToLinkTagPolicy do
|
||||
@moduledoc "Force a Link tag for posts quoting another post. (may break outgoing federation of quote posts with older Pleroma versions)"
|
||||
@behaviour Pleroma.Web.ActivityPub.MRF.Policy
|
||||
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
|
||||
|
||||
require Pleroma.Constants
|
||||
|
||||
@impl Pleroma.Web.ActivityPub.MRF.Policy
|
||||
def filter(%{"object" => %{"quoteUrl" => _} = object} = activity) do
|
||||
{:ok, Map.put(activity, "object", filter_object(object))}
|
||||
end
|
||||
|
||||
@impl Pleroma.Web.ActivityPub.MRF.Policy
|
||||
def filter(object), do: {:ok, object}
|
||||
|
||||
@impl Pleroma.Web.ActivityPub.MRF.Policy
|
||||
def describe, do: {:ok, %{}}
|
||||
|
||||
@impl Pleroma.Web.ActivityPub.MRF.Policy
|
||||
def history_awareness, do: :auto
|
||||
|
||||
defp filter_object(%{"quoteUrl" => quote_url} = object) do
|
||||
tags = object["tag"] || []
|
||||
|
||||
if Enum.any?(tags, fn tag ->
|
||||
CommonFixes.is_object_link_tag(tag) and tag["href"] == quote_url
|
||||
end) do
|
||||
object
|
||||
else
|
||||
object
|
||||
|> Map.put(
|
||||
"tag",
|
||||
tags ++
|
||||
[
|
||||
%{
|
||||
"type" => "Link",
|
||||
"mediaType" => Pleroma.Constants.activity_json_canonical_mime_type(),
|
||||
"href" => quote_url
|
||||
}
|
||||
]
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -84,6 +84,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidator do
|
|||
|> fix_tag()
|
||||
|> fix_replies()
|
||||
|> fix_attachments()
|
||||
|> CommonFixes.fix_quote_url()
|
||||
|> Transmogrifier.fix_emoji()
|
||||
|> Transmogrifier.fix_content_map()
|
||||
end
|
||||
|
|
|
@ -99,6 +99,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AudioImageVideoValidator do
|
|||
data
|
||||
|> CommonFixes.fix_actor()
|
||||
|> CommonFixes.fix_object_defaults()
|
||||
|> CommonFixes.fix_quote_url()
|
||||
|> Transmogrifier.fix_emoji()
|
||||
|> fix_url()
|
||||
|> fix_content()
|
||||
|
|
|
@ -27,7 +27,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFields do
|
|||
end
|
||||
end
|
||||
|
||||
# All objects except Answer and CHatMessage
|
||||
# All objects except Answer and ChatMessage
|
||||
defmacro object_fields do
|
||||
quote bind_quoted: binding() do
|
||||
field(:content, :string)
|
||||
|
@ -58,6 +58,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFields do
|
|||
field(:like_count, :integer, default: 0)
|
||||
field(:announcement_count, :integer, default: 0)
|
||||
field(:inReplyTo, ObjectValidators.ObjectID)
|
||||
field(:quoteUrl, ObjectValidators.ObjectID)
|
||||
field(:url, ObjectValidators.BareUri)
|
||||
|
||||
field(:likes, {:array, ObjectValidators.ObjectID}, default: [])
|
||||
|
|
|
@ -10,6 +10,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes do
|
|||
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||
alias Pleroma.Web.ActivityPub.Utils
|
||||
|
||||
require Pleroma.Constants
|
||||
|
||||
def cast_and_filter_recipients(message, field, follower_collection, field_fallback \\ []) do
|
||||
{:ok, data} = ObjectValidators.Recipients.cast(message[field] || field_fallback)
|
||||
|
||||
|
@ -76,4 +78,48 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes do
|
|||
|
||||
Map.put(data, "to", to)
|
||||
end
|
||||
|
||||
def fix_quote_url(%{"quoteUrl" => _quote_url} = data), do: data
|
||||
|
||||
# Fedibird
|
||||
# https://github.com/fedibird/mastodon/commit/dbd7ae6cf58a92ec67c512296b4daaea0d01e6ac
|
||||
def fix_quote_url(%{"quoteUri" => quote_url} = data) do
|
||||
Map.put(data, "quoteUrl", quote_url)
|
||||
end
|
||||
|
||||
# Old Fedibird (bug)
|
||||
# https://github.com/fedibird/mastodon/issues/9
|
||||
def fix_quote_url(%{"quoteURL" => quote_url} = data) do
|
||||
Map.put(data, "quoteUrl", quote_url)
|
||||
end
|
||||
|
||||
# Misskey fallback
|
||||
def fix_quote_url(%{"_misskey_quote" => quote_url} = data) do
|
||||
Map.put(data, "quoteUrl", quote_url)
|
||||
end
|
||||
|
||||
def fix_quote_url(%{"tag" => [_ | _] = tags} = data) do
|
||||
tag = Enum.find(tags, &is_object_link_tag/1)
|
||||
|
||||
if not is_nil(tag) do
|
||||
data
|
||||
|> Map.put("quoteUrl", tag["href"])
|
||||
else
|
||||
data
|
||||
end
|
||||
end
|
||||
|
||||
def fix_quote_url(data), do: data
|
||||
|
||||
# https://codeberg.org/fediverse/fep/src/branch/main/fep/e232/fep-e232.md
|
||||
def is_object_link_tag(%{
|
||||
"type" => "Link",
|
||||
"mediaType" => media_type,
|
||||
"href" => href
|
||||
})
|
||||
when media_type in Pleroma.Constants.activity_json_mime_types() and is_binary(href) do
|
||||
true
|
||||
end
|
||||
|
||||
def is_object_link_tag(_), do: false
|
||||
end
|
||||
|
|
|
@ -62,6 +62,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.QuestionValidator do
|
|||
data
|
||||
|> CommonFixes.fix_actor()
|
||||
|> CommonFixes.fix_object_defaults()
|
||||
|> CommonFixes.fix_quote_url()
|
||||
|> Transmogrifier.fix_emoji()
|
||||
|> fix_closed()
|
||||
end
|
||||
|
|
|
@ -9,15 +9,20 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.TagValidator do
|
|||
|
||||
import Ecto.Changeset
|
||||
|
||||
require Pleroma.Constants
|
||||
|
||||
@primary_key false
|
||||
embedded_schema do
|
||||
# Common
|
||||
field(:type, :string)
|
||||
field(:name, :string)
|
||||
|
||||
# Mention, Hashtag
|
||||
# Mention, Hashtag, Link
|
||||
field(:href, ObjectValidators.Uri)
|
||||
|
||||
# Link
|
||||
field(:mediaType, :string)
|
||||
|
||||
# Emoji
|
||||
embeds_one :icon, IconObjectValidator, primary_key: false do
|
||||
field(:type, :string)
|
||||
|
@ -68,6 +73,13 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.TagValidator do
|
|||
|> validate_required([:type, :name, :icon])
|
||||
end
|
||||
|
||||
def changeset(struct, %{"type" => "Link"} = data) do
|
||||
struct
|
||||
|> cast(data, [:type, :name, :mediaType, :href])
|
||||
|> validate_inclusion(:mediaType, Pleroma.Constants.activity_json_mime_types())
|
||||
|> validate_required([:type, :href, :mediaType])
|
||||
end
|
||||
|
||||
def changeset(struct, %{"type" => _} = data) do
|
||||
struct
|
||||
|> cast(data, [])
|
||||
|
|
|
@ -166,6 +166,27 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
|
||||
def fix_in_reply_to(object, _options), do: object
|
||||
|
||||
def fix_quote_url_and_maybe_fetch(object, options \\ []) do
|
||||
quote_url =
|
||||
case Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes.fix_quote_url(object) do
|
||||
%{"quoteUrl" => quote_url} -> quote_url
|
||||
_ -> nil
|
||||
end
|
||||
|
||||
with {:quoting?, true} <- {:quoting?, not is_nil(quote_url)},
|
||||
{:ok, quoted_object} <- get_obj_helper(quote_url, options),
|
||||
%Activity{} <- Activity.get_create_by_object_ap_id(quoted_object.data["id"]) do
|
||||
Map.put(object, "quoteUrl", quoted_object.data["id"])
|
||||
else
|
||||
{:quoting?, _} ->
|
||||
object
|
||||
|
||||
e ->
|
||||
Logger.warn("Couldn't fetch #{inspect(quote_url)}, error: #{inspect(e)}")
|
||||
object
|
||||
end
|
||||
end
|
||||
|
||||
defp prepare_in_reply_to(in_reply_to) do
|
||||
cond do
|
||||
is_bitstring(in_reply_to) ->
|
||||
|
@ -454,6 +475,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
|> strip_internal_fields()
|
||||
|> fix_type(fetch_options)
|
||||
|> fix_in_reply_to(fetch_options)
|
||||
|> fix_quote_url_and_maybe_fetch(fetch_options)
|
||||
|
||||
data = Map.put(data, "object", object)
|
||||
options = Keyword.put(options, :local, false)
|
||||
|
@ -628,6 +650,16 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
|
||||
def set_reply_to_uri(obj), do: obj
|
||||
|
||||
@doc """
|
||||
Fedibird compatibility
|
||||
https://github.com/fedibird/mastodon/commit/dbd7ae6cf58a92ec67c512296b4daaea0d01e6ac
|
||||
"""
|
||||
def set_quote_url(%{"quoteUrl" => quote_url} = object) when is_binary(quote_url) do
|
||||
Map.put(object, "quoteUri", quote_url)
|
||||
end
|
||||
|
||||
def set_quote_url(obj), do: obj
|
||||
|
||||
@doc """
|
||||
Serialized Mastodon-compatible `replies` collection containing _self-replies_.
|
||||
Based on Mastodon's ActivityPub::NoteSerializer#replies.
|
||||
|
@ -682,6 +714,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
|> prepare_attachments
|
||||
|> set_conversation
|
||||
|> set_reply_to_uri
|
||||
|> set_quote_url
|
||||
|> set_replies
|
||||
|> strip_internal_fields
|
||||
|> strip_internal_tags
|
||||
|
|
|
@ -10,6 +10,14 @@ defmodule Pleroma.Web.ApiSpec do
|
|||
|
||||
@behaviour OpenApi
|
||||
|
||||
defp streaming_paths do
|
||||
%{
|
||||
"/api/v1/streaming" => %OpenApiSpex.PathItem{
|
||||
get: Pleroma.Web.ApiSpec.StreamingOperation.streaming_operation()
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
@impl OpenApi
|
||||
def spec(opts \\ []) do
|
||||
%OpenApi{
|
||||
|
@ -45,7 +53,7 @@ defmodule Pleroma.Web.ApiSpec do
|
|||
}
|
||||
},
|
||||
# populate the paths from a phoenix router
|
||||
paths: OpenApiSpex.Paths.from_router(Router),
|
||||
paths: Map.merge(streaming_paths(), OpenApiSpex.Paths.from_router(Router)),
|
||||
components: %OpenApiSpex.Components{
|
||||
parameters: %{
|
||||
"accountIdOrNickname" =>
|
||||
|
|
|
@ -581,6 +581,11 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do
|
|||
type: :string,
|
||||
description:
|
||||
"Will reply to a given conversation, addressing only the people who are part of the recipient set of that conversation. Sets the visibility to `direct`."
|
||||
},
|
||||
quote_id: %Schema{
|
||||
nullable: true,
|
||||
allOf: [FlakeID],
|
||||
description: "ID of the status being quoted, if any"
|
||||
}
|
||||
},
|
||||
example: %{
|
||||
|
|
|
@ -0,0 +1,464 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.ApiSpec.StreamingOperation do
|
||||
alias OpenApiSpex.Operation
|
||||
alias OpenApiSpex.Response
|
||||
alias OpenApiSpex.Schema
|
||||
alias Pleroma.Web.ApiSpec.NotificationOperation
|
||||
alias Pleroma.Web.ApiSpec.Schemas.Chat
|
||||
alias Pleroma.Web.ApiSpec.Schemas.Conversation
|
||||
alias Pleroma.Web.ApiSpec.Schemas.FlakeID
|
||||
alias Pleroma.Web.ApiSpec.Schemas.Status
|
||||
|
||||
require Pleroma.Constants
|
||||
|
||||
@spec open_api_operation(atom) :: Operation.t()
|
||||
def open_api_operation(action) do
|
||||
operation = String.to_existing_atom("#{action}_operation")
|
||||
apply(__MODULE__, operation, [])
|
||||
end
|
||||
|
||||
@spec streaming_operation() :: Operation.t()
|
||||
def streaming_operation do
|
||||
%Operation{
|
||||
tags: ["Timelines"],
|
||||
summary: "Establish streaming connection",
|
||||
description: """
|
||||
Receive statuses in real-time via WebSocket.
|
||||
|
||||
You can specify the access token on the query string or through the `sec-websocket-protocol` header. Using
|
||||
the query string to authenticate is considered unsafe and should not be used unless you have to (e.g. to maintain
|
||||
your client's compatibility with Mastodon).
|
||||
|
||||
You may specify a stream on the query string. If you do so and you are connecting to a stream that requires logged-in users,
|
||||
you must specify the access token at the time of the connection (i.e. via query string or header).
|
||||
|
||||
Otherwise, you have the option to authenticate after you have established the connection through client-sent events.
|
||||
|
||||
The "Request body" section below describes what events clients can send through WebSocket, and the "Responses" section
|
||||
describes what events server will send through WebSocket.
|
||||
""",
|
||||
security: [%{"oAuth" => ["read:statuses", "read:notifications"]}],
|
||||
operationId: "WebsocketHandler.streaming",
|
||||
parameters:
|
||||
[
|
||||
Operation.parameter(:connection, :header, %Schema{type: :string}, "connection header",
|
||||
required: true
|
||||
),
|
||||
Operation.parameter(:upgrade, :header, %Schema{type: :string}, "upgrade header",
|
||||
required: true
|
||||
),
|
||||
Operation.parameter(
|
||||
:"sec-websocket-key",
|
||||
:header,
|
||||
%Schema{type: :string},
|
||||
"sec-websocket-key header",
|
||||
required: true
|
||||
),
|
||||
Operation.parameter(
|
||||
:"sec-websocket-version",
|
||||
:header,
|
||||
%Schema{type: :string},
|
||||
"sec-websocket-version header",
|
||||
required: true
|
||||
)
|
||||
] ++ stream_params() ++ access_token_params(),
|
||||
requestBody: request_body("Client-sent events", client_sent_events()),
|
||||
responses: %{
|
||||
101 => switching_protocols_response(),
|
||||
200 =>
|
||||
Operation.response(
|
||||
"Server-sent events",
|
||||
"application/json",
|
||||
server_sent_events()
|
||||
)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
defp stream_params do
|
||||
stream_specifier()
|
||||
|> Enum.map(fn {name, schema} ->
|
||||
Operation.parameter(name, :query, schema, get_schema(schema).description)
|
||||
end)
|
||||
end
|
||||
|
||||
defp access_token_params do
|
||||
[
|
||||
Operation.parameter(:access_token, :query, token(), token().description),
|
||||
Operation.parameter(:"sec-websocket-protocol", :header, token(), token().description)
|
||||
]
|
||||
end
|
||||
|
||||
defp switching_protocols_response do
|
||||
%Response{
|
||||
description: "Switching protocols",
|
||||
headers: %{
|
||||
"connection" => %OpenApiSpex.Header{required: true},
|
||||
"upgrade" => %OpenApiSpex.Header{required: true},
|
||||
"sec-websocket-accept" => %OpenApiSpex.Header{required: true}
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
defp server_sent_events do
|
||||
%Schema{
|
||||
oneOf: [
|
||||
update_event(),
|
||||
status_update_event(),
|
||||
notification_event(),
|
||||
chat_update_event(),
|
||||
follow_relationships_update_event(),
|
||||
conversation_event(),
|
||||
delete_event(),
|
||||
pleroma_respond_event()
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
defp stream do
|
||||
%Schema{
|
||||
type: :array,
|
||||
title: "Stream",
|
||||
description: """
|
||||
The stream identifier.
|
||||
The first item is the name of the stream. If the stream needs a differentiator, the second item will be the corresponding identifier.
|
||||
Currently, for the following stream types, there is a second element in the array:
|
||||
|
||||
- `list`: The second element is the id of the list, as a string.
|
||||
- `hashtag`: The second element is the name of the hashtag.
|
||||
- `public:remote:media` and `public:remote`: The second element is the domain of the corresponding instance.
|
||||
""",
|
||||
maxItems: 2,
|
||||
minItems: 1,
|
||||
items: %Schema{type: :string},
|
||||
example: ["hashtag", "mew"]
|
||||
}
|
||||
end
|
||||
|
||||
defp get_schema(%Schema{} = schema), do: schema
|
||||
defp get_schema(schema), do: schema.schema
|
||||
|
||||
defp server_sent_event_helper(name, description, type, payload, opts \\ []) do
|
||||
payload_type = Keyword.get(opts, :payload_type, :json)
|
||||
has_stream = Keyword.get(opts, :has_stream, true)
|
||||
|
||||
stream_properties =
|
||||
if has_stream do
|
||||
%{stream: stream()}
|
||||
else
|
||||
%{}
|
||||
end
|
||||
|
||||
stream_example = if has_stream, do: %{"stream" => get_schema(stream()).example}, else: %{}
|
||||
|
||||
stream_required = if has_stream, do: [:stream], else: []
|
||||
|
||||
payload_schema =
|
||||
if payload_type == :json do
|
||||
%Schema{
|
||||
title: "Event payload",
|
||||
description: "JSON-encoded string of #{get_schema(payload).title}",
|
||||
allOf: [payload]
|
||||
}
|
||||
else
|
||||
payload
|
||||
end
|
||||
|
||||
payload_example =
|
||||
if payload_type == :json do
|
||||
get_schema(payload).example |> Jason.encode!()
|
||||
else
|
||||
get_schema(payload).example
|
||||
end
|
||||
|
||||
%Schema{
|
||||
type: :object,
|
||||
title: name,
|
||||
description: description,
|
||||
required: [:event, :payload] ++ stream_required,
|
||||
properties:
|
||||
%{
|
||||
event: %Schema{
|
||||
title: "Event type",
|
||||
description: "Type of the event.",
|
||||
type: :string,
|
||||
required: true,
|
||||
enum: [type]
|
||||
},
|
||||
payload: payload_schema
|
||||
}
|
||||
|> Map.merge(stream_properties),
|
||||
example:
|
||||
%{
|
||||
"event" => type,
|
||||
"payload" => payload_example
|
||||
}
|
||||
|> Map.merge(stream_example)
|
||||
}
|
||||
end
|
||||
|
||||
defp update_event do
|
||||
server_sent_event_helper("New status", "A newly-posted status.", "update", Status)
|
||||
end
|
||||
|
||||
defp status_update_event do
|
||||
server_sent_event_helper("Edit", "A status that was just edited", "status.update", Status)
|
||||
end
|
||||
|
||||
defp notification_event do
|
||||
server_sent_event_helper(
|
||||
"Notification",
|
||||
"A new notification.",
|
||||
"notification",
|
||||
NotificationOperation.notification()
|
||||
)
|
||||
end
|
||||
|
||||
defp follow_relationships_update_event do
|
||||
server_sent_event_helper(
|
||||
"Follow relationships update",
|
||||
"An update to follow relationships.",
|
||||
"pleroma:follow_relationships_update",
|
||||
%Schema{
|
||||
type: :object,
|
||||
title: "Follow relationships update",
|
||||
required: [:state, :follower, :following],
|
||||
properties: %{
|
||||
state: %Schema{
|
||||
type: :string,
|
||||
description: "Follow state of the relationship.",
|
||||
enum: ["follow_pending", "follow_accept", "follow_reject", "unfollow"]
|
||||
},
|
||||
follower: %Schema{
|
||||
type: :object,
|
||||
description: "Information about the follower.",
|
||||
required: [:id, :follower_count, :following_count],
|
||||
properties: %{
|
||||
id: FlakeID,
|
||||
follower_count: %Schema{type: :integer},
|
||||
following_count: %Schema{type: :integer}
|
||||
}
|
||||
},
|
||||
following: %Schema{
|
||||
type: :object,
|
||||
description: "Information about the following person.",
|
||||
required: [:id, :follower_count, :following_count],
|
||||
properties: %{
|
||||
id: FlakeID,
|
||||
follower_count: %Schema{type: :integer},
|
||||
following_count: %Schema{type: :integer}
|
||||
}
|
||||
}
|
||||
},
|
||||
example: %{
|
||||
"state" => "follow_pending",
|
||||
"follower" => %{
|
||||
"id" => "someUser1",
|
||||
"follower_count" => 1,
|
||||
"following_count" => 1
|
||||
},
|
||||
"following" => %{
|
||||
"id" => "someUser2",
|
||||
"follower_count" => 1,
|
||||
"following_count" => 1
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
defp chat_update_event do
|
||||
server_sent_event_helper(
|
||||
"Chat update",
|
||||
"A new chat message.",
|
||||
"pleroma:chat_update",
|
||||
Chat
|
||||
)
|
||||
end
|
||||
|
||||
defp conversation_event do
|
||||
server_sent_event_helper(
|
||||
"Conversation update",
|
||||
"An update about a conversation",
|
||||
"conversation",
|
||||
Conversation
|
||||
)
|
||||
end
|
||||
|
||||
defp delete_event do
|
||||
server_sent_event_helper(
|
||||
"Delete",
|
||||
"A status that was just deleted.",
|
||||
"delete",
|
||||
%Schema{
|
||||
type: :string,
|
||||
title: "Status id",
|
||||
description: "Id of the deleted status",
|
||||
allOf: [FlakeID],
|
||||
example: "some-opaque-id"
|
||||
},
|
||||
payload_type: :string,
|
||||
has_stream: false
|
||||
)
|
||||
end
|
||||
|
||||
defp pleroma_respond_event do
|
||||
server_sent_event_helper(
|
||||
"Server response",
|
||||
"A response to a client-sent event.",
|
||||
"pleroma:respond",
|
||||
%Schema{
|
||||
type: :object,
|
||||
title: "Results",
|
||||
required: [:result, :type],
|
||||
properties: %{
|
||||
result: %Schema{
|
||||
type: :string,
|
||||
title: "Result of the request",
|
||||
enum: ["success", "error", "ignored"]
|
||||
},
|
||||
error: %Schema{
|
||||
type: :string,
|
||||
title: "Error code",
|
||||
description: "An error identifier. Only appears if `result` is `error`."
|
||||
},
|
||||
type: %Schema{
|
||||
type: :string,
|
||||
description: "Type of the request."
|
||||
}
|
||||
},
|
||||
example: %{"result" => "success", "type" => "pleroma:authenticate"}
|
||||
},
|
||||
has_stream: false
|
||||
)
|
||||
end
|
||||
|
||||
defp client_sent_events do
|
||||
%Schema{
|
||||
oneOf: [
|
||||
subscribe_event(),
|
||||
unsubscribe_event(),
|
||||
authenticate_event()
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
defp request_body(description, schema, opts \\ []) do
|
||||
%OpenApiSpex.RequestBody{
|
||||
description: description,
|
||||
content: %{
|
||||
"application/json" => %OpenApiSpex.MediaType{
|
||||
schema: schema,
|
||||
example: opts[:example],
|
||||
examples: opts[:examples]
|
||||
}
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
defp client_sent_event_helper(name, description, type, properties, opts) do
|
||||
required = opts[:required] || []
|
||||
|
||||
%Schema{
|
||||
type: :object,
|
||||
title: name,
|
||||
required: [:type] ++ required,
|
||||
description: description,
|
||||
properties:
|
||||
%{
|
||||
type: %Schema{type: :string, enum: [type], description: "Type of the event."}
|
||||
}
|
||||
|> Map.merge(properties),
|
||||
example: opts[:example]
|
||||
}
|
||||
end
|
||||
|
||||
defp subscribe_event do
|
||||
client_sent_event_helper(
|
||||
"Subscribe",
|
||||
"Subscribe to a stream.",
|
||||
"subscribe",
|
||||
stream_specifier(),
|
||||
required: [:stream],
|
||||
example: %{"type" => "subscribe", "stream" => "list", "list" => "1"}
|
||||
)
|
||||
end
|
||||
|
||||
defp unsubscribe_event do
|
||||
client_sent_event_helper(
|
||||
"Unsubscribe",
|
||||
"Unsubscribe from a stream.",
|
||||
"unsubscribe",
|
||||
stream_specifier(),
|
||||
required: [:stream],
|
||||
example: %{
|
||||
"type" => "unsubscribe",
|
||||
"stream" => "public:remote:media",
|
||||
"instance" => "example.org"
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
defp authenticate_event do
|
||||
client_sent_event_helper(
|
||||
"Authenticate",
|
||||
"Authenticate via an access token.",
|
||||
"pleroma:authenticate",
|
||||
%{
|
||||
token: token()
|
||||
},
|
||||
required: [:token]
|
||||
)
|
||||
end
|
||||
|
||||
defp token do
|
||||
%Schema{
|
||||
type: :string,
|
||||
description: "An OAuth access token with corresponding permissions.",
|
||||
example: "some token"
|
||||
}
|
||||
end
|
||||
|
||||
defp stream_specifier do
|
||||
%{
|
||||
stream: %Schema{
|
||||
type: :string,
|
||||
description: "The name of the stream.",
|
||||
enum:
|
||||
Pleroma.Constants.public_streams() ++
|
||||
[
|
||||
"public:remote",
|
||||
"public:remote:media",
|
||||
"user",
|
||||
"user:pleroma_chat",
|
||||
"user:notification",
|
||||
"direct",
|
||||
"list",
|
||||
"hashtag"
|
||||
]
|
||||
},
|
||||
list: %Schema{
|
||||
type: :string,
|
||||
title: "List id",
|
||||
description: "The id of the list. Required when `stream` is `list`.",
|
||||
example: "some-id"
|
||||
},
|
||||
tag: %Schema{
|
||||
type: :string,
|
||||
title: "Hashtag name",
|
||||
description: "The name of the hashtag. Required when `stream` is `hashtag`.",
|
||||
example: "mew"
|
||||
},
|
||||
instance: %Schema{
|
||||
type: :string,
|
||||
title: "Domain name",
|
||||
description:
|
||||
"Domain name of the instance. Required when `stream` is `public:remote` or `public:remote:media`.",
|
||||
example: "example.org"
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
|
@ -193,6 +193,26 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do
|
|||
nullable: true,
|
||||
description: "The `acct` property of User entity for replied user (if any)"
|
||||
},
|
||||
quote: %Schema{
|
||||
allOf: [%OpenApiSpex.Reference{"$ref": "#/components/schemas/Status"}],
|
||||
nullable: true,
|
||||
description: "Quoted status (if any)"
|
||||
},
|
||||
quote_id: %Schema{
|
||||
nullable: true,
|
||||
allOf: [FlakeID],
|
||||
description: "ID of the status being quoted, if any"
|
||||
},
|
||||
quote_url: %Schema{
|
||||
type: :string,
|
||||
format: :uri,
|
||||
nullable: true,
|
||||
description: "URL of the quoted status"
|
||||
},
|
||||
quote_visible: %Schema{
|
||||
type: :boolean,
|
||||
description: "`true` if the quoted post is visible to the user"
|
||||
},
|
||||
local: %Schema{
|
||||
type: :boolean,
|
||||
description: "`true` if the post was made on the local instance"
|
||||
|
|
|
@ -7,10 +7,12 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do
|
|||
alias Pleroma.Conversation.Participation
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Web.ActivityPub.Builder
|
||||
alias Pleroma.Web.ActivityPub.Visibility
|
||||
alias Pleroma.Web.CommonAPI
|
||||
alias Pleroma.Web.CommonAPI.Utils
|
||||
|
||||
import Pleroma.Web.Gettext
|
||||
import Pleroma.Web.Utils.Guards, only: [not_empty_string: 1]
|
||||
|
||||
defstruct valid?: true,
|
||||
errors: [],
|
||||
|
@ -22,6 +24,7 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do
|
|||
attachments: [],
|
||||
in_reply_to: nil,
|
||||
in_reply_to_conversation: nil,
|
||||
quote_post: nil,
|
||||
visibility: nil,
|
||||
expires_at: nil,
|
||||
extra: nil,
|
||||
|
@ -53,7 +56,9 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do
|
|||
|> poll()
|
||||
|> with_valid(&in_reply_to/1)
|
||||
|> with_valid(&in_reply_to_conversation/1)
|
||||
|> with_valid("e_post/1)
|
||||
|> with_valid(&visibility/1)
|
||||
|> with_valid("ing_visibility/1)
|
||||
|> content()
|
||||
|> with_valid(&to_and_cc/1)
|
||||
|> with_valid(&context/1)
|
||||
|
@ -132,6 +137,18 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do
|
|||
|
||||
defp in_reply_to(draft), do: draft
|
||||
|
||||
defp quote_post(%{params: %{quote_id: id}} = draft) when not_empty_string(id) do
|
||||
case Activity.get_by_id_with_object(id) do
|
||||
%Activity{} = activity ->
|
||||
%__MODULE__{draft | quote_post: activity}
|
||||
|
||||
_ ->
|
||||
draft
|
||||
end
|
||||
end
|
||||
|
||||
defp quote_post(draft), do: draft
|
||||
|
||||
defp in_reply_to_conversation(draft) do
|
||||
in_reply_to_conversation = Participation.get(draft.params[:in_reply_to_conversation_id])
|
||||
%__MODULE__{draft | in_reply_to_conversation: in_reply_to_conversation}
|
||||
|
@ -147,6 +164,29 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do
|
|||
end
|
||||
end
|
||||
|
||||
defp can_quote?(_draft, _object, visibility) when visibility in ~w(public unlisted local) do
|
||||
true
|
||||
end
|
||||
|
||||
defp can_quote?(draft, object, "private") do
|
||||
draft.user.ap_id == object.data["actor"]
|
||||
end
|
||||
|
||||
defp can_quote?(_, _, _) do
|
||||
false
|
||||
end
|
||||
|
||||
defp quoting_visibility(%{quote_post: %Activity{}} = draft) do
|
||||
with %Object{} = object <- Object.normalize(draft.quote_post, fetch: false),
|
||||
true <- can_quote?(draft, object, Visibility.get_visibility(object)) do
|
||||
draft
|
||||
else
|
||||
_ -> add_error(draft, dgettext("errors", "Cannot quote private message"))
|
||||
end
|
||||
end
|
||||
|
||||
defp quoting_visibility(draft), do: draft
|
||||
|
||||
defp expires_at(draft) do
|
||||
case CommonAPI.check_expiry_date(draft.params[:expires_in]) do
|
||||
{:ok, expires_at} -> %__MODULE__{draft | expires_at: expires_at}
|
||||
|
@ -164,12 +204,15 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do
|
|||
end
|
||||
end
|
||||
|
||||
defp content(draft) do
|
||||
defp content(%{mentions: mentions} = draft) do
|
||||
{content_html, mentioned_users, tags} = Utils.make_content_html(draft)
|
||||
|
||||
mentioned_ap_ids =
|
||||
Enum.map(mentioned_users, fn {_, mentioned_user} -> mentioned_user.ap_id end)
|
||||
|
||||
mentions =
|
||||
mentioned_users
|
||||
|> Enum.map(fn {_, mentioned_user} -> mentioned_user.ap_id end)
|
||||
mentions
|
||||
|> Kernel.++(mentioned_ap_ids)
|
||||
|> Utils.get_addressed_users(draft.params[:to])
|
||||
|
||||
%__MODULE__{draft | content_html: content_html, mentions: mentions, tags: tags}
|
||||
|
|
|
@ -69,6 +69,7 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do
|
|||
"multifetch",
|
||||
"pleroma:api/v1/notifications:include_types_filter",
|
||||
"editing",
|
||||
"quote_posting",
|
||||
if Config.get([:activitypub, :blockers_visible]) do
|
||||
"blockers_visible"
|
||||
end,
|
||||
|
|
|
@ -57,6 +57,27 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
|
|||
end)
|
||||
end
|
||||
|
||||
defp get_quoted_activities([]), do: %{}
|
||||
|
||||
defp get_quoted_activities(activities) do
|
||||
activities
|
||||
|> Enum.map(fn
|
||||
%{data: %{"type" => "Create"}} = activity ->
|
||||
object = Object.normalize(activity, fetch: false)
|
||||
object && object.data["quoteUrl"] != "" && object.data["quoteUrl"]
|
||||
|
||||
_ ->
|
||||
nil
|
||||
end)
|
||||
|> Enum.filter(& &1)
|
||||
|> Activity.create_by_object_ap_id_with_object()
|
||||
|> Repo.all()
|
||||
|> Enum.reduce(%{}, fn activity, acc ->
|
||||
object = Object.normalize(activity, fetch: false)
|
||||
if object, do: Map.put(acc, object.data["id"], activity), else: acc
|
||||
end)
|
||||
end
|
||||
|
||||
# DEPRECATED This field seems to be a left-over from the StatusNet era.
|
||||
# If your application uses `pleroma.conversation_id`: this field is deprecated.
|
||||
# It is currently stubbed instead by doing a CRC32 of the context, and
|
||||
|
@ -97,6 +118,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
|
|||
# length(activities_with_links) * timeout
|
||||
fetch_rich_media_for_activities(activities)
|
||||
replied_to_activities = get_replied_to_activities(activities)
|
||||
quoted_activities = get_quoted_activities(activities)
|
||||
|
||||
parent_activities =
|
||||
activities
|
||||
|
@ -129,6 +151,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
|
|||
opts =
|
||||
opts
|
||||
|> Map.put(:replied_to_activities, replied_to_activities)
|
||||
|> Map.put(:quoted_activities, quoted_activities)
|
||||
|> Map.put(:parent_activities, parent_activities)
|
||||
|> Map.put(:relationships, relationships_opt)
|
||||
|
||||
|
@ -277,7 +300,6 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
|
|||
end
|
||||
|
||||
reply_to = get_reply_to(activity, opts)
|
||||
|
||||
reply_to_user = reply_to && CommonAPI.get_user(reply_to.data["actor"])
|
||||
|
||||
history_len =
|
||||
|
@ -290,6 +312,22 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
|
|||
# Here the implicit index of the current content is 0
|
||||
chrono_order = history_len - 1
|
||||
|
||||
quote_activity = get_quote(activity, opts)
|
||||
|
||||
quote_id =
|
||||
case quote_activity do
|
||||
%Activity{id: id} -> id
|
||||
_ -> nil
|
||||
end
|
||||
|
||||
quote_post =
|
||||
if visible_for_user?(quote_activity, opts[:for]) and opts[:show_quote] != false do
|
||||
quote_rendering_opts = Map.merge(opts, %{activity: quote_activity, show_quote: false})
|
||||
render("show.json", quote_rendering_opts)
|
||||
else
|
||||
nil
|
||||
end
|
||||
|
||||
content =
|
||||
object
|
||||
|> render_content()
|
||||
|
@ -398,6 +436,10 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
|
|||
conversation_id: get_context_id(activity),
|
||||
context: object.data["context"],
|
||||
in_reply_to_account_acct: reply_to_user && reply_to_user.nickname,
|
||||
quote: quote_post,
|
||||
quote_id: quote_id,
|
||||
quote_url: object.data["quoteUrl"],
|
||||
quote_visible: visible_for_user?(quote_activity, opts[:for]),
|
||||
content: %{"text/plain" => content_plaintext},
|
||||
spoiler_text: %{"text/plain" => summary},
|
||||
expires_at: expires_at,
|
||||
|
@ -633,6 +675,25 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
|
|||
end
|
||||
end
|
||||
|
||||
def get_quote(activity, %{quoted_activities: quoted_activities}) do
|
||||
object = Object.normalize(activity, fetch: false)
|
||||
|
||||
with nil <- quoted_activities[object.data["quoteUrl"]] do
|
||||
# For when a quote post is inside an Announce
|
||||
Activity.get_create_by_object_ap_id_with_object(object.data["quoteUrl"])
|
||||
end
|
||||
end
|
||||
|
||||
def get_quote(%{data: %{"object" => _object}} = activity, _) do
|
||||
object = Object.normalize(activity, fetch: false)
|
||||
|
||||
if object.data["quoteUrl"] && object.data["quoteUrl"] != "" do
|
||||
Activity.get_create_by_object_ap_id(object.data["quoteUrl"])
|
||||
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"]
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do
|
|||
alias Pleroma.User
|
||||
alias Pleroma.Web.OAuth.Token
|
||||
alias Pleroma.Web.Streamer
|
||||
alias Pleroma.Web.StreamerView
|
||||
|
||||
@behaviour :cowboy_websocket
|
||||
|
||||
|
@ -32,8 +33,15 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do
|
|||
req
|
||||
end
|
||||
|
||||
topics =
|
||||
if topic do
|
||||
[topic]
|
||||
else
|
||||
[]
|
||||
end
|
||||
|
||||
{:cowboy_websocket, req,
|
||||
%{user: user, topic: topic, oauth_token: oauth_token, count: 0, timer: nil},
|
||||
%{user: user, topics: topics, oauth_token: oauth_token, count: 0, timer: nil},
|
||||
%{idle_timeout: @timeout}}
|
||||
else
|
||||
{:error, :bad_topic} ->
|
||||
|
@ -50,10 +58,10 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do
|
|||
|
||||
def websocket_init(state) do
|
||||
Logger.debug(
|
||||
"#{__MODULE__} accepted websocket connection for user #{(state.user || %{id: "anonymous"}).id}, topic #{state.topic}"
|
||||
"#{__MODULE__} accepted websocket connection for user #{(state.user || %{id: "anonymous"}).id}, topics #{state.topics}"
|
||||
)
|
||||
|
||||
Streamer.add_socket(state.topic, state.oauth_token)
|
||||
Enum.each(state.topics, fn topic -> Streamer.add_socket(topic, state.oauth_token) end)
|
||||
{:ok, %{state | timer: timer()}}
|
||||
end
|
||||
|
||||
|
@ -66,16 +74,26 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do
|
|||
# We only receive pings for now
|
||||
def websocket_handle(:ping, state), do: {:ok, state}
|
||||
|
||||
def websocket_handle({:text, text}, state) do
|
||||
with {:ok, %{} = event} <- Jason.decode(text) do
|
||||
handle_client_event(event, state)
|
||||
else
|
||||
_ ->
|
||||
Logger.error("#{__MODULE__} received non-JSON event: #{inspect(text)}")
|
||||
{:ok, state}
|
||||
end
|
||||
end
|
||||
|
||||
def websocket_handle(frame, state) do
|
||||
Logger.error("#{__MODULE__} received frame: #{inspect(frame)}")
|
||||
{:ok, state}
|
||||
end
|
||||
|
||||
def websocket_info({:render_with_user, view, template, item}, state) do
|
||||
def websocket_info({:render_with_user, view, template, item, topic}, state) do
|
||||
user = %User{} = User.get_cached_by_ap_id(state.user.ap_id)
|
||||
|
||||
unless Streamer.filtered_by_user?(user, item) do
|
||||
websocket_info({:text, view.render(template, item, user)}, %{state | user: user})
|
||||
websocket_info({:text, view.render(template, item, user, topic)}, %{state | user: user})
|
||||
else
|
||||
{:ok, state}
|
||||
end
|
||||
|
@ -109,10 +127,10 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do
|
|||
|
||||
def terminate(reason, _req, state) do
|
||||
Logger.debug(
|
||||
"#{__MODULE__} terminating websocket connection for user #{(state.user || %{id: "anonymous"}).id}, topic #{state.topic || "?"}: #{inspect(reason)}"
|
||||
"#{__MODULE__} terminating websocket connection for user #{(state.user || %{id: "anonymous"}).id}, topics #{state.topics || "?"}: #{inspect(reason)}"
|
||||
)
|
||||
|
||||
Streamer.remove_socket(state.topic)
|
||||
Enum.each(state.topics, fn topic -> Streamer.remove_socket(topic) end)
|
||||
:ok
|
||||
end
|
||||
|
||||
|
@ -137,4 +155,103 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do
|
|||
defp timer do
|
||||
Process.send_after(self(), :tick, @tick)
|
||||
end
|
||||
|
||||
defp handle_client_event(%{"type" => "subscribe", "stream" => _topic} = params, state) do
|
||||
with {_, {:ok, topic}} <-
|
||||
{:topic, Streamer.get_topic(params["stream"], state.user, state.oauth_token, params)},
|
||||
{_, false} <- {:subscribed, topic in state.topics} do
|
||||
Streamer.add_socket(topic, state.oauth_token)
|
||||
|
||||
{[
|
||||
{:text,
|
||||
StreamerView.render("pleroma_respond.json", %{type: "subscribe", result: "success"})}
|
||||
], %{state | topics: [topic | state.topics]}}
|
||||
else
|
||||
{:subscribed, true} ->
|
||||
{[
|
||||
{:text,
|
||||
StreamerView.render("pleroma_respond.json", %{type: "subscribe", result: "ignored"})}
|
||||
], state}
|
||||
|
||||
{:topic, {:error, error}} ->
|
||||
{[
|
||||
{:text,
|
||||
StreamerView.render("pleroma_respond.json", %{
|
||||
type: "subscribe",
|
||||
result: "error",
|
||||
error: error
|
||||
})}
|
||||
], state}
|
||||
end
|
||||
end
|
||||
|
||||
defp handle_client_event(%{"type" => "unsubscribe", "stream" => _topic} = params, state) do
|
||||
with {_, {:ok, topic}} <-
|
||||
{:topic, Streamer.get_topic(params["stream"], state.user, state.oauth_token, params)},
|
||||
{_, true} <- {:subscribed, topic in state.topics} do
|
||||
Streamer.remove_socket(topic)
|
||||
|
||||
{[
|
||||
{:text,
|
||||
StreamerView.render("pleroma_respond.json", %{type: "unsubscribe", result: "success"})}
|
||||
], %{state | topics: List.delete(state.topics, topic)}}
|
||||
else
|
||||
{:subscribed, false} ->
|
||||
{[
|
||||
{:text,
|
||||
StreamerView.render("pleroma_respond.json", %{type: "unsubscribe", result: "ignored"})}
|
||||
], state}
|
||||
|
||||
{:topic, {:error, error}} ->
|
||||
{[
|
||||
{:text,
|
||||
StreamerView.render("pleroma_respond.json", %{
|
||||
type: "unsubscribe",
|
||||
result: "error",
|
||||
error: error
|
||||
})}
|
||||
], state}
|
||||
end
|
||||
end
|
||||
|
||||
defp handle_client_event(
|
||||
%{"type" => "pleroma:authenticate", "token" => access_token} = _params,
|
||||
state
|
||||
) do
|
||||
with {:auth, nil, nil} <- {:auth, state.user, state.oauth_token},
|
||||
{:ok, user, oauth_token} <- authenticate_request(access_token, nil) do
|
||||
{[
|
||||
{:text,
|
||||
StreamerView.render("pleroma_respond.json", %{
|
||||
type: "pleroma:authenticate",
|
||||
result: "success"
|
||||
})}
|
||||
], %{state | user: user, oauth_token: oauth_token}}
|
||||
else
|
||||
{:auth, _, _} ->
|
||||
{[
|
||||
{:text,
|
||||
StreamerView.render("pleroma_respond.json", %{
|
||||
type: "pleroma:authenticate",
|
||||
result: "error",
|
||||
error: :already_authenticated
|
||||
})}
|
||||
], state}
|
||||
|
||||
_ ->
|
||||
{[
|
||||
{:text,
|
||||
StreamerView.render("pleroma_respond.json", %{
|
||||
type: "pleroma:authenticate",
|
||||
result: "error",
|
||||
error: :unauthorized
|
||||
})}
|
||||
], state}
|
||||
end
|
||||
end
|
||||
|
||||
defp handle_client_event(params, state) do
|
||||
Logger.error("#{__MODULE__} received unknown event: #{inspect(params)}")
|
||||
{[], state}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
defmodule Pleroma.Web.Streamer do
|
||||
require Logger
|
||||
require Pleroma.Constants
|
||||
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.Chat.MessageReference
|
||||
|
@ -24,7 +25,7 @@ defmodule Pleroma.Web.Streamer do
|
|||
|
||||
def registry, do: @registry
|
||||
|
||||
@public_streams ["public", "public:local", "public:media", "public:local:media"]
|
||||
@public_streams Pleroma.Constants.public_streams()
|
||||
@local_streams ["public:local", "public:local:media"]
|
||||
@user_streams ["user", "user:notification", "direct", "user:pleroma_chat"]
|
||||
|
||||
|
@ -59,10 +60,14 @@ defmodule Pleroma.Web.Streamer do
|
|||
end
|
||||
|
||||
@doc "Expand and authorizes a stream"
|
||||
@spec get_topic(stream :: String.t(), User.t() | nil, Token.t() | nil, Map.t()) ::
|
||||
{:ok, topic :: String.t()} | {:error, :bad_topic}
|
||||
@spec get_topic(stream :: String.t() | nil, User.t() | nil, Token.t() | nil, Map.t()) ::
|
||||
{:ok, topic :: String.t() | nil} | {:error, :bad_topic}
|
||||
def get_topic(stream, user, oauth_token, params \\ %{})
|
||||
|
||||
def get_topic(nil = _stream, _user, _oauth_token, _params) do
|
||||
{:ok, nil}
|
||||
end
|
||||
|
||||
# Allow all public steams if the instance allows unauthenticated access.
|
||||
# Otherwise, only allow users with valid oauth tokens.
|
||||
def get_topic(stream, user, oauth_token, _params) when stream in @public_streams do
|
||||
|
@ -219,8 +224,8 @@ defmodule Pleroma.Web.Streamer do
|
|||
end
|
||||
|
||||
defp do_stream("follow_relationship", item) do
|
||||
text = StreamerView.render("follow_relationships_update.json", item)
|
||||
user_topic = "user:#{item.follower.id}"
|
||||
text = StreamerView.render("follow_relationships_update.json", item, user_topic)
|
||||
|
||||
Logger.debug("Trying to push follow relationship update to #{user_topic}\n\n")
|
||||
|
||||
|
@ -266,9 +271,11 @@ defmodule Pleroma.Web.Streamer do
|
|||
|
||||
defp do_stream(topic, %Notification{} = item)
|
||||
when topic in ["user", "user:notification"] do
|
||||
Registry.dispatch(@registry, "#{topic}:#{item.user_id}", fn list ->
|
||||
user_topic = "#{topic}:#{item.user_id}"
|
||||
|
||||
Registry.dispatch(@registry, user_topic, fn list ->
|
||||
Enum.each(list, fn {pid, _auth} ->
|
||||
send(pid, {:render_with_user, StreamerView, "notification.json", item})
|
||||
send(pid, {:render_with_user, StreamerView, "notification.json", item, user_topic})
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
@ -277,7 +284,7 @@ defmodule Pleroma.Web.Streamer do
|
|||
when topic in ["user", "user:pleroma_chat"] do
|
||||
topic = "#{topic}:#{user.id}"
|
||||
|
||||
text = StreamerView.render("chat_update.json", %{chat_message_reference: cm_ref})
|
||||
text = StreamerView.render("chat_update.json", %{chat_message_reference: cm_ref}, topic)
|
||||
|
||||
Registry.dispatch(@registry, topic, fn list ->
|
||||
Enum.each(list, fn {pid, _auth} ->
|
||||
|
@ -305,7 +312,7 @@ defmodule Pleroma.Web.Streamer do
|
|||
end
|
||||
|
||||
defp push_to_socket(topic, %Participation{} = participation) do
|
||||
rendered = StreamerView.render("conversation.json", participation)
|
||||
rendered = StreamerView.render("conversation.json", participation, topic)
|
||||
|
||||
Registry.dispatch(@registry, topic, fn list ->
|
||||
Enum.each(list, fn {pid, _} ->
|
||||
|
@ -333,12 +340,15 @@ defmodule Pleroma.Web.Streamer do
|
|||
Pleroma.Activity.get_create_by_object_ap_id(item.object.data["id"])
|
||||
|> Map.put(:object, item.object)
|
||||
|
||||
anon_render = StreamerView.render("status_update.json", create_activity)
|
||||
anon_render = StreamerView.render("status_update.json", create_activity, topic)
|
||||
|
||||
Registry.dispatch(@registry, topic, fn list ->
|
||||
Enum.each(list, fn {pid, auth?} ->
|
||||
if auth? do
|
||||
send(pid, {:render_with_user, StreamerView, "status_update.json", create_activity})
|
||||
send(
|
||||
pid,
|
||||
{:render_with_user, StreamerView, "status_update.json", create_activity, topic}
|
||||
)
|
||||
else
|
||||
send(pid, {:text, anon_render})
|
||||
end
|
||||
|
@ -347,12 +357,12 @@ defmodule Pleroma.Web.Streamer do
|
|||
end
|
||||
|
||||
defp push_to_socket(topic, item) do
|
||||
anon_render = StreamerView.render("update.json", item)
|
||||
anon_render = StreamerView.render("update.json", item, topic)
|
||||
|
||||
Registry.dispatch(@registry, topic, fn list ->
|
||||
Enum.each(list, fn {pid, auth?} ->
|
||||
if auth? do
|
||||
send(pid, {:render_with_user, StreamerView, "update.json", item})
|
||||
send(pid, {:render_with_user, StreamerView, "update.json", item, topic})
|
||||
else
|
||||
send(pid, {:text, anon_render})
|
||||
end
|
||||
|
|
|
@ -11,8 +11,11 @@ defmodule Pleroma.Web.StreamerView do
|
|||
alias Pleroma.User
|
||||
alias Pleroma.Web.MastodonAPI.NotificationView
|
||||
|
||||
def render("update.json", %Activity{} = activity, %User{} = user) do
|
||||
require Pleroma.Constants
|
||||
|
||||
def render("update.json", %Activity{} = activity, %User{} = user, topic) do
|
||||
%{
|
||||
stream: render("stream.json", %{topic: topic}),
|
||||
event: "update",
|
||||
payload:
|
||||
Pleroma.Web.MastodonAPI.StatusView.render(
|
||||
|
@ -25,8 +28,9 @@ defmodule Pleroma.Web.StreamerView do
|
|||
|> Jason.encode!()
|
||||
end
|
||||
|
||||
def render("status_update.json", %Activity{} = activity, %User{} = user) do
|
||||
def render("status_update.json", %Activity{} = activity, %User{} = user, topic) do
|
||||
%{
|
||||
stream: render("stream.json", %{topic: topic}),
|
||||
event: "status.update",
|
||||
payload:
|
||||
Pleroma.Web.MastodonAPI.StatusView.render(
|
||||
|
@ -39,8 +43,9 @@ defmodule Pleroma.Web.StreamerView do
|
|||
|> Jason.encode!()
|
||||
end
|
||||
|
||||
def render("notification.json", %Notification{} = notify, %User{} = user) do
|
||||
def render("notification.json", %Notification{} = notify, %User{} = user, topic) do
|
||||
%{
|
||||
stream: render("stream.json", %{topic: topic}),
|
||||
event: "notification",
|
||||
payload:
|
||||
NotificationView.render(
|
||||
|
@ -52,8 +57,9 @@ defmodule Pleroma.Web.StreamerView do
|
|||
|> Jason.encode!()
|
||||
end
|
||||
|
||||
def render("update.json", %Activity{} = activity) do
|
||||
def render("update.json", %Activity{} = activity, topic) do
|
||||
%{
|
||||
stream: render("stream.json", %{topic: topic}),
|
||||
event: "update",
|
||||
payload:
|
||||
Pleroma.Web.MastodonAPI.StatusView.render(
|
||||
|
@ -65,8 +71,9 @@ defmodule Pleroma.Web.StreamerView do
|
|||
|> Jason.encode!()
|
||||
end
|
||||
|
||||
def render("status_update.json", %Activity{} = activity) do
|
||||
def render("status_update.json", %Activity{} = activity, topic) do
|
||||
%{
|
||||
stream: render("stream.json", %{topic: topic}),
|
||||
event: "status.update",
|
||||
payload:
|
||||
Pleroma.Web.MastodonAPI.StatusView.render(
|
||||
|
@ -78,7 +85,7 @@ defmodule Pleroma.Web.StreamerView do
|
|||
|> Jason.encode!()
|
||||
end
|
||||
|
||||
def render("chat_update.json", %{chat_message_reference: cm_ref}) do
|
||||
def render("chat_update.json", %{chat_message_reference: cm_ref}, topic) do
|
||||
# Explicitly giving the cmr for the object here, so we don't accidentally
|
||||
# send a later 'last_message' that was inserted between inserting this and
|
||||
# streaming it out
|
||||
|
@ -93,6 +100,7 @@ defmodule Pleroma.Web.StreamerView do
|
|||
)
|
||||
|
||||
%{
|
||||
stream: render("stream.json", %{topic: topic}),
|
||||
event: "pleroma:chat_update",
|
||||
payload:
|
||||
representation
|
||||
|
@ -101,8 +109,9 @@ defmodule Pleroma.Web.StreamerView do
|
|||
|> Jason.encode!()
|
||||
end
|
||||
|
||||
def render("follow_relationships_update.json", item) do
|
||||
def render("follow_relationships_update.json", item, topic) do
|
||||
%{
|
||||
stream: render("stream.json", %{topic: topic}),
|
||||
event: "pleroma:follow_relationships_update",
|
||||
payload:
|
||||
%{
|
||||
|
@ -123,8 +132,9 @@ defmodule Pleroma.Web.StreamerView do
|
|||
|> Jason.encode!()
|
||||
end
|
||||
|
||||
def render("conversation.json", %Participation{} = participation) do
|
||||
def render("conversation.json", %Participation{} = participation, topic) do
|
||||
%{
|
||||
stream: render("stream.json", %{topic: topic}),
|
||||
event: "conversation",
|
||||
payload:
|
||||
Pleroma.Web.MastodonAPI.ConversationView.render("participation.json", %{
|
||||
|
@ -135,4 +145,39 @@ defmodule Pleroma.Web.StreamerView do
|
|||
}
|
||||
|> Jason.encode!()
|
||||
end
|
||||
|
||||
def render("pleroma_respond.json", %{type: type, result: result} = params) do
|
||||
%{
|
||||
event: "pleroma:respond",
|
||||
payload:
|
||||
%{
|
||||
result: result,
|
||||
type: type
|
||||
}
|
||||
|> Map.merge(maybe_error(params))
|
||||
|> Jason.encode!()
|
||||
}
|
||||
|> Jason.encode!()
|
||||
end
|
||||
|
||||
def render("stream.json", %{topic: "user:pleroma_chat:" <> _}), do: ["user:pleroma_chat"]
|
||||
def render("stream.json", %{topic: "user:notification:" <> _}), do: ["user:notification"]
|
||||
def render("stream.json", %{topic: "user:" <> _}), do: ["user"]
|
||||
def render("stream.json", %{topic: "direct:" <> _}), do: ["direct"]
|
||||
def render("stream.json", %{topic: "list:" <> id}), do: ["list", id]
|
||||
def render("stream.json", %{topic: "hashtag:" <> tag}), do: ["hashtag", tag]
|
||||
|
||||
def render("stream.json", %{topic: "public:remote:media:" <> instance}),
|
||||
do: ["public:remote:media", instance]
|
||||
|
||||
def render("stream.json", %{topic: "public:remote:" <> instance}),
|
||||
do: ["public:remote", instance]
|
||||
|
||||
def render("stream.json", %{topic: stream}) when stream in Pleroma.Constants.public_streams(),
|
||||
do: [stream]
|
||||
|
||||
defp maybe_error(%{error: :bad_topic}), do: %{error: "bad_topic"}
|
||||
defp maybe_error(%{error: :unauthorized}), do: %{error: "unauthorized"}
|
||||
defp maybe_error(%{error: :already_authenticated}), do: %{error: "already_authenticated"}
|
||||
defp maybe_error(_), do: %{}
|
||||
end
|
||||
|
|
|
@ -38,6 +38,7 @@ defmodule Pleroma.HTML.Scrubber.Default do
|
|||
Meta.allow_tag_with_these_attributes(:abbr, ["title", "lang"])
|
||||
|
||||
Meta.allow_tag_with_these_attributes(:b, ["lang"])
|
||||
Meta.allow_tag_with_these_attributes(:bdi, [])
|
||||
Meta.allow_tag_with_these_attributes(:blockquote, ["lang"])
|
||||
Meta.allow_tag_with_these_attributes(:br, ["lang"])
|
||||
Meta.allow_tag_with_these_attributes(:code, ["lang"])
|
||||
|
@ -60,7 +61,12 @@ defmodule Pleroma.HTML.Scrubber.Default do
|
|||
Meta.allow_tag_with_these_attributes(:u, ["lang"])
|
||||
Meta.allow_tag_with_these_attributes(:ul, ["lang"])
|
||||
|
||||
Meta.allow_tag_with_this_attribute_values(:span, "class", ["h-card", "recipients-inline"])
|
||||
Meta.allow_tag_with_this_attribute_values(:span, "class", [
|
||||
"h-card",
|
||||
"recipients-inline",
|
||||
"quote-inline"
|
||||
])
|
||||
|
||||
Meta.allow_tag_with_these_attributes(:span, ["lang"])
|
||||
|
||||
Meta.allow_tag_with_this_attribute_values(:code, "class", ["inline"])
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
"ostatus": "http://ostatus.org#",
|
||||
"schema": "http://schema.org#",
|
||||
"toot": "http://joinmastodon.org/ns#",
|
||||
"fedibird": "http://fedibird.com/ns#",
|
||||
"value": "schema:value",
|
||||
"sensitive": "as:sensitive",
|
||||
"litepub": "http://litepub.social/ns#",
|
||||
|
@ -26,6 +27,8 @@
|
|||
"@id": "litepub:listMessage",
|
||||
"@type": "@id"
|
||||
},
|
||||
"quoteUrl": "as:quoteUrl",
|
||||
"quoteUri": "fedibird:quoteUri",
|
||||
"oauthRegistrationEndpoint": {
|
||||
"@id": "litepub:oauthRegistrationEndpoint",
|
||||
"@type": "@id"
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
{
|
||||
"@context": [
|
||||
"https://www.w3.org/ns/activitystreams",
|
||||
{
|
||||
"ostatus": "http://ostatus.org#",
|
||||
"atomUri": "ostatus:atomUri",
|
||||
"inReplyToAtomUri": "ostatus:inReplyToAtomUri",
|
||||
"conversation": "ostatus:conversation",
|
||||
"sensitive": "as:sensitive",
|
||||
"toot": "http://joinmastodon.org/ns#",
|
||||
"votersCount": "toot:votersCount",
|
||||
"fedibird": "http://fedibird.com/ns#",
|
||||
"quoteUri": "fedibird:quoteUri",
|
||||
"expiry": "fedibird:expiry"
|
||||
}
|
||||
],
|
||||
"id": "https://fedibird.com/users/noellabo/statuses/107712183700212249",
|
||||
"type": "Note",
|
||||
"summary": null,
|
||||
"inReplyTo": null,
|
||||
"published": "2022-01-30T15:44:50Z",
|
||||
"url": "https://fedibird.com/@noellabo/107712183700212249",
|
||||
"attributedTo": "https://fedibird.com/users/noellabo",
|
||||
"to": [
|
||||
"https://www.w3.org/ns/activitystreams#Public"
|
||||
],
|
||||
"cc": [
|
||||
"https://fedibird.com/users/noellabo/followers"
|
||||
],
|
||||
"sensitive": false,
|
||||
"atomUri": "https://fedibird.com/users/noellabo/statuses/107712183700212249",
|
||||
"inReplyToAtomUri": null,
|
||||
"conversation": "tag:fedibird.com,2022-01-30:objectId=107712183700170473:objectType=Conversation",
|
||||
"context": "https://fedibird.com/contexts/107712183700170473",
|
||||
"quoteUri": "https://unnerv.jp/users/UN_NERV/statuses/107712176849067434",
|
||||
"_misskey_quote": "https://unnerv.jp/users/UN_NERV/statuses/107712176849067434",
|
||||
"_misskey_content": "揺れていたようだ",
|
||||
"content": "<p>揺れていたようだ<span class=\"quote-inline\"><br/>QT: <a class=\"status-url-link\" data-status-account-acct=\"UN_NERV@unnerv.jp\" data-status-id=\"107712177062934465\" href=\"https://unnerv.jp/@UN_NERV/107712176849067434\" rel=\"nofollow noopener noreferrer\" target=\"_blank\"><span class=\"invisible\">https://</span><span class=\"ellipsis\">unnerv.jp/@UN_NERV/10771217684</span><span class=\"invisible\">9067434</span></a></span></p>",
|
||||
"contentMap": {
|
||||
"ja": "<p>揺れていたようだ<span class=\"quote-inline\"><br/>QT: <a class=\"status-url-link\" data-status-account-acct=\"UN_NERV@unnerv.jp\" data-status-id=\"107712177062934465\" href=\"https://unnerv.jp/@UN_NERV/107712176849067434\" rel=\"nofollow noopener noreferrer\" target=\"_blank\"><span class=\"invisible\">https://</span><span class=\"ellipsis\">unnerv.jp/@UN_NERV/10771217684</span><span class=\"invisible\">9067434</span></a></span></p>"
|
||||
},
|
||||
"attachment": [],
|
||||
"tag": [],
|
||||
"replies": {
|
||||
"id": "https://fedibird.com/users/noellabo/statuses/107712183700212249/replies",
|
||||
"type": "Collection",
|
||||
"first": {
|
||||
"type": "CollectionPage",
|
||||
"next": "https://fedibird.com/users/noellabo/statuses/107712183700212249/replies?only_other_accounts=true&page=true",
|
||||
"partOf": "https://fedibird.com/users/noellabo/statuses/107712183700212249/replies",
|
||||
"items": []
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
{
|
||||
"@context": [
|
||||
"https://www.w3.org/ns/activitystreams",
|
||||
{
|
||||
"ostatus": "http://ostatus.org#",
|
||||
"atomUri": "ostatus:atomUri",
|
||||
"inReplyToAtomUri": "ostatus:inReplyToAtomUri",
|
||||
"conversation": "ostatus:conversation",
|
||||
"sensitive": "as:sensitive",
|
||||
"toot": "http://joinmastodon.org/ns#",
|
||||
"votersCount": "toot:votersCount",
|
||||
"expiry": "toot:expiry"
|
||||
}
|
||||
],
|
||||
"id": "https://fedibird.com/users/noellabo/statuses/107663670404015196",
|
||||
"type": "Note",
|
||||
"summary": null,
|
||||
"inReplyTo": null,
|
||||
"published": "2022-01-22T02:07:16Z",
|
||||
"url": "https://fedibird.com/@noellabo/107663670404015196",
|
||||
"attributedTo": "https://fedibird.com/users/noellabo",
|
||||
"to": [
|
||||
"https://www.w3.org/ns/activitystreams#Public"
|
||||
],
|
||||
"cc": [
|
||||
"https://fedibird.com/users/noellabo/followers"
|
||||
],
|
||||
"sensitive": false,
|
||||
"atomUri": "https://fedibird.com/users/noellabo/statuses/107663670404015196",
|
||||
"inReplyToAtomUri": null,
|
||||
"conversation": "tag:fedibird.com,2022-01-22:objectId=107663670404038002:objectType=Conversation",
|
||||
"context": "https://fedibird.com/contexts/107663670404038002",
|
||||
"quoteURL": "https://misskey.io/notes/8vsn2izjwh",
|
||||
"_misskey_quote": "https://misskey.io/notes/8vsn2izjwh",
|
||||
"_misskey_content": "いつの生まれだシトリン",
|
||||
"content": "<p>いつの生まれだシトリン<span class=\"quote-inline\"><br/>QT: <a class=\"status-url-link\" data-status-account-acct=\"Citrine@misskey.io\" data-status-id=\"107663207194225003\" href=\"https://misskey.io/notes/8vsn2izjwh\" rel=\"nofollow noopener noreferrer\" target=\"_blank\"><span class=\"invisible\">https://</span><span class=\"\">misskey.io/notes/8vsn2izjwh</span><span class=\"invisible\"></span></a></span></p>",
|
||||
"contentMap": {
|
||||
"ja": "<p>いつの生まれだシトリン<span class=\"quote-inline\"><br/>QT: <a class=\"status-url-link\" data-status-account-acct=\"Citrine@misskey.io\" data-status-id=\"107663207194225003\" href=\"https://misskey.io/notes/8vsn2izjwh\" rel=\"nofollow noopener noreferrer\" target=\"_blank\"><span class=\"invisible\">https://</span><span class=\"\">misskey.io/notes/8vsn2izjwh</span><span class=\"invisible\"></span></a></span></p>"
|
||||
},
|
||||
"attachment": [],
|
||||
"tag": [],
|
||||
"replies": {
|
||||
"id": "https://fedibird.com/users/noellabo/statuses/107663670404015196/replies",
|
||||
"type": "Collection",
|
||||
"first": {
|
||||
"type": "CollectionPage",
|
||||
"next": "https://fedibird.com/users/noellabo/statuses/107663670404015196/replies?only_other_accounts=true&page=true",
|
||||
"partOf": "https://fedibird.com/users/noellabo/statuses/107663670404015196/replies",
|
||||
"items": []
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
{
|
||||
"@context": [
|
||||
"https://www.w3.org/ns/activitystreams",
|
||||
{
|
||||
"ostatus": "http://ostatus.org#",
|
||||
"atomUri": "ostatus:atomUri",
|
||||
"inReplyToAtomUri": "ostatus:inReplyToAtomUri",
|
||||
"conversation": "ostatus:conversation",
|
||||
"sensitive": "as:sensitive",
|
||||
"toot": "http://joinmastodon.org/ns#",
|
||||
"votersCount": "toot:votersCount",
|
||||
"fedibird": "http://fedibird.com/ns#",
|
||||
"quoteUri": "fedibird:quoteUri",
|
||||
"expiry": "fedibird:expiry"
|
||||
}
|
||||
],
|
||||
"id": "https://fedibird.com/users/noellabo/statuses/107699335988346142",
|
||||
"type": "Note",
|
||||
"summary": null,
|
||||
"inReplyTo": null,
|
||||
"published": "2022-01-28T09:17:30Z",
|
||||
"url": "https://fedibird.com/@noellabo/107699335988346142",
|
||||
"attributedTo": "https://fedibird.com/users/noellabo",
|
||||
"to": [
|
||||
"https://www.w3.org/ns/activitystreams#Public"
|
||||
],
|
||||
"cc": [
|
||||
"https://fedibird.com/users/noellabo/followers"
|
||||
],
|
||||
"sensitive": false,
|
||||
"atomUri": "https://fedibird.com/users/noellabo/statuses/107699335988346142",
|
||||
"inReplyToAtomUri": null,
|
||||
"conversation": "tag:fedibird.com,2022-01-28:objectId=107699335988345290:objectType=Conversation",
|
||||
"context": "https://fedibird.com/contexts/107699335988345290",
|
||||
"quoteUri": "https://fedibird.com/users/yamako/statuses/107699333438289729",
|
||||
"_misskey_quote": "https://fedibird.com/users/yamako/statuses/107699333438289729",
|
||||
"_misskey_content": "美味しそう",
|
||||
"content": "<p>美味しそう<span class=\"quote-inline\"><br/>QT: <a class=\"status-url-link\" data-status-account-acct=\"yamako\" data-status-id=\"107699333438289729\" href=\"https://fedibird.com/@yamako/107699333438289729\" rel=\"nofollow noopener noreferrer\" target=\"_blank\"><span class=\"invisible\">https://</span><span class=\"ellipsis\">fedibird.com/@yamako/107699333</span><span class=\"invisible\">438289729</span></a></span></p>",
|
||||
"contentMap": {
|
||||
"ja": "<p>美味しそう<span class=\"quote-inline\"><br/>QT: <a class=\"status-url-link\" data-status-account-acct=\"yamako\" data-status-id=\"107699333438289729\" href=\"https://fedibird.com/@yamako/107699333438289729\" rel=\"nofollow noopener noreferrer\" target=\"_blank\"><span class=\"invisible\">https://</span><span class=\"ellipsis\">fedibird.com/@yamako/107699333</span><span class=\"invisible\">438289729</span></a></span></p>"
|
||||
},
|
||||
"attachment": [],
|
||||
"tag": [],
|
||||
"replies": {
|
||||
"id": "https://fedibird.com/users/noellabo/statuses/107699335988346142/replies",
|
||||
"type": "Collection",
|
||||
"first": {
|
||||
"type": "CollectionPage",
|
||||
"next": "https://fedibird.com/users/noellabo/statuses/107699335988346142/replies?only_other_accounts=true&page=true",
|
||||
"partOf": "https://fedibird.com/users/noellabo/statuses/107699335988346142/replies",
|
||||
"items": []
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"@context": "https://www.w3.org/ns/activitystreams",
|
||||
"type": "Note",
|
||||
"content": "This is a quote:<br>RE: https://server.example/objects/123",
|
||||
"tag": [
|
||||
{
|
||||
"type": "Link",
|
||||
"mediaType": "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"",
|
||||
"href": "https://server.example/objects/123",
|
||||
"name": "RE: https://server.example/objects/123"
|
||||
}
|
||||
],
|
||||
"id": "https://server.example/objects/1",
|
||||
"to": "https://server.example/users/1",
|
||||
"attributedTo": "https://server.example/users/1",
|
||||
"actor": "https://server.example/users/1"
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
{
|
||||
"@context": [
|
||||
"https://www.w3.org/ns/activitystreams",
|
||||
"https://w3id.org/security/v1",
|
||||
{
|
||||
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
|
||||
"sensitive": "as:sensitive",
|
||||
"Hashtag": "as:Hashtag",
|
||||
"quoteUrl": "as:quoteUrl",
|
||||
"toot": "http://joinmastodon.org/ns#",
|
||||
"Emoji": "toot:Emoji",
|
||||
"featured": "toot:featured",
|
||||
"discoverable": "toot:discoverable",
|
||||
"schema": "http://schema.org#",
|
||||
"PropertyValue": "schema:PropertyValue",
|
||||
"value": "schema:value",
|
||||
"misskey": "https://misskey.io/ns#",
|
||||
"_misskey_content": "misskey:_misskey_content",
|
||||
"_misskey_quote": "misskey:_misskey_quote",
|
||||
"_misskey_reaction": "misskey:_misskey_reaction",
|
||||
"_misskey_votes": "misskey:_misskey_votes",
|
||||
"_misskey_talk": "misskey:_misskey_talk",
|
||||
"isCat": "misskey:isCat",
|
||||
"vcard": "http://www.w3.org/2006/vcard/ns#"
|
||||
}
|
||||
],
|
||||
"id": "https://misskey.io/notes/8vs6ylpfez",
|
||||
"type": "Note",
|
||||
"attributedTo": "https://misskey.io/users/7rkrarq81i",
|
||||
"summary": null,
|
||||
"content": "<p><span>投稿者の設定によるね<br>Fanboxについても投稿者によっては過去の投稿は高額なプランに移動してることがある<br><br>RE: </span><a href=\"https://misskey.io/notes/8vs6wxufd0\">https://misskey.io/notes/8vs6wxufd0</a></p>",
|
||||
"_misskey_content": "投稿者の設定によるね\nFanboxについても投稿者によっては過去の投稿は高額なプランに移動してることがある",
|
||||
"_misskey_quote": "https://misskey.io/notes/8vs6wxufd0",
|
||||
"quoteUrl": "https://misskey.io/notes/8vs6wxufd0",
|
||||
"published": "2022-01-21T16:38:30.243Z",
|
||||
"to": [
|
||||
"https://www.w3.org/ns/activitystreams#Public"
|
||||
],
|
||||
"cc": [
|
||||
"https://misskey.io/users/7rkrarq81i/followers"
|
||||
],
|
||||
"inReplyTo": null,
|
||||
"attachment": [],
|
||||
"sensitive": false,
|
||||
"tag": []
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
{
|
||||
"@context": [
|
||||
"https://www.w3.org/ns/activitystreams",
|
||||
"https://w3id.org/security/v1",
|
||||
{
|
||||
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
|
||||
"sensitive": "as:sensitive",
|
||||
"Hashtag": "as:Hashtag",
|
||||
"quoteUrl": "as:quoteUrl",
|
||||
"toot": "http://joinmastodon.org/ns#",
|
||||
"Emoji": "toot:Emoji",
|
||||
"featured": "toot:featured",
|
||||
"discoverable": "toot:discoverable",
|
||||
"schema": "http://schema.org#",
|
||||
"PropertyValue": "schema:PropertyValue",
|
||||
"value": "schema:value",
|
||||
"misskey": "https://misskey.io/ns#",
|
||||
"_misskey_content": "misskey:_misskey_content",
|
||||
"_misskey_quote": "misskey:_misskey_quote",
|
||||
"_misskey_reaction": "misskey:_misskey_reaction",
|
||||
"_misskey_votes": "misskey:_misskey_votes",
|
||||
"_misskey_talk": "misskey:_misskey_talk",
|
||||
"isCat": "misskey:isCat",
|
||||
"vcard": "http://www.w3.org/2006/vcard/ns#"
|
||||
}
|
||||
],
|
||||
"type": "Person",
|
||||
"id": "https://misskey.io/users/83ssedkv53",
|
||||
"inbox": "https://misskey.io/users/83ssedkv53/inbox",
|
||||
"outbox": "https://misskey.io/users/83ssedkv53/outbox",
|
||||
"followers": "https://misskey.io/users/83ssedkv53/followers",
|
||||
"following": "https://misskey.io/users/83ssedkv53/following",
|
||||
"sharedInbox": "https://misskey.io/inbox",
|
||||
"endpoints": {
|
||||
"sharedInbox": "https://misskey.io/inbox"
|
||||
},
|
||||
"url": "https://misskey.io/@aimu",
|
||||
"preferredUsername": "aimu",
|
||||
"name": "あいむ",
|
||||
"summary": "<p><span>わずかな作曲要素 巣穴で独り言<br>Twitter </span><a href=\"https://twitter.com/aimu_53\">https://twitter.com/aimu_53</a><span><br>Soundcloud </span><a href=\"https://soundcloud.com/aimu-53\">https://soundcloud.com/aimu-53</a></p>",
|
||||
"icon": {
|
||||
"type": "Image",
|
||||
"url": "https://s3.arkjp.net/misskey/webpublic-3f7e93c0-34f5-443c-acc0-f415cb2342b4.jpg",
|
||||
"sensitive": false,
|
||||
"name": null
|
||||
},
|
||||
"image": {
|
||||
"type": "Image",
|
||||
"url": "https://s3.arkjp.net/misskey/webpublic-2db63d1d-490b-488b-ab62-c93c285f26b6.png",
|
||||
"sensitive": false,
|
||||
"name": null
|
||||
},
|
||||
"tag": [],
|
||||
"manuallyApprovesFollowers": false,
|
||||
"discoverable": true,
|
||||
"publicKey": {
|
||||
"id": "https://misskey.io/users/83ssedkv53#main-key",
|
||||
"type": "Key",
|
||||
"owner": "https://misskey.io/users/83ssedkv53",
|
||||
"publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA1ylhePJ6qGHmwHSBP17b\nIosxGaiFKvgDBgZdm8vzvKeRSqJV9uLHfZL3pO/Zt02EwaZd2GohZAtBZEF8DbMA\n3s93WAesvyGF9mjGrYYKlhp/glwyrrrbf+RdD0DLtyDwRRlrxp3pS2lLmv5Tp1Zl\npH+UKpOnNrpQqjHI5P+lEc9bnflzbRrX+UiyLNsVAP80v4wt7SZfT/telrU6mDru\n998UdfhUo7bDKeDsHG1PfLpyhhtfdoZub4kBpkyacHiwAd+CdCjR54Eu7FDwVK3p\nY3JcrT2q5stgMqN1m4QgSL4XAADIotWwDYttTJejM1n9dr+6VWv5bs0F2Q/6gxOp\nu5DQZLk4Q+64U4LWNox6jCMOq3fYe0g7QalJIHnanYQQo+XjoH6S1Aw64gQ3Ip2Y\nZBmZREAOR7GMFVDPFnVnsbCHnIAv16TdgtLgQBAihkWEUuPqITLi8PMu6kMr3uyq\nYkObEfH0TNTcqaiVpoXv791GZLEUV5ROl0FSUANLNkHZZv29xZ5JDOBOR1rNBLyH\ngVtW8rpszYqOXwzX23hh4WsVXfB7YgNvIijwjiaWbzsecleaENGEnLNMiVKVumTj\nmtyTeFJpH0+OaSrUYpemRRJizmqIjklKsNwUEwUb2WcUUg92o56T2obrBkooabZe\nwgSXSKTOcjsR/ju7+AuIyvkCAwEAAQ==\n-----END PUBLIC KEY-----\n"
|
||||
},
|
||||
"isCat": true,
|
||||
"vcard:bday": "5353-05-03"
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
{
|
||||
"@context": [
|
||||
"https://www.w3.org/ns/activitystreams",
|
||||
"https://w3id.org/security/v1",
|
||||
{
|
||||
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
|
||||
"sensitive": "as:sensitive",
|
||||
"Hashtag": "as:Hashtag",
|
||||
"quoteUrl": "as:quoteUrl",
|
||||
"toot": "http://joinmastodon.org/ns#",
|
||||
"Emoji": "toot:Emoji",
|
||||
"featured": "toot:featured",
|
||||
"discoverable": "toot:discoverable",
|
||||
"schema": "http://schema.org#",
|
||||
"PropertyValue": "schema:PropertyValue",
|
||||
"value": "schema:value",
|
||||
"misskey": "https://misskey.io/ns#",
|
||||
"_misskey_content": "misskey:_misskey_content",
|
||||
"_misskey_quote": "misskey:_misskey_quote",
|
||||
"_misskey_reaction": "misskey:_misskey_reaction",
|
||||
"_misskey_votes": "misskey:_misskey_votes",
|
||||
"_misskey_talk": "misskey:_misskey_talk",
|
||||
"isCat": "misskey:isCat",
|
||||
"vcard": "http://www.w3.org/2006/vcard/ns#"
|
||||
}
|
||||
],
|
||||
"id": "https://misskey.io/notes/8vs6wxufd0",
|
||||
"type": "Note",
|
||||
"attributedTo": "https://misskey.io/users/83ssedkv53",
|
||||
"summary": null,
|
||||
"content": "<p><span>Fantiaこれできないように過去のやつは従量課金だった気がする</span></p>",
|
||||
"_misskey_content": "Fantiaこれできないように過去のやつは従量課金だった気がする",
|
||||
"published": "2022-01-21T16:37:12.663Z",
|
||||
"to": [
|
||||
"https://www.w3.org/ns/activitystreams#Public"
|
||||
],
|
||||
"cc": [
|
||||
"https://misskey.io/users/83ssedkv53/followers"
|
||||
],
|
||||
"inReplyTo": null,
|
||||
"attachment": [],
|
||||
"sensitive": false,
|
||||
"tag": []
|
||||
}
|
|
@ -31,9 +31,22 @@ defmodule Pleroma.Integration.MastodonWebsocketTest do
|
|||
WebsocketClient.start_link(self(), path, headers)
|
||||
end
|
||||
|
||||
defp decode_json(json) do
|
||||
with {:ok, %{"event" => event, "payload" => payload_text}} <- Jason.decode(json),
|
||||
{:ok, payload} <- Jason.decode(payload_text) do
|
||||
{:ok, %{"event" => event, "payload" => payload}}
|
||||
end
|
||||
end
|
||||
|
||||
# Turns atom keys to strings
|
||||
defp atom_key_to_string(json) do
|
||||
json
|
||||
|> Jason.encode!()
|
||||
|> Jason.decode!()
|
||||
end
|
||||
|
||||
test "refuses invalid requests" do
|
||||
capture_log(fn ->
|
||||
assert {:error, %WebSockex.RequestError{code: 404}} = start_socket()
|
||||
assert {:error, %WebSockex.RequestError{code: 404}} = start_socket("?stream=ncjdk")
|
||||
Process.sleep(30)
|
||||
end)
|
||||
|
@ -49,6 +62,10 @@ defmodule Pleroma.Integration.MastodonWebsocketTest do
|
|||
end)
|
||||
end
|
||||
|
||||
test "allows unified stream" do
|
||||
assert {:ok, _} = start_socket()
|
||||
end
|
||||
|
||||
test "allows public streams without authentication" do
|
||||
assert {:ok, _} = start_socket("?stream=public")
|
||||
assert {:ok, _} = start_socket("?stream=public:local")
|
||||
|
@ -68,6 +85,35 @@ defmodule Pleroma.Integration.MastodonWebsocketTest do
|
|||
assert json["payload"]
|
||||
assert {:ok, json} = Jason.decode(json["payload"])
|
||||
|
||||
view_json =
|
||||
Pleroma.Web.MastodonAPI.StatusView.render("show.json", activity: activity, for: nil)
|
||||
|> atom_key_to_string()
|
||||
|
||||
assert json == view_json
|
||||
end
|
||||
|
||||
describe "subscribing via WebSocket" do
|
||||
test "can subscribe" do
|
||||
user = insert(:user)
|
||||
{:ok, pid} = start_socket()
|
||||
WebsocketClient.send_text(pid, %{type: "subscribe", stream: "public"} |> Jason.encode!())
|
||||
assert_receive {:text, raw_json}, 1_000
|
||||
|
||||
assert {:ok,
|
||||
%{
|
||||
"event" => "pleroma:respond",
|
||||
"payload" => %{"type" => "subscribe", "result" => "success"}
|
||||
}} = decode_json(raw_json)
|
||||
|
||||
{:ok, activity} = CommonAPI.post(user, %{status: "nice echo chamber"})
|
||||
|
||||
assert_receive {:text, raw_json}, 1_000
|
||||
assert {:ok, json} = Jason.decode(raw_json)
|
||||
|
||||
assert "update" == json["event"]
|
||||
assert json["payload"]
|
||||
assert {:ok, json} = Jason.decode(json["payload"])
|
||||
|
||||
view_json =
|
||||
Pleroma.Web.MastodonAPI.StatusView.render("show.json", activity: activity, for: nil)
|
||||
|> Jason.encode!()
|
||||
|
@ -76,6 +122,108 @@ defmodule Pleroma.Integration.MastodonWebsocketTest do
|
|||
assert json == view_json
|
||||
end
|
||||
|
||||
test "can subscribe to multiple streams" do
|
||||
user = insert(:user)
|
||||
{:ok, pid} = start_socket()
|
||||
WebsocketClient.send_text(pid, %{type: "subscribe", stream: "public"} |> Jason.encode!())
|
||||
assert_receive {:text, raw_json}, 1_000
|
||||
|
||||
assert {:ok,
|
||||
%{
|
||||
"event" => "pleroma:respond",
|
||||
"payload" => %{"type" => "subscribe", "result" => "success"}
|
||||
}} = decode_json(raw_json)
|
||||
|
||||
WebsocketClient.send_text(
|
||||
pid,
|
||||
%{type: "subscribe", stream: "hashtag", tag: "mew"} |> Jason.encode!()
|
||||
)
|
||||
|
||||
assert_receive {:text, raw_json}, 1_000
|
||||
|
||||
assert {:ok,
|
||||
%{
|
||||
"event" => "pleroma:respond",
|
||||
"payload" => %{"type" => "subscribe", "result" => "success"}
|
||||
}} = decode_json(raw_json)
|
||||
|
||||
{:ok, _activity} = CommonAPI.post(user, %{status: "nice echo chamber #mew"})
|
||||
|
||||
assert_receive {:text, raw_json}, 1_000
|
||||
assert {:ok, %{"stream" => stream1}} = Jason.decode(raw_json)
|
||||
assert_receive {:text, raw_json}, 1_000
|
||||
assert {:ok, %{"stream" => stream2}} = Jason.decode(raw_json)
|
||||
|
||||
streams = [stream1, stream2]
|
||||
assert ["hashtag", "mew"] in streams
|
||||
assert ["public"] in streams
|
||||
end
|
||||
|
||||
test "won't double subscribe" do
|
||||
user = insert(:user)
|
||||
{:ok, pid} = start_socket()
|
||||
WebsocketClient.send_text(pid, %{type: "subscribe", stream: "public"} |> Jason.encode!())
|
||||
assert_receive {:text, raw_json}, 1_000
|
||||
|
||||
assert {:ok,
|
||||
%{
|
||||
"event" => "pleroma:respond",
|
||||
"payload" => %{"type" => "subscribe", "result" => "success"}
|
||||
}} = decode_json(raw_json)
|
||||
|
||||
WebsocketClient.send_text(pid, %{type: "subscribe", stream: "public"} |> Jason.encode!())
|
||||
assert_receive {:text, raw_json}, 1_000
|
||||
|
||||
assert {:ok,
|
||||
%{
|
||||
"event" => "pleroma:respond",
|
||||
"payload" => %{"type" => "subscribe", "result" => "ignored"}
|
||||
}} = decode_json(raw_json)
|
||||
|
||||
{:ok, _activity} = CommonAPI.post(user, %{status: "nice echo chamber"})
|
||||
|
||||
assert_receive {:text, _}, 1_000
|
||||
refute_receive {:text, _}, 1_000
|
||||
end
|
||||
|
||||
test "rejects invalid streams" do
|
||||
{:ok, pid} = start_socket()
|
||||
WebsocketClient.send_text(pid, %{type: "subscribe", stream: "nonsense"} |> Jason.encode!())
|
||||
assert_receive {:text, raw_json}, 1_000
|
||||
|
||||
assert {:ok,
|
||||
%{
|
||||
"event" => "pleroma:respond",
|
||||
"payload" => %{"type" => "subscribe", "result" => "error", "error" => "bad_topic"}
|
||||
}} = decode_json(raw_json)
|
||||
end
|
||||
|
||||
test "can unsubscribe" do
|
||||
user = insert(:user)
|
||||
{:ok, pid} = start_socket()
|
||||
WebsocketClient.send_text(pid, %{type: "subscribe", stream: "public"} |> Jason.encode!())
|
||||
assert_receive {:text, raw_json}, 1_000
|
||||
|
||||
assert {:ok,
|
||||
%{
|
||||
"event" => "pleroma:respond",
|
||||
"payload" => %{"type" => "subscribe", "result" => "success"}
|
||||
}} = decode_json(raw_json)
|
||||
|
||||
WebsocketClient.send_text(pid, %{type: "unsubscribe", stream: "public"} |> Jason.encode!())
|
||||
assert_receive {:text, raw_json}, 1_000
|
||||
|
||||
assert {:ok,
|
||||
%{
|
||||
"event" => "pleroma:respond",
|
||||
"payload" => %{"type" => "unsubscribe", "result" => "success"}
|
||||
}} = decode_json(raw_json)
|
||||
|
||||
{:ok, _activity} = CommonAPI.post(user, %{status: "nice echo chamber"})
|
||||
refute_receive {:text, _}, 1_000
|
||||
end
|
||||
end
|
||||
|
||||
describe "with a valid user token" do
|
||||
setup do
|
||||
{:ok, app} =
|
||||
|
@ -131,6 +279,124 @@ defmodule Pleroma.Integration.MastodonWebsocketTest do
|
|||
end)
|
||||
end
|
||||
|
||||
test "accepts valid token on client-sent event", %{token: token} do
|
||||
assert {:ok, pid} = start_socket()
|
||||
|
||||
WebsocketClient.send_text(
|
||||
pid,
|
||||
%{type: "pleroma:authenticate", token: token.token} |> Jason.encode!()
|
||||
)
|
||||
|
||||
assert_receive {:text, raw_json}, 1_000
|
||||
|
||||
assert {:ok,
|
||||
%{
|
||||
"event" => "pleroma:respond",
|
||||
"payload" => %{"type" => "pleroma:authenticate", "result" => "success"}
|
||||
}} = decode_json(raw_json)
|
||||
|
||||
WebsocketClient.send_text(pid, %{type: "subscribe", stream: "user"} |> Jason.encode!())
|
||||
assert_receive {:text, raw_json}, 1_000
|
||||
|
||||
assert {:ok,
|
||||
%{
|
||||
"event" => "pleroma:respond",
|
||||
"payload" => %{"type" => "subscribe", "result" => "success"}
|
||||
}} = decode_json(raw_json)
|
||||
end
|
||||
|
||||
test "rejects invalid token on client-sent event" do
|
||||
assert {:ok, pid} = start_socket()
|
||||
|
||||
WebsocketClient.send_text(
|
||||
pid,
|
||||
%{type: "pleroma:authenticate", token: "Something else"} |> Jason.encode!()
|
||||
)
|
||||
|
||||
assert_receive {:text, raw_json}, 1_000
|
||||
|
||||
assert {:ok,
|
||||
%{
|
||||
"event" => "pleroma:respond",
|
||||
"payload" => %{
|
||||
"type" => "pleroma:authenticate",
|
||||
"result" => "error",
|
||||
"error" => "unauthorized"
|
||||
}
|
||||
}} = decode_json(raw_json)
|
||||
end
|
||||
|
||||
test "rejects new authenticate request if already logged-in", %{token: token} do
|
||||
assert {:ok, pid} = start_socket()
|
||||
|
||||
WebsocketClient.send_text(
|
||||
pid,
|
||||
%{type: "pleroma:authenticate", token: token.token} |> Jason.encode!()
|
||||
)
|
||||
|
||||
assert_receive {:text, raw_json}, 1_000
|
||||
|
||||
assert {:ok,
|
||||
%{
|
||||
"event" => "pleroma:respond",
|
||||
"payload" => %{"type" => "pleroma:authenticate", "result" => "success"}
|
||||
}} = decode_json(raw_json)
|
||||
|
||||
WebsocketClient.send_text(
|
||||
pid,
|
||||
%{type: "pleroma:authenticate", token: "Something else"} |> Jason.encode!()
|
||||
)
|
||||
|
||||
assert_receive {:text, raw_json}, 1_000
|
||||
|
||||
assert {:ok,
|
||||
%{
|
||||
"event" => "pleroma:respond",
|
||||
"payload" => %{
|
||||
"type" => "pleroma:authenticate",
|
||||
"result" => "error",
|
||||
"error" => "already_authenticated"
|
||||
}
|
||||
}} = decode_json(raw_json)
|
||||
end
|
||||
|
||||
test "accepts the 'list' stream", %{token: token, user: user} do
|
||||
posting_user = insert(:user)
|
||||
|
||||
{:ok, list} = Pleroma.List.create("test", user)
|
||||
Pleroma.List.follow(list, posting_user)
|
||||
|
||||
assert {:ok, _} = start_socket("?stream=list&access_token=#{token.token}&list=#{list.id}")
|
||||
|
||||
assert {:ok, pid} = start_socket("?access_token=#{token.token}")
|
||||
|
||||
WebsocketClient.send_text(
|
||||
pid,
|
||||
%{type: "subscribe", stream: "list", list: list.id} |> Jason.encode!()
|
||||
)
|
||||
|
||||
assert_receive {:text, raw_json}, 1_000
|
||||
|
||||
assert {:ok,
|
||||
%{
|
||||
"event" => "pleroma:respond",
|
||||
"payload" => %{"type" => "subscribe", "result" => "success"}
|
||||
}} = decode_json(raw_json)
|
||||
|
||||
WebsocketClient.send_text(
|
||||
pid,
|
||||
%{type: "subscribe", stream: "list", list: to_string(list.id)} |> Jason.encode!()
|
||||
)
|
||||
|
||||
assert_receive {:text, raw_json}, 1_000
|
||||
|
||||
assert {:ok,
|
||||
%{
|
||||
"event" => "pleroma:respond",
|
||||
"payload" => %{"type" => "subscribe", "result" => "ignored"}
|
||||
}} = decode_json(raw_json)
|
||||
end
|
||||
|
||||
test "disconnect when token is revoked", %{app: app, user: user, token: token} do
|
||||
assert {:ok, _} = start_socket("?stream=user:notification&access_token=#{token.token}")
|
||||
assert {:ok, _} = start_socket("?stream=user&access_token=#{token.token}")
|
||||
|
@ -146,5 +412,85 @@ defmodule Pleroma.Integration.MastodonWebsocketTest do
|
|||
assert_receive {:close, _}
|
||||
refute_receive {:close, _}
|
||||
end
|
||||
|
||||
test "receives private statuses", %{user: reading_user, token: token} do
|
||||
user = insert(:user)
|
||||
CommonAPI.follow(reading_user, user)
|
||||
|
||||
{:ok, _} = start_socket("?stream=user&access_token=#{token.token}")
|
||||
|
||||
{:ok, activity} =
|
||||
CommonAPI.post(user, %{status: "nice echo chamber", visibility: "private"})
|
||||
|
||||
assert_receive {:text, raw_json}, 1_000
|
||||
assert {:ok, json} = Jason.decode(raw_json)
|
||||
|
||||
assert "update" == json["event"]
|
||||
assert json["payload"]
|
||||
assert {:ok, json} = Jason.decode(json["payload"])
|
||||
|
||||
view_json =
|
||||
Pleroma.Web.MastodonAPI.StatusView.render("show.json",
|
||||
activity: activity,
|
||||
for: reading_user
|
||||
)
|
||||
|> Jason.encode!()
|
||||
|> Jason.decode!()
|
||||
|
||||
assert json == view_json
|
||||
end
|
||||
|
||||
test "receives edits", %{user: reading_user, token: token} do
|
||||
user = insert(:user)
|
||||
CommonAPI.follow(reading_user, user)
|
||||
|
||||
{:ok, _} = start_socket("?stream=user&access_token=#{token.token}")
|
||||
|
||||
{:ok, activity} =
|
||||
CommonAPI.post(user, %{status: "nice echo chamber", visibility: "private"})
|
||||
|
||||
assert_receive {:text, _raw_json}, 1_000
|
||||
|
||||
{:ok, _} = CommonAPI.update(user, activity, %{status: "mew mew", visibility: "private"})
|
||||
|
||||
assert_receive {:text, raw_json}, 1_000
|
||||
|
||||
activity = Pleroma.Activity.normalize(activity)
|
||||
|
||||
view_json =
|
||||
Pleroma.Web.MastodonAPI.StatusView.render("show.json",
|
||||
activity: activity,
|
||||
for: reading_user
|
||||
)
|
||||
|> Jason.encode!()
|
||||
|> Jason.decode!()
|
||||
|
||||
assert {:ok, %{"event" => "status.update", "payload" => ^view_json}} = decode_json(raw_json)
|
||||
end
|
||||
|
||||
test "receives notifications", %{user: reading_user, token: token} do
|
||||
user = insert(:user)
|
||||
CommonAPI.follow(reading_user, user)
|
||||
|
||||
{:ok, _} = start_socket("?stream=user:notification&access_token=#{token.token}")
|
||||
|
||||
{:ok, %Pleroma.Activity{id: activity_id} = _activity} =
|
||||
CommonAPI.post(user, %{
|
||||
status: "nice echo chamber @#{reading_user.nickname}",
|
||||
visibility: "private"
|
||||
})
|
||||
|
||||
assert_receive {:text, raw_json}, 1_000
|
||||
|
||||
assert {:ok,
|
||||
%{
|
||||
"event" => "notification",
|
||||
"payload" => %{
|
||||
"status" => %{
|
||||
"id" => ^activity_id
|
||||
}
|
||||
}
|
||||
}} = decode_json(raw_json)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -252,7 +252,7 @@ defmodule Pleroma.NotificationTest do
|
|||
task =
|
||||
Task.async(fn ->
|
||||
{:ok, _topic} = Streamer.get_topic_and_add_socket("user", user, oauth_token)
|
||||
assert_receive {:render_with_user, _, _, _}, 4_000
|
||||
assert_receive {:render_with_user, _, _, _, _}, 4_000
|
||||
end)
|
||||
|
||||
task_user_notification =
|
||||
|
@ -260,7 +260,7 @@ defmodule Pleroma.NotificationTest do
|
|||
{:ok, _topic} =
|
||||
Streamer.get_topic_and_add_socket("user:notification", user, oauth_token)
|
||||
|
||||
assert_receive {:render_with_user, _, _, _}, 4_000
|
||||
assert_receive {:render_with_user, _, _, _, _}, 4_000
|
||||
end)
|
||||
|
||||
activity = insert(:note_activity)
|
||||
|
|
|
@ -44,5 +44,34 @@ defmodule Pleroma.Web.ActivityPub.BuilderTest do
|
|||
|
||||
assert {:ok, ^expected, []} = Builder.note(draft)
|
||||
end
|
||||
|
||||
test "quote post" do
|
||||
user = insert(:user)
|
||||
note = insert(:note)
|
||||
|
||||
draft = %ActivityDraft{
|
||||
user: user,
|
||||
context: "2hu",
|
||||
content_html: "<h1>This is :moominmamma: note</h1>",
|
||||
quote_post: note,
|
||||
extra: %{}
|
||||
}
|
||||
|
||||
expected = %{
|
||||
"actor" => user.ap_id,
|
||||
"attachment" => [],
|
||||
"content" => "<h1>This is :moominmamma: note</h1>",
|
||||
"context" => "2hu",
|
||||
"sensitive" => false,
|
||||
"type" => "Note",
|
||||
"quoteUrl" => note.data["id"],
|
||||
"cc" => [],
|
||||
"summary" => nil,
|
||||
"tag" => [],
|
||||
"to" => []
|
||||
}
|
||||
|
||||
assert {:ok, ^expected, []} = Builder.note(draft)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,112 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.ActivityPub.MRF.InlineQuotePolicyTest do
|
||||
alias Pleroma.Web.ActivityPub.MRF.InlineQuotePolicy
|
||||
use Pleroma.DataCase
|
||||
|
||||
test "adds quote URL to post content" do
|
||||
quote_url = "https://gleasonator.com/objects/1234"
|
||||
|
||||
activity = %{
|
||||
"type" => "Create",
|
||||
"actor" => "https://gleasonator.com/users/alex",
|
||||
"object" => %{
|
||||
"type" => "Note",
|
||||
"content" => "Nice post",
|
||||
"quoteUrl" => quote_url
|
||||
}
|
||||
}
|
||||
|
||||
{:ok, %{"object" => %{"content" => filtered}}} = InlineQuotePolicy.filter(activity)
|
||||
|
||||
assert filtered ==
|
||||
"Nice post<span class=\"quote-inline\"><br/><br/><bdi>RT:</bdi> <a href=\"https://gleasonator.com/objects/1234\">https://gleasonator.com/objects/1234</a></span>"
|
||||
end
|
||||
|
||||
test "adds quote URL to post content, custom template" do
|
||||
clear_config([:mrf_inline_quote, :template], "{url}'s quoting")
|
||||
quote_url = "https://gleasonator.com/objects/1234"
|
||||
|
||||
activity = %{
|
||||
"type" => "Create",
|
||||
"actor" => "https://gleasonator.com/users/alex",
|
||||
"object" => %{
|
||||
"type" => "Note",
|
||||
"content" => "Nice post",
|
||||
"quoteUrl" => quote_url
|
||||
}
|
||||
}
|
||||
|
||||
{:ok, %{"object" => %{"content" => filtered}}} = InlineQuotePolicy.filter(activity)
|
||||
|
||||
assert filtered ==
|
||||
"Nice post<span class=\"quote-inline\"><br/><br/><a href=\"https://gleasonator.com/objects/1234\">https://gleasonator.com/objects/1234</a>'s quoting</span>"
|
||||
end
|
||||
|
||||
test "doesn't add line breaks to markdown posts" do
|
||||
quote_url = "https://gleasonator.com/objects/1234"
|
||||
|
||||
activity = %{
|
||||
"type" => "Create",
|
||||
"actor" => "https://gleasonator.com/users/alex",
|
||||
"object" => %{
|
||||
"type" => "Note",
|
||||
"content" => "<p>Nice post</p>",
|
||||
"quoteUrl" => quote_url
|
||||
}
|
||||
}
|
||||
|
||||
{:ok, %{"object" => %{"content" => filtered}}} = InlineQuotePolicy.filter(activity)
|
||||
|
||||
assert filtered ==
|
||||
"<p>Nice post<span class=\"quote-inline\"><br/><br/><bdi>RT:</bdi> <a href=\"https://gleasonator.com/objects/1234\">https://gleasonator.com/objects/1234</a></span></p>"
|
||||
end
|
||||
|
||||
test "ignores Misskey quote posts" do
|
||||
object = File.read!("test/fixtures/quote_post/misskey_quote_post.json") |> Jason.decode!()
|
||||
|
||||
activity = %{
|
||||
"type" => "Create",
|
||||
"actor" => "https://misskey.io/users/7rkrarq81i",
|
||||
"object" => object
|
||||
}
|
||||
|
||||
{:ok, filtered} = InlineQuotePolicy.filter(activity)
|
||||
assert filtered == activity
|
||||
end
|
||||
|
||||
test "ignores Fedibird quote posts" do
|
||||
object = File.read!("test/fixtures/quote_post/fedibird_quote_post.json") |> Jason.decode!()
|
||||
|
||||
# Normally the ObjectValidator will fix this before it reaches MRF
|
||||
object = Map.put(object, "quoteUrl", object["quoteURL"])
|
||||
|
||||
activity = %{
|
||||
"type" => "Create",
|
||||
"actor" => "https://fedibird.com/users/noellabo",
|
||||
"object" => object
|
||||
}
|
||||
|
||||
{:ok, filtered} = InlineQuotePolicy.filter(activity)
|
||||
assert filtered == activity
|
||||
end
|
||||
|
||||
test "skips objects which already have an .inline-quote span" do
|
||||
object =
|
||||
File.read!("test/fixtures/quote_post/fedibird_quote_mismatched.json") |> Jason.decode!()
|
||||
|
||||
# Normally the ObjectValidator will fix this before it reaches MRF
|
||||
object = Map.put(object, "quoteUrl", object["quoteUri"])
|
||||
|
||||
activity = %{
|
||||
"type" => "Create",
|
||||
"actor" => "https://fedibird.com/users/noellabo",
|
||||
"object" => object
|
||||
}
|
||||
|
||||
{:ok, filtered} = InlineQuotePolicy.filter(activity)
|
||||
assert filtered == activity
|
||||
end
|
||||
end
|
|
@ -0,0 +1,73 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2023 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.ActivityPub.MRF.QuoteToLinkTagPolicyTest do
|
||||
alias Pleroma.Web.ActivityPub.MRF.QuoteToLinkTagPolicy
|
||||
|
||||
use Pleroma.DataCase
|
||||
|
||||
require Pleroma.Constants
|
||||
|
||||
test "Add quote url to Link tag" do
|
||||
quote_url = "https://gleasonator.com/objects/1234"
|
||||
|
||||
activity = %{
|
||||
"type" => "Create",
|
||||
"actor" => "https://gleasonator.com/users/alex",
|
||||
"object" => %{
|
||||
"type" => "Note",
|
||||
"content" => "Nice post",
|
||||
"quoteUrl" => quote_url
|
||||
}
|
||||
}
|
||||
|
||||
{:ok, %{"object" => object}} = QuoteToLinkTagPolicy.filter(activity)
|
||||
|
||||
assert object["tag"] == [
|
||||
%{
|
||||
"type" => "Link",
|
||||
"href" => quote_url,
|
||||
"mediaType" => Pleroma.Constants.activity_json_canonical_mime_type()
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
test "Add quote url to Link tag, append to the end" do
|
||||
quote_url = "https://gleasonator.com/objects/1234"
|
||||
|
||||
activity = %{
|
||||
"type" => "Create",
|
||||
"actor" => "https://gleasonator.com/users/alex",
|
||||
"object" => %{
|
||||
"type" => "Note",
|
||||
"content" => "Nice post",
|
||||
"quoteUrl" => quote_url,
|
||||
"tag" => [%{"type" => "Hashtag", "name" => "#foo"}]
|
||||
}
|
||||
}
|
||||
|
||||
{:ok, %{"object" => object}} = QuoteToLinkTagPolicy.filter(activity)
|
||||
|
||||
assert [_, tag] = object["tag"]
|
||||
|
||||
assert tag == %{
|
||||
"type" => "Link",
|
||||
"href" => quote_url,
|
||||
"mediaType" => Pleroma.Constants.activity_json_canonical_mime_type()
|
||||
}
|
||||
end
|
||||
|
||||
test "Bypass posts without quoteUrl" do
|
||||
activity = %{
|
||||
"type" => "Create",
|
||||
"actor" => "https://gleasonator.com/users/alex",
|
||||
"object" => %{
|
||||
"type" => "Note",
|
||||
"content" => "Nice post"
|
||||
}
|
||||
}
|
||||
|
||||
assert {:ok, ^activity} = QuoteToLinkTagPolicy.filter(activity)
|
||||
end
|
||||
end
|
|
@ -116,4 +116,53 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidatorTest
|
|||
|
||||
%{valid?: true} = ArticleNotePageValidator.cast_and_validate(note)
|
||||
end
|
||||
|
||||
test "Fedibird quote post" do
|
||||
insert(:user, ap_id: "https://fedibird.com/users/noellabo")
|
||||
|
||||
data = File.read!("test/fixtures/quote_post/fedibird_quote_post.json") |> Jason.decode!()
|
||||
cng = ArticleNotePageValidator.cast_and_validate(data)
|
||||
|
||||
assert cng.valid?
|
||||
assert cng.changes.quoteUrl == "https://misskey.io/notes/8vsn2izjwh"
|
||||
end
|
||||
|
||||
test "Fedibird quote post with quoteUri field" do
|
||||
insert(:user, ap_id: "https://fedibird.com/users/noellabo")
|
||||
|
||||
data = File.read!("test/fixtures/quote_post/fedibird_quote_uri.json") |> Jason.decode!()
|
||||
cng = ArticleNotePageValidator.cast_and_validate(data)
|
||||
|
||||
assert cng.valid?
|
||||
assert cng.changes.quoteUrl == "https://fedibird.com/users/yamako/statuses/107699333438289729"
|
||||
end
|
||||
|
||||
test "Misskey quote post" do
|
||||
insert(:user, ap_id: "https://misskey.io/users/7rkrarq81i")
|
||||
|
||||
data = File.read!("test/fixtures/quote_post/misskey_quote_post.json") |> Jason.decode!()
|
||||
cng = ArticleNotePageValidator.cast_and_validate(data)
|
||||
|
||||
assert cng.valid?
|
||||
assert cng.changes.quoteUrl == "https://misskey.io/notes/8vs6wxufd0"
|
||||
end
|
||||
|
||||
test "Parse tag as quote" do
|
||||
# https://codeberg.org/fediverse/fep/src/branch/main/fep/e232/fep-e232.md
|
||||
|
||||
insert(:user, ap_id: "https://server.example/users/1")
|
||||
|
||||
data = File.read!("test/fixtures/quote_post/fep-e232-tag-example.json") |> Jason.decode!()
|
||||
cng = ArticleNotePageValidator.cast_and_validate(data)
|
||||
|
||||
assert cng.valid?
|
||||
assert cng.changes.quoteUrl == "https://server.example/objects/123"
|
||||
|
||||
assert Enum.at(cng.changes.tag, 0).changes == %{
|
||||
type: "Link",
|
||||
mediaType: "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"",
|
||||
href: "https://server.example/objects/123",
|
||||
name: "RE: https://server.example/objects/123"
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -123,7 +123,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
|
|||
assert activity.data["context"] == object.data["context"]
|
||||
end
|
||||
|
||||
test "it drops link tags" do
|
||||
test "it keeps link tags" do
|
||||
insert(:user, ap_id: "https://example.org/users/alice")
|
||||
|
||||
message = File.read!("test/fixtures/fep-e232.json") |> Jason.decode!()
|
||||
|
@ -131,10 +131,29 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
|
|||
assert {:ok, activity} = Transmogrifier.handle_incoming(message)
|
||||
|
||||
object = Object.normalize(activity)
|
||||
assert length(object.data["tag"]) == 1
|
||||
assert [%{"type" => "Mention"}, %{"type" => "Link"}] = object.data["tag"]
|
||||
end
|
||||
|
||||
tag = object.data["tag"] |> List.first()
|
||||
assert tag["type"] == "Mention"
|
||||
test "it accepts quote posts" do
|
||||
insert(:user, ap_id: "https://misskey.io/users/7rkrarq81i")
|
||||
|
||||
object = File.read!("test/fixtures/quote_post/misskey_quote_post.json") |> Jason.decode!()
|
||||
|
||||
message = %{
|
||||
"@context" => "https://www.w3.org/ns/activitystreams",
|
||||
"type" => "Create",
|
||||
"actor" => "https://misskey.io/users/7rkrarq81i",
|
||||
"object" => object
|
||||
}
|
||||
|
||||
assert {:ok, activity} = Transmogrifier.handle_incoming(message)
|
||||
|
||||
# Object was created in the database
|
||||
object = Object.normalize(activity)
|
||||
assert object.data["quoteUrl"] == "https://misskey.io/notes/8vs6wxufd0"
|
||||
|
||||
# It fetched the quoted post
|
||||
assert Object.normalize("https://misskey.io/notes/8vs6wxufd0")
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -350,6 +369,20 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
|
|||
}
|
||||
} = prepared["object"]
|
||||
end
|
||||
|
||||
test "it prepares a quote post" do
|
||||
user = insert(:user)
|
||||
|
||||
{:ok, quoted_post} = CommonAPI.post(user, %{status: "hey"})
|
||||
{:ok, quote_post} = CommonAPI.post(user, %{status: "hey", quote_id: quoted_post.id})
|
||||
|
||||
{:ok, modified} = Transmogrifier.prepare_outgoing(quote_post.data)
|
||||
|
||||
%{data: %{"id" => quote_id}} = Object.normalize(quoted_post)
|
||||
|
||||
assert modified["object"]["quoteUrl"] == quote_id
|
||||
assert modified["object"]["quoteUri"] == quote_id
|
||||
end
|
||||
end
|
||||
|
||||
describe "actor rewriting" do
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.CommonAPI.ActivityDraftTest do
|
||||
use Pleroma.DataCase
|
||||
|
||||
alias Pleroma.Web.CommonAPI
|
||||
alias Pleroma.Web.CommonAPI.ActivityDraft
|
||||
|
||||
import Pleroma.Factory
|
||||
|
||||
test "create/2 with a quote post" do
|
||||
user = insert(:user)
|
||||
another_user = insert(:user)
|
||||
|
||||
{:ok, direct} = CommonAPI.post(user, %{status: ".", visibility: "direct"})
|
||||
{:ok, private} = CommonAPI.post(user, %{status: ".", visibility: "private"})
|
||||
{:ok, unlisted} = CommonAPI.post(user, %{status: ".", visibility: "unlisted"})
|
||||
{:ok, local} = CommonAPI.post(user, %{status: ".", visibility: "local"})
|
||||
{:ok, public} = CommonAPI.post(user, %{status: ".", visibility: "public"})
|
||||
|
||||
{:error, _} = ActivityDraft.create(user, %{status: "nice", quote_id: direct.id})
|
||||
{:ok, _} = ActivityDraft.create(user, %{status: "nice", quote_id: private.id})
|
||||
{:error, _} = ActivityDraft.create(another_user, %{status: "nice", quote_id: private.id})
|
||||
{:ok, _} = ActivityDraft.create(user, %{status: "nice", quote_id: unlisted.id})
|
||||
{:ok, _} = ActivityDraft.create(another_user, %{status: "nice", quote_id: unlisted.id})
|
||||
{:ok, _} = ActivityDraft.create(user, %{status: "nice", quote_id: local.id})
|
||||
{:ok, _} = ActivityDraft.create(another_user, %{status: "nice", quote_id: local.id})
|
||||
{:ok, _} = ActivityDraft.create(user, %{status: "nice", quote_id: public.id})
|
||||
{:ok, _} = ActivityDraft.create(another_user, %{status: "nice", quote_id: public.id})
|
||||
end
|
||||
end
|
|
@ -796,6 +796,53 @@ defmodule Pleroma.Web.CommonAPITest do
|
|||
scheduled_at: expires_at
|
||||
)
|
||||
end
|
||||
|
||||
test "it allows quote posting" do
|
||||
user = insert(:user)
|
||||
|
||||
{:ok, quoted} = CommonAPI.post(user, %{status: "Hello world"})
|
||||
{:ok, quote_post} = CommonAPI.post(user, %{status: "nice post", quote_id: quoted.id})
|
||||
|
||||
quoted = Object.normalize(quoted)
|
||||
quote_post = Object.normalize(quote_post)
|
||||
|
||||
assert quote_post.data["quoteUrl"] == quoted.data["id"]
|
||||
|
||||
# The OP is not mentioned
|
||||
refute quoted.data["actor"] in quote_post.data["to"]
|
||||
end
|
||||
|
||||
test "quote posting with explicit addressing doesn't mention the OP" do
|
||||
user = insert(:user)
|
||||
|
||||
{:ok, quoted} = CommonAPI.post(user, %{status: "Hello world"})
|
||||
|
||||
{:ok, quote_post} =
|
||||
CommonAPI.post(user, %{status: "nice post", quote_id: quoted.id, to: []})
|
||||
|
||||
assert Object.normalize(quote_post).data["to"] == [Pleroma.Constants.as_public()]
|
||||
end
|
||||
|
||||
test "quote posting visibility" do
|
||||
user = insert(:user)
|
||||
another_user = insert(:user)
|
||||
|
||||
{:ok, direct} = CommonAPI.post(user, %{status: ".", visibility: "direct"})
|
||||
{:ok, private} = CommonAPI.post(user, %{status: ".", visibility: "private"})
|
||||
{:ok, unlisted} = CommonAPI.post(user, %{status: ".", visibility: "unlisted"})
|
||||
{:ok, local} = CommonAPI.post(user, %{status: ".", visibility: "local"})
|
||||
{:ok, public} = CommonAPI.post(user, %{status: ".", visibility: "public"})
|
||||
|
||||
{:error, _} = CommonAPI.post(user, %{status: "nice", quote_id: direct.id})
|
||||
{:ok, _} = CommonAPI.post(user, %{status: "nice", quote_id: private.id})
|
||||
{:error, _} = CommonAPI.post(another_user, %{status: "nice", quote_id: private.id})
|
||||
{:ok, _} = CommonAPI.post(user, %{status: "nice", quote_id: unlisted.id})
|
||||
{:ok, _} = CommonAPI.post(another_user, %{status: "nice", quote_id: unlisted.id})
|
||||
{:ok, _} = CommonAPI.post(user, %{status: "nice", quote_id: local.id})
|
||||
{:ok, _} = CommonAPI.post(another_user, %{status: "nice", quote_id: local.id})
|
||||
{:ok, _} = CommonAPI.post(user, %{status: "nice", quote_id: public.id})
|
||||
{:ok, _} = CommonAPI.post(another_user, %{status: "nice", quote_id: public.id})
|
||||
end
|
||||
end
|
||||
|
||||
describe "reactions" do
|
||||
|
|
|
@ -125,6 +125,28 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
|
|||
)
|
||||
end
|
||||
|
||||
test "posting a quote post", %{conn: conn} do
|
||||
user = insert(:user)
|
||||
|
||||
{:ok, %{id: activity_id} = activity} = CommonAPI.post(user, %{status: "yolo"})
|
||||
%{data: %{"id" => quote_url}} = Object.normalize(activity)
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> put_req_header("content-type", "application/json")
|
||||
|> post("/api/v1/statuses", %{
|
||||
"status" => "indeed",
|
||||
"quote_id" => activity_id
|
||||
})
|
||||
|
||||
assert %{
|
||||
"id" => id,
|
||||
"pleroma" => %{"quote" => %{"id" => ^activity_id}, "quote_url" => ^quote_url}
|
||||
} = json_response_and_validate_schema(conn, 200)
|
||||
|
||||
assert Activity.get_by_id(id)
|
||||
end
|
||||
|
||||
test "it fails to create a status if `expires_in` is less or equal than an hour", %{
|
||||
conn: conn
|
||||
} do
|
||||
|
|
|
@ -326,6 +326,10 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do
|
|||
conversation_id: convo_id,
|
||||
context: object_data["context"],
|
||||
in_reply_to_account_acct: nil,
|
||||
quote: nil,
|
||||
quote_id: nil,
|
||||
quote_url: nil,
|
||||
quote_visible: false,
|
||||
content: %{"text/plain" => HTML.strip_tags(object_data["content"])},
|
||||
spoiler_text: %{"text/plain" => HTML.strip_tags(object_data["summary"])},
|
||||
expires_at: nil,
|
||||
|
@ -422,6 +426,88 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do
|
|||
assert status.in_reply_to_id == to_string(note.id)
|
||||
end
|
||||
|
||||
test "a quote post" do
|
||||
post = insert(:note_activity)
|
||||
user = insert(:user)
|
||||
|
||||
{:ok, quote_post} = CommonAPI.post(user, %{status: "he", quote_id: post.id})
|
||||
{:ok, quoted_quote_post} = CommonAPI.post(user, %{status: "yo", quote_id: quote_post.id})
|
||||
|
||||
status = StatusView.render("show.json", %{activity: quoted_quote_post})
|
||||
|
||||
assert status.pleroma.quote.id == to_string(quote_post.id)
|
||||
assert status.pleroma.quote_id == to_string(quote_post.id)
|
||||
assert status.pleroma.quote_url == Object.normalize(quote_post).data["id"]
|
||||
assert status.pleroma.quote_visible
|
||||
|
||||
# Quotes don't go more than one level deep
|
||||
refute status.pleroma.quote.pleroma.quote
|
||||
assert status.pleroma.quote.pleroma.quote_id == to_string(post.id)
|
||||
assert status.pleroma.quote.pleroma.quote_url == Object.normalize(post).data["id"]
|
||||
assert status.pleroma.quote.pleroma.quote_visible
|
||||
|
||||
# In an index
|
||||
[status] = StatusView.render("index.json", %{activities: [quoted_quote_post], as: :activity})
|
||||
|
||||
assert status.pleroma.quote.id == to_string(quote_post.id)
|
||||
end
|
||||
|
||||
test "quoted private post" do
|
||||
user = insert(:user)
|
||||
|
||||
# Insert a private post
|
||||
private = insert(:followers_only_note_activity, user: user)
|
||||
private_object = Object.normalize(private)
|
||||
|
||||
# Create a public post quoting the private post
|
||||
quote_private =
|
||||
insert(:note_activity, note: insert(:note, data: %{"quoteUrl" => private_object.data["id"]}))
|
||||
|
||||
status = StatusView.render("show.json", %{activity: quote_private})
|
||||
|
||||
# The quote isn't rendered
|
||||
refute status.pleroma.quote
|
||||
assert status.pleroma.quote_url == private_object.data["id"]
|
||||
refute status.pleroma.quote_visible
|
||||
|
||||
# After following the user, the quote is rendered
|
||||
follower = insert(:user)
|
||||
CommonAPI.follow(follower, user)
|
||||
|
||||
status = StatusView.render("show.json", %{activity: quote_private, for: follower})
|
||||
assert status.pleroma.quote.id == to_string(private.id)
|
||||
assert status.pleroma.quote_visible
|
||||
end
|
||||
|
||||
test "quoted direct message" do
|
||||
# Insert a direct message
|
||||
direct = insert(:direct_note_activity)
|
||||
direct_object = Object.normalize(direct)
|
||||
|
||||
# Create a public post quoting the direct message
|
||||
quote_direct =
|
||||
insert(:note_activity, note: insert(:note, data: %{"quoteUrl" => direct_object.data["id"]}))
|
||||
|
||||
status = StatusView.render("show.json", %{activity: quote_direct})
|
||||
|
||||
# The quote isn't rendered
|
||||
refute status.pleroma.quote
|
||||
assert status.pleroma.quote_url == direct_object.data["id"]
|
||||
refute status.pleroma.quote_visible
|
||||
end
|
||||
|
||||
test "repost of quote post" do
|
||||
post = insert(:note_activity)
|
||||
user = insert(:user)
|
||||
|
||||
{:ok, quote_post} = CommonAPI.post(user, %{status: "he", quote_id: post.id})
|
||||
{:ok, repost} = CommonAPI.repeat(quote_post.id, user)
|
||||
|
||||
[status] = StatusView.render("index.json", %{activities: [repost], as: :activity})
|
||||
|
||||
assert status.reblog.pleroma.quote.id == to_string(post.id)
|
||||
end
|
||||
|
||||
test "contains mentions" do
|
||||
user = insert(:user)
|
||||
mentioned = insert(:user)
|
||||
|
|
|
@ -22,6 +22,10 @@ defmodule Pleroma.Web.StreamerTest do
|
|||
setup do: clear_config([:instance, :skip_thread_containment])
|
||||
|
||||
describe "get_topic/_ (unauthenticated)" do
|
||||
test "allows no stream" do
|
||||
assert {:ok, nil} = Streamer.get_topic(nil, nil, nil)
|
||||
end
|
||||
|
||||
test "allows public" do
|
||||
assert {:ok, "public"} = Streamer.get_topic("public", nil, nil)
|
||||
assert {:ok, "public:local"} = Streamer.get_topic("public:local", nil, nil)
|
||||
|
@ -242,7 +246,7 @@ defmodule Pleroma.Web.StreamerTest do
|
|||
Streamer.get_topic_and_add_socket("user", user, oauth_token)
|
||||
{:ok, activity} = CommonAPI.post(user, %{status: "hey"})
|
||||
|
||||
assert_receive {:render_with_user, _, _, ^activity}
|
||||
assert_receive {:render_with_user, _, _, ^activity, _}
|
||||
refute Streamer.filtered_by_user?(user, activity)
|
||||
end
|
||||
|
||||
|
@ -253,7 +257,7 @@ defmodule Pleroma.Web.StreamerTest do
|
|||
{:ok, activity} = CommonAPI.post(other_user, %{status: "hey"})
|
||||
{:ok, announce} = CommonAPI.repeat(activity.id, user)
|
||||
|
||||
assert_receive {:render_with_user, Pleroma.Web.StreamerView, "update.json", ^announce}
|
||||
assert_receive {:render_with_user, Pleroma.Web.StreamerView, "update.json", ^announce, _}
|
||||
refute Streamer.filtered_by_user?(user, announce)
|
||||
end
|
||||
|
||||
|
@ -306,7 +310,7 @@ defmodule Pleroma.Web.StreamerTest do
|
|||
{:ok, %Pleroma.Activity{data: _data, local: false} = announce} =
|
||||
Pleroma.Web.ActivityPub.Transmogrifier.handle_incoming(data)
|
||||
|
||||
assert_receive {:render_with_user, Pleroma.Web.StreamerView, "update.json", ^announce}
|
||||
assert_receive {:render_with_user, Pleroma.Web.StreamerView, "update.json", ^announce, _}
|
||||
refute Streamer.filtered_by_user?(user, announce)
|
||||
end
|
||||
|
||||
|
@ -318,7 +322,7 @@ defmodule Pleroma.Web.StreamerTest do
|
|||
Streamer.get_topic_and_add_socket("user", user, oauth_token)
|
||||
Streamer.stream("user", notify)
|
||||
|
||||
assert_receive {:render_with_user, _, _, ^notify}
|
||||
assert_receive {:render_with_user, _, _, ^notify, _}
|
||||
refute Streamer.filtered_by_user?(user, notify)
|
||||
end
|
||||
|
||||
|
@ -330,7 +334,7 @@ defmodule Pleroma.Web.StreamerTest do
|
|||
Streamer.get_topic_and_add_socket("user:notification", user, oauth_token)
|
||||
Streamer.stream("user:notification", notify)
|
||||
|
||||
assert_receive {:render_with_user, _, _, ^notify}
|
||||
assert_receive {:render_with_user, _, _, ^notify, _}
|
||||
refute Streamer.filtered_by_user?(user, notify)
|
||||
end
|
||||
|
||||
|
@ -351,7 +355,12 @@ defmodule Pleroma.Web.StreamerTest do
|
|||
Streamer.get_topic_and_add_socket("user:pleroma_chat", user, oauth_token)
|
||||
Streamer.stream("user:pleroma_chat", {user, cm_ref})
|
||||
|
||||
text = StreamerView.render("chat_update.json", %{chat_message_reference: cm_ref})
|
||||
text =
|
||||
StreamerView.render(
|
||||
"chat_update.json",
|
||||
%{chat_message_reference: cm_ref},
|
||||
"user:pleroma_chat:#{user.id}"
|
||||
)
|
||||
|
||||
assert text =~ "hey cirno"
|
||||
assert_receive {:text, ^text}
|
||||
|
@ -369,7 +378,12 @@ defmodule Pleroma.Web.StreamerTest do
|
|||
Streamer.get_topic_and_add_socket("user", user, oauth_token)
|
||||
Streamer.stream("user", {user, cm_ref})
|
||||
|
||||
text = StreamerView.render("chat_update.json", %{chat_message_reference: cm_ref})
|
||||
text =
|
||||
StreamerView.render(
|
||||
"chat_update.json",
|
||||
%{chat_message_reference: cm_ref},
|
||||
"user:#{user.id}"
|
||||
)
|
||||
|
||||
assert text =~ "hey cirno"
|
||||
assert_receive {:text, ^text}
|
||||
|
@ -390,7 +404,7 @@ defmodule Pleroma.Web.StreamerTest do
|
|||
Streamer.get_topic_and_add_socket("user:notification", user, oauth_token)
|
||||
Streamer.stream("user:notification", notify)
|
||||
|
||||
assert_receive {:render_with_user, _, _, ^notify}
|
||||
assert_receive {:render_with_user, _, _, ^notify, _}
|
||||
refute Streamer.filtered_by_user?(user, notify)
|
||||
end
|
||||
|
||||
|
@ -436,7 +450,7 @@ defmodule Pleroma.Web.StreamerTest do
|
|||
Streamer.get_topic_and_add_socket("user:notification", user, oauth_token)
|
||||
{:ok, favorite_activity} = CommonAPI.favorite(user2, activity.id)
|
||||
|
||||
assert_receive {:render_with_user, _, "notification.json", notif}
|
||||
assert_receive {:render_with_user, _, "notification.json", notif, _}
|
||||
assert notif.activity.id == favorite_activity.id
|
||||
refute Streamer.filtered_by_user?(user, notif)
|
||||
end
|
||||
|
@ -465,7 +479,7 @@ defmodule Pleroma.Web.StreamerTest do
|
|||
Streamer.get_topic_and_add_socket("user:notification", user, oauth_token)
|
||||
{:ok, _follower, _followed, follow_activity} = CommonAPI.follow(user2, user)
|
||||
|
||||
assert_receive {:render_with_user, _, "notification.json", notif}
|
||||
assert_receive {:render_with_user, _, "notification.json", notif, _}
|
||||
assert notif.activity.id == follow_activity.id
|
||||
refute Streamer.filtered_by_user?(user, notif)
|
||||
end
|
||||
|
@ -530,7 +544,7 @@ defmodule Pleroma.Web.StreamerTest do
|
|||
{:ok, edited} = CommonAPI.update(sender, activity, %{status: "mew mew"})
|
||||
create = Pleroma.Activity.get_create_by_object_ap_id_with_object(activity.object.data["id"])
|
||||
|
||||
assert_receive {:render_with_user, _, "status_update.json", ^create}
|
||||
assert_receive {:render_with_user, _, "status_update.json", ^create, _}
|
||||
refute Streamer.filtered_by_user?(user, edited)
|
||||
end
|
||||
|
||||
|
@ -541,7 +555,7 @@ defmodule Pleroma.Web.StreamerTest do
|
|||
{:ok, edited} = CommonAPI.update(user, activity, %{status: "mew mew"})
|
||||
create = Pleroma.Activity.get_create_by_object_ap_id_with_object(activity.object.data["id"])
|
||||
|
||||
assert_receive {:render_with_user, _, "status_update.json", ^create}
|
||||
assert_receive {:render_with_user, _, "status_update.json", ^create, _}
|
||||
refute Streamer.filtered_by_user?(user, edited)
|
||||
end
|
||||
end
|
||||
|
@ -554,7 +568,7 @@ defmodule Pleroma.Web.StreamerTest do
|
|||
Streamer.get_topic_and_add_socket("public", user, oauth_token)
|
||||
|
||||
{:ok, activity} = CommonAPI.post(other_user, %{status: "Test"})
|
||||
assert_receive {:render_with_user, _, _, ^activity}
|
||||
assert_receive {:render_with_user, _, _, ^activity, _}
|
||||
refute Streamer.filtered_by_user?(other_user, activity)
|
||||
end
|
||||
|
||||
|
@ -654,7 +668,7 @@ defmodule Pleroma.Web.StreamerTest do
|
|||
|
||||
Streamer.get_topic_and_add_socket("public", user, oauth_token)
|
||||
Streamer.stream("public", activity)
|
||||
assert_receive {:render_with_user, _, _, ^activity}
|
||||
assert_receive {:render_with_user, _, _, ^activity, _}
|
||||
assert Streamer.filtered_by_user?(user, activity)
|
||||
end
|
||||
|
||||
|
@ -676,7 +690,7 @@ defmodule Pleroma.Web.StreamerTest do
|
|||
Streamer.get_topic_and_add_socket("public", user, oauth_token)
|
||||
Streamer.stream("public", activity)
|
||||
|
||||
assert_receive {:render_with_user, _, _, ^activity}
|
||||
assert_receive {:render_with_user, _, _, ^activity, _}
|
||||
refute Streamer.filtered_by_user?(user, activity)
|
||||
end
|
||||
|
||||
|
@ -699,7 +713,7 @@ defmodule Pleroma.Web.StreamerTest do
|
|||
Streamer.get_topic_and_add_socket("public", user, oauth_token)
|
||||
Streamer.stream("public", activity)
|
||||
|
||||
assert_receive {:render_with_user, _, _, ^activity}
|
||||
assert_receive {:render_with_user, _, _, ^activity, _}
|
||||
refute Streamer.filtered_by_user?(user, activity)
|
||||
end
|
||||
end
|
||||
|
@ -713,7 +727,7 @@ defmodule Pleroma.Web.StreamerTest do
|
|||
|
||||
Streamer.get_topic_and_add_socket("public", user, oauth_token)
|
||||
{:ok, activity} = CommonAPI.post(blocked_user, %{status: "Test"})
|
||||
assert_receive {:render_with_user, _, _, ^activity}
|
||||
assert_receive {:render_with_user, _, _, ^activity, _}
|
||||
assert Streamer.filtered_by_user?(user, activity)
|
||||
end
|
||||
|
||||
|
@ -730,17 +744,17 @@ defmodule Pleroma.Web.StreamerTest do
|
|||
|
||||
{:ok, activity_one} = CommonAPI.post(friend, %{status: "hey! @#{blockee.nickname}"})
|
||||
|
||||
assert_receive {:render_with_user, _, _, ^activity_one}
|
||||
assert_receive {:render_with_user, _, _, ^activity_one, _}
|
||||
assert Streamer.filtered_by_user?(blocker, activity_one)
|
||||
|
||||
{:ok, activity_two} = CommonAPI.post(blockee, %{status: "hey! @#{friend.nickname}"})
|
||||
|
||||
assert_receive {:render_with_user, _, _, ^activity_two}
|
||||
assert_receive {:render_with_user, _, _, ^activity_two, _}
|
||||
assert Streamer.filtered_by_user?(blocker, activity_two)
|
||||
|
||||
{:ok, activity_three} = CommonAPI.post(blockee, %{status: "hey! @#{blocker.nickname}"})
|
||||
|
||||
assert_receive {:render_with_user, _, _, ^activity_three}
|
||||
assert_receive {:render_with_user, _, _, ^activity_three, _}
|
||||
assert Streamer.filtered_by_user?(blocker, activity_three)
|
||||
end
|
||||
end
|
||||
|
@ -801,7 +815,7 @@ defmodule Pleroma.Web.StreamerTest do
|
|||
visibility: "private"
|
||||
})
|
||||
|
||||
assert_receive {:render_with_user, _, _, ^activity}
|
||||
assert_receive {:render_with_user, _, _, ^activity, _}
|
||||
refute Streamer.filtered_by_user?(user_a, activity)
|
||||
end
|
||||
end
|
||||
|
@ -819,7 +833,7 @@ defmodule Pleroma.Web.StreamerTest do
|
|||
|
||||
Streamer.get_topic_and_add_socket("user", user1, user1_token)
|
||||
{:ok, announce_activity} = CommonAPI.repeat(create_activity.id, user2)
|
||||
assert_receive {:render_with_user, _, _, ^announce_activity}
|
||||
assert_receive {:render_with_user, _, _, ^announce_activity, _}
|
||||
assert Streamer.filtered_by_user?(user1, announce_activity)
|
||||
end
|
||||
|
||||
|
@ -835,7 +849,7 @@ defmodule Pleroma.Web.StreamerTest do
|
|||
Streamer.get_topic_and_add_socket("user", user1, user1_token)
|
||||
{:ok, _announce_activity} = CommonAPI.repeat(create_activity.id, user2)
|
||||
|
||||
assert_receive {:render_with_user, _, "notification.json", notif}
|
||||
assert_receive {:render_with_user, _, "notification.json", notif, _}
|
||||
assert Streamer.filtered_by_user?(user1, notif)
|
||||
end
|
||||
|
||||
|
@ -851,7 +865,7 @@ defmodule Pleroma.Web.StreamerTest do
|
|||
Streamer.get_topic_and_add_socket("user", user1, user1_token)
|
||||
{:ok, _favorite_activity} = CommonAPI.favorite(user2, create_activity.id)
|
||||
|
||||
assert_receive {:render_with_user, _, "notification.json", notif}
|
||||
assert_receive {:render_with_user, _, "notification.json", notif, _}
|
||||
refute Streamer.filtered_by_user?(user1, notif)
|
||||
end
|
||||
end
|
||||
|
@ -866,7 +880,7 @@ defmodule Pleroma.Web.StreamerTest do
|
|||
{:ok, activity} = CommonAPI.post(user, %{status: "super hot take"})
|
||||
{:ok, _} = CommonAPI.add_mute(user2, activity)
|
||||
|
||||
assert_receive {:render_with_user, _, _, ^activity}
|
||||
assert_receive {:render_with_user, _, _, ^activity, _}
|
||||
assert Streamer.filtered_by_user?(user2, activity)
|
||||
end
|
||||
end
|
||||
|
@ -908,7 +922,7 @@ defmodule Pleroma.Web.StreamerTest do
|
|||
})
|
||||
|
||||
create_activity_id = create_activity.id
|
||||
assert_receive {:render_with_user, _, _, ^create_activity}
|
||||
assert_receive {:render_with_user, _, _, ^create_activity, _}
|
||||
assert_receive {:text, received_conversation1}
|
||||
assert %{"event" => "conversation", "payload" => _} = Jason.decode!(received_conversation1)
|
||||
|
||||
|
@ -943,8 +957,8 @@ defmodule Pleroma.Web.StreamerTest do
|
|||
visibility: "direct"
|
||||
})
|
||||
|
||||
assert_receive {:render_with_user, _, _, ^create_activity}
|
||||
assert_receive {:render_with_user, _, _, ^create_activity2}
|
||||
assert_receive {:render_with_user, _, _, ^create_activity, _}
|
||||
assert_receive {:render_with_user, _, _, ^create_activity2, _}
|
||||
assert_receive {:text, received_conversation1}
|
||||
assert %{"event" => "conversation", "payload" => _} = Jason.decode!(received_conversation1)
|
||||
assert_receive {:text, received_conversation1}
|
||||
|
@ -973,7 +987,7 @@ defmodule Pleroma.Web.StreamerTest do
|
|||
|
||||
receive do
|
||||
{StreamerTest, :ready} ->
|
||||
assert_receive {:render_with_user, _, "update.json", _}
|
||||
assert_receive {:render_with_user, _, "update.json", _, _}
|
||||
|
||||
receive do
|
||||
{StreamerTest, :revoked} -> finalize.()
|
||||
|
|
|
@ -1380,6 +1380,15 @@ defmodule HttpRequestMock do
|
|||
}}
|
||||
end
|
||||
|
||||
def get("https://misskey.io/users/83ssedkv53", _, _, _) do
|
||||
{:ok,
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body: File.read!("test/fixtures/tesla_mock/aimu@misskey.io.json"),
|
||||
headers: activitypub_object_headers()
|
||||
}}
|
||||
end
|
||||
|
||||
def get("https://gleasonator.com/users/macgirvin", _, _, _) do
|
||||
{:ok,
|
||||
%Tesla.Env{
|
||||
|
@ -1446,6 +1455,15 @@ defmodule HttpRequestMock do
|
|||
}}
|
||||
end
|
||||
|
||||
def get("https://misskey.io/notes/8vs6wxufd0", _, _, _) do
|
||||
{:ok,
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body: File.read!("test/fixtures/tesla_mock/misskey.io_8vs6wxufd0.json"),
|
||||
headers: activitypub_object_headers()
|
||||
}}
|
||||
end
|
||||
|
||||
def get(url, query, body, headers) do
|
||||
{:error,
|
||||
"Mock response not implemented for GET #{inspect(url)}, #{query}, #{inspect(body)}, #{inspect(headers)}"}
|
||||
|
|
Loading…
Reference in New Issue