Compare commits

..

No commits in common. "c18c4cd1d1ad9ba49f557834cc3eccbf3c2f2c2c" and "4c479926869f6c020c4ef1e22a90d3c17d90bd69" have entirely different histories.

36 changed files with 16 additions and 871 deletions

View file

@ -6,10 +6,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [Unreleased] ## [Unreleased]
### Added
- extended runtime module support, see config cheatsheet
- quote posting; quotes are limited to public posts
### Fixed ### Fixed
- Updated mastoFE path, for the newer version - Updated mastoFE path, for the newer version
@ -22,7 +18,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- `/api/v1/notifications/dismiss` - `/api/v1/notifications/dismiss`
- `/api/v1/search` - `/api/v1/search`
- `/api/v1/statuses/{id}/card` - `/api/v1/statuses/{id}/card`
- LDAP authenticator (use the akkoma-contrib-authenticator-ldap runtime module) - LDAP authenticator
- Chats, they were half-baked. Just use PMs. - Chats, they were half-baked. Just use PMs.
- Prometheus, it causes massive slowdown - Prometheus, it causes massive slowdown

View file

@ -407,8 +407,6 @@
accept: [], accept: [],
reject: [] reject: []
config :pleroma, :mrf_inline_quote, prefix: "RE"
# threshold of 7 days # threshold of 7 days
config :pleroma, :mrf_object_age, config :pleroma, :mrf_object_age,
threshold: 604_800, threshold: 604_800,

View file

@ -300,28 +300,3 @@
```sh ```sh
mix pleroma.user unconfirm_all mix pleroma.user unconfirm_all
``` ```
## Fix following state
Sometimes the system can get into a situation where
it think you're already following someone and won't send a request
to the remote instance, or won't let you unfollow someone. This
bug was fixed, but in case you encounter these weird states:
=== "OTP"
```sh
./bin/pleroma_ctl user fix_follow_state localuser remoteuser@example.com
```
=== "From Source"
```sh
mix pleroma.user fix_follow_state localuser remoteuser@example.com
```
The first argument is the local user's nickname - if you are `myuser@myinstance`, this should be `myuser`.
The second is the remote user, consisting of both nickname AND domain.
If you are a weird follow state situation and cannot resolve it with the above, you may need to co-operate with the remote admin to clear the state their side too - they should provide the arguments *backwards*, i.e `fix_follow_state remote local`.

View file

@ -1012,22 +1012,7 @@ config :pleroma, Pleroma.Formatter,
## Custom Runtime Modules (`:modules`) ## Custom Runtime Modules (`:modules`)
* `runtime_dir`: A path to custom Elixir modules, such as MRF policies or * `runtime_dir`: A path to custom Elixir modules (such as MRF policies).
custom authenticators. These modules will be loaded on boot, and can be
contained in subdirectories. It is advised to use version-controlled
subdirectories to make management of them a bit easier. Note that only
files with the extension `.ex` will be loaded.
```elixir
config :pleroma, :modules, runtime_dir: "instance/modules"
```
### Adding a module
```bash
cd instance/modules/
git clone <MY MODULE>
```
## :configurable_from_database ## :configurable_from_database

View file

@ -292,12 +292,6 @@ def get_in_reply_to_activity(%Activity{} = activity) do
get_in_reply_to_activity_from_object(Object.normalize(activity, fetch: false)) get_in_reply_to_activity_from_object(Object.normalize(activity, fetch: false))
end end
def get_quoted_activity_from_object(%Object{data: %{"quoteUri" => ap_id}}) do
get_create_by_object_ap_id_with_object(ap_id)
end
def get_quoted_activity_from_object(_), do: nil
def normalize(%Activity{data: %{"id" => ap_id}}), do: get_by_ap_id_with_object(ap_id) def normalize(%Activity{data: %{"id" => ap_id}}), do: get_by_ap_id_with_object(ap_id)
def normalize(%{"id" => ap_id}), do: get_by_ap_id_with_object(ap_id) def normalize(%{"id" => ap_id}), do: get_by_ap_id_with_object(ap_id)
def normalize(ap_id) when is_binary(ap_id), do: get_by_ap_id_with_object(ap_id) def normalize(ap_id) when is_binary(ap_id), do: get_by_ap_id_with_object(ap_id)

View file

@ -730,12 +730,12 @@ def maybe_validate_required_email(changeset, _) do
end end
end end
def put_ap_id(changeset) do defp put_ap_id(changeset) do
ap_id = ap_id(%User{nickname: get_field(changeset, :nickname)}) ap_id = ap_id(%User{nickname: get_field(changeset, :nickname)})
put_change(changeset, :ap_id, ap_id) put_change(changeset, :ap_id, ap_id)
end end
def put_following_and_follower_and_featured_address(changeset) do defp put_following_and_follower_and_featured_address(changeset) do
user = %User{nickname: get_field(changeset, :nickname)} user = %User{nickname: get_field(changeset, :nickname)}
followers = ap_followers(user) followers = ap_followers(user)
following = ap_following(user) following = ap_following(user)
@ -2041,7 +2041,7 @@ defp normalize_tags(tags) do
|> Enum.map(&String.downcase/1) |> Enum.map(&String.downcase/1)
end end
def local_nickname_regex do defp local_nickname_regex do
if Config.get([:instance, :extended_nickname_format]) do if Config.get([:instance, :extended_nickname_format]) do
@extended_local_nickname_regex @extended_local_nickname_regex
else else

View file

@ -14,23 +14,10 @@ defmodule Pleroma.Utils do
@repo_timeout Pleroma.Config.get([Pleroma.Repo, :timeout], 15_000) @repo_timeout Pleroma.Config.get([Pleroma.Repo, :timeout], 15_000)
def compile_dir(dir) when is_binary(dir) do def compile_dir(dir) when is_binary(dir) do
dir
|> elixir_files()
|> Kernel.ParallelCompiler.compile()
end
defp elixir_files(dir) when is_binary(dir) do
dir dir
|> File.ls!() |> File.ls!()
|> Enum.map(&Path.join(dir, &1)) |> Enum.map(&Path.join(dir, &1))
|> Enum.flat_map(fn path -> |> Kernel.ParallelCompiler.compile()
if File.dir?(path) do
elixir_files(path)
else
[path]
end
end)
|> Enum.filter(fn path -> String.ends_with?(path, ".ex") end)
end end
@doc """ @doc """

View file

@ -168,7 +168,6 @@ def note(%ActivityDraft{} = draft) do
"tag" => Keyword.values(draft.tags) |> Enum.uniq() "tag" => Keyword.values(draft.tags) |> Enum.uniq()
} }
|> add_in_reply_to(draft.in_reply_to) |> add_in_reply_to(draft.in_reply_to)
|> add_quote(draft.quote)
|> Map.merge(draft.extra) |> Map.merge(draft.extra)
{:ok, data, []} {:ok, data, []}
@ -184,16 +183,6 @@ defp add_in_reply_to(object, in_reply_to) do
end end
end end
defp add_quote(object, nil), do: object
defp add_quote(object, quote) do
with %Object{} = quote_object <- Object.normalize(quote, fetch: false) do
Map.put(object, "quoteUri", quote_object.data["id"])
else
_ -> object
end
end
def answer(user, object, name) do def answer(user, object, name) do
{:ok, {:ok,
%{ %{

View file

@ -1,71 +0,0 @@
# 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(prefix, url) do
"<span class=\"quote-inline\"><br/><br/>#{prefix}: <a href=\"#{url}\">#{url}</a></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(%{"quoteUri" => quote_url} = object) do
content = object["content"] || ""
if has_inline_quote?(content, quote_url) do
object
else
prefix = Pleroma.Config.get([:mrf_inline_quote, :prefix])
content =
if String.ends_with?(content, "</p>") do
String.trim_trailing(content, "</p>") <> build_inline_quote(prefix, quote_url) <> "</p>"
else
content <> build_inline_quote(prefix, quote_url)
end
Map.put(object, "content", content)
end
end
@impl true
def filter(%{"object" => %{"quoteUri" => _} = 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 true
def config_description do
%{
key: :mrf_inline_quote,
related_policy: "Pleroma.Web.ActivityPub.MRF.InlineQuotePolicy",
label: "MRF Inline Quote",
description: "Force quote post URLs inline",
children: [
%{
key: :prefix,
type: :string,
description: "Prefix before the link",
suggestions: ["RE", "QT", "RT", "RN"]
}
]
}
end
end

View file

@ -156,7 +156,6 @@ defp fix(data) do
|> fix_replies() |> fix_replies()
|> fix_source() |> fix_source()
|> fix_misskey_content() |> fix_misskey_content()
|> Transmogrifier.fix_quote_url()
|> Transmogrifier.fix_attachments() |> Transmogrifier.fix_attachments()
|> Transmogrifier.fix_emoji() |> Transmogrifier.fix_emoji()
|> Transmogrifier.fix_content_map() |> Transmogrifier.fix_content_map()

View file

@ -59,7 +59,6 @@ defmacro status_object_fields do
field(:like_count, :integer, default: 0) field(:like_count, :integer, default: 0)
field(:announcement_count, :integer, default: 0) field(:announcement_count, :integer, default: 0)
field(:inReplyTo, ObjectValidators.ObjectID) field(:inReplyTo, ObjectValidators.ObjectID)
field(:quoteUri, ObjectValidators.ObjectID)
field(:url, ObjectValidators.Uri) field(:url, ObjectValidators.Uri)
field(:likes, {:array, ObjectValidators.ObjectID}, default: []) field(:likes, {:array, ObjectValidators.ObjectID}, default: [])

View file

@ -598,12 +598,6 @@ def set_reply_to_uri(%{"inReplyTo" => in_reply_to} = object) when is_binary(in_r
def set_reply_to_uri(obj), do: obj def set_reply_to_uri(obj), do: obj
def set_quote_url(%{"quoteUri" => quote} = object) when is_binary(quote) do
Map.put(object, "quoteUrl", quote)
end
def set_quote_url(obj), do: obj
@doc """ @doc """
Serialized Mastodon-compatible `replies` collection containing _self-replies_. Serialized Mastodon-compatible `replies` collection containing _self-replies_.
Based on Mastodon's ActivityPub::NoteSerializer#replies. Based on Mastodon's ActivityPub::NoteSerializer#replies.
@ -658,7 +652,6 @@ def prepare_object(object) do
|> prepare_attachments |> prepare_attachments
|> set_conversation |> set_conversation
|> set_reply_to_uri |> set_reply_to_uri
|> set_quote_url()
|> set_replies |> set_replies
|> strip_internal_fields |> strip_internal_fields
|> strip_internal_tags |> strip_internal_tags
@ -886,43 +879,6 @@ defp strip_internal_tags(%{"tag" => tags} = object) do
defp strip_internal_tags(object), do: object defp strip_internal_tags(object), do: object
def fix_quote_url(object, options \\ [])
def fix_quote_url(%{"quoteUri" => quote_url} = object, options)
when not is_nil(quote_url) do
with {: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, "quoteUri", quoted_object.data["id"])
else
e ->
Logger.warn("Couldn't fetch #{inspect(quote_url)}, error: #{inspect(e)}")
object
end
end
# Soapbox
def fix_quote_url(%{"quoteUrl" => quote_url} = object, options) do
object
|> Map.put("quoteUri", quote_url)
|> fix_quote_url(options)
end
# Old Fedibird (bug)
# https://github.com/fedibird/mastodon/issues/9
def fix_quote_url(%{"quoteURL" => quote_url} = object, options) do
object
|> Map.put("quoteUri", quote_url)
|> fix_quote_url(options)
end
def fix_quote_url(%{"_misskey_quote" => quote_url} = object, options) do
object
|> Map.put("quoteUri", quote_url)
|> fix_quote_url(options)
end
def fix_quote_url(object, _), do: object
def perform(:user_upgrade, user) do def perform(:user_upgrade, user) do
# we pass a fake user so that the followers collection is stripped away # we pass a fake user so that the followers collection is stripped away
old_follower_address = User.ap_followers(%User{nickname: user.nickname}) old_follower_address = User.ap_followers(%User{nickname: user.nickname})

View file

@ -496,11 +496,6 @@ defp create_request do
type: :string, type: :string,
description: 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`." "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,
type: :string,
description: "Will quote a given status."
} }
}, },
example: %{ example: %{

View file

@ -133,16 +133,6 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do
type: :boolean, type: :boolean,
description: "Have you pinned this status? Only appears if the status is pinnable." description: "Have you pinned this status? Only appears if the status is pinnable."
}, },
quote_id: %Schema{
type: :string,
description: "ID of the status being quoted",
nullable: true
},
quote: %Schema{
allOf: [%OpenApiSpex.Reference{"$ref": "#/components/schemas/Status"}],
nullable: true,
description: "Quoted status (if any)"
},
pleroma: %Schema{ pleroma: %Schema{
type: :object, type: :object,
properties: %{ properties: %{
@ -214,33 +204,6 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do
} }
} }
}, },
akkoma: %Schema{
type: :object,
properties: %{
source: %Schema{
nullable: true,
oneOf: [
%Schema{type: :string, example: 'plaintext content'},
%Schema{
type: :object,
properties: %{
content: %Schema{
type: :string,
description: "The source content of the status",
nullable: true
},
mediaType: %Schema{
type: :string,
description: "The source MIME type of the status",
example: "text/plain",
nullable: true
}
}
}
]
}
}
},
poll: %Schema{allOf: [Poll], nullable: true, description: "The poll attached to the status"}, poll: %Schema{allOf: [Poll], nullable: true, description: "The poll attached to the status"},
reblog: %Schema{ reblog: %Schema{
allOf: [%OpenApiSpex.Reference{"$ref": "#/components/schemas/Status"}], allOf: [%OpenApiSpex.Reference{"$ref": "#/components/schemas/Status"}],

View file

@ -319,10 +319,6 @@ def get_replied_to_visibility(activity) do
end end
end end
def get_quoted_visibility(nil), do: nil
def get_quoted_visibility(activity), do: get_replied_to_visibility(activity)
def check_expiry_date({:ok, nil} = res), do: res def check_expiry_date({:ok, nil} = res), do: res
def check_expiry_date({:ok, in_seconds}) do def check_expiry_date({:ok, in_seconds}) do

View file

@ -22,8 +22,6 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do
attachments: [], attachments: [],
in_reply_to: nil, in_reply_to: nil,
in_reply_to_conversation: nil, in_reply_to_conversation: nil,
quote_id: nil,
quote: nil,
visibility: nil, visibility: nil,
expires_at: nil, expires_at: nil,
extra: nil, extra: nil,
@ -56,7 +54,6 @@ def create(user, params) do
|> with_valid(&in_reply_to/1) |> with_valid(&in_reply_to/1)
|> with_valid(&in_reply_to_conversation/1) |> with_valid(&in_reply_to_conversation/1)
|> with_valid(&visibility/1) |> with_valid(&visibility/1)
|> with_valid(&quote_id/1)
|> content() |> content()
|> with_valid(&to_and_cc/1) |> with_valid(&to_and_cc/1)
|> with_valid(&context/1) |> with_valid(&context/1)
@ -111,28 +108,6 @@ defp in_reply_to_conversation(draft) do
%__MODULE__{draft | in_reply_to_conversation: in_reply_to_conversation} %__MODULE__{draft | in_reply_to_conversation: in_reply_to_conversation}
end end
defp quote_id(%{params: %{quote_id: ""}} = draft), do: draft
defp quote_id(%{params: %{quote_id: id}} = draft) when is_binary(id) do
with {:activity, %Activity{} = quote} <- {:activity, Activity.get_by_id(id)},
visibility <- CommonAPI.get_quoted_visibility(quote),
{:visibility, true} <- {:visibility, visibility in ["public", "unlisted"]} do
%__MODULE__{draft | quote: Activity.get_by_id(id)}
else
{:activity, _} ->
add_error(draft, dgettext("errors", "You can't quote a status that doesn't exist"))
{:visibility, false} ->
add_error(draft, dgettext("errors", "You can only quote public or unlisted statuses"))
end
end
defp quote_id(%{params: %{quote_id: %Activity{} = quote}} = draft) do
%__MODULE__{draft | quote: quote}
end
defp quote_id(draft), do: draft
defp visibility(%{params: params} = draft) do defp visibility(%{params: params} = draft) do
case CommonAPI.get_visibility(params, draft.in_reply_to, draft.in_reply_to_conversation) do case CommonAPI.get_visibility(params, draft.in_reply_to, draft.in_reply_to_conversation) do
{visibility, "direct"} when visibility != "direct" -> {visibility, "direct"} when visibility != "direct" ->

View file

@ -329,8 +329,6 @@ def render("show.json", %{activity: %{data: %{"object" => _object}} = activity}
{pinned?, pinned_at} = pin_data(object, user) {pinned?, pinned_at} = pin_data(object, user)
quote = Activity.get_quoted_activity_from_object(object)
%{ %{
id: to_string(activity.id), id: to_string(activity.id),
uri: object.data["id"], uri: object.data["id"],
@ -365,8 +363,6 @@ def render("show.json", %{activity: %{data: %{"object" => _object}} = activity}
application: build_application(object.data["generator"]), application: build_application(object.data["generator"]),
language: nil, language: nil,
emojis: build_emojis(object.data["emoji"]), emojis: build_emojis(object.data["emoji"]),
quote_id: if(quote, do: quote.id, else: nil),
quote: maybe_render_quote(quote, opts),
pleroma: %{ pleroma: %{
local: activity.local, local: activity.local,
conversation_id: get_context_id(activity), conversation_id: get_context_id(activity),
@ -608,19 +604,4 @@ defp build_image_url(%URI{} = image_url_data, %URI{} = page_url_data) do
end end
defp build_image_url(_, _), do: nil defp build_image_url(_, _), do: nil
defp maybe_render_quote(nil, _), do: nil
defp maybe_render_quote(quote, opts) do
if opts[:do_not_recurse] || !visible_for_user?(quote, opts[:for]) do
nil
else
opts =
opts
|> Map.put(:activity, quote)
|> Map.put(:do_not_recurse, true)
render("show.json", opts)
end
end
end end

View file

@ -117,7 +117,7 @@ defp csp_string do
if Config.get(:env) == :dev do if Config.get(:env) == :dev do
"script-src 'self' 'unsafe-eval'" "script-src 'self' 'unsafe-eval'"
else else
"script-src 'self' 'unsafe-eval'" "script-src 'self'"
end end
report = if report_uri, do: ["report-uri ", report_uri, ";report-to csp-endpoint"] report = if report_uri, do: ["report-uri ", report_uri, ";report-to csp-endpoint"]

View file

@ -4,7 +4,7 @@ defmodule Pleroma.Mixfile do
def project do def project do
[ [
app: :pleroma, app: :pleroma,
version: version("3.0.1"), version: version("3.0.0"),
elixir: "~> 1.9", elixir: "~> 1.9",
elixirc_paths: elixirc_paths(Mix.env()), elixirc_paths: elixirc_paths(Mix.env()),
compilers: [:phoenix, :gettext] ++ Mix.compilers(), compilers: [:phoenix, :gettext] ++ Mix.compilers(),
@ -34,7 +34,7 @@ def project do
releases: [ releases: [
pleroma: [ pleroma: [
include_executables_for: [:unix], include_executables_for: [:unix],
applications: [ex_syslogger: :load, syslog: :load, eldap: :transient], applications: [ex_syslogger: :load, syslog: :load],
steps: [:assemble, &put_otp_version/1, &copy_files/1, &copy_nginx_config/1], steps: [:assemble, &put_otp_version/1, &copy_files/1, &copy_nginx_config/1],
config_providers: [{Pleroma.Config.ReleaseRuntimeProvider, []}] config_providers: [{Pleroma.Config.ReleaseRuntimeProvider, []}]
] ]

View file

@ -17,8 +17,6 @@
"ostatus": "http://ostatus.org#", "ostatus": "http://ostatus.org#",
"schema": "http://schema.org#", "schema": "http://schema.org#",
"toot": "http://joinmastodon.org/ns#", "toot": "http://joinmastodon.org/ns#",
"misskey": "https://misskey-hub.net/ns#",
"fedibird": "http://fedibird.com/ns#",
"value": "schema:value", "value": "schema:value",
"sensitive": "as:sensitive", "sensitive": "as:sensitive",
"litepub": "http://litepub.social/ns#", "litepub": "http://litepub.social/ns#",
@ -28,8 +26,6 @@
"@id": "litepub:listMessage", "@id": "litepub:listMessage",
"@type": "@id" "@type": "@id"
}, },
"quoteUrl": "as:quoteUrl",
"quoteUri": "fedibird:quoteUri",
"oauthRegistrationEndpoint": { "oauthRegistrationEndpoint": {
"@id": "litepub:oauthRegistrationEndpoint", "@id": "litepub:oauthRegistrationEndpoint",
"@type": "@id" "@type": "@id"

View file

@ -1,73 +0,0 @@
{
"@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",
"references": {
"@id": "fedibird:references",
"@type": "@id"
},
"emojiReactions": {
"@id": "fedibird:emojiReactions",
"@type": "@id"
}
}
],
"id": "https://fedibird.com/users/akkoma_ap_integration_tester/statuses/108707679228362674",
"type": "Note",
"summary": null,
"inReplyTo": null,
"published": "2022-07-25T11:12:26Z",
"url": "https://fedibird.com/@akkoma_ap_integration_tester/108707679228362674",
"attributedTo": "https://fedibird.com/users/akkoma_ap_integration_tester",
"to": [
"https://fedibird.com/users/akkoma_ap_integration_tester/followers"
],
"cc": [
"https://www.w3.org/ns/activitystreams#Public"
],
"sensitive": false,
"atomUri": "https://fedibird.com/users/akkoma_ap_integration_tester/statuses/108707679228362674",
"inReplyToAtomUri": null,
"conversation": "tag:fedibird.com,2022-07-25:objectId=108707679228389900:objectType=Conversation",
"context": "https://fedibird.com/contexts/108707679228389900",
"quoteUri": "https://example.com/objects/43479e20-c0f8-4f49-bf7f-13fab8234924",
"_misskey_quote": "https://example.com/objects/43479e20-c0f8-4f49-bf7f-13fab8234924",
"_misskey_content": "public quote",
"content": "<p>public quote<span class=\"quote-inline\"><br/>QT: <a class=\"status-url-link\" data-status-account-acct=\"ArtMirror@example.com\" data-status-id=\"108703793483919195\" href=\"https://example.com/objects/24d9f2e1-32d2-4bd5-bdf2-8ea61d3fb5e8\" rel=\"nofollow noopener noreferrer\" target=\"_blank\"><span class=\"invisible\">https://</span><span class=\"ellipsis\">example.com/objects/24d9f</span><span class=\"invisible\">2e1-32d2-4bd5-bdf2-8ea61d3fb5e8</span></a></span><span class=\"reference-link-inline\"> <a href=\"https://fedibird.com/@akkoma_ap_integration_tester/108707679228362674/references\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"status-link unhandled-link\" data-status-id=\"108707679228362674\">[参照]</a></span></p>",
"contentMap": {
"ja": "<p>public quote<span class=\"quote-inline\"><br/>QT: <a class=\"status-url-link\" data-status-account-acct=\"ArtMirror@example.com\" data-status-id=\"108703793483919195\" href=\"https://example.com/objects/24d9f2e1-32d2-4bd5-bdf2-8ea61d3fb5e8\" rel=\"nofollow noopener noreferrer\" target=\"_blank\"><span class=\"invisible\">https://</span><span class=\"ellipsis\">example.com/objects/24d9f</span><span class=\"invisible\">2e1-32d2-4bd5-bdf2-8ea61d3fb5e8</span></a></span><span class=\"reference-link-inline\"> <a href=\"https://fedibird.com/@akkoma_ap_integration_tester/108707679228362674/references\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"status-link unhandled-link\" data-status-id=\"108707679228362674\">[参照]</a></span></p>"
},
"attachment": [],
"tag": [],
"replies": {
"id": "https://fedibird.com/users/akkoma_ap_integration_tester/statuses/108707679228362674/replies",
"type": "Collection",
"first": {
"type": "CollectionPage",
"next": "https://fedibird.com/users/akkoma_ap_integration_tester/statuses/108707679228362674/replies?only_other_accounts=true&page=true",
"partOf": "https://fedibird.com/users/akkoma_ap_integration_tester/statuses/108707679228362674/replies",
"items": []
}
},
"references": {
"id": "https://fedibird.com/users/akkoma_ap_integration_tester/statuses/108707679228362674/references",
"type": "Collection",
"first": {
"type": "CollectionPage",
"partOf": "https://fedibird.com/users/akkoma_ap_integration_tester/statuses/108707679228362674/references",
"items": [
"https://example.com/objects/24d9f2e1-32d2-4bd5-bdf2-8ea61d3fb5e8"
]
}
}
}

View file

@ -1,50 +0,0 @@
{
"@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-hub.net/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/934gok3482",
"type": "Note",
"attributedTo": "https://misskey.io/users/93492q0ip0",
"summary": null,
"content": "<p><span>i quompt u<br><br>RE: </span><a href=\"https://example.com/objects/30c543fb-a165-40dd-87fd-4e249ec5a40b\">https://example.com/objects/30c543fb-a165-40dd-87fd-4e249ec5a40b</a></p>",
"_misskey_content": "i quompt u",
"source": {
"content": "i quompt u",
"mediaType": "text/x.misskeymarkdown"
},
"_misskey_quote": "https://example.com/objects/43479e20-c0f8-4f49-bf7f-13fab8234924",
"quoteUrl": "https://example.com/objects/43479e20-c0f8-4f49-bf7f-13fab8234924",
"published": "2022-07-25T15:21:48.208Z",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"cc": [
"https://misskey.io/users/93492q0ip0/followers"
],
"inReplyTo": null,
"attachment": [],
"sensitive": false,
"tag": []
}

View file

@ -1,52 +0,0 @@
{
"@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": []
}
}
}

View file

@ -1,54 +0,0 @@
{
"@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": []
}
}
}

View file

@ -1,46 +0,0 @@
{
"@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": []
}

View file

@ -1,38 +0,0 @@
{
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://example.com/schemas/litepub-0.1.jsonld",
{
"@language": "und"
}
],
"actor": "https://example.com/users/user",
"attachment": [
{
"mediaType": "image/png",
"name": "",
"type": "Document",
"url": "https://example.com/media/4d6097ae20200ac371f51d24eae0a94cb4b424b6aff81dcc0f7411b1a74c796f.png"
}
],
"attributedTo": "https://example.com/users/user",
"cc": [
"https://example.com/users/user/followers"
],
"content": "",
"context": "https://example.com/contexts/c2c52511-977e-4168-996c-bcf006789dca",
"conversation": "https://example.com/contexts/c2c52511-977e-4168-996c-bcf006789dca",
"id": "https://example.com/objects/43479e20-c0f8-4f49-bf7f-13fab8234924",
"published": "2022-07-24T17:25:51.614495Z",
"sensitive": null,
"source": {
"content": "",
"mediaType": "text/plain"
},
"summary": "",
"tag": [],
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"type": "Note"
}

View file

@ -1,2 +0,0 @@
defmodule DynamicModule.First do
end

View file

@ -1 +0,0 @@
def thisisnotloaded

View file

@ -1,2 +0,0 @@
defmodule DynamicModule.Second do
end

View file

@ -12,11 +12,4 @@ test "returns unique temporary directory" do
File.rm_rf(path) File.rm_rf(path)
end end
end end
describe "compile_dir/1" do
test "recursively compiles directories" do
{:ok, [DynamicModule.First, DynamicModule.Second], []} =
Pleroma.Utils.compile_dir("test/fixtures/runtime_modules")
end
end
end end

View file

@ -13,7 +13,6 @@ defmodule Pleroma.Web.ActivityPub.BuilderTest do
test "returns note data" do test "returns note data" do
user = insert(:user) user = insert(:user)
note = insert(:note) note = insert(:note)
quote = insert(:note)
user2 = insert(:user) user2 = insert(:user)
user3 = insert(:user) user3 = insert(:user)
@ -26,8 +25,7 @@ test "returns note data" do
tags: [name: "jimm"], tags: [name: "jimm"],
summary: "test summary", summary: "test summary",
cc: [user3.ap_id], cc: [user3.ap_id],
extra: %{"custom_tag" => "test"}, extra: %{"custom_tag" => "test"}
quote: quote
} }
expected = %{ expected = %{
@ -41,8 +39,7 @@ test "returns note data" do
"tag" => ["jimm"], "tag" => ["jimm"],
"to" => [user2.ap_id], "to" => [user2.ap_id],
"type" => "Note", "type" => "Note",
"custom_tag" => "test", "custom_tag" => "test"
"quoteUri" => quote.data["id"]
} }
assert {:ok, ^expected, []} = Builder.note(draft) assert {:ok, ^expected, []} = Builder.note(draft)

View file

@ -1,56 +0,0 @@
# 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://example.com/objects/1234"
activity = %{
"type" => "Create",
"actor" => "https://example.com/users/alex",
"object" => %{
"type" => "Note",
"content" => "<p>Nice post</p>",
"quoteUri" => quote_url
}
}
{:ok, %{"object" => %{"content" => filtered}}} = InlineQuotePolicy.filter(activity)
assert filtered ==
"<p>Nice post<span class=\"quote-inline\"><br/><br/>RE: <a href=\"https://example.com/objects/1234\">https://example.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
end

View file

@ -143,61 +143,5 @@ test "a misskey MFM status with a _misskey_content field should work and be link
} }
} = ArticleNotePageValidator.cast_and_validate(note) } = ArticleNotePageValidator.cast_and_validate(note)
end end
test "a misskey quote should work", _ do
Tesla.Mock.mock(fn %{
method: :get,
url: "https://example.com/objects/43479e20-c0f8-4f49-bf7f-13fab8234924"
} ->
%Tesla.Env{
status: 200,
body: File.read!("test/fixtures/quoted_status.json"),
headers: HttpRequestMock.activitypub_object_headers()
}
end)
insert(:user, %{ap_id: "https://misskey.io/users/93492q0ip0"})
insert(:user, %{ap_id: "https://example.com/users/user"})
note =
"test/fixtures/misskey/quote.json"
|> File.read!()
|> Jason.decode!()
%{
valid?: true,
changes: %{
quoteUri: "https://example.com/objects/43479e20-c0f8-4f49-bf7f-13fab8234924"
}
} = ArticleNotePageValidator.cast_and_validate(note)
end
test "a fedibird quote should work", _ do
Tesla.Mock.mock(fn %{
method: :get,
url: "https://example.com/objects/43479e20-c0f8-4f49-bf7f-13fab8234924"
} ->
%Tesla.Env{
status: 200,
body: File.read!("test/fixtures/quoted_status.json"),
headers: HttpRequestMock.activitypub_object_headers()
}
end)
insert(:user, %{ap_id: "https://fedibird.com/users/akkoma_ap_integration_tester"})
insert(:user, %{ap_id: "https://example.com/users/user"})
note =
"test/fixtures/fedibird/quote.json"
|> File.read!()
|> Jason.decode!()
%{
valid?: true,
changes: %{
quoteUri: "https://example.com/objects/43479e20-c0f8-4f49-bf7f-13fab8234924"
}
} = ArticleNotePageValidator.cast_and_validate(note)
end
end end
end end

View file

@ -193,14 +193,10 @@ test "with adding expires_at", %{conn: conn, user: user} do
assert response["irreversible"] == true assert response["irreversible"] == true
expected_time = assert response["expires_at"] ==
NaiveDateTime.utc_now() NaiveDateTime.utc_now()
|> NaiveDateTime.add(in_seconds) |> NaiveDateTime.add(in_seconds)
|> Pleroma.Web.CommonAPI.Utils.to_masto_date()
assert NaiveDateTime.diff(
NaiveDateTime.from_iso8601!(response["expires_at"]),
expected_time
) < 5
filter = Filter.get(response["id"], user) filter = Filter.get(response["id"], user)

View file

@ -1944,102 +1944,4 @@ test "show" do
} = result } = result
end end
end end
describe "posting quotes" do
setup do: oauth_access(["write:statuses"])
test "posting a quote", %{conn: conn} do
user = insert(:user)
{:ok, quoted_status} = CommonAPI.post(user, %{status: "tell me, for whom do you fight?"})
conn =
conn
|> put_req_header("content-type", "application/json")
|> post("/api/v1/statuses", %{
"status" => "Hmph, how very glib",
"quote_id" => quoted_status.id
})
response = json_response_and_validate_schema(conn, 200)
assert response["quote_id"] == quoted_status.id
assert response["quote"]["id"] == quoted_status.id
assert response["quote"]["content"] == quoted_status.object.data["content"]
end
test "posting a quote, quoting a status that isn't public", %{conn: conn} do
user = insert(:user)
Enum.each(["private", "local", "direct"], fn visibility ->
{:ok, quoted_status} =
CommonAPI.post(user, %{
status: "tell me, for whom do you fight?",
visibility: visibility
})
assert %{"error" => "You can only quote public or unlisted statuses"} =
conn
|> put_req_header("content-type", "application/json")
|> post("/api/v1/statuses", %{
"status" => "Hmph, how very glib",
"quote_id" => quoted_status.id
})
|> json_response_and_validate_schema(422)
end)
end
test "posting a quote, after quote, the status gets deleted", %{conn: conn} do
user = insert(:user)
{:ok, quoted_status} =
CommonAPI.post(user, %{status: "tell me, for whom do you fight?", visibility: "public"})
resp =
conn
|> put_req_header("content-type", "application/json")
|> post("/api/v1/statuses", %{
"status" => "I fight for eorzea!",
"quote_id" => quoted_status.id
})
|> json_response_and_validate_schema(200)
{:ok, _} = CommonAPI.delete(quoted_status.id, user)
resp =
conn
|> get("/api/v1/statuses/#{resp["id"]}")
|> json_response_and_validate_schema(200)
assert is_nil(resp["quote"])
end
test "posting a quote of a deleted status", %{conn: conn} do
user = insert(:user)
{:ok, quoted_status} =
CommonAPI.post(user, %{status: "tell me, for whom do you fight?", visibility: "public"})
{:ok, _} = CommonAPI.delete(quoted_status.id, user)
assert %{"error" => _} =
conn
|> put_req_header("content-type", "application/json")
|> post("/api/v1/statuses", %{
"status" => "I fight for eorzea!",
"quote_id" => quoted_status.id
})
|> json_response_and_validate_schema(422)
end
test "posting a quote of a status that doesn't exist", %{conn: conn} do
assert %{"error" => "You can't quote a status that doesn't exist"} =
conn
|> put_req_header("content-type", "application/json")
|> post("/api/v1/statuses", %{
"status" => "I fight for eorzea!",
"quote_id" => "oops"
})
|> json_response_and_validate_schema(422)
end
end
end end

View file

@ -305,9 +305,7 @@ test "a note activity" do
}, },
akkoma: %{ akkoma: %{
source: HTML.filter_tags(object_data["content"]) source: HTML.filter_tags(object_data["content"])
}, }
quote_id: nil,
quote: nil
} }
assert status == expected assert status == expected
@ -395,30 +393,6 @@ test "a reply" do
assert status.in_reply_to_id == to_string(note.id) assert status.in_reply_to_id == to_string(note.id)
end end
test "a quote" do
note = insert(:note_activity)
user = insert(:user)
{:ok, activity} = CommonAPI.post(user, %{status: "hehe", quote_id: note.id})
status = StatusView.render("show.json", %{activity: activity})
assert status.quote_id == to_string(note.id)
[status] = StatusView.render("index.json", %{activities: [activity], as: :activity})
assert status.quote_id == to_string(note.id)
end
test "a quote that we can't resolve" do
note = insert(:note_activity, quoteUri: "oopsie")
status = StatusView.render("show.json", %{activity: note})
assert is_nil(status.quote_id)
assert is_nil(status.quote)
end
test "contains mentions" do test "contains mentions" do
user = insert(:user) user = insert(:user)
mentioned = insert(:user) mentioned = insert(:user)