forked from AkkomaGang/akkoma
Compare commits
6 commits
4c47992686
...
c18c4cd1d1
Author | SHA1 | Date | |
---|---|---|---|
c18c4cd1d1 | |||
1419eee5df | |||
516d155558 | |||
c4e9c4bc95 | |||
f9a7b456eb | |||
8e94cbcac7 |
36 changed files with 871 additions and 16 deletions
|
@ -6,6 +6,10 @@ 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
|
||||||
|
|
||||||
|
@ -18,7 +22,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
|
- LDAP authenticator (use the akkoma-contrib-authenticator-ldap runtime module)
|
||||||
- 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
|
||||||
|
|
||||||
|
|
|
@ -407,6 +407,8 @@
|
||||||
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,
|
||||||
|
|
|
@ -300,3 +300,28 @@
|
||||||
```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`.
|
||||||
|
|
|
@ -1012,7 +1012,22 @@ config :pleroma, Pleroma.Formatter,
|
||||||
|
|
||||||
## Custom Runtime Modules (`:modules`)
|
## Custom Runtime Modules (`:modules`)
|
||||||
|
|
||||||
* `runtime_dir`: A path to custom Elixir modules (such as MRF policies).
|
* `runtime_dir`: A path to custom Elixir modules, such as MRF policies or
|
||||||
|
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
|
||||||
|
|
||||||
|
|
|
@ -292,6 +292,12 @@ 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)
|
||||||
|
|
|
@ -730,12 +730,12 @@ def maybe_validate_required_email(changeset, _) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp put_ap_id(changeset) do
|
def 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
|
||||||
|
|
||||||
defp put_following_and_follower_and_featured_address(changeset) do
|
def 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
|
||||||
|
|
||||||
defp local_nickname_regex do
|
def 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
|
||||||
|
|
|
@ -14,10 +14,23 @@ 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))
|
||||||
|> Kernel.ParallelCompiler.compile()
|
|> Enum.flat_map(fn path ->
|
||||||
|
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 """
|
||||||
|
|
|
@ -168,6 +168,7 @@ 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, []}
|
||||||
|
@ -183,6 +184,16 @@ 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,
|
||||||
%{
|
%{
|
||||||
|
|
71
lib/pleroma/web/activity_pub/mrf/inline_quote_policy.ex
Normal file
71
lib/pleroma/web/activity_pub/mrf/inline_quote_policy.ex
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
# 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
|
|
@ -156,6 +156,7 @@ 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()
|
||||||
|
|
|
@ -59,6 +59,7 @@ 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: [])
|
||||||
|
|
|
@ -598,6 +598,12 @@ 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.
|
||||||
|
@ -652,6 +658,7 @@ 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
|
||||||
|
@ -879,6 +886,43 @@ 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})
|
||||||
|
|
|
@ -496,6 +496,11 @@ 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: %{
|
||||||
|
|
|
@ -133,6 +133,16 @@ 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: %{
|
||||||
|
@ -204,6 +214,33 @@ 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"}],
|
||||||
|
|
|
@ -319,6 +319,10 @@ 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
|
||||||
|
|
|
@ -22,6 +22,8 @@ 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,
|
||||||
|
@ -54,6 +56,7 @@ 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("e_id/1)
|
||||||
|> content()
|
|> content()
|
||||||
|> with_valid(&to_and_cc/1)
|
|> with_valid(&to_and_cc/1)
|
||||||
|> with_valid(&context/1)
|
|> with_valid(&context/1)
|
||||||
|
@ -108,6 +111,28 @@ 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" ->
|
||||||
|
|
|
@ -329,6 +329,8 @@ 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"],
|
||||||
|
@ -363,6 +365,8 @@ 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),
|
||||||
|
@ -604,4 +608,19 @@ 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
|
||||||
|
|
|
@ -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'"
|
"script-src 'self' 'unsafe-eval'"
|
||||||
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"]
|
||||||
|
|
4
mix.exs
4
mix.exs
|
@ -4,7 +4,7 @@ defmodule Pleroma.Mixfile do
|
||||||
def project do
|
def project do
|
||||||
[
|
[
|
||||||
app: :pleroma,
|
app: :pleroma,
|
||||||
version: version("3.0.0"),
|
version: version("3.0.1"),
|
||||||
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],
|
applications: [ex_syslogger: :load, syslog: :load, eldap: :transient],
|
||||||
steps: [:assemble, &put_otp_version/1, ©_files/1, ©_nginx_config/1],
|
steps: [:assemble, &put_otp_version/1, ©_files/1, ©_nginx_config/1],
|
||||||
config_providers: [{Pleroma.Config.ReleaseRuntimeProvider, []}]
|
config_providers: [{Pleroma.Config.ReleaseRuntimeProvider, []}]
|
||||||
]
|
]
|
||||||
|
|
|
@ -17,6 +17,8 @@
|
||||||
"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#",
|
||||||
|
@ -26,6 +28,8 @@
|
||||||
"@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"
|
||||||
|
|
73
test/fixtures/fedibird/quote.json
vendored
Normal file
73
test/fixtures/fedibird/quote.json
vendored
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
{
|
||||||
|
"@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"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
50
test/fixtures/misskey/quote.json
vendored
Normal file
50
test/fixtures/misskey/quote.json
vendored
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
{
|
||||||
|
"@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": []
|
||||||
|
}
|
52
test/fixtures/quote_post/fedibird_quote_post.json
vendored
Normal file
52
test/fixtures/quote_post/fedibird_quote_post.json
vendored
Normal file
|
@ -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": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
54
test/fixtures/quote_post/fedibird_quote_uri.json
vendored
Normal file
54
test/fixtures/quote_post/fedibird_quote_uri.json
vendored
Normal file
|
@ -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": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
46
test/fixtures/quote_post/misskey_quote_post.json
vendored
Normal file
46
test/fixtures/quote_post/misskey_quote_post.json
vendored
Normal file
|
@ -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": []
|
||||||
|
}
|
38
test/fixtures/quoted_status.json
vendored
Normal file
38
test/fixtures/quoted_status.json
vendored
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
{
|
||||||
|
"@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"
|
||||||
|
}
|
2
test/fixtures/runtime_modules/first.ex
vendored
Normal file
2
test/fixtures/runtime_modules/first.ex
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
defmodule DynamicModule.First do
|
||||||
|
end
|
1
test/fixtures/runtime_modules/nope.exs
vendored
Normal file
1
test/fixtures/runtime_modules/nope.exs
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
def thisisnotloaded
|
2
test/fixtures/runtime_modules/subdir/second.ex
vendored
Normal file
2
test/fixtures/runtime_modules/subdir/second.ex
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
defmodule DynamicModule.Second do
|
||||||
|
end
|
|
@ -12,4 +12,11 @@ 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
|
||||||
|
|
|
@ -13,6 +13,7 @@ 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)
|
||||||
|
|
||||||
|
@ -25,7 +26,8 @@ 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 = %{
|
||||||
|
@ -39,7 +41,8 @@ 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)
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
# 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
|
|
@ -143,5 +143,61 @@ 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
|
||||||
|
|
|
@ -193,10 +193,14 @@ test "with adding expires_at", %{conn: conn, user: user} do
|
||||||
|
|
||||||
assert response["irreversible"] == true
|
assert response["irreversible"] == true
|
||||||
|
|
||||||
assert response["expires_at"] ==
|
expected_time =
|
||||||
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)
|
||||||
|
|
||||||
|
|
|
@ -1944,4 +1944,102 @@ 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
|
||||||
|
|
|
@ -305,7 +305,9 @@ 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
|
||||||
|
@ -393,6 +395,30 @@ 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)
|
||||||
|
|
Loading…
Reference in a new issue