Merge branch 'develop' of https://git.pleroma.social/pleroma/pleroma into emr_develop

This commit is contained in:
a1batross 2022-08-22 03:50:38 +02:00
commit be39ed35b8
33 changed files with 999 additions and 173 deletions

View File

@ -13,6 +13,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
### Changed
- Allow users to remove their emails if instance does not need email to register
- Uploadfilter `Pleroma.Upload.Filter.Exiftool` has been renamed to `Pleroma.Upload.Filter.Exiftool.StripLocation`
- Updated the recommended pleroma.vcl configuration for Varnish to target Varnish 7.0+
### Added
- `activeMonth` and `activeHalfyear` fields in NodeInfo usage.users object
@ -48,6 +49,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Fixed crash when pinned_objects is nil
- Fixed slow timelines when there are a lot of deactivated users
- Fixed account deletion API
- Fixed lowercase HTTP HEAD method in the Media Proxy Preview code
### Removed

View File

@ -12,7 +12,7 @@ RUN apk add git gcc g++ musl-dev make cmake file-dev &&\
mkdir release &&\
mix release --path release
FROM alpine:3.14
FROM alpine
ARG BUILD_DATE
ARG VCS_REF

View File

@ -725,3 +725,42 @@ Emoji reactions work a lot like favourites do. They make it possible to react to
* Authentication: required
* Params: none
* Response: HTTP 200 on success, 500 on error
## `/api/v1/pleroma/settings/:app`
### Gets settings for some application
* Method `GET`
* Authentication: `read:accounts`
* Response: JSON. The settings for that application, or empty object if there is none.
* Example response:
```json
{
"some key": "some value"
}
```
### Updates settings for some application
* Method `PATCH`
* Authentication: `write:accounts`
* Request body: JSON object. The object will be merged recursively with old settings. If some field is set to null, it is removed.
* Example request:
```json
{
"some key": "some value",
"key to remove": null,
"nested field": {
"some key": "some value",
"key to remove": null
}
}
```
* Response: JSON. Updated (merged) settings for that application.
* Example response:
```json
{
"some key": "some value",
"nested field": {
"some key": "some value",
}
}
```

View File

@ -1,4 +1,5 @@
# Recommended varnishncsa logging format: '%h %l %u %t "%m %{X-Forwarded-Proto}i://%{Host}i%U%q %H" %s %b "%{Referer}i" "%{User-agent}i"'
# Please use Varnish 7.0+ for proper Range Requests / Chunked encoding support
vcl 4.1;
import std;
@ -22,11 +23,6 @@ sub vcl_recv {
set req.http.X-Forwarded-Proto = "https";
}
# CHUNKED SUPPORT
if (req.http.Range ~ "bytes=") {
set req.http.x-range = req.http.Range;
}
# Pipe if WebSockets request is coming through
if (req.http.upgrade ~ "(?i)websocket") {
return (pipe);
@ -35,9 +31,9 @@ sub vcl_recv {
# Allow purging of the cache
if (req.method == "PURGE") {
if (!client.ip ~ purge) {
return(synth(405,"Not allowed."));
return (synth(405,"Not allowed."));
}
return(purge);
return (purge);
}
}
@ -53,17 +49,11 @@ sub vcl_backend_response {
return (retry);
}
# CHUNKED SUPPORT
if (bereq.http.x-range ~ "bytes=" && beresp.status == 206) {
set beresp.ttl = 10m;
set beresp.http.CR = beresp.http.content-range;
}
# Bypass cache for large files
# 50000000 ~ 50MB
if (std.integer(beresp.http.content-length, 0) > 50000000) {
set beresp.uncacheable = true;
return(deliver);
return (deliver);
}
# Don't cache objects that require authentication
@ -94,7 +84,7 @@ sub vcl_synth {
if (resp.status == 750) {
set resp.status = 301;
set resp.http.Location = req.http.x-redir;
return(deliver);
return (deliver);
}
}
@ -106,25 +96,12 @@ sub vcl_pipe {
}
}
sub vcl_hash {
# CHUNKED SUPPORT
if (req.http.x-range ~ "bytes=") {
hash_data(req.http.x-range);
unset req.http.Range;
}
}
sub vcl_backend_fetch {
# Be more lenient for slow servers on the fediverse
if (bereq.url ~ "^/proxy/") {
set bereq.first_byte_timeout = 300s;
}
# CHUNKED SUPPORT
if (bereq.http.x-range) {
set bereq.http.Range = bereq.http.x-range;
}
if (bereq.retries == 0) {
# Clean up the X-Varnish-Backend-503 flag that is used internally
# to mark broken backend responses that should be retried.
@ -143,14 +120,6 @@ sub vcl_backend_fetch {
}
}
sub vcl_deliver {
# CHUNKED SUPPORT
if (resp.http.CR) {
set resp.http.Content-Range = resp.http.CR;
unset resp.http.CR;
}
}
sub vcl_backend_error {
# Retry broken backend responses.
set bereq.http.X-Varnish-Backend-503 = "1";

View File

@ -421,6 +421,38 @@ defmodule Mix.Tasks.Pleroma.User do
|> Stream.run()
end
def run(["fix_follow_state", local_user, remote_user]) do
start_pleroma()
with {:local, %User{} = local} <- {:local, User.get_by_nickname(local_user)},
{:remote, %User{} = remote} <- {:remote, User.get_by_nickname(remote_user)},
{:follow_data, %{data: %{"state" => request_state}}} <-
{:follow_data, Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(local, remote)} do
calculated_state = User.following?(local, remote)
shell_info(
"Request state is #{request_state}, vs calculated state of following=#{calculated_state}"
)
if calculated_state == false && request_state == "accept" do
shell_info("Discrepancy found, fixing")
Pleroma.Web.CommonAPI.reject_follow_request(local, remote)
shell_info("Relationship fixed")
else
shell_info("No discrepancy found")
end
else
{:local, _} ->
shell_error("No local user #{local_user}")
{:remote, _} ->
shell_error("No remote user #{remote_user}")
{:follow_data, _} ->
shell_error("No follow data for #{local_user} and #{remote_user}")
end
end
defp set_moderator(user, value) do
{:ok, user} =
user

View File

@ -30,7 +30,7 @@ defmodule Pleroma.Activity.Search 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 @@ defmodule Pleroma.Activity.Search 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

@ -112,7 +112,17 @@ defmodule Pleroma.Application do
# See http://elixir-lang.org/docs/stable/elixir/Supervisor.html
# for other strategies and supported options
opts = [strategy: :one_for_one, name: Pleroma.Supervisor]
# If we have a lot of caches, default max_restarts can cause test
# resets to fail.
# Go for the default 3 unless we're in test
max_restarts =
if @mix_env == :test do
100
else
3
end
opts = [strategy: :one_for_one, name: Pleroma.Supervisor, max_restarts: max_restarts]
result = Supervisor.start_link(children, opts)
set_postgres_server_version()

View File

@ -1574,13 +1574,19 @@ defmodule Pleroma.User do
blocker
end
# clear any requested follows as well
# clear any requested follows from both sides as well
blocked =
case CommonAPI.reject_follow_request(blocked, blocker) do
{:ok, %User{} = updated_blocked} -> updated_blocked
nil -> blocked
end
blocker =
case CommonAPI.reject_follow_request(blocker, blocked) do
{:ok, %User{} = updated_blocker} -> updated_blocker
nil -> blocker
end
unsubscribe(blocked, blocker)
unfollowing_blocked = Config.get([:activitypub, :unfollow_blocked], true)

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 @@ defmodule Pleroma.User.Backup 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

@ -511,9 +511,18 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub 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)
@ -613,9 +622,11 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
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
@ -702,8 +713,12 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub 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

@ -84,7 +84,10 @@ defmodule Pleroma.Web.ActivityPub.Visibility do
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

@ -0,0 +1,72 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.PleromaSettingsOperation do
alias OpenApiSpex.Operation
alias OpenApiSpex.Schema
import Pleroma.Web.ApiSpec.Helpers
def open_api_operation(action) do
operation = String.to_existing_atom("#{action}_operation")
apply(__MODULE__, operation, [])
end
def show_operation do
%Operation{
tags: ["Settings"],
summary: "Get settings for an application",
description: "Get synchronized settings for an application",
operationId: "SettingsController.show",
parameters: [app_name_param()],
security: [%{"oAuth" => ["read:accounts"]}],
responses: %{
200 => Operation.response("object", "application/json", object())
}
}
end
def update_operation do
%Operation{
tags: ["Settings"],
summary: "Update settings for an application",
description: "Update synchronized settings for an application",
operationId: "SettingsController.update",
parameters: [app_name_param()],
security: [%{"oAuth" => ["write:accounts"]}],
requestBody: request_body("Parameters", update_request(), required: true),
responses: %{
200 => Operation.response("object", "application/json", object())
}
}
end
def app_name_param do
Operation.parameter(:app, :path, %Schema{type: :string}, "Application name",
example: "pleroma-fe",
required: true
)
end
def object do
%Schema{
title: "Settings object",
description: "The object that contains settings for the application.",
type: :object
}
end
def update_request do
%Schema{
title: "SettingsUpdateRequest",
type: :object,
description:
"The settings object to be merged with the current settings. To remove a field, set it to null.",
example: %{
"config1" => true,
"config2_to_unset" => nil
}
}
end
end

View File

@ -112,6 +112,8 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController 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

@ -54,7 +54,7 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do
media_proxy_url = MediaProxy.url(url)
with {:ok, %{status: status} = head_response} when status in 200..299 <-
Pleroma.HTTP.request("head", media_proxy_url, [], [], pool: :media) do
Pleroma.HTTP.request("HEAD", media_proxy_url, [], [], pool: :media) do
content_type = Tesla.get_header(head_response, "content-type")
content_length = Tesla.get_header(head_response, "content-length")
content_length = content_length && String.to_integer(content_length)

View File

@ -0,0 +1,79 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.PleromaAPI.SettingsController do
use Pleroma.Web, :controller
alias Pleroma.Web.Plugs.OAuthScopesPlug
plug(Pleroma.Web.ApiSpec.CastAndValidate)
plug(
OAuthScopesPlug,
%{scopes: ["write:accounts"]} when action in [:update]
)
plug(
OAuthScopesPlug,
%{scopes: ["read:accounts"]} when action in [:show]
)
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaSettingsOperation
@doc "GET /api/v1/pleroma/settings/:app"
def show(%{assigns: %{user: user}} = conn, %{app: app} = _params) do
conn
|> json(get_settings(user, app))
end
@doc "PATCH /api/v1/pleroma/settings/:app"
def update(%{assigns: %{user: user}, body_params: body_params} = conn, %{app: app} = _params) do
settings =
get_settings(user, app)
|> merge_recursively(body_params)
with changeset <-
Pleroma.User.update_changeset(
user,
%{pleroma_settings_store: %{app => settings}}
),
{:ok, _} <- Pleroma.Repo.update(changeset) do
conn
|> json(settings)
end
end
defp merge_recursively(old, %{} = new) do
old = ensure_object(old)
Enum.reduce(
new,
old,
fn
{k, nil}, acc ->
Map.drop(acc, [k])
{k, %{} = new_child}, acc ->
Map.put(acc, k, merge_recursively(acc[k], new_child))
{k, v}, acc ->
Map.put(acc, k, v)
end
)
end
defp get_settings(user, app) do
user.pleroma_settings_store
|> Map.get(app, %{})
|> ensure_object()
end
defp ensure_object(%{} = object) do
object
end
defp ensure_object(_) do
%{}
end
end

View File

@ -463,6 +463,13 @@ defmodule Pleroma.Web.Router do
get("/birthdays", AccountController, :birthdays)
end
scope [] do
pipe_through(:authenticated_api)
get("/settings/:app", SettingsController, :show)
patch("/settings/:app", SettingsController, :update)
end
post("/accounts/confirmation_resend", AccountController, :confirmation_resend)
end

View File

@ -37,10 +37,7 @@ defmodule Pleroma.Workers.BackupWorker do
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 @@ defmodule Pleroma.Workers.BackupWorker 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

@ -9,6 +9,12 @@ defmodule Pleroma.Workers.ReceiverWorker do
@impl Oban.Worker
def perform(%Job{args: %{"op" => "incoming_ap_doc", "params" => params}}) do
Federator.perform(:incoming_ap_doc, params)
with {:ok, res} <- Federator.perform(:incoming_ap_doc, params) do
{:ok, res}
else
{:error, :origin_containment_failed} -> {:cancel, :origin_containment_failed}
{:error, {:reject, reason}} -> {:cancel, reason}
e -> e
end
end
end

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

@ -18,6 +18,23 @@ defmodule Pleroma.Activity.SearchTest 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 @@ defmodule Pleroma.User.BackupTest 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

@ -247,6 +247,27 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest 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 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest 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

@ -61,9 +61,11 @@ defmodule Pleroma.Web.CommonAPITest do
describe "blocking" do
setup do
blocker = insert(:user)
blocked = insert(:user)
User.follow(blocker, blocked)
User.follow(blocked, blocker)
blocked = insert(:user, local: false)
CommonAPI.follow(blocker, blocked)
CommonAPI.follow(blocked, blocker)
CommonAPI.accept_follow_request(blocker, blocked)
CommonAPI.accept_follow_request(blocked, blocked)
%{blocker: blocker, blocked: blocked}
end
@ -72,6 +74,9 @@ defmodule Pleroma.Web.CommonAPITest do
with_mock Pleroma.Web.Federator,
publish: fn _ -> nil end do
assert User.get_follow_state(blocker, blocked) == :follow_accept
refute is_nil(Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(blocker, blocked))
assert {:ok, block} = CommonAPI.block(blocker, blocked)
assert block.local
@ -79,6 +84,11 @@ defmodule Pleroma.Web.CommonAPITest do
refute User.following?(blocker, blocked)
refute User.following?(blocked, blocker)
refute User.get_follow_state(blocker, blocked)
assert %{data: %{"state" => "reject"}} =
Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(blocker, blocked)
assert called(Pleroma.Web.Federator.publish(block))
end
end

View File

@ -153,7 +153,7 @@ defmodule Pleroma.Web.FederatorTest do
}
assert {:ok, job} = Federator.incoming_ap_doc(params)
assert {:error, :origin_containment_failed} = ObanHelpers.perform(job)
assert {:cancel, :origin_containment_failed} = ObanHelpers.perform(job)
end
test "it does not crash if MRF rejects the post" do
@ -169,7 +169,7 @@ defmodule Pleroma.Web.FederatorTest do
|> Jason.decode!()
assert {:ok, job} = Federator.incoming_ap_doc(params)
assert {:error, _} = ObanHelpers.perform(job)
assert {:cancel, _} = ObanHelpers.perform(job)
end
end
end

View File

@ -408,6 +408,20 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest 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"])

View File

@ -79,6 +79,51 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest 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

@ -1901,23 +1901,50 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest 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 @@ defmodule Pleroma.Web.MastodonAPI.TimelineControllerTest 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

@ -158,7 +158,7 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyControllerTest do
media_proxy_url: media_proxy_url
} do
Tesla.Mock.mock(fn
%{method: "head", url: ^media_proxy_url} ->
%{method: "HEAD", url: ^media_proxy_url} ->
%Tesla.Env{status: 500, body: ""}
end)
@ -173,7 +173,7 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyControllerTest do
media_proxy_url: media_proxy_url
} do
Tesla.Mock.mock(fn
%{method: "head", url: ^media_proxy_url} ->
%{method: "HEAD", url: ^media_proxy_url} ->
%Tesla.Env{status: 200, body: "", headers: [{"content-type", "application/pdf"}]}
end)
@ -193,7 +193,7 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyControllerTest do
clear_config([:media_preview_proxy, :min_content_length], 1_000_000_000)
Tesla.Mock.mock(fn
%{method: "head", url: ^media_proxy_url} ->
%{method: "HEAD", url: ^media_proxy_url} ->
%Tesla.Env{
status: 200,
body: "",
@ -218,7 +218,7 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyControllerTest do
media_proxy_url: media_proxy_url
} do
Tesla.Mock.mock(fn
%{method: "head", url: ^media_proxy_url} ->
%{method: "HEAD", url: ^media_proxy_url} ->
%Tesla.Env{status: 200, body: "", headers: [{"content-type", "image/gif"}]}
end)
@ -236,7 +236,7 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyControllerTest do
media_proxy_url: media_proxy_url
} do
Tesla.Mock.mock(fn
%{method: "head", url: ^media_proxy_url} ->
%{method: "HEAD", url: ^media_proxy_url} ->
%Tesla.Env{status: 200, body: "", headers: [{"content-type", "image/jpeg"}]}
end)
@ -256,7 +256,7 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyControllerTest do
clear_config([:media_preview_proxy, :min_content_length], 100_000)
Tesla.Mock.mock(fn
%{method: "head", url: ^media_proxy_url} ->
%{method: "HEAD", url: ^media_proxy_url} ->
%Tesla.Env{
status: 200,
body: "",
@ -278,7 +278,7 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyControllerTest do
assert_dependencies_installed()
Tesla.Mock.mock(fn
%{method: "head", url: ^media_proxy_url} ->
%{method: "HEAD", url: ^media_proxy_url} ->
%Tesla.Env{status: 200, body: "", headers: [{"content-type", "image/png"}]}
%{method: :get, url: ^media_proxy_url} ->
@ -300,7 +300,7 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyControllerTest do
assert_dependencies_installed()
Tesla.Mock.mock(fn
%{method: "head", url: ^media_proxy_url} ->
%{method: "HEAD", url: ^media_proxy_url} ->
%Tesla.Env{status: 200, body: "", headers: [{"content-type", "image/jpeg"}]}
%{method: :get, url: ^media_proxy_url} ->
@ -320,7 +320,7 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyControllerTest do
media_proxy_url: media_proxy_url
} do
Tesla.Mock.mock(fn
%{method: "head", url: ^media_proxy_url} ->
%{method: "HEAD", url: ^media_proxy_url} ->
%Tesla.Env{status: 200, body: "", headers: [{"content-type", "image/jpeg"}]}
%{method: :get, url: ^media_proxy_url} ->

View File

@ -82,4 +82,24 @@ defmodule Pleroma.Web.PleromaAPI.BackupControllerTest 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

@ -0,0 +1,126 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.PleromaAPI.SettingsControllerTest do
use Pleroma.Web.ConnCase
import Pleroma.Factory
describe "GET /api/v1/pleroma/settings/:app" do
setup do
oauth_access(["read:accounts"])
end
test "it gets empty settings", %{conn: conn} do
response =
conn
|> get("/api/v1/pleroma/settings/pleroma-fe")
|> json_response_and_validate_schema(:ok)
assert response == %{}
end
test "it gets settings", %{conn: conn, user: user} do
response =
conn
|> assign(
:user,
struct(user,
pleroma_settings_store: %{
"pleroma-fe" => %{
"foo" => "bar"
}
}
)
)
|> get("/api/v1/pleroma/settings/pleroma-fe")
|> json_response_and_validate_schema(:ok)
assert %{"foo" => "bar"} == response
end
end
describe "POST /api/v1/pleroma/settings/:app" do
setup do
settings = %{
"foo" => "bar",
"nested" => %{
"1" => "2"
}
}
user =
insert(
:user,
%{
pleroma_settings_store: %{
"pleroma-fe" => settings
}
}
)
%{conn: conn} = oauth_access(["write:accounts"], user: user)
%{conn: conn, user: user, settings: settings}
end
test "it adds keys", %{conn: conn} do
response =
conn
|> put_req_header("content-type", "application/json")
|> patch("/api/v1/pleroma/settings/pleroma-fe", %{
"foo" => "edited",
"bar" => "new",
"nested" => %{"3" => "4"}
})
|> json_response_and_validate_schema(:ok)
assert response == %{
"foo" => "edited",
"bar" => "new",
"nested" => %{
"1" => "2",
"3" => "4"
}
}
end
test "it removes keys", %{conn: conn} do
response =
conn
|> put_req_header("content-type", "application/json")
|> patch("/api/v1/pleroma/settings/pleroma-fe", %{
"foo" => nil,
"bar" => nil,
"nested" => %{
"1" => nil,
"3" => nil
}
})
|> json_response_and_validate_schema(:ok)
assert response == %{
"nested" => %{}
}
end
test "it does not override settings for other apps", %{
conn: conn,
user: user,
settings: settings
} do
conn
|> put_req_header("content-type", "application/json")
|> patch("/api/v1/pleroma/settings/admin-fe", %{"foo" => "bar"})
|> json_response_and_validate_schema(:ok)
user = Pleroma.User.get_by_id(user.id)
assert user.pleroma_settings_store == %{
"pleroma-fe" => settings,
"admin-fe" => %{"foo" => "bar"}
}
end
end
end

View File

@ -0,0 +1,25 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Workers.ReceiverWorkerTest do
use Pleroma.DataCase, async: true
use Oban.Testing, repo: Pleroma.Repo
import Mock
import Pleroma.Factory
alias Pleroma.Workers.ReceiverWorker
test "it ignores MRF reject" do
params = insert(:note).data
with_mock Pleroma.Web.ActivityPub.Transmogrifier,
handle_incoming: fn _ -> {:reject, "MRF"} end do
assert {:cancel, "MRF"} =
ReceiverWorker.perform(%Oban.Job{
args: %{"op" => "incoming_ap_doc", "params" => params}
})
end
end
end