Merge remote-tracking branch 'soapbox/develop' into pleroma-events

Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
marcin mikołajczak 2022-11-09 14:17:09 +01:00
commit 8661703c16
65 changed files with 1343 additions and 526 deletions

View file

@ -30,7 +30,7 @@ LABEL maintainer="hello@soapbox.pub" \
org.opencontainers.image.description="Rebased" \
org.opencontainers.image.authors="hello@soapbox.pub" \
org.opencontainers.image.vendor="soapbox.pub" \
org.opencontainers.image.documentation="https://gitlab.com/soapbox-pub/soapbox-be" \
org.opencontainers.image.documentation="https://gitlab.com/soapbox-pub/rebased" \
org.opencontainers.image.licenses="AGPL-3.0" \
org.opencontainers.image.url="https://soapbox.pub" \
org.opencontainers.image.revision=$VCS_REF \
@ -40,13 +40,16 @@ ARG HOME=/opt/pleroma
ARG DATA=/var/lib/pleroma
RUN apt-get update &&\
apt-get install -y --no-install-recommends imagemagick libmagic-dev ffmpeg libimage-exiftool-perl libncurses5 postgresql-client &&\
apt-get install -y --no-install-recommends curl ca-certificates imagemagick libmagic-dev ffmpeg libimage-exiftool-perl libncurses5 postgresql-client fasttext &&\
adduser --system --shell /bin/false --home ${HOME} pleroma &&\
mkdir -p ${DATA}/uploads &&\
mkdir -p ${DATA}/static &&\
chown -R pleroma ${DATA} &&\
mkdir -p /etc/pleroma &&\
chown -R pleroma /etc/pleroma
chown -R pleroma /etc/pleroma &&\
mkdir -p /usr/share/fasttext &&\
curl -L https://dl.fbaipublicfiles.com/fasttext/supervised-models/lid.176.ftz -o /usr/share/fasttext/lid.176.ftz &&\
chmod 0644 /usr/share/fasttext/lid.176.ftz
USER pleroma

View file

@ -110,17 +110,6 @@
"xmpp"
]
websocket_config = [
path: "/websocket",
serializer: [
{Phoenix.Socket.V1.JSONSerializer, "~> 1.0.0"},
{Phoenix.Socket.V2.JSONSerializer, "~> 2.0.0"}
],
timeout: 60_000,
transport_log: false,
compress: false
]
# Configures the endpoint
config :pleroma, Pleroma.Web.Endpoint,
url: [host: "localhost"],
@ -130,9 +119,6 @@
{:_,
[
{"/api/v1/streaming", Pleroma.Web.MastodonAPI.WebsocketHandler, []},
{"/websocket", Phoenix.Endpoint.CowboyWebSocket,
{Phoenix.Transports.WebSocket,
{Pleroma.Web.Endpoint, Pleroma.Web.UserSocket, websocket_config}}},
{:_, Phoenix.Endpoint.Cowboy2Handler, {Pleroma.Web.Endpoint, []}}
]}
]
@ -428,6 +414,11 @@
config :pleroma, :mrf_inline_quote, prefix: "RT"
config :pleroma, :mrf_remote_report,
reject_all: false,
reject_anonymous: true,
reject_empty_message: true
config :pleroma, :rich_media,
enabled: true,
ignore_hosts: [],
@ -475,10 +466,6 @@
image_quality: 85,
min_content_length: 100 * 1024
config :pleroma, :shout,
enabled: true,
limit: 5_000
config :phoenix, :format_encoders, json: Jason, "activity+json": Jason, ics: ICalendar
config :phoenix, :json_library, Jason

View file

@ -566,6 +566,12 @@
"Cool instance"
]
},
%{
key: :contact_username,
type: :string,
description: "Instance owner username",
suggestions: ["admin"]
},
%{
key: :limit,
type: :integer,
@ -2742,27 +2748,6 @@
}
]
},
%{
group: :pleroma,
key: :shout,
type: :group,
description: "Pleroma shout settings",
children: [
%{
key: :enabled,
type: :boolean,
description: "Enables the backend Shoutbox chat feature."
},
%{
key: :limit,
type: :integer,
description: "Shout message character limit.",
suggestions: [
5_000
]
}
]
},
%{
group: :pleroma,
key: :http,
@ -3550,5 +3535,71 @@
description: "Update nickname according to host-meta, when refetching the user"
}
]
},
%{
group: :pleroma,
key: Pleroma.Language.Translation,
type: :group,
description: "Translation providers",
children: [
%{
key: :provider,
type: :module,
suggestions: [
Pleroma.Language.Translation.Deepl,
Pleroma.Language.Translation.Libretranslate
]
},
%{
group: {:subgroup, Pleroma.Language.Translation.Deepl},
key: :base_url,
label: "DeepL base URL",
type: :string,
suggestions: ["https://api-free.deepl.com", "https://api.deepl.com"]
},
%{
group: {:subgroup, Pleroma.Language.Translation.Deepl},
key: :api_key,
label: "DeepL API Key",
type: :string,
suggestions: ["YOUR_API_KEY"]
},
%{
group: {:subgroup, Pleroma.Language.Translation.Libretranslate},
key: :base_url,
label: "LibreTranslate instance URL",
type: :string,
suggestions: ["https://libretranslate.com"]
},
%{
group: {:subgroup, Pleroma.Language.Translation.Libretranslate},
key: :api_key,
label: "LibreTranslate API Key",
type: :string,
suggestions: ["YOUR_API_KEY"]
}
]
},
%{
group: :pleroma,
key: Pleroma.Language.LanguageDetector,
type: :group,
description: "Language detection providers",
children: [
%{
key: :provider,
type: :module,
suggestions: [
Pleroma.Language.LanguageDetector.Fasttext
]
},
%{
group: {:subgroup, Pleroma.Language.LanguageDetector.Fasttext},
key: :model,
label: "fastText language detection model",
type: :string,
suggestions: ["/usr/share/fasttext/lid.176.bin"]
}
]
}
]

View file

@ -32,6 +32,12 @@
config :pleroma, :instance, static_dir: "/var/lib/pleroma/static"
config :pleroma, Pleroma.Uploaders.Local, uploads: "/var/lib/pleroma/uploads"
config :pleroma, Pleroma.Language.LanguageDetector,
provider: Pleroma.Language.LanguageDetector.Fasttext
config :pleroma, Pleroma.Language.LanguageDetector.Fasttext,
model: "/usr/share/fasttext/lid.176.ftz"
# We can't store the secrets in this file, since this is baked into the docker image
if not File.exists?("/var/lib/pleroma/secret.exs") do
secret = :crypto.strong_rand_bytes(64) |> Base.encode64() |> binary_part(0, 64)

View file

@ -8,11 +8,6 @@ For from source installations Pleroma configuration works by first importing the
To add configuration to your config file, you can copy it from the base config. The latest version of it can be viewed [here](https://git.pleroma.social/pleroma/pleroma/blob/develop/config/config.exs). You can also use this file if you don't know how an option is supposed to be formatted.
## :shout
* `enabled` - Enables the backend Shoutbox chat feature. Defaults to `true`.
* `limit` - Shout character limit. Defaults to `5_000`
## :instance
* `name`: The instances name.
* `email`: Email used to reach an Administrator/Moderator of the instance.

View file

@ -103,7 +103,6 @@ Has these additional fields under the `pleroma` object:
- `hide_followers_count`: boolean, true when the user has follower stat hiding enabled
- `hide_follows_count`: boolean, true when the user has follow stat hiding enabled
- `settings_store`: A generic map of settings for frontends. Opaque to the backend. Only returned in `/api/v1/accounts/verify_credentials` and `/api/v1/accounts/update_credentials`
- `chat_token`: The token needed for Pleroma shoutbox. Only returned in `/api/v1/accounts/verify_credentials`
- `deactivated`: boolean, true when the user is deactivated
- `allow_following_move`: boolean, true when the user allows automatically follow moved following accounts
- `unread_conversation_count`: The count of unread conversations. Only returned to the account owner.

View file

@ -45,7 +45,6 @@ See also [the Nodeinfo standard](https://nodeinfo.diaspora.software/).
"multifetch",
"pleroma:api/v1/notifications:include_types_filter",
"chat",
"shout",
"relay",
"pleroma_emoji_reactions",
"pleroma_chat_messages"
@ -205,7 +204,6 @@ See also [the Nodeinfo standard](https://nodeinfo.diaspora.software/).
"multifetch",
"pleroma:api/v1/notifications:include_types_filter",
"chat",
"shout",
"relay",
"pleroma_emoji_reactions",
"pleroma_chat_messages"

View file

@ -113,7 +113,6 @@ def start(_type, _args) do
] ++
task_children(@mix_env) ++
dont_run_in_test(@mix_env) ++
shout_child(shout_enabled?()) ++
[Pleroma.Gopher.Server]
# See http://elixir-lang.org/docs/stable/elixir/Supervisor.html
@ -214,7 +213,8 @@ defp cachex_children do
build_cachex("chat_message_id_idempotency_key",
expiration: chat_message_id_idempotency_key_expiration(),
limit: 500_000
)
),
build_cachex("translations", default_ttl: :timer.hours(24), limit: 5_000)
]
end
@ -238,8 +238,6 @@ def build_cachex(type, opts),
type: :worker
}
defp shout_enabled?, do: Config.get([:shout, :enabled])
defp dont_run_in_test(env) when env in [:test, :benchmark], do: []
defp dont_run_in_test(_) do
@ -260,15 +258,6 @@ defp background_migrators do
]
end
defp shout_child(true) do
[
Pleroma.Web.ShoutChannel.ShoutChannelState,
{Phoenix.PubSub, [name: Pleroma.PubSub, adapter: Phoenix.PubSub.PG2]}
]
end
defp shout_child(_), do: []
defp task_children(:test) do
[
%{

View file

@ -187,7 +187,27 @@ defp check_system_commands!(:ok) do
false
end
if Enum.all?([preview_proxy_commands_status | filter_commands_statuses], & &1) do
language_detector_commands_status =
if Pleroma.Language.LanguageDetector.missing_dependencies() == [] do
true
else
Logger.error(
"The following dependencies required by the currently enabled " <>
"language detection provider are not installed: " <>
inspect(Pleroma.Language.LanguageDetector.missing_dependencies())
)
false
end
if Enum.all?(
[
preview_proxy_commands_status,
language_detector_commands_status
| filter_commands_statuses
],
& &1
) do
:ok
else
{:error,

View file

@ -214,7 +214,6 @@ def warn do
check_activity_expiration_config(),
check_remote_ip_plug_name(),
check_uploders_s3_public_endpoint(),
check_old_chat_shoutbox(),
check_quarantined_instances_tuples(),
check_transparency_exclusions_tuples(),
check_simple_policy_tuples(),
@ -392,27 +391,4 @@ def check_uploders_s3_public_endpoint do
:ok
end
end
@spec check_old_chat_shoutbox() :: :ok | nil
def check_old_chat_shoutbox do
instance_config = Pleroma.Config.get([:instance])
chat_config = Pleroma.Config.get([:chat]) || []
use_old_config =
Keyword.has_key?(instance_config, :chat_limit) or
Keyword.has_key?(chat_config, :enabled)
if use_old_config do
Logger.error("""
!!!DEPRECATION WARNING!!!
Your config is using the old namespace for the Shoutbox configuration. You need to convert to the new namespace. e.g.,
\n* `config :pleroma, :chat, enabled` and `config :pleroma, :instance, chat_limit` are now equal to:
\n* `config :pleroma, :shout, enabled` and `config :pleroma, :shout, limit`
""")
:error
else
:ok
end
end
end

View file

@ -16,7 +16,6 @@ defmodule Pleroma.Config.TransferTask do
defp reboot_time_keys,
do: [
{:pleroma, :hackney_pools},
{:pleroma, :shout},
{:pleroma, Oban},
{:pleroma, :rate_limit},
{:pleroma, :markup},

View file

@ -23,6 +23,7 @@ defmodule Pleroma.Constants do
"assigned_account",
"rules",
"content_type",
"language",
"participations",
"participation_count",
"participation_request_count",
@ -46,6 +47,7 @@ defmodule Pleroma.Constants do
"sensitive",
"attachment",
"generator",
"language",
"startTime",
"endTime",
"location",

View file

@ -0,0 +1,44 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Language.LanguageDetector do
@words_threshold 4
def missing_dependencies do
provider = get_provider()
if provider do
provider.missing_dependencies()
else
[]
end
end
# Strip tags from text, etc.
defp prepare_text(text) do
text
|> Floki.parse_fragment!()
|> Floki.filter_out(
".h-card, .mention, .hashtag, .u-url, .quote-inline, .recipients-inline, code, pre"
)
|> Floki.text()
end
def detect(text) do
provider = get_provider()
text = prepare_text(text)
word_count = text |> String.split(~r/\s+/) |> Enum.count()
if word_count < @words_threshold or !provider or !provider.configured? do
nil
else
provider.detect(text)
end
end
defp get_provider do
Pleroma.Config.get([__MODULE__, :provider])
end
end

View file

@ -0,0 +1,47 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Language.LanguageDetector.Fasttext do
import Pleroma.Web.Utils.Guards, only: [not_empty_string: 1]
alias Pleroma.Language.LanguageDetector.Provider
@behaviour Provider
@impl Provider
def missing_dependencies do
if Pleroma.Utils.command_available?("fasttext") do
[]
else
["fasttext"]
end
end
@impl Provider
def configured?, do: not_empty_string(get_model())
@impl Provider
def detect(text) do
text_path = Path.join(System.tmp_dir!(), "fasttext-#{Ecto.UUID.generate()}")
File.write(text_path, text |> String.replace(~r/\s+/, " "))
detected_language =
case System.cmd("fasttext", ["predict", get_model(), text_path]) do
{"__label__" <> language, _} ->
language |> String.trim()
_ ->
nil
end
File.rm(text_path)
detected_language
end
defp get_model do
Pleroma.Config.get([__MODULE__, :model])
end
end

View file

@ -0,0 +1,11 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Language.LanguageDetector.Provider do
@callback missing_dependencies() :: [String.t()]
@callback configured?() :: boolean()
@callback detect(text :: String.t()) :: String.t() | nil
end

View file

@ -0,0 +1,53 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Language.Translation do
@cachex Pleroma.Config.get([:cachex, :provider], Cachex)
def configured? do
provider = get_provider()
!!provider and provider.configured?
end
def translate(text, source_language, target_language) do
cache_key = get_cache_key(text, source_language, target_language)
case @cachex.get(:translations_cache, cache_key) do
{:ok, nil} ->
provider = get_provider()
result =
if !configured?() do
{:error, :not_found}
else
provider.translate(text, source_language, target_language)
end
store_result(result, cache_key)
result
{:ok, result} ->
{:ok, result}
{:error, error} ->
{:error, error}
end
end
defp get_provider, do: Pleroma.Config.get([__MODULE__, :provider])
defp get_cache_key(text, source_language, target_language) do
"#{source_language}/#{target_language}/#{content_hash(text)}"
end
defp store_result({:ok, result}, cache_key) do
@cachex.put(:translations_cache, cache_key, result)
end
defp store_result(_, _), do: nil
defp content_hash(text), do: :crypto.hash(:sha256, text) |> Base.encode64()
end

View file

@ -0,0 +1,74 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Language.Translation.Deepl do
import Pleroma.Web.Utils.Guards, only: [not_empty_string: 1]
alias Pleroma.Language.Translation.Provider
@behaviour Provider
@impl Provider
def configured? do
not_empty_string(get_base_url()) and not_empty_string(get_api_key())
end
@impl Provider
def translate(content, source_language, target_language) do
endpoint = get_endpoint()
case Pleroma.HTTP.post(
endpoint <>
"?" <>
URI.encode_query(%{
text: content,
source_lang: source_language |> String.upcase(),
target_lang: target_language,
tag_handling: "html"
}),
"",
[
{"Content-Type", "application/x-www-form-urlencoded"},
{"Authorization", "DeepL-Auth-Key #{get_api_key()}"}
]
) do
{:ok, %{status: 429}} ->
{:error, :too_many_requests}
{:ok, %{status: 456}} ->
{:error, :quota_exceeded}
{:ok, %{status: 200} = res} ->
%{
"translations" => [
%{"text" => content, "detected_source_language" => detected_source_language}
]
} = Jason.decode!(res.body)
{:ok,
%{
content: content,
detected_source_language: detected_source_language,
provider: "DeepL"
}}
_ ->
{:error, :internal_server_error}
end
end
defp get_endpoint do
get_base_url()
|> URI.merge("/v2/translate")
|> URI.to_string()
end
defp get_base_url do
Pleroma.Config.get([__MODULE__, :base_url])
end
defp get_api_key do
Pleroma.Config.get([__MODULE__, :api_key])
end
end

View file

@ -0,0 +1,66 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Language.Translation.Libretranslate do
import Pleroma.Web.Utils.Guards, only: [not_empty_string: 1]
alias Pleroma.Language.Translation.Provider
@behaviour Provider
@impl Provider
def configured?, do: not_empty_string(get_base_url())
@impl Provider
def translate(content, source_language, target_language) do
endpoint = endpoint_url()
case Pleroma.HTTP.post(
endpoint,
Jason.encode!(%{
q: content,
source: source_language |> String.upcase(),
target: target_language,
format: "html",
api_key: get_api_key()
}),
[
{"Content-Type", "application/json"}
]
) do
{:ok, %{status: 429}} ->
{:error, :too_many_requests}
{:ok, %{status: 403}} ->
{:error, :quota_exceeded}
{:ok, %{status: 200} = res} ->
%{
"translatedText" => content
} = Jason.decode!(res.body)
{:ok,
%{
content: content,
detected_source_language: source_language,
provider: "LibreTranslate"
}}
_ ->
{:error, :internal_server_error}
end
end
defp endpoint_url do
get_base_url() <> "/translate"
end
defp get_base_url do
Pleroma.Config.get([__MODULE__, :base_url])
end
defp get_api_key do
Pleroma.Config.get([__MODULE__, :api_key], "")
end
end

View file

@ -0,0 +1,20 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Language.Translation.Provider do
@callback configured?() :: boolean()
@callback translate(
content :: String.t(),
source_language :: String.t(),
target_language :: String.t()
) ::
{:ok,
%{
content: String.t(),
detected_source_language: String.t(),
provider: String.t()
}}
| {:error, atom()}
end

View file

@ -246,7 +246,7 @@ def update(actor, object) do
{to, cc, bcc} =
if object["type"] in Pleroma.Constants.actor_types() do
# User updates, always public
{[Pleroma.Constants.as_public(), actor.follower_address], []}
{[Pleroma.Constants.as_public(), actor.follower_address], [], []}
else
# Status updates, follow the recipients in the object
{object["to"] || [], object["cc"] || [], object["participations"] || []}

View file

@ -0,0 +1,95 @@
defmodule Pleroma.Web.ActivityPub.MRF.RemoteReportPolicy do
@moduledoc "Drop remote reports if they don't contain enough information."
@behaviour Pleroma.Web.ActivityPub.MRF.Policy
alias Pleroma.Config
@impl true
def filter(%{"type" => "Flag"} = object) do
with {_, false} <- {:local, local?(object)},
{:ok, _} <- maybe_reject_all(object),
{:ok, _} <- maybe_reject_anonymous(object),
{:ok, _} <- maybe_reject_empty_message(object) do
{:ok, object}
else
{:local, true} -> {:ok, object}
{:reject, message} -> {:reject, message}
error -> {:reject, error}
end
end
def filter(object), do: {:ok, object}
defp maybe_reject_all(object) do
if Config.get([:mrf_remote_report, :reject_all]) do
{:reject, "[RemoteReportPolicy] Remote report"}
else
{:ok, object}
end
end
defp maybe_reject_anonymous(%{"actor" => actor} = object) do
with true <- Config.get([:mrf_remote_report, :reject_anonymous]),
%URI{path: "/actor"} <- URI.parse(actor) do
{:reject, "[RemoteReportPolicy] Anonymous: #{actor}"}
else
_ -> {:ok, object}
end
end
defp maybe_reject_empty_message(%{"content" => content} = object)
when is_binary(content) and content != "" do
{:ok, object}
end
defp maybe_reject_empty_message(object) do
if Config.get([:mrf_remote_report, :reject_empty_message]) do
{:reject, ["RemoteReportPolicy] No content"]}
else
{:ok, object}
end
end
defp local?(%{"actor" => actor}) do
String.starts_with?(actor, Pleroma.Web.Endpoint.url())
end
@impl true
def describe do
mrf_remote_report =
Config.get(:mrf_remote_report)
|> Enum.into(%{})
{:ok, %{mrf_remote_report: mrf_remote_report}}
end
@impl true
def config_description do
%{
key: :mrf_remote_report,
related_policy: "Pleroma.Web.ActivityPub.MRF.RemoteReportPolicy",
label: "MRF Remote Report",
description: "Drop remote reports if they don't contain enough information.",
children: [
%{
key: :reject_all,
type: :boolean,
description: "Reject all remote reports? (this option takes precedence)",
suggestions: [false]
},
%{
key: :reject_anonymous,
type: :boolean,
description: "Reject anonymous remote reports?",
suggestions: [true]
},
%{
key: :reject_empty_message,
type: :boolean,
description: "Reject remote reports with no message?",
suggestions: [true]
}
]
}
end
end

View file

@ -108,6 +108,7 @@ defp fix(data) do
|> fix_attachments()
|> Transmogrifier.fix_emoji()
|> Transmogrifier.fix_content_map()
|> Transmogrifier.maybe_add_language()
end
def changeset(struct, data) do

View file

@ -59,6 +59,7 @@ defmacro status_object_fields do
field(:like_count, :integer, default: 0)
field(:announcement_count, :integer, default: 0)
field(:quotes_count, :integer, default: 0)
field(:language, :string)
field(:inReplyTo, ObjectValidators.ObjectID)
field(:quoteUrl, ObjectValidators.ObjectID)
field(:url, ObjectValidators.Uri)

View file

@ -8,6 +8,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
"""
alias Pleroma.Activity
alias Pleroma.EctoType.ActivityPub.ObjectValidators
alias Pleroma.Language.LanguageDetector
alias Pleroma.Maps
alias Pleroma.Object
alias Pleroma.Object.Containment
@ -23,6 +24,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
alias Pleroma.Workers.TransmogrifierWorker
import Ecto.Query
import Pleroma.Web.CommonAPI.Utils, only: [get_valid_language: 1]
import Pleroma.Web.Utils.Guards, only: [not_empty_string: 1]
require Logger
require Pleroma.Constants
@ -43,6 +46,7 @@ def fix_object(object, options \\ []) do
|> fix_content_map()
|> fix_addressing()
|> fix_summary()
|> maybe_add_language()
end
def fix_summary(%{"summary" => nil} = object) do
@ -389,6 +393,8 @@ def fix_tag(%{"tag" => %{} = tag} = object) do
def fix_tag(object), do: object
def fix_content_map(%{"content" => content} = object) when not_empty_string(content), do: object
# content map usually only has one language so this will do for now.
def fix_content_map(%{"contentMap" => content_map} = object) do
content_groups = Map.to_list(content_map)
@ -526,6 +532,7 @@ def handle_incoming(
|> fix_type(fetch_options)
|> fix_in_reply_to(fetch_options)
|> fix_quote_url(fetch_options)
|> maybe_add_language_from_activity(data)
data = Map.put(data, "object", object)
options = Keyword.put(options, :local, false)
@ -769,6 +776,7 @@ def prepare_object(object) do
|> add_mention_tags
|> add_emoji_tags
|> add_attributed_to
|> maybe_add_content_map
|> prepare_attachments
|> set_conversation
|> set_reply_to_uri
@ -814,7 +822,7 @@ def prepare_outgoing(%{"type" => activity_type, "object" => object_id} = data)
data =
data
|> Map.put("object", object)
|> Map.merge(Utils.make_json_ld_header())
|> Map.merge(Utils.make_json_ld_header(data))
|> Map.delete("bcc")
{:ok, data}
@ -829,7 +837,7 @@ def prepare_outgoing(%{"type" => "Update", "object" => %{"type" => objtype} = ob
data =
data
|> Map.put("object", object)
|> Map.merge(Utils.make_json_ld_header())
|> Map.merge(Utils.make_json_ld_header(data))
|> Map.delete("bcc")
{:ok, data}
@ -850,7 +858,7 @@ def prepare_outgoing(%{"type" => "Announce", "actor" => ap_id, "object" => objec
data =
data
|> strip_internal_fields
|> Map.merge(Utils.make_json_ld_header())
|> Map.merge(Utils.make_json_ld_header(data))
|> Map.delete("bcc")
{:ok, data}
@ -870,7 +878,7 @@ def prepare_outgoing(%{"type" => "Accept"} = data) do
data =
data
|> Map.put("object", object)
|> Map.merge(Utils.make_json_ld_header())
|> Map.merge(Utils.make_json_ld_header(data))
{:ok, data}
end
@ -888,7 +896,7 @@ def prepare_outgoing(%{"type" => "Reject"} = data) do
data =
data
|> Map.put("object", object)
|> Map.merge(Utils.make_json_ld_header())
|> Map.merge(Utils.make_json_ld_header(data))
{:ok, data}
end
@ -899,7 +907,7 @@ def prepare_outgoing(%{"type" => _type} = data) do
data
|> strip_internal_fields
|> maybe_fix_object_url
|> Map.merge(Utils.make_json_ld_header())
|> Map.merge(Utils.make_json_ld_header(data))
{:ok, data}
end
@ -1085,4 +1093,64 @@ def maybe_fix_user_url(%{"url" => url} = data) when is_map(url) do
def maybe_fix_user_url(data), do: data
def maybe_fix_user_object(data), do: maybe_fix_user_url(data)
defp maybe_add_content_map(%{"language" => language, "content" => content} = object)
when not_empty_string(language) do
Map.put(object, "contentMap", Map.put(%{}, language, content))
end
defp maybe_add_content_map(object), do: object
def maybe_add_language(object) do
language =
get_language_from_context(object) |> get_valid_language() ||
get_language_from_content_map(object) |> get_valid_language() ||
get_language_from_content(object) |> get_valid_language()
if language do
Map.put(object, "language", language)
else
object
end
end
def maybe_add_language_from_activity(object, activity) do
language = get_language_from_context(activity) |> get_valid_language()
if language do
Map.put(object, "language", language)
else
object
end
end
defp get_language_from_context(%{"@context" => context}) when is_list(context) do
case context
|> Enum.find(fn
%{"@language" => language} -> language != "und"
_ -> nil
end) do
%{"@language" => language} -> language
_ -> nil
end
end
defp get_language_from_context(_), do: nil
defp get_language_from_content_map(%{"contentMap" => content_map, "content" => source_content}) do
content_groups = Map.to_list(content_map)
case Enum.find(content_groups, fn {_, content} -> content == source_content end) do
{language, _} -> language
_ -> nil
end
end
defp get_language_from_content_map(_), do: nil
defp get_language_from_content(%{"content" => content}) do
LanguageDetector.detect(content)
end
defp get_language_from_content(_), do: nil
end

View file

@ -19,6 +19,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
alias Pleroma.Web.Router.Helpers
import Ecto.Query
import Pleroma.Web.Utils.Guards, only: [not_empty_string: 1]
require Logger
require Pleroma.Constants
@ -107,18 +108,24 @@ def maybe_splice_recipient(ap_id, params) do
end
end
def make_json_ld_header do
def make_json_ld_header(data \\ %{}) do
%{
"@context" => [
"https://www.w3.org/ns/activitystreams",
"#{Endpoint.url()}/schemas/litepub-0.1.jsonld",
%{
"@language" => "und"
"@language" => get_language(data)
}
]
}
end
defp get_language(%{"language" => language}) when not_empty_string(language) do
language
end
defp get_language(_), do: "und"
def make_date do
DateTime.utc_now() |> DateTime.to_iso8601()
end

View file

@ -9,7 +9,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectView do
alias Pleroma.Web.ActivityPub.Transmogrifier
def render("object.json", %{object: %Object{} = object}) do
base = Pleroma.Web.ActivityPub.Utils.make_json_ld_header()
base = Pleroma.Web.ActivityPub.Utils.make_json_ld_header(object.data)
additional = Transmogrifier.prepare_object(object.data)
Map.merge(base, additional)
@ -17,7 +17,7 @@ def render("object.json", %{object: %Object{} = object}) do
def render("object.json", %{object: %Activity{data: %{"type" => activity_type}} = activity})
when activity_type in ["Create", "Listen"] do
base = Pleroma.Web.ActivityPub.Utils.make_json_ld_header()
base = Pleroma.Web.ActivityPub.Utils.make_json_ld_header(activity.data)
object = Object.normalize(activity, fetch: false)
additional =
@ -28,7 +28,7 @@ def render("object.json", %{object: %Activity{data: %{"type" => activity_type}}
end
def render("object.json", %{object: %Activity{} = activity}) do
base = Pleroma.Web.ActivityPub.Utils.make_json_ld_header()
base = Pleroma.Web.ActivityPub.Utils.make_json_ld_header(activity.data)
object_id = Object.normalize(activity, id_only: true)
additional =

View file

@ -410,6 +410,38 @@ def context_operation do
}
end
def translate_operation do
%Operation{
tags: ["Retrieve status information"],
summary: "Translate status",
description: "Translate status with an external API",
operationId: "StatusController.translate",
security: [%{"oAuth" => ["read:statuses"]}],
parameters: [id_param()],
requestBody:
request_body(
"Parameters",
%Schema{
type: :object,
properties: %{
target_language: %Schema{
type: :string,
nullable: true,
description: "Translation target language."
}
}
},
required: false
),
responses: %{
200 => Operation.response("Translation", "application/json", translation()),
400 => Operation.response("Error", "application/json", ApiError),
404 => Operation.response("Error", "application/json", ApiError),
503 => Operation.response("Error", "application/json", ApiError)
}
}
end
def favourites_operation do
%Operation{
tags: ["Timelines"],
@ -800,4 +832,32 @@ defp context do
}
}
end
defp translation do
%Schema{
title: "StatusTranslation",
description: "Represents status translation with related information.",
type: :object,
required: [:content, :detected_source_language, :provider],
properties: %{
content: %Schema{
type: :string,
description: "Translated status content"
},
detected_source_language: %Schema{
type: :string,
description: "Detected source language"
},
provider: %Schema{
type: :string,
description: "Translation provider service name"
}
},
example: %{
"content" => "Software für die nächste Generation der sozialen Medien.",
"detected_source_language" => "en",
"provider" => "Deepl"
}
}
end
end

View file

@ -49,7 +49,6 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
},
background_image: %Schema{type: :string, nullable: true, format: :uri},
birthday: %Schema{type: :string, nullable: true, format: :date},
chat_token: %Schema{type: :string},
is_confirmed: %Schema{
type: :boolean,
description:
@ -180,8 +179,6 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
"is_moderator" => false,
"skip_thread_containment" => false,
"accepts_chat_messages" => true,
"chat_token" =>
"SFMyNTY.g3QAAAACZAAEZGF0YW0AAAASOXRLaTNlc2JHN09RZ1oyOTIwZAAGc2lnbmVkbgYARNplS3EB.Mb_Iaqew2bN1I1o79B_iP7encmVCpTKC4OtHZRxdjKc",
"unread_conversation_count" => 0,
"tags" => [],
"notification_settings" => %{

View file

@ -1,45 +0,0 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.UserSocket do
use Phoenix.Socket
alias Pleroma.User
## Channels
# channel "room:*", Pleroma.Web.RoomChannel
channel("chat:*", Pleroma.Web.ShoutChannel)
# Socket params are passed from the client and can
# be used to verify and authenticate a user. After
# verification, you can put default assigns into
# the socket that will be set for all channels, ie
#
# {:ok, assign(socket, :user_id, verified_user_id)}
#
# To deny connection, return `:error`.
#
# See `Phoenix.Token` documentation for examples in
# performing token verification on connect.
def connect(%{"token" => token}, socket) do
with true <- Pleroma.Config.get([:shout, :enabled]),
{:ok, user_id} <- Phoenix.Token.verify(socket, "user socket", token, max_age: 84_600),
%User{} = user <- Pleroma.User.get_cached_by_id(user_id) do
{:ok, assign(socket, :user_name, user.nickname)}
else
_e -> :error
end
end
# Socket id's are topics that allow you to identify all sockets for a given user:
#
# def id(socket), do: "user_socket:#{socket.assigns.user_id}"
#
# Would allow you to broadcast a "disconnect" event and terminate
# all active sockets and channels for a given user:
#
# Pleroma.Web.Endpoint.broadcast("user_socket:#{user.id}", "disconnect", %{})
#
# Returning `nil` makes this socket anonymous.
def id(_socket), do: nil
end

View file

@ -5,6 +5,7 @@
defmodule Pleroma.Web.CommonAPI.ActivityDraft do
alias Pleroma.Activity
alias Pleroma.Conversation.Participation
alias Pleroma.Language.LanguageDetector
alias Pleroma.Object
alias Pleroma.Repo
alias Pleroma.Web.ActivityPub.Builder
@ -37,6 +38,7 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do
cc: [],
context: nil,
sensitive: false,
language: nil,
object: nil,
preview?: false,
changes: %{},
@ -68,6 +70,7 @@ def create(user, params) do
|> content()
|> with_valid(&to_and_cc/1)
|> with_valid(&context/1)
|> with_valid(&language/1)
|> sensitive()
|> with_valid(&object/1)
|> preview?()
@ -284,6 +287,14 @@ defp sensitive(draft) do
%__MODULE__{draft | sensitive: sensitive}
end
defp language(draft) do
language =
Utils.get_valid_language(draft.params[:language]) ||
LanguageDetector.detect(draft.content_html <> " " <> draft.summary)
%__MODULE__{draft | language: language}
end
defp object(draft) do
emoji = Map.merge(Pleroma.Emoji.Formatter.get_emoji_map(draft.full_payload), draft.emoji)
@ -324,6 +335,7 @@ defp object(draft) do
})
|> Map.put("generator", draft.params[:generator])
|> Map.put("content_type", draft.params[:content_type])
|> Map.put("language", draft.language)
%__MODULE__{draft | object: object}
end

View file

@ -23,6 +23,15 @@ defmodule Pleroma.Web.CommonAPI.Utils do
require Logger
require Pleroma.Constants
@supported_locales ~w(
aa ab ae af ak am an ar as av ay az ba be bg bh bi bm bn bo br bs ca ce ch co cr cs cu cv cy da
de dv dz ee el en eo es et eu fa ff fi fj fo fr fy ga gd gl gn gu gv ha he hi ho hr ht hu hy hz
ia id ie ig ii ik io is it iu ja jv ka kg ki kj kk kl km kn ko kr ks ku kv kw ky la lb lg li ln
lo lt lu lv mg mh mi mk ml mn mr ms mt my na nb nd ne ng nl nn no nr nv ny oc oj om or os pa pi
pl ps pt qu rm rn ro ru rw sa sc sd se sg si sk sl sm sn so sq sr ss st su sv sw ta te tg th ti
tk tl tn to tr ts tt tw ty ug uk ur uz ve vi vo wa wo xh yi yo za zh zu ast ckb kab kmr zgh
)
def attachments_from_ids(%{media_ids: ids, descriptions: desc}) do
attachments_from_ids_descs(ids, desc)
end
@ -497,4 +506,13 @@ def validate_attachments_count(attachments) do
{:error, dgettext("errors", "Too many attachments")}
end
end
def get_valid_language(language) when is_binary(language) do
case language |> String.split("_") |> Enum.at(0) do
locale when locale in @supported_locales -> locale
_ -> nil
end
end
def get_valid_language(_), do: nil
end

View file

@ -9,7 +9,6 @@ defmodule Pleroma.Web.Endpoint do
alias Pleroma.Config
socket("/socket", Pleroma.Web.UserSocket)
socket("/live", Phoenix.LiveView.Socket)
plug(Unplug,

View file

@ -157,13 +157,10 @@ defp validate_email_param(_) do
@doc "GET /api/v1/accounts/verify_credentials"
def verify_credentials(%{assigns: %{user: user}} = conn, _) do
chat_token = Phoenix.Token.sign(conn, "user socket", user.id)
render(conn, "show.json",
user: user,
for: user,
with_pleroma_settings: true,
with_chat_token: chat_token
with_pleroma_settings: true
)
end

View file

@ -12,6 +12,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
alias Pleroma.Activity
alias Pleroma.Bookmark
alias Pleroma.Language.Translation
alias Pleroma.Object
alias Pleroma.Repo
alias Pleroma.ScheduledActivity
@ -43,6 +44,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
]
)
plug(OAuthScopesPlug, %{scopes: ["read:statuses"]} when action == :translate)
plug(
OAuthScopesPlug,
%{scopes: ["write:statuses"]}
@ -84,7 +87,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
%{scopes: ["write:bookmarks"]} when action in [:bookmark, :unbookmark]
)
@rate_limited_status_actions ~w(reblog unreblog favourite unfavourite create delete)a
@rate_limited_status_actions ~w(reblog unreblog favourite unfavourite create delete translate)a
plug(
RateLimiter,
@ -455,6 +458,35 @@ def context(%{assigns: %{user: user}} = conn, %{id: id}) do
end
end
@doc "POST /api/v1/statuses/:id/translate"
def translate(%{body_params: params, assigns: %{user: user}} = conn, %{id: status_id}) do
with %Activity{object: object} <- Activity.get_by_id_with_object(status_id),
{:visibility, visibility} when visibility in ["public", "unlisted"] <-
{:visibility, Visibility.get_visibility(object)},
{:language, language} when is_binary(language) <-
{:language, Map.get(params, :target_language) || user.language},
{:ok, result} <-
Translation.translate(
object.data["content"],
object.data["language"],
language
) do
render(conn, "translation.json", result)
else
{:language, nil} ->
render_error(conn, :bad_request, "Language not specified")
{:visibility, _} ->
render_error(conn, :not_found, "Record not found")
{:error, :not_found} ->
render_error(conn, :not_found, "Translation service not configured")
{:error, error} when error in [:unexpected_response, :quota_exceeded, :too_many_requests] ->
render_error(conn, :service_unavailable, "Translation service not available")
end
end
@doc "GET /api/v1/favourites"
def favourites(%{assigns: %{user: %User{} = user}} = conn, params) do
activities = ActivityPub.fetch_favourites(user, params)

View file

@ -305,7 +305,6 @@ defp do_render("show.json", %{user: user} = opts) do
|> maybe_put_settings(user, opts[:for], opts)
|> maybe_put_notification_settings(user, opts[:for])
|> maybe_put_settings_store(user, opts[:for], opts)
|> maybe_put_chat_token(user, opts[:for], opts)
|> maybe_put_activation_status(user, opts[:for])
|> maybe_put_follow_requests_count(user, opts[:for])
|> maybe_put_allow_following_move(user, opts[:for])
@ -362,15 +361,6 @@ defp maybe_put_settings_store(data, %User{} = user, %User{}, %{
defp maybe_put_settings_store(data, _, _, _), do: data
defp maybe_put_chat_token(data, %User{id: id}, %User{id: id}, %{
with_chat_token: token
}) do
data
|> Kernel.put_in([:pleroma, :chat_token], token)
end
defp maybe_put_chat_token(data, _, _, _), do: data
defp maybe_put_role(data, %User{show_role: true} = user, _) do
data
|> Kernel.put_in([:pleroma, :is_admin], user.is_admin)

View file

@ -6,7 +6,9 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do
use Pleroma.Web, :view
alias Pleroma.Config
alias Pleroma.User
alias Pleroma.Web.ActivityPub.MRF
alias Pleroma.Web.MastodonAPI
@mastodon_api_level "2.7.2"
@ -31,6 +33,7 @@ def render("show.json", _) do
registrations: Keyword.get(instance, :registrations_open),
approval_required: Keyword.get(instance, :account_approval_required),
configuration: configuration(),
contact_account: contact_account(Keyword.get(instance, :contact_username)),
rules: render(__MODULE__, "rules.json"),
# Extra (not present in Mastodon):
max_toot_chars: Keyword.get(instance, :limit),
@ -41,7 +44,6 @@ def render("show.json", _) do
background_upload_limit: Keyword.get(instance, :background_upload_limit),
banner_upload_limit: Keyword.get(instance, :banner_upload_limit),
background_image: Pleroma.Web.Endpoint.url() <> Keyword.get(instance, :background_image),
shout_limit: Config.get([:shout, :limit]),
description_limit: Keyword.get(instance, :description_limit),
pleroma: pleroma_configuration(instance),
soapbox: %{
@ -74,7 +76,7 @@ def render("show2.json", _) do
},
contact: %{
email: Keyword.get(instance, :email),
account: nil
account: contact_account(Keyword.get(instance, :contact_username))
},
rules: render(__MODULE__, "rules.json"),
# Extra (not present in Mastodon):
@ -120,13 +122,6 @@ def features do
if Config.get([:gopher, :enabled]) do
"gopher"
end,
# backwards compat
if Config.get([:shout, :enabled]) do
"chat"
end,
if Config.get([:shout, :enabled]) do
"shout"
end,
if Config.get([:instance, :allow_relay]) do
"relay"
end,
@ -142,7 +137,10 @@ def features do
if Config.get([:instance, :profile_directory]) do
"profile_directory"
end,
"pleroma:get:main/ostatus"
"pleroma:get:main/ostatus",
if Pleroma.Language.Translation.configured?() do
"translation"
end
]
|> Enum.filter(& &1)
end
@ -206,7 +204,7 @@ def configuration2 do
configuration()
|> Map.merge(%{
urls: %{streaming: Pleroma.Web.Endpoint.websocket_url()},
translation: %{enabled: false}
translation: %{enabled: Pleroma.Language.Translation.configured?()}
})
end
@ -245,9 +243,24 @@ defp pleroma_configuration2(instance) do
banner_upload_limit: Keyword.get(instance, :banner_upload_limit),
background_image:
Pleroma.Web.Endpoint.url() <> Keyword.get(instance, :background_image),
shout_limit: Config.get([:shout, :limit]),
description_limit: Keyword.get(instance, :description_limit)
})
})
end
defp contact_account(nil), do: nil
defp contact_account("@" <> username) do
contact_account(username)
end
defp contact_account(username) do
user = User.get_cached_by_nickname(username)
if user do
MastodonAPI.AccountView.render("show.json", %{user: user, for: nil})
else
nil
end
end
end

View file

@ -225,7 +225,7 @@ def render(
mentions: mentions,
tags: reblogged[:tags] || [],
application: build_application(object.data["generator"]),
language: nil,
language: object.data["language"],
emojis: [],
pleroma: %{
local: activity.local,
@ -428,7 +428,7 @@ def render("show.json", %{activity: %{data: %{"object" => _object}} = activity}
mentions: mentions,
tags: build_tags(tags),
application: build_application(object.data["generator"]),
language: nil,
language: object.data["language"],
emojis: build_emojis(object.data["emoji"]),
pleroma: %{
local: activity.local,
@ -667,6 +667,14 @@ def render("context.json", %{activity: activity, activities: activities, user: u
}
end
def render("translation.json", %{
content: content,
detected_source_language: detected_source_language,
provider: provider
}) do
%{content: content, detected_source_language: detected_source_language, provider: provider}
end
def get_reply_to(activity, %{replied_to_activities: replied_to_activities}) do
object = Object.normalize(activity, fetch: false)

View file

@ -127,7 +127,7 @@ def decode_url(encoded) do
end
defp signed_url(url) do
:crypto.mac(:hmac, :sha, Config.get([Endpoint, :secret_key_base]), url)
:crypto.mac(:hmac, :sha256, Config.get([Endpoint, :secret_key_base]), url)
end
def filename(url_or_path) do

View file

@ -641,6 +641,7 @@ defmodule Pleroma.Web.Router do
post("/statuses/:id/unbookmark", StatusController, :unbookmark)
post("/statuses/:id/mute", StatusController, :mute_conversation)
post("/statuses/:id/unmute", StatusController, :unmute_conversation)
post("/statuses/:id/translate", StatusController, :translate)
post("/push/subscription", SubscriptionController, :create)
get("/push/subscription", SubscriptionController, :show)

View file

@ -1,59 +0,0 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ShoutChannel do
use Phoenix.Channel
alias Pleroma.User
alias Pleroma.Web.MastodonAPI.AccountView
alias Pleroma.Web.ShoutChannel.ShoutChannelState
def join("chat:public", _message, socket) do
send(self(), :after_join)
{:ok, socket}
end
def handle_info(:after_join, socket) do
push(socket, "messages", %{messages: ShoutChannelState.messages()})
{:noreply, socket}
end
def handle_in("new_msg", %{"text" => text}, %{assigns: %{user_name: user_name}} = socket) do
text = String.trim(text)
if String.length(text) in 1..Pleroma.Config.get([:shout, :limit]) do
author = User.get_cached_by_nickname(user_name)
author_json = AccountView.render("show.json", user: author, skip_visibility_check: true)
message = ShoutChannelState.add_message(%{text: text, author: author_json})
broadcast!(socket, "new_msg", message)
end
{:noreply, socket}
end
end
defmodule Pleroma.Web.ShoutChannel.ShoutChannelState do
use Agent
@max_messages 20
def start_link(_) do
Agent.start_link(fn -> %{max_id: 1, messages: []} end, name: __MODULE__)
end
def add_message(message) do
Agent.get_and_update(__MODULE__, fn state ->
id = state[:max_id] + 1
message = Map.put(message, "id", id)
messages = [message | state[:messages]] |> Enum.take(@max_messages)
{message, %{max_id: id, messages: messages}}
end)
end
def messages do
Agent.get(__MODULE__, fn state -> state[:messages] |> Enum.reverse() end)
end
end

View file

@ -7,75 +7,6 @@ defmodule Pleroma.Repo.Migrations.RenameInstanceChat do
alias Pleroma.ConfigDB
@instance_params %{group: :pleroma, key: :instance}
@shout_params %{group: :pleroma, key: :shout}
@chat_params %{group: :pleroma, key: :chat}
def up do
instance_updated? = maybe_update_instance_key(:up) != :noop
chat_updated? = maybe_update_chat_key(:up) != :noop
case Enum.any?([instance_updated?, chat_updated?]) do
true -> :ok
false -> :noop
end
end
def down do
instance_updated? = maybe_update_instance_key(:down) != :noop
chat_updated? = maybe_update_chat_key(:down) != :noop
case Enum.any?([instance_updated?, chat_updated?]) do
true -> :ok
false -> :noop
end
end
# pleroma.instance.chat_limit -> pleroma.shout.limit
defp maybe_update_instance_key(:up) do
with %ConfigDB{value: values} <- ConfigDB.get_by_params(@instance_params),
limit when is_integer(limit) <- values[:chat_limit] do
@shout_params |> Map.put(:value, limit: limit) |> ConfigDB.update_or_create()
@instance_params |> Map.put(:subkeys, [":chat_limit"]) |> ConfigDB.delete()
else
_ ->
:noop
end
end
# pleroma.shout.limit -> pleroma.instance.chat_limit
defp maybe_update_instance_key(:down) do
with %ConfigDB{value: values} <- ConfigDB.get_by_params(@shout_params),
limit when is_integer(limit) <- values[:limit] do
@instance_params |> Map.put(:value, chat_limit: limit) |> ConfigDB.update_or_create()
@shout_params |> Map.put(:subkeys, [":limit"]) |> ConfigDB.delete()
else
_ ->
:noop
end
end
# pleroma.chat.enabled -> pleroma.shout.enabled
defp maybe_update_chat_key(:up) do
with %ConfigDB{value: values} <- ConfigDB.get_by_params(@chat_params),
enabled? when is_boolean(enabled?) <- values[:enabled] do
@shout_params |> Map.put(:value, enabled: enabled?) |> ConfigDB.update_or_create()
@chat_params |> Map.put(:subkeys, [":enabled"]) |> ConfigDB.delete()
else
_ ->
:noop
end
end
# pleroma.shout.enabled -> pleroma.chat.enabled
defp maybe_update_chat_key(:down) do
with %ConfigDB{value: values} <- ConfigDB.get_by_params(@shout_params),
enabled? when is_boolean(enabled?) <- values[:enabled] do
@chat_params |> Map.put(:value, enabled: enabled?) |> ConfigDB.update_or_create()
@shout_params |> Map.put(:subkeys, [":enabled"]) |> ConfigDB.delete()
else
_ ->
:noop
end
end
def up, do: :noop
def down, do: :noop
end

View file

@ -0,0 +1 @@
{"translations":[{"detected_source_language":"PL","text":"REMOVE THE FOLLOWER!Paste this on your follower. If we get 70% of nk users...they will remove the follower!!!"}]}

View file

@ -1,80 +1,80 @@
%{
"@context" => [
{
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1",
%{
"addressRegion" => "sc:addressRegion",
"timezone" => %{"@id" => "mz:timezone", "@type" => "sc:Text"},
"isOnline" => %{"@id" => "mz:isOnline", "@type" => "sc:Boolean"},
"pt" => "https://joinpeertube.org/ns#",
"manuallyApprovesFollowers" => "as:manuallyApprovesFollowers",
"inLanguage" => "sc:inLanguage",
"address" => %{"@id" => "sc:address", "@type" => "sc:PostalAddress"},
"discoverable" => "toot:discoverable",
"repliesModerationOption" => %{
"@id" => "mz:repliesModerationOption",
"@type" => "mz:repliesModerationOptionType"
{
"addressRegion": "sc:addressRegion",
"timezone": {"@id": "mz:timezone", "@type": "sc:Text"},
"isOnline": {"@id": "mz:isOnline", "@type": "sc:Boolean"},
"pt": "https://joinpeertube.org/ns#",
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
"inLanguage": "sc:inLanguage",
"address": {"@id": "sc:address", "@type": "sc:PostalAddress"},
"discoverable": "toot:discoverable",
"repliesModerationOption": {
"@id": "mz:repliesModerationOption",
"@type": "mz:repliesModerationOptionType"
},
"sc" => "http://schema.org#",
"mz" => "https://joinmobilizon.org/ns#",
"category" => "sc:category",
"joinModeType" => %{"@id" => "mz:joinModeType", "@type" => "rdfs:Class"},
"Hashtag" => "as:Hashtag",
"propertyID" => "sc:propertyID",
"PostalAddress" => "sc:PostalAddress",
"discussions" => %{"@id" => "mz:discussions", "@type" => "@id"},
"remainingAttendeeCapacity" => "sc:remainingAttendeeCapacity",
"streetAddress" => "sc:streetAddress",
"anonymousParticipationEnabled" => %{
"@id" => "mz:anonymousParticipationEnabled",
"@type" => "sc:Boolean"
"sc": "http://schema.org#",
"mz": "https://joinmobilizon.org/ns#",
"category": "sc:category",
"joinModeType": {"@id": "mz:joinModeType", "@type": "rdfs:Class"},
"Hashtag": "as:Hashtag",
"propertyID": "sc:propertyID",
"PostalAddress": "sc:PostalAddress",
"discussions": {"@id": "mz:discussions", "@type": "@id"},
"remainingAttendeeCapacity": "sc:remainingAttendeeCapacity",
"streetAddress": "sc:streetAddress",
"anonymousParticipationEnabled": {
"@id": "mz:anonymousParticipationEnabled",
"@type": "sc:Boolean"
},
"addressLocality" => "sc:addressLocality",
"joinMode" => %{"@id" => "mz:joinMode", "@type" => "mz:joinModeType"},
"location" => %{"@id" => "sc:location", "@type" => "sc:Place"},
"toot" => "http://joinmastodon.org/ns#",
"participantCount" => %{
"@id" => "mz:participantCount",
"@type" => "sc:Integer"
"addressLocality": "sc:addressLocality",
"joinMode": {"@id": "mz:joinMode", "@type": "mz:joinModeType"},
"location": {"@id": "sc:location", "@type": "sc:Place"},
"toot": "http://joinmastodon.org/ns#",
"participantCount": {
"@id": "mz:participantCount",
"@type": "sc:Integer"
},
"uuid" => "sc:identifier",
"maximumAttendeeCapacity" => "sc:maximumAttendeeCapacity",
"participationMessage" => %{
"@id" => "mz:participationMessage",
"@type" => "sc:Text"
"uuid": "sc:identifier",
"maximumAttendeeCapacity": "sc:maximumAttendeeCapacity",
"participationMessage": {
"@id": "mz:participationMessage",
"@type": "sc:Text"
},
"openness" => %{"@id" => "mz:openness", "@type" => "@id"},
"members" => %{"@id" => "mz:members", "@type" => "@id"},
"events" => %{"@id" => "mz:events", "@type" => "@id"},
"resources" => %{"@id" => "mz:resources", "@type" => "@id"},
"addressCountry" => "sc:addressCountry",
"posts" => %{"@id" => "mz:posts", "@type" => "@id"},
"commentsEnabled" => %{
"@id" => "pt:commentsEnabled",
"@type" => "sc:Boolean"
"openness": {"@id": "mz:openness", "@type": "@id"},
"members": {"@id": "mz:members", "@type": "@id"},
"events": {"@id": "mz:events", "@type": "@id"},
"resources": {"@id": "mz:resources", "@type": "@id"},
"addressCountry": "sc:addressCountry",
"posts": {"@id": "mz:posts", "@type": "@id"},
"commentsEnabled": {
"@id": "pt:commentsEnabled",
"@type": "sc:Boolean"
},
"value" => "sc:value",
"PropertyValue" => "sc:PropertyValue",
"repliesModerationOptionType" => %{
"@id" => "mz:repliesModerationOptionType",
"@type" => "rdfs:Class"
"value": "sc:value",
"PropertyValue": "sc:PropertyValue",
"repliesModerationOptionType": {
"@id": "mz:repliesModerationOptionType",
"@type": "rdfs:Class"
},
"todos" => %{"@id" => "mz:todos", "@type" => "@id"},
"ical" => "http://www.w3.org/2002/12/cal/ical#",
"postalCode" => "sc:postalCode",
"memberCount" => %{"@id" => "mz:memberCount", "@type" => "sc:Integer"},
"@language" => "und"
"todos": {"@id": "mz:todos", "@type": "@id"},
"ical": "http://www.w3.org/2002/12/cal/ical#",
"postalCode": "sc:postalCode",
"memberCount": {"@id": "mz:memberCount", "@type": "sc:Integer"},
"@language": "und"
}
],
"actor" => "https://mobilizon.org/@tcit",
"id" => "https://mobilizon.mkljczk.pl/accept/join/fef2a925-cce5-4b8e-b12f-20afe01e5a0f",
"object" => %{
"actor" => "https://pleroma.mkljczk.pl/users/mkljczk",
"id" => "https://pleroma.mkljczk.pl/activities/7d1f3986-8b2c-48c2-b89e-d27ba8459777",
"object" => "https://mobilizon.mkljczk.pl/events/d9d08e46-81af-4ee9-91e5-1298f49beea9",
"participationMessage" => nil,
"published" => "2022-10-07T18:53:53Z",
"type" => "Join"
"actor": "https://mobilizon.org/@tcit",
"id": "https://mobilizon.mkljczk.pl/accept/join/fef2a925-cce5-4b8e-b12f-20afe01e5a0f",
"object": {
"actor": "https://pleroma.mkljczk.pl/users/mkljczk",
"id": "https://pleroma.mkljczk.pl/activities/7d1f3986-8b2c-48c2-b89e-d27ba8459777",
"object": "https://mobilizon.mkljczk.pl/events/d9d08e46-81af-4ee9-91e5-1298f49beea9",
"participationMessage": null,
"published": "2022-10-07T18:53:53Z",
"type": "Join"
},
"type" => "Accept"
"type": "Accept"
}

View file

@ -379,14 +379,4 @@ test "pool timeout" do
"Your config is using old setting name `timeout` instead of `recv_timeout` in pool settings"
end
end
test "check_old_chat_shoutbox/0" do
clear_config([:instance, :chat_limit], 1_000)
clear_config([:chat, :enabled], true)
assert capture_log(fn ->
DeprecationWarnings.check_old_chat_shoutbox()
end) =~
"Your config is using the old namespace for the Shoutbox configuration."
end
end

View file

@ -110,8 +110,8 @@ test "don't restart if no reboot time settings were changed" do
end
test "on reboot time key" do
clear_config(:shout)
insert(:config, key: :shout, value: [enabled: false])
clear_config([:rate_limit, :enabled], true)
insert(:config, key: :rate_limit, value: [enabled: false])
# Note that we don't actually restart Pleroma.
# See module Restarter.Pleroma
@ -144,10 +144,10 @@ test "on reboot time subkey" do
end
test "don't restart pleroma on reboot time key and subkey if there is false flag" do
clear_config(:shout)
clear_config([:rate_limit, :enabled], true)
clear_config(Pleroma.Captcha)
insert(:config, key: :shout, value: [enabled: false])
insert(:config, key: :rate_limit, value: [enabled: false])
insert(:config, key: Pleroma.Captcha, value: [seconds_valid: 60])
refute String.contains?(

View file

@ -0,0 +1,31 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Language.LanguageDetectorTest do
use Pleroma.Web.ConnCase
alias Pleroma.Language.LanguageDetector
setup do: clear_config([Pleroma.Language.LanguageDetector, :provider], LanguageDetectorMock)
test "it detects text language" do
detected_language = LanguageDetector.detect("Je viens d'atterrir en Tchéquie.")
assert detected_language == "fr"
end
test "it returns nil if text is not long enough" do
detected_language = LanguageDetector.detect("it returns nil")
assert detected_language == nil
end
test "it returns nil if no provider specified" do
clear_config([Pleroma.Language.LanguageDetector, :provider], nil)
detected_language = LanguageDetector.detect("this should also return nil")
assert detected_language == nil
end
end

View file

@ -0,0 +1,27 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Language.Translation.DeeplTest do
use Pleroma.Web.ConnCase
alias Pleroma.Language.Translation.Deepl
test "it translates text" do
Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
clear_config([Pleroma.Language.Translation.Deepl, :base_url], "https://api-free.deepl.com")
clear_config([Pleroma.Language.Translation.Deepl, :api_key], "API_KEY")
{:ok, res} =
Deepl.translate(
"USUNĄĆ ŚLEDZIKA!Wklej to na swojego śledzika. Jeżeli uzbieramy 70% użytkowników nk...to usuną śledzika!!!",
"pl",
"en"
)
assert %{
detected_source_language: "PL",
provider: "DeepL"
} = res
end
end

View file

@ -0,0 +1,32 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Language.TranslationTest do
use Pleroma.Web.ConnCase
alias Pleroma.Language.Translation
setup do: clear_config([Pleroma.Language.Translation, :provider], TranslationMock)
test "it translates text" do
assert {:ok,
%{
content: "txet emos",
detected_source_language: _,
provider: _
}} = Translation.translate("some text", "en", "uk")
end
test "it stores translation result in cache" do
Translation.translate("some text", "en", "uk")
assert {:ok, result} =
Cachex.get(
:translations_cache,
"en/uk/#{:crypto.hash(:sha256, "some text") |> Base.encode64()}"
)
assert result.content == "txet emos"
end
end

View file

@ -1,56 +0,0 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Repo.Migrations.RenameInstanceChatTest do
use Pleroma.DataCase
import Pleroma.Factory
import Pleroma.Tests.Helpers
alias Pleroma.ConfigDB
setup do: clear_config([:instance])
setup do: clear_config([:chat])
setup_all do: require_migration("20200806175913_rename_instance_chat")
describe "up/0" do
test "migrates chat settings to shout", %{migration: migration} do
insert(:config, group: :pleroma, key: :instance, value: [chat_limit: 6000])
insert(:config, group: :pleroma, key: :chat, value: [enabled: true])
assert migration.up() == :ok
assert ConfigDB.get_by_params(%{group: :pleroma, key: :chat}) == nil
assert ConfigDB.get_by_params(%{group: :pleroma, key: :instance}) == nil
assert ConfigDB.get_by_params(%{group: :pleroma, key: :shout}).value == [
limit: 6000,
enabled: true
]
end
test "does nothing when chat settings are not set", %{migration: migration} do
assert migration.up() == :noop
assert ConfigDB.get_by_params(%{group: :pleroma, key: :chat}) == nil
assert ConfigDB.get_by_params(%{group: :pleroma, key: :shout}) == nil
end
end
describe "down/0" do
test "migrates shout settings back to instance and chat", %{migration: migration} do
insert(:config, group: :pleroma, key: :shout, value: [limit: 42, enabled: true])
assert migration.down() == :ok
assert ConfigDB.get_by_params(%{group: :pleroma, key: :chat}).value == [enabled: true]
assert ConfigDB.get_by_params(%{group: :pleroma, key: :instance}).value == [chat_limit: 42]
assert ConfigDB.get_by_params(%{group: :pleroma, key: :shout}) == nil
end
test "does nothing when shout settings are not set", %{migration: migration} do
assert migration.down() == :noop
assert ConfigDB.get_by_params(%{group: :pleroma, key: :chat}) == nil
assert ConfigDB.get_by_params(%{group: :pleroma, key: :instance}) == nil
assert ConfigDB.get_by_params(%{group: :pleroma, key: :shout}) == nil
end
end
end

View file

@ -0,0 +1,108 @@
defmodule Pleroma.Web.ActivityPub.MRF.RemoteReportPolicyTest do
use Pleroma.DataCase, async: true
alias Pleroma.Web.ActivityPub.MRF.RemoteReportPolicy
setup do
clear_config([:mrf_remote_report, :reject_all], false)
end
test "doesn't impact local report" do
clear_config([:mrf_remote_report, :reject_anonymous], true)
clear_config([:mrf_remote_report, :reject_empty_message], true)
activity = %{
"type" => "Flag",
"actor" => "http://localhost:4001/actor"
}
assert {:ok, _} = RemoteReportPolicy.filter(activity)
end
test "rejects anonymous report if `reject_anonymous: true`" do
clear_config([:mrf_remote_report, :reject_anonymous], true)
clear_config([:mrf_remote_report, :reject_empty_message], true)
activity = %{
"type" => "Flag",
"actor" => "https://mastodon.social/actor"
}
assert {:reject, _} = RemoteReportPolicy.filter(activity)
end
test "preserves anonymous report if `reject_anonymous: false`" do
clear_config([:mrf_remote_report, :reject_anonymous], false)
clear_config([:mrf_remote_report, :reject_empty_message], false)
activity = %{
"type" => "Flag",
"actor" => "https://mastodon.social/actor"
}
assert {:ok, _} = RemoteReportPolicy.filter(activity)
end
test "rejects empty message report if `reject_empty_message: true`" do
clear_config([:mrf_remote_report, :reject_anonymous], false)
clear_config([:mrf_remote_report, :reject_empty_message], true)
activity = %{
"type" => "Flag",
"actor" => "https://mastodon.social/users/Gargron"
}
assert {:reject, _} = RemoteReportPolicy.filter(activity)
end
test "rejects empty message report (\"\") if `reject_empty_message: true`" do
clear_config([:mrf_remote_report, :reject_anonymous], false)
clear_config([:mrf_remote_report, :reject_empty_message], true)
activity = %{
"type" => "Flag",
"actor" => "https://mastodon.social/users/Gargron",
"content" => ""
}
assert {:reject, _} = RemoteReportPolicy.filter(activity)
end
test "preserves empty message report if `reject_empty_message: false`" do
clear_config([:mrf_remote_report, :reject_anonymous], false)
clear_config([:mrf_remote_report, :reject_empty_message], false)
activity = %{
"type" => "Flag",
"actor" => "https://mastodon.social/users/Gargron"
}
assert {:ok, _} = RemoteReportPolicy.filter(activity)
end
test "preserves anonymous, empty message report with all settings disabled" do
clear_config([:mrf_remote_report, :reject_anonymous], false)
clear_config([:mrf_remote_report, :reject_empty_message], false)
activity = %{
"type" => "Flag",
"actor" => "https://mastodon.social/actor"
}
assert {:ok, _} = RemoteReportPolicy.filter(activity)
end
test "reject remote report if `reject_all: true`" do
clear_config([:mrf_remote_report, :reject_all], true)
clear_config([:mrf_remote_report, :reject_anonymous], false)
clear_config([:mrf_remote_report, :reject_empty_message], false)
activity = %{
"type" => "Flag",
"actor" => "https://mastodon.social/users/Gargron",
"content" => "Transphobia"
}
assert {:reject, _} = RemoteReportPolicy.filter(activity)
end
end

View file

@ -95,7 +95,7 @@ test "it works for incoming Mobilizon join accepts" do
event_author = insert(:user)
participant = insert(:user)
event = insert(:event, %{data: %{"joinMode" => "restricted"}})
event = insert(:event, %{user: event_author, data: %{"joinMode" => "restricted"}})
event_activity = insert(:event_activity, event: event)
{:ok, join_activity} = CommonAPI.join(participant, event_activity.id)

View file

@ -9,13 +9,10 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.JoinHandlingTest do
alias Pleroma.Notification
alias Pleroma.Object
alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.Web.ActivityPub.Transmogrifier
alias Pleroma.Web.ActivityPub.Utils
import Pleroma.Factory
import Ecto.Query
import Mock
setup_all do
Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
@ -34,8 +31,7 @@ test "it works for incoming Mobilizon joins" do
|> Map.put("actor", user.ap_id)
|> Map.put("object", event.data["id"])
{:ok, %Activity{data: data, local: false} = activity} =
Transmogrifier.handle_incoming(join_data)
{:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(join_data)
event = Object.get_by_id(event.id)
@ -46,18 +42,17 @@ test "it works for incoming Mobilizon joins" do
end
test "with restricted events, it does create a Join, but not an Accept" do
user = insert(:user)
[participant, event_author] = insert_pair(:user)
event = insert(:event, %{data: %{"joinMode" => "restricted"}})
event = insert(:event, %{user: event_author, data: %{"joinMode" => "restricted"}})
join_data =
File.read!("test/fixtures/tesla_mock/mobilizon-event-join.json")
|> Jason.decode!()
|> Map.put("actor", user.ap_id)
|> Map.put("actor", participant.ap_id)
|> Map.put("object", event.data["id"])
{:ok, %Activity{data: data, local: false} = activity} =
Transmogrifier.handle_incoming(join_data)
{:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(join_data)
event = Object.get_by_id(event.id)
@ -74,7 +69,7 @@ test "with restricted events, it does create a Join, but not an Accept" do
assert Enum.empty?(accepts)
[notification] = Notification.for_user(user)
[notification] = Notification.for_user(event_author)
assert notification.type == "pleroma:participation_request"
end
end

View file

@ -220,6 +220,36 @@ test "it works for incoming notices with contentMap" do
"<p><span class=\"h-card\"><a href=\"http://localtesting.pleroma.lol/users/lain\" class=\"u-url mention\">@<span>lain</span></a></span></p>"
end
test "it only uses contentMap if content is not present" do
user = insert(:user)
message = %{
"@context" => "https://www.w3.org/ns/activitystreams",
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
"cc" => [],
"type" => "Create",
"object" => %{
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
"cc" => [],
"id" => Utils.generate_object_id(),
"type" => "Note",
"content" => "Hi",
"contentMap" => %{
"de" => "Hallo",
"uk" => "Привіт"
},
"inReplyTo" => nil,
"attributedTo" => user.ap_id
},
"actor" => user.ap_id
}
{:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(message)
object = Object.normalize(data["object"], fetch: false)
assert object.data["content"] == "Hi"
end
test "it works for incoming notices with to/cc not being an array (kroeg)" do
data = File.read!("test/fixtures/kroeg-post-activity.json") |> Jason.decode!()
@ -355,6 +385,87 @@ test "it correctly processes messages with weirdness in address fields" do
assert ["http://mastodon.example.org/users/admin/followers"] == activity.data["cc"]
assert ["https://www.w3.org/ns/activitystreams#Public"] == activity.data["to"]
end
test "it detects language from context" do
user = insert(:user)
message = %{
"@context" => ["https://www.w3.org/ns/activitystreams", %{"@language" => "pl"}],
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
"cc" => [],
"type" => "Create",
"object" => %{
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
"cc" => [],
"id" => Utils.generate_object_id(),
"type" => "Note",
"content" => "Szczęść Boże",
"attributedTo" => user.ap_id
},
"actor" => user.ap_id
}
{:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(message)
object = Object.normalize(data["object"], fetch: false)
assert object.data["language"] == "pl"
end
test "it detects language from contentMap" do
user = insert(:user)
message = %{
"@context" => "https://www.w3.org/ns/activitystreams",
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
"cc" => [],
"type" => "Create",
"object" => %{
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
"cc" => [],
"id" => Utils.generate_object_id(),
"type" => "Note",
"content" => "Szczęść Boże",
"contentMap" => %{
"de" => "Gott segne",
"pl" => "Szczęść Boże"
},
"attributedTo" => user.ap_id
},
"actor" => user.ap_id
}
{:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(message)
object = Object.normalize(data["object"], fetch: false)
assert object.data["language"] == "pl"
end
test "it detects language from content" do
clear_config([Pleroma.Language.LanguageDetector, :provider], LanguageDetectorMock)
user = insert(:user)
message = %{
"@context" => ["https://www.w3.org/ns/activitystreams"],
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
"cc" => [],
"type" => "Create",
"object" => %{
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
"cc" => [],
"id" => Utils.generate_object_id(),
"type" => "Note",
"content" => "Dieu vous bénisse, Fédivers.",
"attributedTo" => user.ap_id
},
"actor" => user.ap_id
}
{:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(message)
object = Object.normalize(data["object"], fetch: false)
assert object.data["language"] == "fr"
end
end
describe "`handle_incoming/2`, Mastodon format `replies` handling" do

View file

@ -392,6 +392,18 @@ test "it prepares a quote post" do
assert modified["object"]["quoteUrl"] == quote_id
assert modified["object"]["quoteUri"] == quote_id
end
test "it adds contentMap if language is specified" do
user = insert(:user)
{:ok, activity} = CommonAPI.post(user, %{status: "тест", language: "uk"})
{:ok, prepared} = Transmogrifier.prepare_outgoing(activity.data)
assert prepared["object"]["contentMap"] == %{
"uk" => "тест"
}
end
end
describe "user upgrade" do

View file

@ -138,16 +138,30 @@ test "does not adress actor's follower address if the activity is not public", %
end
end
test "make_json_ld_header/0" do
assert Utils.make_json_ld_header() == %{
"@context" => [
"https://www.w3.org/ns/activitystreams",
"http://localhost:4001/schemas/litepub-0.1.jsonld",
%{
"@language" => "und"
}
]
}
describe "make_json_ld_header/1" do
test "makes jsonld header" do
assert Utils.make_json_ld_header() == %{
"@context" => [
"https://www.w3.org/ns/activitystreams",
"http://localhost:4001/schemas/litepub-0.1.jsonld",
%{
"@language" => "und"
}
]
}
end
test "includes language if specified" do
assert Utils.make_json_ld_header(%{"language" => "pl"}) == %{
"@context" => [
"https://www.w3.org/ns/activitystreams",
"http://localhost:4001/schemas/litepub-0.1.jsonld",
%{
"@language" => "pl"
}
]
}
end
end
describe "get_existing_votes" do

View file

@ -409,7 +409,7 @@ test "saving config with partial update", %{conn: conn} do
end
test "saving config which need pleroma reboot", %{conn: conn} do
clear_config([:shout, :enabled], true)
clear_config([:streamer, :workers], 3)
assert conn
|> put_req_header("content-type", "application/json")
@ -417,17 +417,17 @@ test "saving config which need pleroma reboot", %{conn: conn} do
"/api/pleroma/admin/config",
%{
configs: [
%{group: ":pleroma", key: ":shout", value: [%{"tuple" => [":enabled", true]}]}
%{group: ":pleroma", key: ":streamer", value: [%{"tuple" => [":workers", 5]}]}
]
}
)
|> json_response_and_validate_schema(200) == %{
"configs" => [
%{
"db" => [":enabled"],
"db" => [":workers"],
"group" => ":pleroma",
"key" => ":shout",
"value" => [%{"tuple" => [":enabled", true]}]
"key" => ":streamer",
"value" => [%{"tuple" => [":workers", 5]}]
}
],
"need_reboot" => true
@ -454,7 +454,7 @@ test "saving config which need pleroma reboot", %{conn: conn} do
end
test "update setting which need reboot, don't change reboot flag until reboot", %{conn: conn} do
clear_config([:shout, :enabled], true)
clear_config([:streamer, :workers], 3)
assert conn
|> put_req_header("content-type", "application/json")
@ -462,17 +462,17 @@ test "update setting which need reboot, don't change reboot flag until reboot",
"/api/pleroma/admin/config",
%{
configs: [
%{group: ":pleroma", key: ":shout", value: [%{"tuple" => [":enabled", true]}]}
%{group: ":pleroma", key: ":streamer", value: [%{"tuple" => [":workers", 5]}]}
]
}
)
|> json_response_and_validate_schema(200) == %{
"configs" => [
%{
"db" => [":enabled"],
"db" => [":workers"],
"group" => ":pleroma",
"key" => ":shout",
"value" => [%{"tuple" => [":enabled", true]}]
"key" => ":streamer",
"value" => [%{"tuple" => [":workers", 5]}]
}
],
"need_reboot" => true

View file

@ -1823,7 +1823,6 @@ test "verify_credentials" do
response = json_response_and_validate_schema(conn, 200)
assert %{"id" => id, "source" => %{"privacy" => "public"}} = response
assert response["pleroma"]["chat_token"]
assert response["pleroma"]["unread_notifications_count"] == 6
assert id == to_string(user.id)
end

View file

@ -43,7 +43,6 @@ test "get instance information", %{conn: conn} do
"background_upload_limit" => _,
"banner_upload_limit" => _,
"background_image" => from_config_background,
"shout_limit" => _,
"description_limit" => _,
"rules" => _,
"pleroma" => %{
@ -139,9 +138,19 @@ test "get oauth_consumer_strategies", %{conn: conn} do
assert result["pleroma"]["oauth_consumer_strategies"] == ["keycloak"]
end
test "get instance information v2", %{conn: conn} do
clear_config([:auth, :oauth_consumer_strategies], [])
test "get instance contact information", %{conn: conn} do
user = insert(:user, %{local: true})
clear_config([:instance, :contact_username], user.nickname)
conn = get(conn, "/api/v1/instance")
assert result = json_response_and_validate_schema(conn, 200)
assert result["contact_account"]["id"] == user.id
end
test "get instance information v2", %{conn: conn} do
assert get(conn, "/api/v2/instance")
|> json_response_and_validate_schema(200)
end

View file

@ -2229,4 +2229,64 @@ test "it returns 404 if the user cannot see the post", %{conn: conn} do
|> json_response_and_validate_schema(:not_found)
end
end
describe "translating statuses" do
setup do: clear_config([Pleroma.Language.Translation, :provider], TranslationMock)
test "it translates a status to user language" do
user = insert(:user, language: "fr")
%{conn: conn, user: user} = oauth_access(["read:statuses"], user: user)
another_user = insert(:user)
{:ok, activity} =
CommonAPI.post(another_user, %{
status: "Cześć!",
visibility: "public",
language: "pl"
})
response =
conn
|> post("/api/v1/statuses/#{activity.id}/translate")
|> json_response_and_validate_schema(200)
assert response == %{
"content" => "!ćśezC",
"detected_source_language" => "pl",
"provider" => "TranslationMock"
}
end
test "it returns an error if no target language provided" do
%{conn: conn, user: user} = oauth_access(["read:statuses"])
another_user = insert(:user)
{:ok, activity} =
CommonAPI.post(another_user, %{
status: "Cześć!",
language: "pl"
})
response =
conn
|> post("/api/v1/statuses/#{activity.id}/translate")
|> json_response_and_validate_schema(400)
end
test "it doesn't translate non-public statuses" do
%{conn: conn, user: user} = oauth_access(["read:statuses"])
{:ok, activity} =
CommonAPI.post(user, %{
status: "Cześć!",
visibility: "private",
language: "pl"
})
response =
conn
|> post("/api/v1/statuses/#{activity.id}/translate")
|> json_response_and_validate_schema(404)
end
end
end

View file

@ -819,6 +819,16 @@ test "it shows edited_at" do
assert status.edited_at
end
test "it shows post language" do
user = insert(:user)
{:ok, post} = CommonAPI.post(user, %{status: "Szczęść Boże", language: "pl"})
status = StatusView.render("show.json", activity: post)
assert status.language == "pl"
end
test "with a source object" do
note =
insert(:note,

View file

@ -144,7 +144,7 @@ test "video attachments have image thumbnail with WxH metadata with Preview Prox
[
property: "og:image",
content:
"http://localhost:4001/proxy/preview/LzAnlke-l5oZbNzWsrHfprX1rGw/aHR0cHM6Ly9wbGVyb21hLmdvdi9hYm91dC9qdWNoZS53ZWJt/juche.webm"
"http://localhost:4001/proxy/preview/YyXfYXyaEAGVHVfKF-HRc4GrFMY03Smi7effZ8WKow8/aHR0cHM6Ly9wbGVyb21hLmdvdi9hYm91dC9qdWNoZS53ZWJt/juche.webm"
], []} in result
end

View file

@ -1,41 +0,0 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ShoutChannelTest do
use Pleroma.Web.ChannelCase
alias Pleroma.Web.ShoutChannel
alias Pleroma.Web.UserSocket
import Pleroma.Factory
setup do
user = insert(:user)
{:ok, _, socket} =
socket(UserSocket, "", %{user_name: user.nickname})
|> subscribe_and_join(ShoutChannel, "chat:public")
{:ok, socket: socket}
end
test "it broadcasts a message", %{socket: socket} do
push(socket, "new_msg", %{"text" => "why is tenshi eating a corndog so cute?"})
assert_broadcast("new_msg", %{text: "why is tenshi eating a corndog so cute?"})
end
describe "message lengths" do
setup do: clear_config([:shout, :limit])
test "it ignores messages of length zero", %{socket: socket} do
push(socket, "new_msg", %{"text" => ""})
refute_broadcast("new_msg", %{text: ""})
end
test "it ignores messages above a certain length", %{socket: socket} do
clear_config([:shout, :limit], 2)
push(socket, "new_msg", %{"text" => "123"})
refute_broadcast("new_msg", %{text: "123"})
end
end
end

View file

@ -1588,6 +1588,15 @@ def post("http://404.site" <> _, _, _, _) do
}}
end
def post("https://api-free.deepl.com/v2/translate" <> _, _, _, _) do
{:ok,
%Tesla.Env{
status: 200,
body: File.read!("test/fixtures/tesla_mock/deepl-translation.json"),
headers: [{"content-type", "application/json"}]
}}
end
def post(url, query, body, headers) do
{:error,
"Mock response not implemented for POST #{inspect(url)}, #{query}, #{inspect(body)}, #{inspect(headers)}"}

View file

@ -0,0 +1,18 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule LanguageDetectorMock do
alias Pleroma.Language.LanguageDetector.Provider
@behaviour Provider
@impl Provider
def missing_dependencies, do: []
@impl Provider
def configured?, do: true
@impl Provider
def detect(_text), do: "fr"
end

View file

@ -0,0 +1,22 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule TranslationMock do
alias Pleroma.Language.Translation.Provider
@behaviour Provider
@impl Provider
def configured?, do: true
@impl Provider
def translate(content, source_language, _target_language) do
{:ok,
%{
content: content |> String.reverse(),
detected_source_language: source_language,
provider: "TranslationMock"
}}
end
end