Merge branch 'merge-pleroma' into 'develop'
merge-pleroma See merge request soapbox-pub/soapbox-be!145
This commit is contained in:
commit
3b964ee555
30 changed files with 794 additions and 214 deletions
|
@ -32,6 +32,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
- PleromaAPI: Add `GET /api/v1/pleroma/birthdays` API endpoint
|
||||
- Make backend-rendered pages translatable. This includes emails. Pages returned as a HTTP response are translated using the language specified in the `userLanguage` cookie, or the `Accept-Language` header. Emails are translated using the `language` field when registering. This language can be changed by `PATCH /api/v1/accounts/update_credentials` with the `language` field.
|
||||
- Uploadfilter `Pleroma.Upload.Filter.Exiftool.ReadDescription` returns description values to the FE so they can pre fill the image description field
|
||||
- Added move account API
|
||||
|
||||
### Fixed
|
||||
- Subscription(Bell) Notifications: Don't create from Pipeline Ingested replies
|
||||
|
|
|
@ -1737,6 +1737,11 @@
|
|||
type: :boolean,
|
||||
description: "Sign object fetches with HTTP signatures"
|
||||
},
|
||||
%{
|
||||
key: :authorized_fetch_mode,
|
||||
type: :boolean,
|
||||
description: "Require HTTP signatures for AP fetches"
|
||||
},
|
||||
%{
|
||||
key: :note_replies_output_limit,
|
||||
type: :integer,
|
||||
|
|
|
@ -30,7 +30,7 @@ def search(user, search_query, options \\ []) do
|
|||
Activity
|
||||
|> Activity.with_preloaded_object()
|
||||
|> Activity.restrict_deactivated_users()
|
||||
|> restrict_public()
|
||||
|> restrict_public(user)
|
||||
|> query_with(index_type, search_query, search_function)
|
||||
|> maybe_restrict_local(user)
|
||||
|> maybe_restrict_author(author)
|
||||
|
@ -57,7 +57,19 @@ def maybe_restrict_blocked(query, %User{} = user) do
|
|||
|
||||
def maybe_restrict_blocked(query, _), do: query
|
||||
|
||||
defp restrict_public(q) do
|
||||
defp restrict_public(q, user) when not is_nil(user) do
|
||||
intended_recipients = [
|
||||
Pleroma.Constants.as_public(),
|
||||
Pleroma.Web.ActivityPub.Utils.as_local_public()
|
||||
]
|
||||
|
||||
from([a, o] in q,
|
||||
where: fragment("?->>'type' = 'Create'", a.data),
|
||||
where: fragment("? && ?", ^intended_recipients, a.recipients)
|
||||
)
|
||||
end
|
||||
|
||||
defp restrict_public(q, _user) do
|
||||
from([a, o] in q,
|
||||
where: fragment("?->>'type' = 'Create'", a.data),
|
||||
where: ^Pleroma.Constants.as_public() in a.recipients
|
||||
|
|
|
@ -9,6 +9,7 @@ defmodule Pleroma.Emoji do
|
|||
"""
|
||||
use GenServer
|
||||
|
||||
alias Pleroma.Emoji.Combinations
|
||||
alias Pleroma.Emoji.Loader
|
||||
|
||||
require Logger
|
||||
|
@ -137,4 +138,17 @@ def is_unicode_emoji?(unquote(emoji)), do: true
|
|||
end
|
||||
|
||||
def is_unicode_emoji?(_), do: false
|
||||
|
||||
emoji_qualification_map =
|
||||
emojis
|
||||
|> Enum.filter(&String.contains?(&1, "\uFE0F"))
|
||||
|> Combinations.variate_emoji_qualification()
|
||||
|
||||
for {qualified, unqualified_list} <- emoji_qualification_map do
|
||||
for unqualified <- unqualified_list do
|
||||
def fully_qualify_emoji(unquote(unqualified)), do: unquote(qualified)
|
||||
end
|
||||
end
|
||||
|
||||
def fully_qualify_emoji(emoji), do: emoji
|
||||
end
|
||||
|
|
45
lib/pleroma/emoji/combinations.ex
Normal file
45
lib/pleroma/emoji/combinations.ex
Normal file
|
@ -0,0 +1,45 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Emoji.Combinations do
|
||||
# FE0F is the emoji variation sequence. It is used for fully-qualifying
|
||||
# emoji, and that includes emoji combinations.
|
||||
# This code generates combinations per emoji: for each FE0F, all possible
|
||||
# combinations of the character being removed or staying will be generated.
|
||||
# This is made as an attempt to find all partially-qualified and unqualified
|
||||
# versions of a fully-qualified emoji.
|
||||
# I have found *no cases* for which this would be a problem, after browsing
|
||||
# the entire emoji list in emoji-test.txt. This is safe, and, sadly, most
|
||||
# likely sane too.
|
||||
|
||||
defp qualification_combinations(codepoints) do
|
||||
qualification_combinations([[]], codepoints)
|
||||
end
|
||||
|
||||
defp qualification_combinations(acc, []), do: acc
|
||||
|
||||
defp qualification_combinations(acc, ["\uFE0F" | tail]) do
|
||||
acc
|
||||
|> Enum.flat_map(fn x -> [x, x ++ ["\uFE0F"]] end)
|
||||
|> qualification_combinations(tail)
|
||||
end
|
||||
|
||||
defp qualification_combinations(acc, [codepoint | tail]) do
|
||||
acc
|
||||
|> Enum.map(&Kernel.++(&1, [codepoint]))
|
||||
|> qualification_combinations(tail)
|
||||
end
|
||||
|
||||
def variate_emoji_qualification(emoji) when is_binary(emoji) do
|
||||
emoji
|
||||
|> String.codepoints()
|
||||
|> qualification_combinations()
|
||||
|> Enum.map(&List.to_string/1)
|
||||
end
|
||||
|
||||
def variate_emoji_qualification(emoji) when is_list(emoji) do
|
||||
emoji
|
||||
|> Enum.map(fn emoji -> {emoji, variate_emoji_qualification(emoji)} end)
|
||||
end
|
||||
end
|
|
@ -1493,12 +1493,12 @@ def get_recipients_from_activity(%Activity{recipients: to, actor: actor}) do
|
|||
{:ok, list(UserRelationship.t())} | {:error, String.t()}
|
||||
def mute(%User{} = muter, %User{} = mutee, params \\ %{}) do
|
||||
notifications? = Map.get(params, :notifications, true)
|
||||
expires_in = Map.get(params, :expires_in, 0)
|
||||
duration = Map.get(params, :duration, 0)
|
||||
|
||||
expires_at =
|
||||
if expires_in > 0 do
|
||||
if duration > 0 do
|
||||
DateTime.utc_now()
|
||||
|> DateTime.add(expires_in)
|
||||
|> DateTime.add(duration)
|
||||
else
|
||||
nil
|
||||
end
|
||||
|
@ -1512,7 +1512,7 @@ def mute(%User{} = muter, %User{} = mutee, params \\ %{}) do
|
|||
expires_at
|
||||
)) ||
|
||||
{:ok, nil} do
|
||||
if expires_in > 0 do
|
||||
if duration > 0 do
|
||||
Pleroma.Workers.MuteExpireWorker.enqueue(
|
||||
"unmute_user",
|
||||
%{"muter_id" => muter.id, "mutee_id" => mutee.id},
|
||||
|
|
|
@ -32,9 +32,7 @@ defmodule Pleroma.User.Backup do
|
|||
end
|
||||
|
||||
def create(user, admin_id \\ nil) do
|
||||
with :ok <- validate_email_enabled(),
|
||||
:ok <- validate_user_email(user),
|
||||
:ok <- validate_limit(user, admin_id),
|
||||
with :ok <- validate_limit(user, admin_id),
|
||||
{:ok, backup} <- user |> new() |> Repo.insert() do
|
||||
BackupWorker.process(backup, admin_id)
|
||||
end
|
||||
|
@ -86,20 +84,6 @@ defp validate_limit(user, nil) do
|
|||
end
|
||||
end
|
||||
|
||||
defp validate_email_enabled do
|
||||
if Pleroma.Config.get([Pleroma.Emails.Mailer, :enabled]) do
|
||||
:ok
|
||||
else
|
||||
{:error, dgettext("errors", "Backups require enabled email")}
|
||||
end
|
||||
end
|
||||
|
||||
defp validate_user_email(%User{email: nil}) do
|
||||
{:error, dgettext("errors", "Email is required")}
|
||||
end
|
||||
|
||||
defp validate_user_email(%User{email: email}) when is_binary(email), do: :ok
|
||||
|
||||
def get_last(user_id) do
|
||||
__MODULE__
|
||||
|> where(user_id: ^user_id)
|
||||
|
|
|
@ -517,9 +517,18 @@ def fetch_activities(recipients, opts \\ %{}, pagination \\ :keyset) do
|
|||
|
||||
@spec fetch_public_or_unlisted_activities(map(), Pagination.type()) :: [Activity.t()]
|
||||
def fetch_public_or_unlisted_activities(opts \\ %{}, pagination \\ :keyset) do
|
||||
includes_local_public = Map.get(opts, :includes_local_public, false)
|
||||
|
||||
opts = Map.delete(opts, :user)
|
||||
|
||||
[Constants.as_public()]
|
||||
intended_recipients =
|
||||
if includes_local_public do
|
||||
[Constants.as_public(), as_local_public()]
|
||||
else
|
||||
[Constants.as_public()]
|
||||
end
|
||||
|
||||
intended_recipients
|
||||
|> fetch_activities_query(opts)
|
||||
|> restrict_unlisted(opts)
|
||||
|> fetch_paginated_optimized(opts, pagination)
|
||||
|
@ -619,9 +628,11 @@ defp restrict_thread_visibility(query, %{user: %User{skip_thread_containment: tr
|
|||
do: query
|
||||
|
||||
defp restrict_thread_visibility(query, %{user: %User{ap_id: ap_id}}, _) do
|
||||
local_public = as_local_public()
|
||||
|
||||
from(
|
||||
a in query,
|
||||
where: fragment("thread_visibility(?, (?)->>'id') = true", ^ap_id, a.data)
|
||||
where: fragment("thread_visibility(?, (?)->>'id', ?) = true", ^ap_id, a.data, ^local_public)
|
||||
)
|
||||
end
|
||||
|
||||
|
@ -710,8 +721,12 @@ defp fetch_activities_for_reading_user(reading_user, params) do
|
|||
defp user_activities_recipients(%{godmode: true}), do: []
|
||||
|
||||
defp user_activities_recipients(%{reading_user: reading_user}) do
|
||||
if reading_user do
|
||||
[Constants.as_public(), reading_user.ap_id | User.following(reading_user)]
|
||||
if not is_nil(reading_user) and reading_user.local do
|
||||
[
|
||||
Constants.as_public(),
|
||||
as_local_public(),
|
||||
reading_user.ap_id | User.following(reading_user)
|
||||
]
|
||||
else
|
||||
[Constants.as_public()]
|
||||
end
|
||||
|
|
|
@ -63,8 +63,7 @@ defp fix(data) do
|
|||
end
|
||||
|
||||
defp fix_emoji_qualification(%{"content" => emoji} = data) do
|
||||
# Emoji variation sequence
|
||||
new_emoji = emoji <> "\uFE0F"
|
||||
new_emoji = Pleroma.Emoji.fully_qualify_emoji(emoji)
|
||||
|
||||
cond do
|
||||
Pleroma.Emoji.is_unicode_emoji?(emoji) ->
|
||||
|
|
|
@ -84,7 +84,10 @@ def visible_for_user?(%{__struct__: module} = message, user)
|
|||
when module in [Activity, Object] do
|
||||
x = [user.ap_id | User.following(user)]
|
||||
y = [message.data["actor"]] ++ message.data["to"] ++ (message.data["cc"] || [])
|
||||
is_public?(message) || Enum.any?(x, &(&1 in y))
|
||||
|
||||
user_is_local = user.local
|
||||
federatable = not is_local_public?(message)
|
||||
(is_public?(message) || Enum.any?(x, &(&1 in y))) and (user_is_local || federatable)
|
||||
end
|
||||
|
||||
def entire_thread_visible_for_user?(%Activity{} = activity, %User{} = user) do
|
||||
|
|
|
@ -278,11 +278,17 @@ def mute_operation do
|
|||
%Schema{allOf: [BooleanLike], default: true},
|
||||
"Mute notifications in addition to statuses? Defaults to `true`."
|
||||
),
|
||||
Operation.parameter(
|
||||
:duration,
|
||||
:query,
|
||||
%Schema{type: :integer},
|
||||
"Expire the mute in `duration` seconds. Default 0 for infinity"
|
||||
),
|
||||
Operation.parameter(
|
||||
:expires_in,
|
||||
:query,
|
||||
%Schema{type: :integer, default: 0},
|
||||
"Expire the mute in `expires_in` seconds. Default 0 for infinity"
|
||||
"Deprecated, use `duration` instead"
|
||||
)
|
||||
],
|
||||
responses: %{
|
||||
|
@ -908,10 +914,15 @@ defp mute_request do
|
|||
description: "Mute notifications in addition to statuses? Defaults to true.",
|
||||
default: true
|
||||
},
|
||||
duration: %Schema{
|
||||
type: :integer,
|
||||
nullable: true,
|
||||
description: "Expire the mute in `expires_in` seconds. Default 0 for infinity"
|
||||
},
|
||||
expires_in: %Schema{
|
||||
type: :integer,
|
||||
nullable: true,
|
||||
description: "Expire the mute in `expires_in` seconds. Default 0 for infinity",
|
||||
description: "Deprecated, use `duration` instead",
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
|
|
|
@ -415,6 +415,10 @@ def unfollow(%{assigns: %{user: follower, account: followed}} = conn, _params) d
|
|||
|
||||
@doc "POST /api/v1/accounts/:id/mute"
|
||||
def mute(%{assigns: %{user: muter, account: muted}, body_params: params} = conn, _params) do
|
||||
params =
|
||||
params
|
||||
|> Map.put_new(:duration, Map.get(params, :expires_in, 0))
|
||||
|
||||
with {:ok, _user_relationships} <- User.mute(muter, muted, params) do
|
||||
render(conn, "relationship.json", user: muter, target: muted)
|
||||
else
|
||||
|
@ -509,7 +513,7 @@ def mutes(%{assigns: %{user: user}} = conn, params) do
|
|||
users =
|
||||
user
|
||||
|> User.muted_users_relation(_restrict_deactivated = true)
|
||||
|> Pleroma.Pagination.fetch_paginated(Map.put(params, :skip_order, true))
|
||||
|> Pleroma.Pagination.fetch_paginated(params)
|
||||
|
||||
conn
|
||||
|> add_link_headers(users)
|
||||
|
@ -527,7 +531,7 @@ def blocks(%{assigns: %{user: user}} = conn, params) do
|
|||
users =
|
||||
user
|
||||
|> User.blocked_users_relation(_restrict_deactivated = true)
|
||||
|> Pleroma.Pagination.fetch_paginated(Map.put(params, :skip_order, true))
|
||||
|> Pleroma.Pagination.fetch_paginated(params)
|
||||
|
||||
conn
|
||||
|> add_link_headers(users)
|
||||
|
|
|
@ -112,6 +112,8 @@ def public(%{assigns: %{user: user}} = conn, params) do
|
|||
|> Map.put(:muting_user, user)
|
||||
|> Map.put(:reply_filtering_user, user)
|
||||
|> Map.put(:instance, params[:instance])
|
||||
# Restricts unfederated content to authenticated users
|
||||
|> Map.put(:includes_local_public, not is_nil(user))
|
||||
|> ActivityPub.fetch_public_activities()
|
||||
|
||||
conn
|
||||
|
|
|
@ -37,10 +37,7 @@ def perform(%Job{
|
|||
backup_id |> Backup.get() |> Backup.process(),
|
||||
{:ok, _job} <- schedule_deletion(backup),
|
||||
:ok <- Backup.remove_outdated(backup),
|
||||
{:ok, _} <-
|
||||
backup
|
||||
|> Pleroma.Emails.UserEmail.backup_is_ready_email(admin_user_id)
|
||||
|> Pleroma.Emails.Mailer.deliver() do
|
||||
:ok <- maybe_deliver_email(backup, admin_user_id) do
|
||||
{:ok, backup}
|
||||
end
|
||||
end
|
||||
|
@ -51,4 +48,23 @@ def perform(%Job{args: %{"op" => "delete", "backup_id" => backup_id}}) do
|
|||
nil -> :ok
|
||||
end
|
||||
end
|
||||
|
||||
defp has_email?(user) do
|
||||
not is_nil(user.email) and user.email != ""
|
||||
end
|
||||
|
||||
defp maybe_deliver_email(backup, admin_user_id) do
|
||||
has_mailer = Pleroma.Config.get([Pleroma.Emails.Mailer, :enabled])
|
||||
backup = backup |> Pleroma.Repo.preload(:user)
|
||||
|
||||
if has_email?(backup.user) and has_mailer do
|
||||
backup
|
||||
|> Pleroma.Emails.UserEmail.backup_is_ready_email(admin_user_id)
|
||||
|> Pleroma.Emails.Mailer.deliver()
|
||||
|
||||
:ok
|
||||
else
|
||||
:ok
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -5997,3 +5997,27 @@ msgstr ""
|
|||
msgctxt "config label at :web_push_encryption-:vapid_details > :subject"
|
||||
msgid "Subject"
|
||||
msgstr ""
|
||||
|
||||
#, elixir-autogen, elixir-format
|
||||
#: lib/pleroma/docs/translator.ex:5
|
||||
msgctxt "config description at :pleroma-:activitypub > :authorized_fetch_mode"
|
||||
msgid "Require HTTP signatures for AP fetches"
|
||||
msgstr ""
|
||||
|
||||
#, elixir-autogen, elixir-format
|
||||
#: lib/pleroma/docs/translator.ex:5
|
||||
msgctxt "config description at :pleroma-:instance > :short_description"
|
||||
msgid "Shorter version of instance description. It can be seen on `/api/v1/instance`"
|
||||
msgstr ""
|
||||
|
||||
#, elixir-autogen, elixir-format
|
||||
#: lib/pleroma/docs/translator.ex:5
|
||||
msgctxt "config label at :pleroma-:activitypub > :authorized_fetch_mode"
|
||||
msgid "Authorized fetch mode"
|
||||
msgstr ""
|
||||
|
||||
#, elixir-autogen, elixir-format
|
||||
#: lib/pleroma/docs/translator.ex:5
|
||||
msgctxt "config label at :pleroma-:instance > :short_description"
|
||||
msgid "Short description"
|
||||
msgstr ""
|
||||
|
|
|
@ -3,7 +3,7 @@ msgstr ""
|
|||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2022-07-21 04:21+0300\n"
|
||||
"PO-Revision-Date: 2022-07-22 19:00+0000\n"
|
||||
"PO-Revision-Date: 2022-07-24 10:04+0000\n"
|
||||
"Last-Translator: Yating Zhan <thestrandedvalley@protonmail.com>\n"
|
||||
"Language-Team: Chinese (Simplified) <http://weblate.pleroma-dev.ebin.club/"
|
||||
"projects/pleroma/pleroma-backend-domain-config_descriptions/zh_Hans/>\n"
|
||||
|
@ -419,13 +419,13 @@ msgstr "包含不能直接被「Oban」解读的自定工人选项"
|
|||
#, elixir-autogen, elixir-format
|
||||
msgctxt "config description at :pleroma-ConcurrentLimiter"
|
||||
msgid "Limits configuration for background tasks."
|
||||
msgstr ""
|
||||
msgstr "后台任务的限制的配置。"
|
||||
|
||||
#: lib/pleroma/docs/translator.ex:5
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "config description at :pleroma-Oban"
|
||||
msgid "[Oban](https://github.com/sorentwo/oban) asynchronous job processor configuration."
|
||||
msgstr ""
|
||||
msgstr "[Oban](https://github.com/sorentwo/oban) 异步工作处理器的配置。"
|
||||
|
||||
#: lib/pleroma/docs/translator.ex:5
|
||||
#, elixir-autogen, elixir-format
|
||||
|
@ -438,12 +438,15 @@ msgstr "验证码相关设定"
|
|||
msgctxt "config description at :pleroma-Pleroma.Captcha.Kocaptcha"
|
||||
msgid "Kocaptcha is a very simple captcha service with a single API endpoint, the source code is here: https://github.com/koto-bank/kocaptcha. The default endpoint (https://captcha.kotobank.ch) is hosted by the developer."
|
||||
msgstr ""
|
||||
"Kocaptcha 是一个非常简单的验证码服务,只有一个 API 终点,源码在此: "
|
||||
"https://github.com/koto-bank/kocaptcha 。默认终点( https://"
|
||||
"captcha.kotobank.ch )由开发者托管。"
|
||||
|
||||
#: lib/pleroma/docs/translator.ex:5
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "config description at :pleroma-Pleroma.Emails.Mailer"
|
||||
msgid "Mailer-related settings"
|
||||
msgstr ""
|
||||
msgstr "邮递员相关设置"
|
||||
|
||||
#: lib/pleroma/docs/translator.ex:5
|
||||
#, elixir-autogen, elixir-format
|
||||
|
@ -491,13 +494,13 @@ msgstr ""
|
|||
#, elixir-autogen, elixir-format
|
||||
msgctxt "config description at :pleroma-Pleroma.Uploaders.Local"
|
||||
msgid "Local uploader-related settings"
|
||||
msgstr ""
|
||||
msgstr "本地上传器相关设置"
|
||||
|
||||
#: lib/pleroma/docs/translator.ex:5
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "config description at :pleroma-Pleroma.Uploaders.S3"
|
||||
msgid "S3 uploader-related settings"
|
||||
msgstr ""
|
||||
msgstr "S3 上传器相关设置"
|
||||
|
||||
#: lib/pleroma/docs/translator.ex:5
|
||||
#, elixir-autogen, elixir-format
|
||||
|
@ -509,7 +512,7 @@ msgstr "账户备份"
|
|||
#, elixir-autogen, elixir-format
|
||||
msgctxt "config description at :pleroma-Pleroma.Web.MediaProxy.Invalidation.Http"
|
||||
msgid "HTTP invalidate settings"
|
||||
msgstr ""
|
||||
msgstr "HTTP 无效化设置"
|
||||
|
||||
#: lib/pleroma/docs/translator.ex:5
|
||||
#, elixir-autogen, elixir-format
|
||||
|
@ -557,19 +560,19 @@ msgstr ""
|
|||
#, elixir-autogen, elixir-format
|
||||
msgctxt "config label at :ex_aws-:s3"
|
||||
msgid "S3"
|
||||
msgstr ""
|
||||
msgstr "S3"
|
||||
|
||||
#: lib/pleroma/docs/translator.ex:5
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "config label at :logger-:console"
|
||||
msgid "Console Logger"
|
||||
msgstr ""
|
||||
msgstr "终端日志器"
|
||||
|
||||
#: lib/pleroma/docs/translator.ex:5
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "config label at :logger-:ex_syslogger"
|
||||
msgid "ExSyslogger"
|
||||
msgstr ""
|
||||
msgstr "ExSyslogger"
|
||||
|
||||
#: lib/pleroma/docs/translator.ex:5
|
||||
#, elixir-autogen, elixir-format
|
||||
|
@ -593,7 +596,7 @@ msgstr "验证"
|
|||
#, elixir-autogen, elixir-format
|
||||
msgctxt "config label at :pleroma-:connections_pool"
|
||||
msgid "Connections pool"
|
||||
msgstr ""
|
||||
msgstr "连接池"
|
||||
|
||||
#: lib/pleroma/docs/translator.ex:5
|
||||
#, elixir-autogen, elixir-format
|
||||
|
@ -731,7 +734,7 @@ msgstr ""
|
|||
#, elixir-autogen, elixir-format
|
||||
msgctxt "config label at :pleroma-:mrf_hashtag"
|
||||
msgid "MRF Hashtag"
|
||||
msgstr ""
|
||||
msgstr "MRF 标签"
|
||||
|
||||
#: lib/pleroma/docs/translator.ex:5
|
||||
#, elixir-autogen, elixir-format
|
||||
|
@ -875,7 +878,7 @@ msgstr "欢迎"
|
|||
#, elixir-autogen, elixir-format
|
||||
msgctxt "config label at :pleroma-:workers"
|
||||
msgid "Workers"
|
||||
msgstr ""
|
||||
msgstr "工人"
|
||||
|
||||
#: lib/pleroma/docs/translator.ex:5
|
||||
#, elixir-autogen, elixir-format
|
||||
|
@ -1121,7 +1124,7 @@ msgstr "日志等级"
|
|||
#, elixir-autogen, elixir-format
|
||||
msgctxt "config description at :pleroma > :admin_token"
|
||||
msgid "Admin token"
|
||||
msgstr ""
|
||||
msgstr "管理令牌"
|
||||
|
||||
#: lib/pleroma/docs/translator.ex:5
|
||||
#, elixir-autogen, elixir-format
|
||||
|
@ -1157,7 +1160,7 @@ msgstr ""
|
|||
#, elixir-autogen, elixir-format
|
||||
msgctxt "config description at :pleroma-:activitypub > :unfollow_blocked"
|
||||
msgid "Whether blocks result in people getting unfollowed"
|
||||
msgstr ""
|
||||
msgstr "屏蔽对象时是否同时取消对其的关注"
|
||||
|
||||
#: lib/pleroma/docs/translator.ex:5
|
||||
#, elixir-autogen, elixir-format
|
||||
|
@ -1169,7 +1172,7 @@ msgstr ""
|
|||
#, elixir-autogen, elixir-format
|
||||
msgctxt "config description at :pleroma-:assets > :default_user_avatar"
|
||||
msgid "URL of the default user avatar"
|
||||
msgstr ""
|
||||
msgstr "默认用户头像的网址"
|
||||
|
||||
#: lib/pleroma/docs/translator.ex:5
|
||||
#, elixir-autogen, elixir-format
|
||||
|
@ -1205,7 +1208,7 @@ msgstr ""
|
|||
#, elixir-autogen, elixir-format
|
||||
msgctxt "config description at :pleroma-:connections_pool > :connect_timeout"
|
||||
msgid "Timeout while `gun` will wait until connection is up. Default: 5000ms."
|
||||
msgstr ""
|
||||
msgstr "「Gun」等待连接时触发超时的上限。默认为5000ms。"
|
||||
|
||||
#: lib/pleroma/docs/translator.ex:5
|
||||
#, elixir-autogen, elixir-format
|
||||
|
@ -1253,7 +1256,7 @@ msgstr "非活跃用户数量最低门槛"
|
|||
#, elixir-autogen, elixir-format
|
||||
msgctxt "config description at :pleroma-:email_notifications > :digest > :interval"
|
||||
msgid "Minimum interval between digest emails to one user"
|
||||
msgstr "单个用户能收到摘要邮件的间隔频次"
|
||||
msgstr "单个用户每次收到摘要邮件的间隔"
|
||||
|
||||
#: lib/pleroma/docs/translator.ex:5
|
||||
#, elixir-autogen, elixir-format
|
||||
|
@ -1301,7 +1304,7 @@ msgstr ""
|
|||
#, elixir-autogen, elixir-format
|
||||
msgctxt "config description at :pleroma-:feed > :post_title > :max_length"
|
||||
msgid "Maximum number of characters before truncating title"
|
||||
msgstr "不被折叠的用户名的字符上限"
|
||||
msgstr "不被折叠的用户名的字数上限"
|
||||
|
||||
#: lib/pleroma/docs/translator.ex:5
|
||||
#, elixir-autogen, elixir-format
|
||||
|
@ -1325,7 +1328,7 @@ msgstr "当被停用时,自动隐藏未被填写的标题栏"
|
|||
#, elixir-autogen, elixir-format
|
||||
msgctxt "config description at :pleroma-:frontend_configurations > :pleroma_fe > :background"
|
||||
msgid "URL of the background, unless viewing a user profile with a background that is set"
|
||||
msgstr ""
|
||||
msgstr "输入背景的网址,若浏览已设定背景的用户资料时此处将不生效"
|
||||
|
||||
#: lib/pleroma/docs/translator.ex:5
|
||||
#, elixir-autogen, elixir-format
|
||||
|
@ -1392,7 +1395,8 @@ msgstr ""
|
|||
#, elixir-autogen, elixir-format
|
||||
msgctxt "config description at :pleroma-:frontend_configurations > :pleroma_fe > :minimalScopesMode"
|
||||
msgid "Limit scope selection to Direct, User default, and Scope of post replying to. Also prevents replying to a DM with a public post from PleromaFE."
|
||||
msgstr ""
|
||||
msgstr "可见范围选项将只保留私信与用户默认,或是跟随被回复帖文的设定。这能够帮助 "
|
||||
"Pleroma FE 的用户不会意外将对私信的回复设置为公开。"
|
||||
|
||||
#: lib/pleroma/docs/translator.ex:5
|
||||
#, elixir-autogen, elixir-format
|
||||
|
@ -1422,7 +1426,7 @@ msgstr ""
|
|||
#, elixir-autogen, elixir-format
|
||||
msgctxt "config description at :pleroma-:frontend_configurations > :pleroma_fe > :scopeCopy"
|
||||
msgid "Copy the scope (private/unlisted/public) in replies to posts by default"
|
||||
msgstr ""
|
||||
msgstr "回复的可见范围(仅关注者/不公开/公开)将默认跟随原贴文的设定"
|
||||
|
||||
#: lib/pleroma/docs/translator.ex:5
|
||||
#, elixir-autogen, elixir-format
|
||||
|
@ -1440,7 +1444,7 @@ msgstr "是否展示该实例的自定义面板"
|
|||
#, elixir-autogen, elixir-format
|
||||
msgctxt "config description at :pleroma-:frontend_configurations > :pleroma_fe > :sidebarRight"
|
||||
msgid "Change alignment of sidebar and panels to the right"
|
||||
msgstr ""
|
||||
msgstr "将面板与侧栏向右对齐"
|
||||
|
||||
#: lib/pleroma/docs/translator.ex:5
|
||||
#, elixir-autogen, elixir-format
|
||||
|
@ -1452,19 +1456,19 @@ msgstr ""
|
|||
#, elixir-autogen, elixir-format
|
||||
msgctxt "config description at :pleroma-:frontend_configurations > :pleroma_fe > :theme"
|
||||
msgid "Which theme to use. Available themes are defined in styles.json"
|
||||
msgstr "使用某个主题。styles.json 中已限定了可用主题"
|
||||
msgstr "使用某个主题。styles.json 中已限定了可用的主题"
|
||||
|
||||
#: lib/pleroma/docs/translator.ex:5
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "config description at :pleroma-:frontends > :admin"
|
||||
msgid "Admin frontend"
|
||||
msgstr ""
|
||||
msgstr "管理员前端"
|
||||
|
||||
#: lib/pleroma/docs/translator.ex:5
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "config description at :pleroma-:frontends > :admin > name"
|
||||
msgid "Name of the installed frontend. Valid config must include both `Name` and `Reference` values."
|
||||
msgstr ""
|
||||
msgstr "已安装的前端名称。只有包含了「名称」与「引用」数值才能被算作有效配置。"
|
||||
|
||||
#: lib/pleroma/docs/translator.ex:5
|
||||
#, elixir-autogen, elixir-format
|
||||
|
@ -1506,7 +1510,7 @@ msgstr ""
|
|||
#, elixir-autogen, elixir-format
|
||||
msgctxt "config description at :pleroma-:frontends > :available > name"
|
||||
msgid "Name of the frontend."
|
||||
msgstr ""
|
||||
msgstr "前端名称。"
|
||||
|
||||
#: lib/pleroma/docs/translator.ex:5
|
||||
#, elixir-autogen, elixir-format
|
||||
|
@ -1524,7 +1528,7 @@ msgstr ""
|
|||
#, elixir-autogen, elixir-format
|
||||
msgctxt "config description at :pleroma-:frontends > :primary > name"
|
||||
msgid "Name of the installed frontend. Valid config must include both `Name` and `Reference` values."
|
||||
msgstr ""
|
||||
msgstr "已安装的前端名称。只有包含了「名称」与「引用」数值才能被算作有效配置。"
|
||||
|
||||
#: lib/pleroma/docs/translator.ex:5
|
||||
#, elixir-autogen, elixir-format
|
||||
|
@ -1542,13 +1546,13 @@ msgstr ""
|
|||
#, elixir-autogen, elixir-format
|
||||
msgctxt "config description at :pleroma-:gopher > :enabled"
|
||||
msgid "Enables the gopher interface"
|
||||
msgstr ""
|
||||
msgstr "启用 gopher 界面"
|
||||
|
||||
#: lib/pleroma/docs/translator.ex:5
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "config description at :pleroma-:gopher > :ip"
|
||||
msgid "IP address to bind to"
|
||||
msgstr ""
|
||||
msgstr "指定绑定IP地址"
|
||||
|
||||
#: lib/pleroma/docs/translator.ex:5
|
||||
#, elixir-autogen, elixir-format
|
||||
|
@ -1566,7 +1570,7 @@ msgstr ""
|
|||
#, elixir-autogen, elixir-format
|
||||
msgctxt "config description at :pleroma-:hackney_pools > :federation > :max_connections"
|
||||
msgid "Number workers in the pool."
|
||||
msgstr ""
|
||||
msgstr "池内的工人数量。"
|
||||
|
||||
#: lib/pleroma/docs/translator.ex:5
|
||||
#, elixir-autogen, elixir-format
|
||||
|
@ -1578,13 +1582,13 @@ msgstr ""
|
|||
#, elixir-autogen, elixir-format
|
||||
msgctxt "config description at :pleroma-:hackney_pools > :media"
|
||||
msgid "Settings for media pool."
|
||||
msgstr ""
|
||||
msgstr "媒体池设定。"
|
||||
|
||||
#: lib/pleroma/docs/translator.ex:5
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "config description at :pleroma-:hackney_pools > :media > :max_connections"
|
||||
msgid "Number workers in the pool."
|
||||
msgstr ""
|
||||
msgstr "池内的工人数量。"
|
||||
|
||||
#: lib/pleroma/docs/translator.ex:5
|
||||
#, elixir-autogen, elixir-format
|
||||
|
@ -1632,7 +1636,7 @@ msgstr ""
|
|||
#, elixir-autogen, elixir-format
|
||||
msgctxt "config description at :pleroma-:http > :proxy_url"
|
||||
msgid "Proxy URL"
|
||||
msgstr "代理地址"
|
||||
msgstr "代理网址"
|
||||
|
||||
#: lib/pleroma/docs/translator.ex:5
|
||||
#, elixir-autogen, elixir-format
|
||||
|
@ -1680,7 +1684,7 @@ msgstr ""
|
|||
#, elixir-autogen, elixir-format
|
||||
msgctxt "config description at :pleroma-:instance > :account_activation_required"
|
||||
msgid "Require users to confirm their emails before signing in"
|
||||
msgstr ""
|
||||
msgstr "要求用户登陆时必须确认邮件"
|
||||
|
||||
#: lib/pleroma/docs/translator.ex:5
|
||||
#, elixir-autogen, elixir-format
|
||||
|
@ -1692,13 +1696,13 @@ msgstr "用户登陆需要管理员同意"
|
|||
#, elixir-autogen, elixir-format
|
||||
msgctxt "config description at :pleroma-:instance > :account_field_name_length"
|
||||
msgid "An account field name maximum length. Default: 512."
|
||||
msgstr ""
|
||||
msgstr "单个用户信息名称的字数上限。默认为512。"
|
||||
|
||||
#: lib/pleroma/docs/translator.ex:5
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "config description at :pleroma-:instance > :account_field_value_length"
|
||||
msgid "An account field value maximum length. Default: 2048."
|
||||
msgstr ""
|
||||
msgstr "单个用户信息内容的字数上限。默认为2048。"
|
||||
|
||||
#: lib/pleroma/docs/translator.ex:5
|
||||
#, elixir-autogen, elixir-format
|
||||
|
@ -1716,19 +1720,19 @@ msgstr ""
|
|||
#, elixir-autogen, elixir-format
|
||||
msgctxt "config description at :pleroma-:instance > :attachment_links"
|
||||
msgid "Enable to automatically add attachment link text to statuses"
|
||||
msgstr ""
|
||||
msgstr "启用此功能将自动添加附件链接至状态中"
|
||||
|
||||
#: lib/pleroma/docs/translator.ex:5
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "config description at :pleroma-:instance > :autofollowed_nicknames"
|
||||
msgid "Set to nicknames of (local) users that every new user should automatically follow"
|
||||
msgstr "为一个会被新用户自动关注的(本地)用户设定昵称"
|
||||
msgstr "为会被新用户自动关注的(本地)用户设定昵称"
|
||||
|
||||
#: lib/pleroma/docs/translator.ex:5
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "config description at :pleroma-:instance > :autofollowing_nicknames"
|
||||
msgid "Set to nicknames of (local) users that automatically follows every newly registered user"
|
||||
msgstr ""
|
||||
msgstr "为会自动关注每一个新用户的(本地)用户设定昵称"
|
||||
|
||||
#: lib/pleroma/docs/translator.ex:5
|
||||
#, elixir-autogen, elixir-format
|
||||
|
@ -1758,7 +1762,7 @@ msgstr "创建账户的最低年龄限制。只有当需要输入生日时才生
|
|||
#, elixir-autogen, elixir-format
|
||||
msgctxt "config description at :pleroma-:instance > :birthday_required"
|
||||
msgid "Require users to enter their birthday."
|
||||
msgstr ""
|
||||
msgstr "要求用户输入出生日期。"
|
||||
|
||||
#: lib/pleroma/docs/translator.ex:5
|
||||
#, elixir-autogen, elixir-format
|
||||
|
@ -1788,7 +1792,7 @@ msgstr ""
|
|||
#, elixir-autogen, elixir-format
|
||||
msgctxt "config description at :pleroma-:instance > :external_user_synchronization"
|
||||
msgid "Enabling following/followers counters synchronization for external users"
|
||||
msgstr ""
|
||||
msgstr "为外部用户启用对关注者与正在关注数量的同步"
|
||||
|
||||
#: lib/pleroma/docs/translator.ex:5
|
||||
#, elixir-autogen, elixir-format
|
||||
|
@ -1809,10 +1813,10 @@ msgid "Timeout (in days) of each external federation target being unreachable pr
|
|||
msgstr ""
|
||||
|
||||
#: lib/pleroma/docs/translator.ex:5
|
||||
#, elixir-autogen, elixir-format
|
||||
#, elixir-autogen, elixir-format, fuzzy
|
||||
msgctxt "config description at :pleroma-:instance > :healthcheck"
|
||||
msgid "If enabled, system data will be shown on `/api/pleroma/healthcheck`"
|
||||
msgstr ""
|
||||
msgstr "若启用,「/api/pleroma/healthcheck」下将显示系统数据"
|
||||
|
||||
#: lib/pleroma/docs/translator.ex:5
|
||||
#, elixir-autogen, elixir-format
|
||||
|
@ -1824,13 +1828,13 @@ msgstr ""
|
|||
#, elixir-autogen, elixir-format
|
||||
msgctxt "config description at :pleroma-:instance > :invites_enabled"
|
||||
msgid "Enable user invitations for admins (depends on `registrations_open` being disabled)"
|
||||
msgstr ""
|
||||
msgstr "只有管理员邀请的用户方能注册(需要关闭「registrations_open」选项)"
|
||||
|
||||
#: lib/pleroma/docs/translator.ex:5
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "config description at :pleroma-:instance > :limit"
|
||||
msgid "Posts character limit (CW/Subject included in the counter)"
|
||||
msgstr ""
|
||||
msgstr "贴文字数上限(内容警告/标题包含在内)"
|
||||
|
||||
#: lib/pleroma/docs/translator.ex:5
|
||||
#, elixir-autogen, elixir-format
|
||||
|
@ -1842,13 +1846,13 @@ msgstr ""
|
|||
#, elixir-autogen, elixir-format
|
||||
msgctxt "config description at :pleroma-:instance > :max_account_fields"
|
||||
msgid "The maximum number of custom fields in the user profile. Default: 10."
|
||||
msgstr ""
|
||||
msgstr "用户资料中可展示的自定用户信息最大上限。默认为10。"
|
||||
|
||||
#: lib/pleroma/docs/translator.ex:5
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "config description at :pleroma-:instance > :max_endorsed_users"
|
||||
msgid "The maximum number of recommended accounts. 0 will disable the feature."
|
||||
msgstr ""
|
||||
msgstr "推荐账户的最大数量。设置为0将关闭该功能。"
|
||||
|
||||
#: lib/pleroma/docs/translator.ex:5
|
||||
#, elixir-autogen, elixir-format
|
||||
|
@ -1920,7 +1924,7 @@ msgstr ""
|
|||
#, elixir-autogen, elixir-format
|
||||
msgctxt "config description at :pleroma-:instance > :name"
|
||||
msgid "Name of the instance"
|
||||
msgstr ""
|
||||
msgstr "实例名称"
|
||||
|
||||
#: lib/pleroma/docs/translator.ex:5
|
||||
#, elixir-autogen, elixir-format
|
||||
|
@ -1938,13 +1942,13 @@ msgstr ""
|
|||
#, elixir-autogen, elixir-format
|
||||
msgctxt "config description at :pleroma-:instance > :poll_limits > :max_expiration"
|
||||
msgid "Maximum expiration time (in seconds)"
|
||||
msgstr ""
|
||||
msgstr "最大有效时间(以秒为单位)"
|
||||
|
||||
#: lib/pleroma/docs/translator.ex:5
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "config description at :pleroma-:instance > :poll_limits > :max_option_chars"
|
||||
msgid "Maximum number of characters per option"
|
||||
msgstr "单个选项的字符上限"
|
||||
msgstr "单个选项的字数上限"
|
||||
|
||||
#: lib/pleroma/docs/translator.ex:5
|
||||
#, elixir-autogen, elixir-format
|
||||
|
@ -1956,13 +1960,14 @@ msgstr "选项数量上限"
|
|||
#, elixir-autogen, elixir-format
|
||||
msgctxt "config description at :pleroma-:instance > :poll_limits > :min_expiration"
|
||||
msgid "Minimum expiration time (in seconds)"
|
||||
msgstr ""
|
||||
msgstr "最小有效时间(以秒为单位)"
|
||||
|
||||
#: lib/pleroma/docs/translator.ex:5
|
||||
#, elixir-autogen, elixir-format
|
||||
#, elixir-autogen, elixir-format, fuzzy
|
||||
msgctxt "config description at :pleroma-:instance > :privileged_staff"
|
||||
msgid "Let moderators access sensitive data (e.g. updating user credentials, get password reset token, delete users, index and read private statuses and chats)"
|
||||
msgstr ""
|
||||
msgstr "允许管理员访问敏感信息(例,更新用户凭据、取得密码重置令牌、删除用户、能够索"
|
||||
"引并阅览私密状态与聊天信息)"
|
||||
|
||||
#: lib/pleroma/docs/translator.ex:5
|
||||
#, elixir-autogen, elixir-format
|
||||
|
@ -1986,13 +1991,13 @@ msgstr ""
|
|||
#, elixir-autogen, elixir-format
|
||||
msgctxt "config description at :pleroma-:instance > :registration_reason_length"
|
||||
msgid "Maximum registration reason length. Default: 500."
|
||||
msgstr "注册申请理由的字符上限。默认为500。"
|
||||
msgstr "申请注册理由的字数上限。默认为500。"
|
||||
|
||||
#: lib/pleroma/docs/translator.ex:5
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "config description at :pleroma-:instance > :registrations_open"
|
||||
msgid "Enable registrations for anyone. Invitations require this setting to be disabled."
|
||||
msgstr ""
|
||||
msgstr "开放注册。若要启用邀请制注册则需关闭此项。"
|
||||
|
||||
#: lib/pleroma/docs/translator.ex:5
|
||||
#, elixir-autogen, elixir-format
|
||||
|
@ -2011,12 +2016,14 @@ msgstr ""
|
|||
msgctxt "config description at :pleroma-:instance > :safe_dm_mentions"
|
||||
msgid "If enabled, only mentions at the beginning of a post will be used to address people in direct messages. This is to prevent accidental mentioning of people when talking about them (e.g. \"@admin please keep an eye on @bad_actor\"). Default: disabled"
|
||||
msgstr ""
|
||||
"启用后,只有处于私信最开头的用户名才会被提及。这将有助于防止意外提及不想要的"
|
||||
"用户(例,“@admin 请留意 @bad_actor”)。默认下为关闭状态"
|
||||
|
||||
#: lib/pleroma/docs/translator.ex:5
|
||||
#, elixir-autogen, elixir-format
|
||||
#, elixir-autogen, elixir-format, fuzzy
|
||||
msgctxt "config description at :pleroma-:instance > :show_reactions"
|
||||
msgid "Let favourites and emoji reactions be viewed through the API."
|
||||
msgstr ""
|
||||
msgstr "允许通过此API来看见喜欢数量与表情反应。"
|
||||
|
||||
#: lib/pleroma/docs/translator.ex:5
|
||||
#, elixir-autogen, elixir-format
|
||||
|
@ -2034,25 +2041,25 @@ msgstr ""
|
|||
#, elixir-autogen, elixir-format
|
||||
msgctxt "config description at :pleroma-:instance > :upload_limit"
|
||||
msgid "File size limit of uploads (except for avatar, background, banner)"
|
||||
msgstr ""
|
||||
msgstr "上传文件大小上限(不包括头像、背景与横幅)"
|
||||
|
||||
#: lib/pleroma/docs/translator.ex:5
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "config description at :pleroma-:instance > :user_bio_length"
|
||||
msgid "A user bio maximum length. Default: 5000."
|
||||
msgstr "用户自传的字符上限。默认为5000。"
|
||||
msgstr "用户自传的字数上限。默认为5000。"
|
||||
|
||||
#: lib/pleroma/docs/translator.ex:5
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "config description at :pleroma-:instance > :user_name_length"
|
||||
msgid "A user name maximum length. Default: 100."
|
||||
msgstr "用户名的字符上限。默认为100。"
|
||||
msgstr "用户名的字数上限。默认为100。"
|
||||
|
||||
#: lib/pleroma/docs/translator.ex:5
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "config description at :pleroma-:instances_favicons > :enabled"
|
||||
msgid "Allow/disallow displaying and getting instances favicons"
|
||||
msgstr ""
|
||||
msgstr "允许/不允许获取并展示实例图标"
|
||||
|
||||
#: lib/pleroma/docs/translator.ex:5
|
||||
#, elixir-autogen, elixir-format
|
||||
|
@ -2148,13 +2155,13 @@ msgstr ""
|
|||
#, elixir-autogen, elixir-format
|
||||
msgctxt "config description at :pleroma-:manifest > :icons"
|
||||
msgid "Describe the icons of the app"
|
||||
msgstr ""
|
||||
msgstr "描述此应用的图标"
|
||||
|
||||
#: lib/pleroma/docs/translator.ex:5
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "config description at :pleroma-:manifest > :theme_color"
|
||||
msgid "Describe the theme color of the app"
|
||||
msgstr ""
|
||||
msgstr "描述此应用的主题颜色"
|
||||
|
||||
#: lib/pleroma/docs/translator.ex:5
|
||||
#, elixir-autogen, elixir-format
|
||||
|
@ -2184,13 +2191,13 @@ msgstr ""
|
|||
#, elixir-autogen, elixir-format
|
||||
msgctxt "config description at :pleroma-:media_preview_proxy > :thumbnail_max_height"
|
||||
msgid "Max height of preview thumbnail for images (video preview always has original dimensions)."
|
||||
msgstr ""
|
||||
msgstr "图像的生成预览缩略图的长度上限(视频预览则始终保持原始尺寸)。"
|
||||
|
||||
#: lib/pleroma/docs/translator.ex:5
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "config description at :pleroma-:media_preview_proxy > :thumbnail_max_width"
|
||||
msgid "Max width of preview thumbnail for images (video preview always has original dimensions)."
|
||||
msgstr ""
|
||||
msgstr "图像的生成预览缩略图的宽度上限(视频预览则始终保持原始尺寸)。"
|
||||
|
||||
#: lib/pleroma/docs/translator.ex:5
|
||||
#, elixir-autogen, elixir-format
|
||||
|
@ -2352,13 +2359,13 @@ msgstr ""
|
|||
#, elixir-autogen, elixir-format
|
||||
msgctxt "config description at :pleroma-:mrf_rejectnonpublic > :allow_direct"
|
||||
msgid "Whether to allow direct messages"
|
||||
msgstr ""
|
||||
msgstr "是否允许私信"
|
||||
|
||||
#: lib/pleroma/docs/translator.ex:5
|
||||
#, elixir-autogen, elixir-format
|
||||
msgctxt "config description at :pleroma-:mrf_rejectnonpublic > :allow_followersonly"
|
||||
msgid "Whether to allow followers-only posts"
|
||||
msgstr ""
|
||||
msgstr "是否允许仅限关注者的帖文"
|
||||
|
||||
#: lib/pleroma/docs/translator.ex:5
|
||||
#, elixir-autogen, elixir-format
|
||||
|
@ -2526,7 +2533,7 @@ msgstr ""
|
|||
#, elixir-autogen, elixir-format
|
||||
msgctxt "config description at :pleroma-:pools > :media"
|
||||
msgid "Settings for media pool."
|
||||
msgstr ""
|
||||
msgstr "媒体池设定。"
|
||||
|
||||
#: lib/pleroma/docs/translator.ex:5
|
||||
#, elixir-autogen, elixir-format
|
||||
|
@ -5472,7 +5479,7 @@ msgstr ""
|
|||
#, elixir-autogen, elixir-format
|
||||
msgctxt "config label at :pleroma-Pleroma.Captcha.Kocaptcha > :endpoint"
|
||||
msgid "Endpoint"
|
||||
msgstr ""
|
||||
msgstr "终点"
|
||||
|
||||
#: lib/pleroma/docs/translator.ex:5
|
||||
#, elixir-autogen, elixir-format
|
||||
|
@ -5562,7 +5569,7 @@ msgstr ""
|
|||
#, elixir-autogen, elixir-format
|
||||
msgctxt "config label at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.SMTP-:password"
|
||||
msgid "Password"
|
||||
msgstr ""
|
||||
msgstr "密码"
|
||||
|
||||
#: lib/pleroma/docs/translator.ex:5
|
||||
#, elixir-autogen, elixir-format
|
||||
|
@ -5694,7 +5701,7 @@ msgstr "链接颜色"
|
|||
#, elixir-autogen, elixir-format
|
||||
msgctxt "config label at :pleroma-Pleroma.Emails.UserEmail > :styling > :text_color"
|
||||
msgid "Text color"
|
||||
msgstr ""
|
||||
msgstr "文本颜色"
|
||||
|
||||
#: lib/pleroma/docs/translator.ex:5
|
||||
#, elixir-autogen, elixir-format
|
||||
|
@ -5952,7 +5959,7 @@ msgstr ""
|
|||
#, elixir-autogen, elixir-format
|
||||
msgctxt "config label at :pleroma-Pleroma.Workers.PurgeExpiredActivity > :enabled"
|
||||
msgid "Enabled"
|
||||
msgstr ""
|
||||
msgstr "已启用"
|
||||
|
||||
#: lib/pleroma/docs/translator.ex:5
|
||||
#, elixir-autogen, elixir-format
|
||||
|
|
|
@ -0,0 +1,153 @@
|
|||
defmodule Pleroma.Repo.Migrations.ChangeThreadVisibilityToBeLocalOnlyAware do
|
||||
use Ecto.Migration
|
||||
|
||||
def up do
|
||||
execute("DROP FUNCTION IF EXISTS thread_visibility(actor varchar, activity_id varchar)")
|
||||
execute(update_thread_visibility())
|
||||
end
|
||||
|
||||
def down do
|
||||
execute(
|
||||
"DROP FUNCTION IF EXISTS thread_visibility(actor varchar, activity_id varchar, local_public varchar)"
|
||||
)
|
||||
|
||||
execute(restore_thread_visibility())
|
||||
end
|
||||
|
||||
def update_thread_visibility do
|
||||
"""
|
||||
CREATE OR REPLACE FUNCTION thread_visibility(actor varchar, activity_id varchar, local_public varchar default '') RETURNS boolean AS $$
|
||||
DECLARE
|
||||
public varchar := 'https://www.w3.org/ns/activitystreams#Public';
|
||||
child objects%ROWTYPE;
|
||||
activity activities%ROWTYPE;
|
||||
author_fa varchar;
|
||||
valid_recipients varchar[];
|
||||
actor_user_following varchar[];
|
||||
BEGIN
|
||||
--- Fetch actor following
|
||||
SELECT array_agg(following.follower_address) INTO actor_user_following FROM following_relationships
|
||||
JOIN users ON users.id = following_relationships.follower_id
|
||||
JOIN users AS following ON following.id = following_relationships.following_id
|
||||
WHERE users.ap_id = actor;
|
||||
|
||||
--- Fetch our initial activity.
|
||||
SELECT * INTO activity FROM activities WHERE activities.data->>'id' = activity_id;
|
||||
|
||||
LOOP
|
||||
--- Ensure that we have an activity before continuing.
|
||||
--- If we don't, the thread is not satisfiable.
|
||||
IF activity IS NULL THEN
|
||||
RETURN false;
|
||||
END IF;
|
||||
|
||||
--- We only care about Create activities.
|
||||
IF activity.data->>'type' != 'Create' THEN
|
||||
RETURN true;
|
||||
END IF;
|
||||
|
||||
--- Normalize the child object into child.
|
||||
SELECT * INTO child FROM objects
|
||||
INNER JOIN activities ON COALESCE(activities.data->'object'->>'id', activities.data->>'object') = objects.data->>'id'
|
||||
WHERE COALESCE(activity.data->'object'->>'id', activity.data->>'object') = objects.data->>'id';
|
||||
|
||||
--- Fetch the author's AS2 following collection.
|
||||
SELECT COALESCE(users.follower_address, '') INTO author_fa FROM users WHERE users.ap_id = activity.actor;
|
||||
|
||||
--- Prepare valid recipients array.
|
||||
valid_recipients := ARRAY[actor, public];
|
||||
--- If we specified local public, add it.
|
||||
IF local_public <> '' THEN
|
||||
valid_recipients := valid_recipients || local_public;
|
||||
END IF;
|
||||
IF ARRAY[author_fa] && actor_user_following THEN
|
||||
valid_recipients := valid_recipients || author_fa;
|
||||
END IF;
|
||||
|
||||
--- Check visibility.
|
||||
IF NOT valid_recipients && activity.recipients THEN
|
||||
--- activity not visible, break out of the loop
|
||||
RETURN false;
|
||||
END IF;
|
||||
|
||||
--- If there's a parent, load it and do this all over again.
|
||||
IF (child.data->'inReplyTo' IS NOT NULL) AND (child.data->'inReplyTo' != 'null'::jsonb) THEN
|
||||
SELECT * INTO activity FROM activities
|
||||
INNER JOIN objects ON COALESCE(activities.data->'object'->>'id', activities.data->>'object') = objects.data->>'id'
|
||||
WHERE child.data->>'inReplyTo' = objects.data->>'id';
|
||||
ELSE
|
||||
RETURN true;
|
||||
END IF;
|
||||
END LOOP;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql IMMUTABLE;
|
||||
"""
|
||||
end
|
||||
|
||||
# priv/repo/migrations/20191007073319_create_following_relationships.exs
|
||||
def restore_thread_visibility do
|
||||
"""
|
||||
CREATE OR REPLACE FUNCTION thread_visibility(actor varchar, activity_id varchar) RETURNS boolean AS $$
|
||||
DECLARE
|
||||
public varchar := 'https://www.w3.org/ns/activitystreams#Public';
|
||||
child objects%ROWTYPE;
|
||||
activity activities%ROWTYPE;
|
||||
author_fa varchar;
|
||||
valid_recipients varchar[];
|
||||
actor_user_following varchar[];
|
||||
BEGIN
|
||||
--- Fetch actor following
|
||||
SELECT array_agg(following.follower_address) INTO actor_user_following FROM following_relationships
|
||||
JOIN users ON users.id = following_relationships.follower_id
|
||||
JOIN users AS following ON following.id = following_relationships.following_id
|
||||
WHERE users.ap_id = actor;
|
||||
|
||||
--- Fetch our initial activity.
|
||||
SELECT * INTO activity FROM activities WHERE activities.data->>'id' = activity_id;
|
||||
|
||||
LOOP
|
||||
--- Ensure that we have an activity before continuing.
|
||||
--- If we don't, the thread is not satisfiable.
|
||||
IF activity IS NULL THEN
|
||||
RETURN false;
|
||||
END IF;
|
||||
|
||||
--- We only care about Create activities.
|
||||
IF activity.data->>'type' != 'Create' THEN
|
||||
RETURN true;
|
||||
END IF;
|
||||
|
||||
--- Normalize the child object into child.
|
||||
SELECT * INTO child FROM objects
|
||||
INNER JOIN activities ON COALESCE(activities.data->'object'->>'id', activities.data->>'object') = objects.data->>'id'
|
||||
WHERE COALESCE(activity.data->'object'->>'id', activity.data->>'object') = objects.data->>'id';
|
||||
|
||||
--- Fetch the author's AS2 following collection.
|
||||
SELECT COALESCE(users.follower_address, '') INTO author_fa FROM users WHERE users.ap_id = activity.actor;
|
||||
|
||||
--- Prepare valid recipients array.
|
||||
valid_recipients := ARRAY[actor, public];
|
||||
IF ARRAY[author_fa] && actor_user_following THEN
|
||||
valid_recipients := valid_recipients || author_fa;
|
||||
END IF;
|
||||
|
||||
--- Check visibility.
|
||||
IF NOT valid_recipients && activity.recipients THEN
|
||||
--- activity not visible, break out of the loop
|
||||
RETURN false;
|
||||
END IF;
|
||||
|
||||
--- If there's a parent, load it and do this all over again.
|
||||
IF (child.data->'inReplyTo' IS NOT NULL) AND (child.data->'inReplyTo' != 'null'::jsonb) THEN
|
||||
SELECT * INTO activity FROM activities
|
||||
INNER JOIN objects ON COALESCE(activities.data->'object'->>'id', activities.data->>'object') = objects.data->>'id'
|
||||
WHERE child.data->>'inReplyTo' = objects.data->>'id';
|
||||
ELSE
|
||||
RETURN true;
|
||||
END IF;
|
||||
END LOOP;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql IMMUTABLE;
|
||||
"""
|
||||
end
|
||||
end
|
30
test/fixtures/emoji-reaction-unqualified.json
vendored
30
test/fixtures/emoji-reaction-unqualified.json
vendored
|
@ -1,30 +0,0 @@
|
|||
{
|
||||
"type": "EmojiReact",
|
||||
"signature": {
|
||||
"type": "RsaSignature2017",
|
||||
"signatureValue": "fdxMfQSMwbC6wP6sh6neS/vM5879K67yQkHTbiT5Npr5wAac0y6+o3Ij+41tN3rL6wfuGTosSBTHOtta6R4GCOOhCaCSLMZKypnp1VltCzLDoyrZELnYQIC8gpUXVmIycZbREk22qWUe/w7DAFaKK4UscBlHDzeDVcA0K3Se5Sluqi9/Zh+ldAnEzj/rSEPDjrtvf5wGNf3fHxbKSRKFt90JvKK6hS+vxKUhlRFDf6/SMETw+EhwJSNW4d10yMUakqUWsFv4Acq5LW7l+HpYMvlYY1FZhNde1+uonnCyuQDyvzkff8zwtEJmAXC4RivO/VVLa17SmqheJZfI8oluVg==",
|
||||
"creator": "http://mastodon.example.org/users/admin#main-key",
|
||||
"created": "2018-02-17T18:57:49Z"
|
||||
},
|
||||
"object": "http://localtesting.pleroma.lol/objects/eb92579d-3417-42a8-8652-2492c2d4f454",
|
||||
"content": "❤",
|
||||
"nickname": "lain",
|
||||
"id": "http://mastodon.example.org/users/admin#reactions/2",
|
||||
"actor": "http://mastodon.example.org/users/admin",
|
||||
"@context": [
|
||||
"https://www.w3.org/ns/activitystreams",
|
||||
"https://w3id.org/security/v1",
|
||||
{
|
||||
"toot": "http://joinmastodon.org/ns#",
|
||||
"sensitive": "as:sensitive",
|
||||
"ostatus": "http://ostatus.org#",
|
||||
"movedTo": "as:movedTo",
|
||||
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
|
||||
"inReplyToAtomUri": "ostatus:inReplyToAtomUri",
|
||||
"conversation": "ostatus:conversation",
|
||||
"atomUri": "ostatus:atomUri",
|
||||
"Hashtag": "as:Hashtag",
|
||||
"Emoji": "toot:Emoji"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -18,6 +18,23 @@ test "it finds something" do
|
|||
assert result.id == post.id
|
||||
end
|
||||
|
||||
test "it finds local-only posts for authenticated users" do
|
||||
user = insert(:user)
|
||||
reader = insert(:user)
|
||||
{:ok, post} = CommonAPI.post(user, %{status: "it's wednesday my dudes", visibility: "local"})
|
||||
|
||||
[result] = Search.search(reader, "wednesday")
|
||||
|
||||
assert result.id == post.id
|
||||
end
|
||||
|
||||
test "it does not find local-only posts for anonymous users" do
|
||||
user = insert(:user)
|
||||
{:ok, _post} = CommonAPI.post(user, %{status: "it's wednesday my dudes", visibility: "local"})
|
||||
|
||||
assert [] = Search.search(nil, "wednesday")
|
||||
end
|
||||
|
||||
test "using plainto_tsquery on postgres < 11" do
|
||||
old_version = :persistent_term.get({Pleroma.Repo, :postgres_version})
|
||||
:persistent_term.put({Pleroma.Repo, :postgres_version}, 10.0)
|
||||
|
|
|
@ -22,15 +22,15 @@ defmodule Pleroma.User.BackupTest do
|
|||
clear_config([Pleroma.Emails.Mailer, :enabled], true)
|
||||
end
|
||||
|
||||
test "it requries enabled email" do
|
||||
test "it does not requrie enabled email" do
|
||||
clear_config([Pleroma.Emails.Mailer, :enabled], false)
|
||||
user = insert(:user)
|
||||
assert {:error, "Backups require enabled email"} == Backup.create(user)
|
||||
assert {:ok, _} = Backup.create(user)
|
||||
end
|
||||
|
||||
test "it requries user's email" do
|
||||
test "it does not require user's email" do
|
||||
user = insert(:user, %{email: nil})
|
||||
assert {:error, "Email is required"} == Backup.create(user)
|
||||
assert {:ok, _} = Backup.create(user)
|
||||
end
|
||||
|
||||
test "it creates a backup record and an Oban job" do
|
||||
|
@ -75,6 +75,43 @@ test "it process a backup record" do
|
|||
)
|
||||
end
|
||||
|
||||
test "it does not send an email if the user does not have an email" do
|
||||
clear_config([Pleroma.Upload, :uploader], Pleroma.Uploaders.Local)
|
||||
%{id: user_id} = user = insert(:user, %{email: nil})
|
||||
|
||||
assert {:ok, %Oban.Job{args: %{"backup_id" => backup_id} = args}} = Backup.create(user)
|
||||
assert {:ok, backup} = perform_job(BackupWorker, args)
|
||||
assert backup.file_size > 0
|
||||
assert %Backup{id: ^backup_id, processed: true, user_id: ^user_id} = backup
|
||||
|
||||
assert_no_email_sent()
|
||||
end
|
||||
|
||||
test "it does not send an email if mailer is not on" do
|
||||
clear_config([Pleroma.Emails.Mailer, :enabled], false)
|
||||
clear_config([Pleroma.Upload, :uploader], Pleroma.Uploaders.Local)
|
||||
%{id: user_id} = user = insert(:user)
|
||||
|
||||
assert {:ok, %Oban.Job{args: %{"backup_id" => backup_id} = args}} = Backup.create(user)
|
||||
assert {:ok, backup} = perform_job(BackupWorker, args)
|
||||
assert backup.file_size > 0
|
||||
assert %Backup{id: ^backup_id, processed: true, user_id: ^user_id} = backup
|
||||
|
||||
assert_no_email_sent()
|
||||
end
|
||||
|
||||
test "it does not send an email if the user has an empty email" do
|
||||
clear_config([Pleroma.Upload, :uploader], Pleroma.Uploaders.Local)
|
||||
%{id: user_id} = user = insert(:user, %{email: ""})
|
||||
|
||||
assert {:ok, %Oban.Job{args: %{"backup_id" => backup_id} = args}} = Backup.create(user)
|
||||
assert {:ok, backup} = perform_job(BackupWorker, args)
|
||||
assert backup.file_size > 0
|
||||
assert %Backup{id: ^backup_id, processed: true, user_id: ^user_id} = backup
|
||||
|
||||
assert_no_email_sent()
|
||||
end
|
||||
|
||||
test "it removes outdated backups after creating a fresh one" do
|
||||
clear_config([Backup, :limit_days], -1)
|
||||
clear_config([Pleroma.Upload, :uploader], Pleroma.Uploaders.Local)
|
||||
|
|
|
@ -1177,7 +1177,7 @@ test "expiring" do
|
|||
user = insert(:user)
|
||||
muted_user = insert(:user)
|
||||
|
||||
{:ok, _user_relationships} = User.mute(user, muted_user, %{expires_in: 60})
|
||||
{:ok, _user_relationships} = User.mute(user, muted_user, %{duration: 60})
|
||||
assert User.mutes?(user, muted_user)
|
||||
|
||||
worker = Pleroma.Workers.MuteExpireWorker
|
||||
|
@ -2633,41 +2633,6 @@ defp object_id_from_created_activity(user) do
|
|||
object_id
|
||||
end
|
||||
|
||||
describe "account endorsements" do
|
||||
test "it pins people" do
|
||||
user = insert(:user)
|
||||
pinned_user = insert(:user)
|
||||
|
||||
{:ok, _pinned_user, _user} = User.follow(user, pinned_user)
|
||||
|
||||
refute User.endorses?(user, pinned_user)
|
||||
|
||||
{:ok, _user_relationship} = User.endorse(user, pinned_user)
|
||||
|
||||
assert User.endorses?(user, pinned_user)
|
||||
end
|
||||
|
||||
test "it unpins users" do
|
||||
user = insert(:user)
|
||||
pinned_user = insert(:user)
|
||||
|
||||
{:ok, _pinned_user, _user} = User.follow(user, pinned_user)
|
||||
{:ok, _user_relationship} = User.endorse(user, pinned_user)
|
||||
{:ok, _user_pin} = User.unendorse(user, pinned_user)
|
||||
|
||||
refute User.endorses?(user, pinned_user)
|
||||
end
|
||||
|
||||
test "it doesn't pin users you do not follow" do
|
||||
user = insert(:user)
|
||||
pinned_user = insert(:user)
|
||||
|
||||
assert {:error, _message} = User.endorse(user, pinned_user)
|
||||
|
||||
refute User.endorses?(user, pinned_user)
|
||||
end
|
||||
end
|
||||
|
||||
describe "add_alias/2" do
|
||||
test "should add alias for another user" do
|
||||
user = insert(:user)
|
||||
|
@ -2743,4 +2708,39 @@ test "should report error on non-existing alias" do
|
|||
assert user.ap_id in user3_updated.also_known_as
|
||||
end
|
||||
end
|
||||
|
||||
describe "account endorsements" do
|
||||
test "it pins people" do
|
||||
user = insert(:user)
|
||||
pinned_user = insert(:user)
|
||||
|
||||
{:ok, _pinned_user, _user} = User.follow(user, pinned_user)
|
||||
|
||||
refute User.endorses?(user, pinned_user)
|
||||
|
||||
{:ok, _user_relationship} = User.endorse(user, pinned_user)
|
||||
|
||||
assert User.endorses?(user, pinned_user)
|
||||
end
|
||||
|
||||
test "it unpins users" do
|
||||
user = insert(:user)
|
||||
pinned_user = insert(:user)
|
||||
|
||||
{:ok, _pinned_user, _user} = User.follow(user, pinned_user)
|
||||
{:ok, _user_relationship} = User.endorse(user, pinned_user)
|
||||
{:ok, _user_pin} = User.unendorse(user, pinned_user)
|
||||
|
||||
refute User.endorses?(user, pinned_user)
|
||||
end
|
||||
|
||||
test "it doesn't pin users you do not follow" do
|
||||
user = insert(:user)
|
||||
pinned_user = insert(:user)
|
||||
|
||||
assert {:error, _message} = User.endorse(user, pinned_user)
|
||||
|
||||
refute User.endorses?(user, pinned_user)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -247,6 +247,27 @@ test "returns local-only objects when authenticated", %{conn: conn} do
|
|||
assert json_response(response, 200) == ObjectView.render("object.json", %{object: object})
|
||||
end
|
||||
|
||||
test "does not return local-only objects for remote users", %{conn: conn} do
|
||||
user = insert(:user)
|
||||
reader = insert(:user, local: false)
|
||||
|
||||
{:ok, post} =
|
||||
CommonAPI.post(user, %{status: "test @#{reader.nickname}", visibility: "local"})
|
||||
|
||||
assert Pleroma.Web.ActivityPub.Visibility.is_local_public?(post)
|
||||
|
||||
object = Object.normalize(post, fetch: false)
|
||||
uuid = String.split(object.data["id"], "/") |> List.last()
|
||||
|
||||
assert response =
|
||||
conn
|
||||
|> assign(:user, reader)
|
||||
|> put_req_header("accept", "application/activity+json")
|
||||
|> get("/objects/#{uuid}")
|
||||
|
||||
json_response(response, 404)
|
||||
end
|
||||
|
||||
test "it returns a json representation of the object with accept application/json", %{
|
||||
conn: conn
|
||||
} do
|
||||
|
@ -1297,6 +1318,35 @@ test "it returns 200 even if there're no activities", %{conn: conn} do
|
|||
assert outbox_endpoint == result["id"]
|
||||
end
|
||||
|
||||
test "it returns a local note activity when authenticated as local user", %{conn: conn} do
|
||||
user = insert(:user)
|
||||
reader = insert(:user)
|
||||
{:ok, note_activity} = CommonAPI.post(user, %{status: "mew mew", visibility: "local"})
|
||||
ap_id = note_activity.data["id"]
|
||||
|
||||
resp =
|
||||
conn
|
||||
|> assign(:user, reader)
|
||||
|> put_req_header("accept", "application/activity+json")
|
||||
|> get("/users/#{user.nickname}/outbox?page=true")
|
||||
|> json_response(200)
|
||||
|
||||
assert %{"orderedItems" => [%{"id" => ^ap_id}]} = resp
|
||||
end
|
||||
|
||||
test "it does not return a local note activity when unauthenticated", %{conn: conn} do
|
||||
user = insert(:user)
|
||||
{:ok, _note_activity} = CommonAPI.post(user, %{status: "mew mew", visibility: "local"})
|
||||
|
||||
resp =
|
||||
conn
|
||||
|> put_req_header("accept", "application/activity+json")
|
||||
|> get("/users/#{user.nickname}/outbox?page=true")
|
||||
|> json_response(200)
|
||||
|
||||
assert %{"orderedItems" => []} = resp
|
||||
end
|
||||
|
||||
test "it returns a note activity in a collection", %{conn: conn} do
|
||||
note_activity = insert(:note_activity)
|
||||
note_object = Object.normalize(note_activity, fetch: false)
|
||||
|
|
|
@ -42,11 +42,15 @@ test "it works for incoming unqualified emoji reactions" do
|
|||
other_user = insert(:user, local: false)
|
||||
{:ok, activity} = CommonAPI.post(user, %{status: "hello"})
|
||||
|
||||
# woman detective emoji, unqualified
|
||||
unqualified_emoji = [0x1F575, 0x200D, 0x2640] |> List.to_string()
|
||||
|
||||
data =
|
||||
File.read!("test/fixtures/emoji-reaction-unqualified.json")
|
||||
File.read!("test/fixtures/emoji-reaction.json")
|
||||
|> Jason.decode!()
|
||||
|> Map.put("object", activity.data["object"])
|
||||
|> Map.put("actor", other_user.ap_id)
|
||||
|> Map.put("content", unqualified_emoji)
|
||||
|
||||
{:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
|
||||
|
||||
|
@ -54,13 +58,14 @@ test "it works for incoming unqualified emoji reactions" do
|
|||
assert data["type"] == "EmojiReact"
|
||||
assert data["id"] == "http://mastodon.example.org/users/admin#reactions/2"
|
||||
assert data["object"] == activity.data["object"]
|
||||
# heart emoji with added emoji variation sequence
|
||||
assert data["content"] == "❤\uFE0F"
|
||||
# woman detective emoji, fully qualified
|
||||
emoji = [0x1F575, 0xFE0F, 0x200D, 0x2640, 0xFE0F] |> List.to_string()
|
||||
assert data["content"] == emoji
|
||||
|
||||
object = Object.get_by_ap_id(data["object"])
|
||||
|
||||
assert object.data["reaction_count"] == 1
|
||||
assert match?([["❤\uFE0F", _]], object.data["reactions"])
|
||||
assert match?([[emoji, _]], object.data["reactions"])
|
||||
end
|
||||
|
||||
test "it reject invalid emoji reactions" do
|
||||
|
|
|
@ -9,6 +9,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
|
|||
alias Pleroma.Repo
|
||||
alias Pleroma.Tests.ObanHelpers
|
||||
alias Pleroma.User
|
||||
alias Pleroma.UserRelationship
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.Web.ActivityPub.InternalFetchActor
|
||||
alias Pleroma.Web.CommonAPI
|
||||
|
@ -407,6 +408,20 @@ test "gets users statuses", %{conn: conn} do
|
|||
assert id_two == to_string(activity.id)
|
||||
end
|
||||
|
||||
test "gets local-only statuses for authenticated users", %{user: _user, conn: conn} do
|
||||
user_one = insert(:user)
|
||||
|
||||
{:ok, activity} = CommonAPI.post(user_one, %{status: "HI!!!", visibility: "local"})
|
||||
|
||||
resp =
|
||||
conn
|
||||
|> get("/api/v1/accounts/#{user_one.id}/statuses")
|
||||
|> json_response_and_validate_schema(200)
|
||||
|
||||
assert [%{"id" => id}] = resp
|
||||
assert id == to_string(activity.id)
|
||||
end
|
||||
|
||||
test "gets an users media, excludes reblogs", %{conn: conn} do
|
||||
note = insert(:note_activity)
|
||||
user = User.get_cached_by_ap_id(note.data["actor"])
|
||||
|
@ -1011,6 +1026,40 @@ test "without notifications", %{conn: conn} do
|
|||
assert %{"id" => _id, "muting" => false, "muting_notifications" => false} =
|
||||
json_response_and_validate_schema(conn, 200)
|
||||
end
|
||||
|
||||
test "expiring", %{conn: conn, user: user} do
|
||||
other_user = insert(:user)
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> put_req_header("content-type", "multipart/form-data")
|
||||
|> post("/api/v1/accounts/#{other_user.id}/mute", %{"duration" => "86400"})
|
||||
|
||||
assert %{"id" => _id, "muting" => true} = json_response_and_validate_schema(conn, 200)
|
||||
|
||||
mute_expires_at = UserRelationship.get_mute_expire_date(user, other_user)
|
||||
|
||||
assert DateTime.diff(
|
||||
mute_expires_at,
|
||||
DateTime.utc_now() |> DateTime.add(24 * 60 * 60)
|
||||
) in -3..3
|
||||
end
|
||||
|
||||
test "falls back to expires_in", %{conn: conn, user: user} do
|
||||
other_user = insert(:user)
|
||||
|
||||
conn
|
||||
|> put_req_header("content-type", "multipart/form-data")
|
||||
|> post("/api/v1/accounts/#{other_user.id}/mute", %{"expires_in" => "86400"})
|
||||
|> json_response_and_validate_schema(200)
|
||||
|
||||
mute_expires_at = UserRelationship.get_mute_expire_date(user, other_user)
|
||||
|
||||
assert DateTime.diff(
|
||||
mute_expires_at,
|
||||
DateTime.utc_now() |> DateTime.add(24 * 60 * 60)
|
||||
) in -3..3
|
||||
end
|
||||
end
|
||||
|
||||
describe "pinned statuses" do
|
||||
|
@ -1844,21 +1893,21 @@ test "getting a list of mutes" do
|
|||
|> get("/api/v1/mutes")
|
||||
|> json_response_and_validate_schema(200)
|
||||
|
||||
assert [id1, id2, id3] == Enum.map(result, & &1["id"])
|
||||
assert [id3, id2, id1] == Enum.map(result, & &1["id"])
|
||||
|
||||
result =
|
||||
conn
|
||||
|> get("/api/v1/mutes?limit=1")
|
||||
|> json_response_and_validate_schema(200)
|
||||
|
||||
assert [%{"id" => ^id1}] = result
|
||||
assert [%{"id" => ^id3}] = result
|
||||
|
||||
result =
|
||||
conn
|
||||
|> get("/api/v1/mutes?since_id=#{id1}")
|
||||
|> json_response_and_validate_schema(200)
|
||||
|
||||
assert [%{"id" => ^id2}, %{"id" => ^id3}] = result
|
||||
assert [%{"id" => ^id3}, %{"id" => ^id2}] = result
|
||||
|
||||
result =
|
||||
conn
|
||||
|
@ -1872,7 +1921,7 @@ test "getting a list of mutes" do
|
|||
|> get("/api/v1/mutes?since_id=#{id1}&limit=1")
|
||||
|> json_response_and_validate_schema(200)
|
||||
|
||||
assert [%{"id" => ^id2}] = result
|
||||
assert [%{"id" => ^id3}] = result
|
||||
end
|
||||
|
||||
test "list of mutes with with_relationships parameter" do
|
||||
|
@ -1891,7 +1940,7 @@ test "list of mutes with with_relationships parameter" do
|
|||
|
||||
assert [
|
||||
%{
|
||||
"id" => ^id1,
|
||||
"id" => ^id3,
|
||||
"pleroma" => %{"relationship" => %{"muting" => true, "followed_by" => true}}
|
||||
},
|
||||
%{
|
||||
|
@ -1899,7 +1948,7 @@ test "list of mutes with with_relationships parameter" do
|
|||
"pleroma" => %{"relationship" => %{"muting" => true, "followed_by" => true}}
|
||||
},
|
||||
%{
|
||||
"id" => ^id3,
|
||||
"id" => ^id1,
|
||||
"pleroma" => %{"relationship" => %{"muting" => true, "followed_by" => true}}
|
||||
}
|
||||
] =
|
||||
|
@ -1924,7 +1973,7 @@ test "getting a list of blocks" do
|
|||
|> get("/api/v1/blocks")
|
||||
|> json_response_and_validate_schema(200)
|
||||
|
||||
assert [id1, id2, id3] == Enum.map(result, & &1["id"])
|
||||
assert [id3, id2, id1] == Enum.map(result, & &1["id"])
|
||||
|
||||
result =
|
||||
conn
|
||||
|
@ -1932,7 +1981,7 @@ test "getting a list of blocks" do
|
|||
|> get("/api/v1/blocks?limit=1")
|
||||
|> json_response_and_validate_schema(200)
|
||||
|
||||
assert [%{"id" => ^id1}] = result
|
||||
assert [%{"id" => ^id3}] = result
|
||||
|
||||
result =
|
||||
conn
|
||||
|
@ -1940,7 +1989,7 @@ test "getting a list of blocks" do
|
|||
|> get("/api/v1/blocks?since_id=#{id1}")
|
||||
|> json_response_and_validate_schema(200)
|
||||
|
||||
assert [%{"id" => ^id2}, %{"id" => ^id3}] = result
|
||||
assert [%{"id" => ^id3}, %{"id" => ^id2}] = result
|
||||
|
||||
result =
|
||||
conn
|
||||
|
@ -1956,7 +2005,31 @@ test "getting a list of blocks" do
|
|||
|> get("/api/v1/blocks?since_id=#{id1}&limit=1")
|
||||
|> json_response_and_validate_schema(200)
|
||||
|
||||
assert [%{"id" => ^id2}] = result
|
||||
assert [%{"id" => ^id3}] = result
|
||||
|
||||
conn_res =
|
||||
conn
|
||||
|> assign(:user, user)
|
||||
|> get("/api/v1/blocks?limit=2")
|
||||
|
||||
next_url =
|
||||
~r{<.+?(?<link>/api[^>]+)>; rel=\"next\"}
|
||||
|> Regex.named_captures(get_resp_header(conn_res, "link") |> Enum.at(0))
|
||||
|> Map.get("link")
|
||||
|
||||
result =
|
||||
conn_res
|
||||
|> json_response_and_validate_schema(200)
|
||||
|
||||
assert [%{"id" => ^id3}, %{"id" => ^id2}] = result
|
||||
|
||||
result =
|
||||
conn
|
||||
|> assign(:user, user)
|
||||
|> get(next_url)
|
||||
|> json_response_and_validate_schema(200)
|
||||
|
||||
assert [%{"id" => ^id1}] = result
|
||||
end
|
||||
|
||||
test "account lookup", %{conn: conn} do
|
||||
|
|
|
@ -79,6 +79,51 @@ test "search", %{conn: conn} do
|
|||
assert status["id"] == to_string(activity.id)
|
||||
end
|
||||
|
||||
test "search local-only status as an authenticated user" do
|
||||
user = insert(:user)
|
||||
%{conn: conn} = oauth_access(["read:search"])
|
||||
|
||||
{:ok, activity} =
|
||||
CommonAPI.post(user, %{status: "This is about 2hu private 天子", visibility: "local"})
|
||||
|
||||
results =
|
||||
conn
|
||||
|> get("/api/v2/search?#{URI.encode_query(%{q: "2hu"})}")
|
||||
|> json_response_and_validate_schema(200)
|
||||
|
||||
[status] = results["statuses"]
|
||||
assert status["id"] == to_string(activity.id)
|
||||
end
|
||||
|
||||
test "search local-only status as an unauthenticated user" do
|
||||
user = insert(:user)
|
||||
%{conn: conn} = oauth_access([])
|
||||
|
||||
{:ok, _activity} =
|
||||
CommonAPI.post(user, %{status: "This is about 2hu private 天子", visibility: "local"})
|
||||
|
||||
results =
|
||||
conn
|
||||
|> get("/api/v2/search?#{URI.encode_query(%{q: "2hu"})}")
|
||||
|> json_response_and_validate_schema(200)
|
||||
|
||||
assert [] = results["statuses"]
|
||||
end
|
||||
|
||||
test "search local-only status as an anonymous user" do
|
||||
user = insert(:user)
|
||||
|
||||
{:ok, _activity} =
|
||||
CommonAPI.post(user, %{status: "This is about 2hu private 天子", visibility: "local"})
|
||||
|
||||
results =
|
||||
build_conn()
|
||||
|> get("/api/v2/search?#{URI.encode_query(%{q: "2hu"})}")
|
||||
|> json_response_and_validate_schema(200)
|
||||
|
||||
assert [] = results["statuses"]
|
||||
end
|
||||
|
||||
@tag capture_log: true
|
||||
test "constructs hashtags from search query", %{conn: conn} do
|
||||
results =
|
||||
|
|
|
@ -1941,23 +1941,50 @@ test "expires_at is nil for another user" do
|
|||
|> json_response_and_validate_schema(:ok)
|
||||
end
|
||||
|
||||
test "posting a local only status" do
|
||||
%{user: _user, conn: conn} = oauth_access(["write:statuses"])
|
||||
describe "local-only statuses" do
|
||||
test "posting a local only status" do
|
||||
%{user: _user, conn: conn} = oauth_access(["write:statuses"])
|
||||
|
||||
conn_one =
|
||||
conn
|
||||
|> put_req_header("content-type", "application/json")
|
||||
|> post("/api/v1/statuses", %{
|
||||
"status" => "cofe",
|
||||
"visibility" => "local"
|
||||
})
|
||||
conn_one =
|
||||
conn
|
||||
|> put_req_header("content-type", "application/json")
|
||||
|> post("/api/v1/statuses", %{
|
||||
"status" => "cofe",
|
||||
"visibility" => "local"
|
||||
})
|
||||
|
||||
local = Utils.as_local_public()
|
||||
local = Utils.as_local_public()
|
||||
|
||||
assert %{"content" => "cofe", "id" => id, "visibility" => "local"} =
|
||||
json_response_and_validate_schema(conn_one, 200)
|
||||
assert %{"content" => "cofe", "id" => id, "visibility" => "local"} =
|
||||
json_response_and_validate_schema(conn_one, 200)
|
||||
|
||||
assert %Activity{id: ^id, data: %{"to" => [^local]}} = Activity.get_by_id(id)
|
||||
assert %Activity{id: ^id, data: %{"to" => [^local]}} = Activity.get_by_id(id)
|
||||
end
|
||||
|
||||
test "other users can read local-only posts" do
|
||||
user = insert(:user)
|
||||
%{user: _reader, conn: conn} = oauth_access(["read:statuses"])
|
||||
|
||||
{:ok, activity} = CommonAPI.post(user, %{status: "#2hu #2HU", visibility: "local"})
|
||||
|
||||
received =
|
||||
conn
|
||||
|> get("/api/v1/statuses/#{activity.id}")
|
||||
|> json_response_and_validate_schema(:ok)
|
||||
|
||||
assert received["id"] == activity.id
|
||||
end
|
||||
|
||||
test "anonymous users cannot see local-only posts" do
|
||||
user = insert(:user)
|
||||
|
||||
{:ok, activity} = CommonAPI.post(user, %{status: "#2hu #2HU", visibility: "local"})
|
||||
|
||||
_received =
|
||||
build_conn()
|
||||
|> get("/api/v1/statuses/#{activity.id}")
|
||||
|> json_response_and_validate_schema(:not_found)
|
||||
end
|
||||
end
|
||||
|
||||
describe "muted reactions" do
|
||||
|
|
|
@ -367,6 +367,47 @@ test "muted emotions", %{conn: conn} do
|
|||
}
|
||||
] = result
|
||||
end
|
||||
|
||||
test "should return local-only posts for authenticated users" do
|
||||
user = insert(:user)
|
||||
%{user: _reader, conn: conn} = oauth_access(["read:statuses"])
|
||||
|
||||
{:ok, %{id: id}} = CommonAPI.post(user, %{status: "#2hu #2HU", visibility: "local"})
|
||||
|
||||
result =
|
||||
conn
|
||||
|> get("/api/v1/timelines/public")
|
||||
|> json_response_and_validate_schema(200)
|
||||
|
||||
assert [%{"id" => ^id}] = result
|
||||
end
|
||||
|
||||
test "should not return local-only posts for users without read:statuses" do
|
||||
user = insert(:user)
|
||||
%{user: _reader, conn: conn} = oauth_access([])
|
||||
|
||||
{:ok, _activity} = CommonAPI.post(user, %{status: "#2hu #2HU", visibility: "local"})
|
||||
|
||||
result =
|
||||
conn
|
||||
|> get("/api/v1/timelines/public")
|
||||
|> json_response_and_validate_schema(200)
|
||||
|
||||
assert [] = result
|
||||
end
|
||||
|
||||
test "should not return local-only posts for anonymous users" do
|
||||
user = insert(:user)
|
||||
|
||||
{:ok, _activity} = CommonAPI.post(user, %{status: "#2hu #2HU", visibility: "local"})
|
||||
|
||||
result =
|
||||
build_conn()
|
||||
|> get("/api/v1/timelines/public")
|
||||
|> json_response_and_validate_schema(200)
|
||||
|
||||
assert [] = result
|
||||
end
|
||||
end
|
||||
|
||||
defp local_and_remote_activities do
|
||||
|
|
|
@ -659,7 +659,7 @@ test "renders mute expiration date" do
|
|||
other_user = insert(:user)
|
||||
|
||||
{:ok, _user_relationships} =
|
||||
User.mute(user, other_user, %{notifications: true, expires_in: 24 * 60 * 60})
|
||||
User.mute(user, other_user, %{notifications: true, duration: 24 * 60 * 60})
|
||||
|
||||
%{
|
||||
mute_expires_at: mute_expires_at
|
||||
|
|
|
@ -82,4 +82,24 @@ test "POST /api/v1/pleroma/backups", %{user: _user, conn: conn} do
|
|||
|> post("/api/v1/pleroma/backups")
|
||||
|> json_response_and_validate_schema(400)
|
||||
end
|
||||
|
||||
test "Backup without email address" do
|
||||
user = Pleroma.Factory.insert(:user, email: nil)
|
||||
%{conn: conn} = oauth_access(["read:accounts"], user: user)
|
||||
|
||||
assert is_nil(user.email)
|
||||
|
||||
assert [
|
||||
%{
|
||||
"content_type" => "application/zip",
|
||||
"url" => _url,
|
||||
"file_size" => 0,
|
||||
"processed" => false,
|
||||
"inserted_at" => _
|
||||
}
|
||||
] =
|
||||
conn
|
||||
|> post("/api/v1/pleroma/backups")
|
||||
|> json_response_and_validate_schema(:ok)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -553,7 +553,7 @@ test "with proper permissions and invalid password", %{conn: conn} do
|
|||
assert json_response_and_validate_schema(conn, 200) == %{"error" => "Invalid password."}
|
||||
end
|
||||
|
||||
test "with proper permissions, valid password and target account does not alias this",
|
||||
test "with proper permissions, valid password and target account does not alias it",
|
||||
%{
|
||||
conn: conn
|
||||
} do
|
||||
|
@ -592,7 +592,7 @@ test "with proper permissions, valid password and target account does not exist"
|
|||
}
|
||||
end
|
||||
|
||||
test "with proper permissions, valid password, remote target account aliases this and local cache does not exist",
|
||||
test "with proper permissions, valid password, remote target account aliases it and local cache does not exist",
|
||||
%{} do
|
||||
user = insert(:user, ap_id: "https://lm.kazv.moe/users/testuser")
|
||||
%{user: _user, conn: conn} = oauth_access(["write:accounts"], user: user)
|
||||
|
@ -610,7 +610,7 @@ test "with proper permissions, valid password, remote target account aliases thi
|
|||
assert json_response_and_validate_schema(conn, 200) == %{"status" => "success"}
|
||||
end
|
||||
|
||||
test "with proper permissions, valid password, remote target account aliases this and local cache does not alias this",
|
||||
test "with proper permissions, valid password, remote target account aliases it and local cache does not aliases it",
|
||||
%{} do
|
||||
user = insert(:user, ap_id: "https://lm.kazv.moe/users/testuser")
|
||||
%{user: _user, conn: conn} = oauth_access(["write:accounts"], user: user)
|
||||
|
@ -636,7 +636,7 @@ test "with proper permissions, valid password, remote target account aliases thi
|
|||
assert json_response_and_validate_schema(conn, 200) == %{"status" => "success"}
|
||||
end
|
||||
|
||||
test "with proper permissions, valid password, remote target account does not alias this and local cache aliases this",
|
||||
test "with proper permissions, valid password, remote target account does not aliases it and local cache aliases it",
|
||||
%{
|
||||
user: user,
|
||||
conn: conn
|
||||
|
@ -665,7 +665,7 @@ test "with proper permissions, valid password, remote target account does not al
|
|||
}
|
||||
end
|
||||
|
||||
test "with proper permissions, valid password and target account aliases this", %{
|
||||
test "with proper permissions, valid password and target account aliases it", %{
|
||||
conn: conn,
|
||||
user: user
|
||||
} do
|
||||
|
|
Loading…
Reference in a new issue