Merge branch 'merge-pleroma' into 'develop'

merge-pleroma

See merge request soapbox-pub/soapbox-be!145
This commit is contained in:
marcin mikołajczak 2022-08-03 22:04:39 +00:00
commit 3b964ee555
30 changed files with 794 additions and 214 deletions

View file

@ -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

View file

@ -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,

View file

@ -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

View file

@ -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

View 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

View file

@ -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},

View file

@ -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)

View file

@ -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

View file

@ -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) ->

View file

@ -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

View file

@ -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
}
},

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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 ""

View file

@ -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

View file

@ -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

View file

@ -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"
}
]
}

View file

@ -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)

View file

@ -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)

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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 =

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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