merge develop

This commit is contained in:
Roman Chvanikov 2019-07-12 18:08:27 +03:00
commit eae991b06a
160 changed files with 4263 additions and 1310 deletions

2
.mailmap Normal file
View File

@ -0,0 +1,2 @@
Ariadne Conill <ariadne@dereferenced.org> <nenolod@dereferenced.org>
Ariadne Conill <ariadne@dereferenced.org> <nenolod@gmail.com>

View File

@ -4,28 +4,36 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [Unreleased]
### Added
- MRF: Support for priming the mediaproxy cache (`Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy`)
Configuration: `federation_incoming_replies_max_depth` option
- Mastodon API: Support for the [`tagged` filter](https://github.com/tootsuite/mastodon/pull/9755) in [`GET /api/v1/accounts/:id/statuses`](https://docs.joinmastodon.org/api/rest/accounts/#get-api-v1-accounts-id-statuses)
- Mastodon API, streaming: Add support for passing the token in the `Sec-WebSocket-Protocol` header
- Admin API: Return users' tags when querying reports
- Admin API: Return avatar and display name when querying users
- Admin API: Allow querying user by ID
- Added synchronization of following/followers counters for external users
### Changed
- **Breaking:** Configuration: A setting to explicitly disable the mailer was added, defaulting to true, if you are using a mailer add `config :pleroma, Pleroma.Emails.Mailer, enabled: true` to your config
- Configuration: OpenGraph and TwitterCard providers enabled by default
- Configuration: Filter.AnonymizeFilename added ability to retain file extension with custom text
- NodeInfo: Return `skipThreadContainment` in `metadata` for the `skip_thread_containment` option
### Fixed
- Not being able to pin unlisted posts
- Metadata rendering errors resulting in the entire page being inaccessible
- Mastodon API: Handling of search timeouts (`/api/v1/search` and `/api/v2/search`)
- Mastodon API: Embedded relationships not being properly rendered in the Account entity of Status entity
- Mastodon API: Add `account_id`, `type`, `offset`, and `limit` to search API (`/api/v1/search` and `/api/v2/search`)
### Added
- MRF: Support for priming the mediaproxy cache (`Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy`)
Configuration: `federation_incoming_replies_max_depth` option
- Mastodon API: Support for the [`tagged` filter](https://github.com/tootsuite/mastodon/pull/9755) in [`GET /api/v1/accounts/:id/statuses`](https://docs.joinmastodon.org/api/rest/accounts/#get-api-v1-accounts-id-statuses)
- Mastodon API, streaming: Add support for passing the token in the `Sec-WebSocket-Protocol` header
- Mastodon API, extension: Ability to reset avatar, profile banner, and background
- Admin API: Return users' tags when querying reports
- Admin API: Return avatar and display name when querying users
- Admin API: Allow querying user by ID
- Admin API: Added support for `tuples`.
- Added synchronization of following/followers counters for external users
- Configuration: `enabled` option for `Pleroma.Emails.Mailer`, defaulting to `false`.
- Mastodon API: Add support for categories for custom emojis by reusing the group feature. <https://github.com/tootsuite/mastodon/pull/11196>
### Changed
- Configuration: OpenGraph and TwitterCard providers enabled by default
- Configuration: Filter.AnonymizeFilename added ability to retain file extension with custom text
### Changed
- NodeInfo: Return `skipThreadContainment` in `metadata` for the `skip_thread_containment` option
- Admin API: changed json structure for saving config settings.
## [1.0.0] - 2019-06-29
### Security
@ -88,7 +96,6 @@ Configuration: `federation_incoming_replies_max_depth` option
- OAuth: added job to clean expired access tokens
- MRF: Support for rejecting reports from specific instances (`mrf_simple`)
- MRF: Support for stripping avatars and banner images from specific instances (`mrf_simple`)
- Ability to reset avatar, profile banner and backgroud
- MRF: Support for running subchains.
- Configuration: `skip_thread_containment` option
- Configuration: `rate_limit` option. See `Pleroma.Plugs.RateLimiter` documentation for details.

View File

@ -250,13 +250,7 @@
skip_thread_containment: true,
limit_to_local_content: :unauthenticated,
dynamic_configuration: false,
external_user_synchronization: [
enabled: false,
# every 2 hours
interval: 60 * 60 * 2,
max_retries: 3,
limit: 500
]
external_user_synchronization: true
config :pleroma, :markup,
# XXX - unfortunately, inline images must be enabled by default right now, because
@ -501,7 +495,7 @@
config :pleroma, :auth, oauth_consumer_strategies: oauth_consumer_strategies
config :pleroma, Pleroma.Emails.Mailer, adapter: Swoosh.Adapters.Sendmail
config :pleroma, Pleroma.Emails.Mailer, adapter: Swoosh.Adapters.Sendmail, enabled: false
config :prometheus, Pleroma.Web.Endpoint.MetricsExporter, path: "/api/pleroma/app_metrics"

View File

@ -23,7 +23,7 @@
config :pleroma, Pleroma.Uploaders.Local, uploads: "test/uploads"
config :pleroma, Pleroma.Emails.Mailer, adapter: Swoosh.Adapters.Test
config :pleroma, Pleroma.Emails.Mailer, adapter: Swoosh.Adapters.Test, enabled: true
config :pleroma, :instance,
email: "admin@example.com",
@ -65,7 +65,9 @@
total_user_limit: 3,
enabled: false
config :pleroma, :rate_limit, app_account_creation: {10_000, 5}
config :pleroma, :rate_limit,
search: [{1000, 30}, {1000, 30}],
app_account_creation: {10_000, 5}
config :pleroma, :http_security, report_uri: "https://endpoint.com"

View File

@ -573,7 +573,7 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
configs: [
{
"group": string,
"key": string,
"key": string or string with leading `:` for atoms,
"value": string or {} or [] or {"tuple": []}
}
]
@ -583,10 +583,11 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
## `/api/pleroma/admin/config`
### Update config settings
Module name can be passed as string, which starts with `Pleroma`, e.g. `"Pleroma.Upload"`.
Atom or boolean value can be passed with `:` in the beginning, e.g. `":true"`, `":upload"`. For keys it is not needed.
Integer with `i:`, e.g. `"i:150"`.
Tuple with more than 2 values with `{"tuple": ["first_val", Pleroma.Module, []]}`.
Atom keys and values can be passed with `:` in the beginning, e.g. `":upload"`.
Tuples can be passed as `{"tuple": ["first_val", Pleroma.Module, []]}`.
`{"tuple": ["some_string", "Pleroma.Some.Module", []]}` will be converted to `{"some_string", Pleroma.Some.Module, []}`.
Keywords can be passed as lists with 2 child tuples, e.g.
`[{"tuple": ["first_val", Pleroma.Module]}, {"tuple": ["second_val", true]}]`.
Compile time settings (need instance reboot):
- all settings by this keys:
@ -603,7 +604,7 @@ Compile time settings (need instance reboot):
- Params:
- `configs` => [
- `group` (string)
- `key` (string)
- `key` (string or string with leading `:` for atoms)
- `value` (string, [], {} or {"tuple": []})
- `delete` = true (optional, if parameter must be deleted)
]
@ -616,24 +617,25 @@ Compile time settings (need instance reboot):
{
"group": "pleroma",
"key": "Pleroma.Upload",
"value": {
"uploader": "Pleroma.Uploaders.Local",
"filters": ["Pleroma.Upload.Filter.Dedupe"],
"link_name": ":true",
"proxy_remote": ":false",
"proxy_opts": {
"redirect_on_failure": ":false",
"max_body_length": "i:1048576",
"http": {
"follow_redirect": ":true",
"pool": ":upload"
}
},
"dispatch": {
"value": [
{"tuple": [":uploader", "Pleroma.Uploaders.Local"]},
{"tuple": [":filters", ["Pleroma.Upload.Filter.Dedupe"]]},
{"tuple": [":link_name", true]},
{"tuple": [":proxy_remote", false]},
{"tuple": [":proxy_opts", [
{"tuple": [":redirect_on_failure", false]},
{"tuple": [":max_body_length", 1048576]},
{"tuple": [":http": [
{"tuple": [":follow_redirect", true]},
{"tuple": [":pool", ":upload"]},
]]}
]
]},
{"tuple": [":dispatch", {
"tuple": ["/api/v1/streaming", "Pleroma.Web.MastodonAPI.WebsocketHandler", []]
}
}
}
}]}
]
}
]
}
@ -644,7 +646,7 @@ Compile time settings (need instance reboot):
configs: [
{
"group": string,
"key": string,
"key": string or string with leading `:` for atoms,
"value": string or {} or [] or {"tuple": []}
}
]

View File

@ -46,6 +46,14 @@ Has these additional fields under the `pleroma` object:
- `settings_store`: A generic map of settings for frontends. Opaque to the backend. Only returned in `verify_credentials` and `update_credentials`
- `chat_token`: The token needed for Pleroma chat. Only returned in `verify_credentials`
### Extensions for PleromaFE
These endpoints added for controlling PleromaFE features over the Mastodon API
- PATCH `/api/v1/accounts/update_avatar`: Set/clear user avatar image
- PATCH `/api/v1/accounts/update_banner`: Set/clear user banner image
- PATCH `/api/v1/accounts/update_background`: Set/clear user background image
### Source
Has these additional fields under the `pleroma` object:

View File

@ -41,6 +41,7 @@ This filter replaces the filename (not the path) of an upload. For complete obfu
## Pleroma.Emails.Mailer
* `adapter`: one of the mail adapters listed in [Swoosh readme](https://github.com/swoosh/swoosh#adapters), or `Swoosh.Adapters.Local` for in-memory mailbox.
* `api_key` / `password` and / or other adapter-specific settings, per the above documentation.
* `enabled`: Allows enable/disable send emails. Default: `false`.
An example for Sendgrid adapter:
@ -125,11 +126,7 @@ config :pleroma, Pleroma.Emails.Mailer,
* `skip_thread_containment`: Skip filter out broken threads. The default is `false`.
* `limit_to_local_content`: Limit unauthenticated users to search for local statutes and users only. Possible values: `:unauthenticated`, `:all` and `false`. The default is `:unauthenticated`.
* `dynamic_configuration`: Allow transferring configuration to DB with the subsequent customization from Admin api.
* `external_user_synchronization`: Following/followers counters synchronization settings.
* `enabled`: Enables synchronization
* `interval`: Interval between synchronization.
* `max_retries`: Max rettries for host. After exceeding the limit, the check will not be carried out for users from this host.
* `limit`: Users batch size for processing in one time.
* `external_user_synchronization`: Enabling following/followers counters synchronization for external users.

View File

@ -1,3 +1,7 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Healthcheck do
@moduledoc """
Module collects metrics about app and assign healthy status.

View File

@ -1,3 +1,7 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
Postgrex.Types.define(
Pleroma.PostgresTypes,
[] ++ Ecto.Adapters.Postgres.extensions(),

View File

@ -1,3 +1,7 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Mix.Tasks.Pleroma.Benchmark do
import Mix.Pleroma
use Mix.Task

View File

@ -1,3 +1,7 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Mix.Tasks.Pleroma.Config do
use Mix.Task
import Mix.Pleroma

View File

@ -34,6 +34,8 @@ defmodule Mix.Tasks.Pleroma.Instance do
- `--db-configurable Y/N` - Allow/disallow configuring instance from admin part
- `--uploads-dir` - the directory uploads go in when using a local uploader
- `--static-dir` - the directory custom public files should be read from (custom emojis, frontend bundle overrides, robots.txt, etc.)
- `--listen-ip` - the ip the app should listen to, defaults to 127.0.0.1
- `--listen-port` - the port the app should listen to, defaults to 4000
"""
def run(["gen" | rest]) do
@ -56,7 +58,9 @@ def run(["gen" | rest]) do
indexable: :string,
db_configurable: :string,
uploads_dir: :string,
static_dir: :string
static_dir: :string,
listen_ip: :string,
listen_port: :string
],
aliases: [
o: :output,
@ -146,6 +150,22 @@ def run(["gen" | rest]) do
"n"
) === "y"
listen_port =
get_option(
options,
:listen_port,
"What port will the app listen to (leave it if you are using the default setup with nginx)?",
4000
)
listen_ip =
get_option(
options,
:listen_ip,
"What ip will the app listen to (leave it if you are using the default setup with nginx)?",
"127.0.0.1"
)
uploads_dir =
get_option(
options,
@ -188,7 +208,9 @@ def run(["gen" | rest]) do
db_configurable?: db_configurable?,
static_dir: static_dir,
uploads_dir: uploads_dir,
rum_enabled: rum_enabled
rum_enabled: rum_enabled,
listen_ip: listen_ip,
listen_port: listen_port
)
result_psql =

View File

@ -344,5 +344,5 @@ def restrict_deactivated_users(query) do
)
end
defdelegate search(user, query), to: Pleroma.Activity.Search
defdelegate search(user, query, options \\ []), to: Pleroma.Activity.Search
end

View File

@ -5,14 +5,17 @@
defmodule Pleroma.Activity.Search do
alias Pleroma.Activity
alias Pleroma.Object.Fetcher
alias Pleroma.Repo
alias Pleroma.Pagination
alias Pleroma.User
alias Pleroma.Web.ActivityPub.Visibility
import Ecto.Query
def search(user, search_query) do
def search(user, search_query, options \\ []) do
index_type = if Pleroma.Config.get([:database, :rum_enabled]), do: :rum, else: :gin
limit = Enum.min([Keyword.get(options, :limit), 40])
offset = Keyword.get(options, :offset, 0)
author = Keyword.get(options, :author)
Activity
|> Activity.with_preloaded_object()
@ -20,15 +23,23 @@ def search(user, search_query) do
|> restrict_public()
|> query_with(index_type, search_query)
|> maybe_restrict_local(user)
|> Repo.all()
|> maybe_restrict_author(author)
|> Pagination.fetch_paginated(%{"offset" => offset, "limit" => limit}, :offset)
|> maybe_fetch(user, search_query)
end
def maybe_restrict_author(query, %User{} = author) do
from([a, o] in query,
where: a.actor == ^author.ap_id
)
end
def maybe_restrict_author(query, _), do: query
defp restrict_public(q) do
from([a, o] in q,
where: fragment("?->>'type' = 'Create'", a.data),
where: "https://www.w3.org/ns/activitystreams#Public" in a.recipients,
limit: 40
where: "https://www.w3.org/ns/activitystreams#Public" in a.recipients
)
end

View File

@ -155,11 +155,7 @@ def start(_type, _args) do
start: {Pleroma.Web.Endpoint, :start_link, []},
type: :supervisor
},
%{id: Pleroma.Gopher.Server, start: {Pleroma.Gopher.Server, :start_link, []}},
%{
id: Pleroma.User.SynchronizationWorker,
start: {Pleroma.User.SynchronizationWorker, :start_link, []}
}
%{id: Pleroma.Gopher.Server, start: {Pleroma.Gopher.Server, :start_link, []}}
]
# See http://elixir-lang.org/docs/stable/elixir/Supervisor.html

View File

@ -1,3 +1,7 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.BBS.Authenticator do
use Sshd.PasswordAuthenticator
alias Comeonin.Pbkdf2

View File

@ -1,3 +1,7 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.BBS.Handler do
use Sshd.ShellHandler
alias Pleroma.Activity

View File

@ -1,3 +1,7 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Bookmark do
use Ecto.Schema

View File

@ -3,6 +3,8 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Captcha do
import Pleroma.Web.Gettext
alias Calendar.DateTime
alias Plug.Crypto.KeyGenerator
alias Plug.Crypto.MessageEncryptor
@ -83,10 +85,11 @@ def handle_call({:validate, token, captcha, answer_data}, _from, state) do
with {:ok, data} <- MessageEncryptor.decrypt(answer_data, secret, sign_secret),
%{at: at, answer_data: answer_md5} <- :erlang.binary_to_term(data) do
try do
if DateTime.before?(at, valid_if_after), do: throw({:error, "CAPTCHA expired"})
if DateTime.before?(at, valid_if_after),
do: throw({:error, dgettext("errors", "CAPTCHA expired")})
if not is_nil(Cachex.get!(:used_captcha_cache, token)),
do: throw({:error, "CAPTCHA already used"})
do: throw({:error, dgettext("errors", "CAPTCHA already used")})
res = method().validate(token, captcha, answer_md5)
# Throw if an error occurs
@ -101,7 +104,7 @@ def handle_call({:validate, token, captcha, answer_data}, _from, state) do
:throw, e -> e
end
else
_ -> {:error, "Invalid answer data"}
_ -> {:error, dgettext("errors", "Invalid answer data")}
end
{:reply, result, state}

View File

@ -3,6 +3,7 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Captcha.Kocaptcha do
import Pleroma.Web.Gettext
alias Pleroma.Captcha.Service
@behaviour Service
@ -12,7 +13,7 @@ def new do
case Tesla.get(endpoint <> "/new") do
{:error, _} ->
%{error: "Kocaptcha service unavailable"}
%{error: dgettext("errors", "Kocaptcha service unavailable")}
{:ok, res} ->
json_resp = Jason.decode!(res.body)
@ -32,6 +33,6 @@ def validate(_token, captcha, answer_data) do
if not is_nil(captcha) and
:crypto.hash(:md5, captcha) |> Base.encode16() == String.upcase(answer_data),
do: :ok,
else: {:error, "Invalid CAPTCHA"}
else: {:error, dgettext("errors", "Invalid CAPTCHA")}
end
end

View File

@ -1,3 +1,7 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Config.TransferTask do
use Task
alias Pleroma.Web.AdminAPI.Config

View File

@ -3,11 +3,58 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Emails.Mailer do
use Swoosh.Mailer, otp_app: :pleroma
@moduledoc """
Defines the Pleroma mailer.
The module contains functions to delivery email using Swoosh.Mailer.
"""
alias Swoosh.DeliveryError
@otp_app :pleroma
@mailer_config [otp: :pleroma]
@spec enabled?() :: boolean()
def enabled?, do: Pleroma.Config.get([__MODULE__, :enabled])
@doc "add email to queue"
def deliver_async(email, config \\ []) do
PleromaJobQueue.enqueue(:mailer, __MODULE__, [:deliver_async, email, config])
end
@doc "callback to perform send email from queue"
def perform(:deliver_async, email, config), do: deliver(email, config)
@spec deliver(Swoosh.Email.t(), Keyword.t()) :: {:ok, term} | {:error, term}
def deliver(email, config \\ [])
def deliver(email, config) do
case enabled?() do
true -> Swoosh.Mailer.deliver(email, parse_config(config))
false -> {:error, :deliveries_disabled}
end
end
@spec deliver!(Swoosh.Email.t(), Keyword.t()) :: term | no_return
def deliver!(email, config \\ [])
def deliver!(email, config) do
case deliver(email, config) do
{:ok, result} -> result
{:error, reason} -> raise DeliveryError, reason: reason
end
end
@on_load :validate_dependency
@doc false
def validate_dependency do
parse_config([])
|> Keyword.get(:adapter)
|> Swoosh.Mailer.validate_dependency()
end
defp parse_config(config) do
Swoosh.Mailer.parse_config(@otp_app, __MODULE__, @mailer_config, config)
end
end

View File

@ -1,3 +1,7 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Instances do
@moduledoc "Instances context."

View File

@ -1,3 +1,7 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Instances.Instance do
@moduledoc "Instance."

View File

@ -44,7 +44,15 @@ def get_by_ap_id(ap_id) do
Repo.one(from(object in Object, where: fragment("(?)->>'id' = ?", object.data, ^ap_id)))
end
defp warn_on_no_object_preloaded(ap_id) do
"Object.normalize() called without preloaded object (#{ap_id}). Consider preloading the object"
|> Logger.debug()
Logger.debug("Backtrace: #{inspect(Process.info(:erlang.self(), :current_stacktrace))}")
end
def normalize(_, fetch_remote \\ true, options \\ [])
# If we pass an Activity to Object.normalize(), we can try to use the preloaded object.
# Use this whenever possible, especially when walking graphs in an O(N) loop!
def normalize(%Object{} = object, _, _), do: object
@ -55,25 +63,15 @@ def normalize(%Activity{data: %{"object" => %{"fake" => true} = data}}, _, _) do
%Object{id: "pleroma:fake_object_id", data: data}
end
# Catch and log Object.normalize() calls where the Activity's child object is not
# preloaded.
# No preloaded object
def normalize(%Activity{data: %{"object" => %{"id" => ap_id}}}, fetch_remote, _) do
Logger.debug(
"Object.normalize() called without preloaded object (#{ap_id}). Consider preloading the object!"
)
Logger.debug("Backtrace: #{inspect(Process.info(:erlang.self(), :current_stacktrace))}")
warn_on_no_object_preloaded(ap_id)
normalize(ap_id, fetch_remote)
end
# No preloaded object
def normalize(%Activity{data: %{"object" => ap_id}}, fetch_remote, _) do
Logger.debug(
"Object.normalize() called without preloaded object (#{ap_id}). Consider preloading the object!"
)
Logger.debug("Backtrace: #{inspect(Process.info(:erlang.self(), :current_stacktrace))}")
warn_on_no_object_preloaded(ap_id)
normalize(ap_id, fetch_remote)
end

View File

@ -1,3 +1,7 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Object.Fetcher do
alias Pleroma.HTTP
alias Pleroma.Object

View File

@ -1,3 +1,7 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.ObjectTombstone do
@enforce_keys [:id, :formerType, :deleted]
defstruct [:id, :formerType, :deleted, type: "Tombstone"]

View File

@ -1,3 +1,7 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Pagination do
@moduledoc """
Implements Mastodon-compatible pagination.
@ -10,16 +14,28 @@ defmodule Pleroma.Pagination do
@default_limit 20
def fetch_paginated(query, params) do
def fetch_paginated(query, params, type \\ :keyset)
def fetch_paginated(query, params, :keyset) do
options = cast_params(params)
query
|> paginate(options)
|> paginate(options, :keyset)
|> Repo.all()
|> enforce_order(options)
end
def paginate(query, options) do
def fetch_paginated(query, params, :offset) do
options = cast_params(params)
query
|> paginate(options, :offset)
|> Repo.all()
end
def paginate(query, options, method \\ :keyset)
def paginate(query, options, :keyset) do
query
|> restrict(:min_id, options)
|> restrict(:since_id, options)
@ -28,11 +44,18 @@ def paginate(query, options) do
|> restrict(:limit, options)
end
def paginate(query, options, :offset) do
query
|> restrict(:offset, options)
|> restrict(:limit, options)
end
defp cast_params(params) do
param_types = %{
min_id: :string,
since_id: :string,
max_id: :string,
offset: :integer,
limit: :integer
}
@ -66,6 +89,10 @@ defp restrict(query, :order, _options) do
order_by(query, [u], fragment("? desc nulls last", u.id))
end
defp restrict(query, :offset, %{offset: offset}) do
offset(query, ^offset)
end
defp restrict(query, :limit, options) do
limit = Map.get(options, :limit, @default_limit)

View File

@ -4,6 +4,7 @@
defmodule Pleroma.Plugs.EnsureAuthenticatedPlug do
import Plug.Conn
import Pleroma.Web.TranslationHelpers
alias Pleroma.User
def init(options) do
@ -16,8 +17,7 @@ def call(%{assigns: %{user: %User{}}} = conn, _) do
def call(conn, _) do
conn
|> put_resp_content_type("application/json")
|> send_resp(403, Jason.encode!(%{error: "Invalid credentials."}))
|> render_error(:forbidden, "Invalid credentials.")
|> halt
end
end

View File

@ -3,6 +3,7 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug do
import Pleroma.Web.TranslationHelpers
import Plug.Conn
alias Pleroma.Config
alias Pleroma.User
@ -23,8 +24,7 @@ def call(conn, _) do
{false, _} ->
conn
|> put_resp_content_type("application/json")
|> send_resp(403, Jason.encode!(%{error: "This resource requires authentication."}))
|> render_error(:forbidden, "This resource requires authentication.")
|> halt
end
end

View File

@ -4,6 +4,7 @@
defmodule Pleroma.Plugs.OAuthScopesPlug do
import Plug.Conn
import Pleroma.Web.Gettext
@behaviour Plug
@ -30,11 +31,14 @@ def call(%Plug.Conn{assigns: assigns} = conn, %{scopes: scopes} = options) do
true ->
missing_scopes = scopes -- token.scopes
error_message = "Insufficient permissions: #{Enum.join(missing_scopes, " #{op} ")}."
permissions = Enum.join(missing_scopes, " #{op} ")
error_message =
dgettext("errors", "Insufficient permissions: %{permissions}.", permissions: permissions)
conn
|> put_resp_content_type("application/json")
|> send_resp(403, Jason.encode!(%{error: error_message}))
|> send_resp(:forbidden, Jason.encode!(%{error: error_message}))
|> halt()
end
end

View File

@ -44,8 +44,7 @@ defmodule Pleroma.Plugs.RateLimiter do
...
end
"""
import Phoenix.Controller, only: [json: 2]
import Pleroma.Web.TranslationHelpers
import Plug.Conn
alias Pleroma.User
@ -63,7 +62,7 @@ def call(conn, nil), do: conn
def call(conn, opts) do
case check_rate(conn, opts) do
{:ok, _count} -> conn
{:error, _count} -> render_error(conn)
{:error, _count} -> render_throttled_error(conn)
end
end
@ -85,10 +84,9 @@ def ip(%{remote_ip: remote_ip}) do
|> Enum.join(".")
end
defp render_error(conn) do
defp render_throttled_error(conn) do
conn
|> put_status(:too_many_requests)
|> json(%{error: "Throttled"})
|> render_error(:too_many_requests, "Throttled")
|> halt()
end
end

View File

@ -0,0 +1,63 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
# NOTE: this module is based on https://github.com/smeevil/set_locale
defmodule Pleroma.Plugs.SetLocalePlug do
import Plug.Conn, only: [get_req_header: 2, assign: 3]
def init(_), do: nil
def call(conn, _) do
locale = get_locale_from_header(conn) || Gettext.get_locale()
Gettext.put_locale(locale)
assign(conn, :locale, locale)
end
defp get_locale_from_header(conn) do
conn
|> extract_accept_language()
|> Enum.find(&supported_locale?/1)
end
defp extract_accept_language(conn) do
case get_req_header(conn, "accept-language") do
[value | _] ->
value
|> String.split(",")
|> Enum.map(&parse_language_option/1)
|> Enum.sort(&(&1.quality > &2.quality))
|> Enum.map(& &1.tag)
|> Enum.reject(&is_nil/1)
|> ensure_language_fallbacks()
_ ->
[]
end
end
defp supported_locale?(locale) do
Pleroma.Web.Gettext
|> Gettext.known_locales()
|> Enum.member?(locale)
end
defp parse_language_option(string) do
captures = Regex.named_captures(~r/^\s?(?<tag>[\w\-]+)(?:;q=(?<quality>[\d\.]+))?$/i, string)
quality =
case Float.parse(captures["quality"] || "1.0") do
{val, _} -> val
:error -> 1.0
end
%{tag: captures["tag"], quality: quality}
end
defp ensure_language_fallbacks(tags) do
Enum.flat_map(tags, fn tag ->
[language | _] = String.split(tag, "-")
if Enum.member?(tags, language), do: [tag], else: [tag, language]
end)
end
end

View File

@ -7,6 +7,7 @@ defmodule Pleroma.Plugs.UploadedMedia do
"""
import Plug.Conn
import Pleroma.Web.Gettext
require Logger
@behaviour Plug
@ -45,7 +46,7 @@ def call(%{request_path: <<"/", @path, "/", file::binary>>} = conn, opts) do
else
_ ->
conn
|> send_resp(500, "Failed")
|> send_resp(:internal_server_error, dgettext("errors", "Failed"))
|> halt()
end
end
@ -64,7 +65,7 @@ defp get_media(conn, {:static_dir, directory}, _, opts) do
conn
else
conn
|> send_resp(404, "Not found")
|> send_resp(:not_found, dgettext("errors", "Not found"))
|> halt()
end
end
@ -84,7 +85,7 @@ defp get_media(conn, unknown, _, _) do
Logger.error("#{__MODULE__}: Unknown get startegy: #{inspect(unknown)}")
conn
|> send_resp(500, "Internal Error")
|> send_resp(:internal_server_error, dgettext("errors", "Internal Error"))
|> halt()
end
end

View File

@ -3,6 +3,7 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Plugs.UserIsAdminPlug do
import Pleroma.Web.TranslationHelpers
import Plug.Conn
alias Pleroma.User
@ -16,8 +17,7 @@ def call(%{assigns: %{user: %User{info: %{is_admin: true}}}} = conn, _) do
def call(conn, _) do
conn
|> put_resp_content_type("application/json")
|> send_resp(403, Jason.encode!(%{error: "User is not admin."}))
|> render_error(:forbidden, "User is not admin.")
|> halt
end
end

View File

@ -1,3 +1,7 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.ReverseProxy.Client do
@callback request(atom(), String.t(), [tuple()], String.t(), list()) ::
{:ok, pos_integer(), [tuple()], reference() | map()}

View File

@ -3,6 +3,8 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Uploaders.Uploader do
import Pleroma.Web.Gettext
@moduledoc """
Defines the contract to put and get an uploaded file to any backend.
"""
@ -66,7 +68,7 @@ defp handle_callback(uploader, upload) do
{:error, error}
end
after
30_000 -> {:error, "Uploader callback timeout"}
30_000 -> {:error, dgettext("errors", "Uploader callback timeout")}
end
end
end

View File

@ -52,6 +52,7 @@ defmodule Pleroma.User do
field(:avatar, :map)
field(:local, :boolean, default: true)
field(:follower_address, :string)
field(:following_address, :string)
field(:search_rank, :float, virtual: true)
field(:search_type, :integer, virtual: true)
field(:tags, {:array, :string}, default: [])
@ -108,6 +109,10 @@ def ap_id(%User{nickname: nickname}) do
def ap_followers(%User{follower_address: fa}) when is_binary(fa), do: fa
def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers"
@spec ap_following(User.t()) :: Sring.t()
def ap_following(%User{following_address: fa}) when is_binary(fa), do: fa
def ap_following(%User{} = user), do: "#{ap_id(user)}/following"
def user_info(%User{} = user, args \\ %{}) do
following_count =
if args[:following_count], do: args[:following_count], else: following_count(user)
@ -129,6 +134,7 @@ def set_info_cache(user, args) do
Cachex.put(:user_cache, "user_info:#{user.id}", user_info(user, args))
end
@spec restrict_deactivated(Ecto.Query.t()) :: Ecto.Query.t()
def restrict_deactivated(query) do
from(u in query,
where: not fragment("? \\? 'deactivated' AND ?->'deactivated' @> 'true'", u.info, u.info)
@ -163,9 +169,10 @@ def remote_user_creation(params) do
if changes.valid? do
case info_cng.changes[:source_data] do
%{"followers" => followers} ->
%{"followers" => followers, "following" => following} ->
changes
|> put_change(:follower_address, followers)
|> put_change(:following_address, following)
_ ->
followers = User.ap_followers(%User{nickname: changes.changes[:nickname]})
@ -197,7 +204,14 @@ def upgrade_changeset(struct, params \\ %{}) do
|> User.Info.user_upgrade(params[:info])
struct
|> cast(params, [:bio, :name, :follower_address, :avatar, :last_refreshed_at])
|> cast(params, [
:bio,
:name,
:follower_address,
:following_address,
:avatar,
:last_refreshed_at
])
|> unique_constraint(:nickname)
|> validate_format(:nickname, local_nickname_regex())
|> validate_length(:bio, max: 5000)
@ -938,6 +952,8 @@ def delete(%User{} = user),
@spec perform(atom(), User.t()) :: {:ok, User.t()}
def perform(:delete, %User{} = user) do
{:ok, _user} = ActivityPub.delete(user)
# Remove all relationships
{:ok, followers} = User.get_followers(user)
@ -954,8 +970,8 @@ def perform(:delete, %User{} = user) do
end)
delete_user_activities(user)
{:ok, _user} = Repo.delete(user)
invalidate_cache(user)
Repo.delete(user)
end
@spec perform(atom(), User.t()) :: {:ok, User.t()}
@ -1011,42 +1027,20 @@ def perform(:follow_import, %User{} = follower, followed_identifiers)
)
end
@spec sync_follow_counter() :: :ok
def sync_follow_counter,
do: PleromaJobQueue.enqueue(:background, __MODULE__, [:sync_follow_counters])
@spec perform(:sync_follow_counters) :: :ok
def perform(:sync_follow_counters) do
{:ok, _pid} = Agent.start_link(fn -> %{} end, name: :domain_errors)
config = Pleroma.Config.get([:instance, :external_user_synchronization])
:ok = sync_follow_counters(config)
Agent.stop(:domain_errors)
end
@spec sync_follow_counters(keyword()) :: :ok
def sync_follow_counters(opts \\ []) do
users = external_users(opts)
if length(users) > 0 do
errors = Agent.get(:domain_errors, fn state -> state end)
{last, updated_errors} = User.Synchronization.call(users, errors, opts)
Agent.update(:domain_errors, fn _state -> updated_errors end)
sync_follow_counters(max_id: last.id, limit: opts[:limit])
else
:ok
end
@spec external_users_query() :: Ecto.Query.t()
def external_users_query do
User.Query.build(%{
external: true,
active: true,
order_by: :id
})
end
@spec external_users(keyword()) :: [User.t()]
def external_users(opts \\ []) do
query =
User.Query.build(%{
external: true,
active: true,
order_by: :id,
select: [:id, :ap_id, :info]
})
external_users_query()
|> select([u], struct(u, [:id, :ap_id, :info]))
query =
if opts[:max_id],

View File

@ -3,6 +3,7 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.User.Search do
alias Pleroma.Pagination
alias Pleroma.Repo
alias Pleroma.User
import Ecto.Query
@ -18,8 +19,7 @@ def search(query_string, opts \\ []) do
for_user = Keyword.get(opts, :for_user)
# Strip the beginning @ off if there is a query
query_string = String.trim_leading(query_string, "@")
query_string = format_query(query_string)
maybe_resolve(resolve, for_user, query_string)
@ -33,13 +33,24 @@ def search(query_string, opts \\ []) do
query_string
|> search_query(for_user, following)
|> paginate(result_limit, offset)
|> Repo.all()
|> Pagination.fetch_paginated(%{"offset" => offset, "limit" => result_limit}, :offset)
end)
results
end
defp format_query(query_string) do
# Strip the beginning @ off if there is a query
query_string = String.trim_leading(query_string, "@")
with [name, domain] <- String.split(query_string, "@"),
formatted_domain <- String.replace(domain, ~r/[!-\-|@|[-`|{-~|\/|:]+/, "") do
name <> "@" <> to_string(:idna.encode(formatted_domain))
else
_ -> query_string
end
end
defp search_query(query_string, for_user, following) do
for_user
|> base_query(following)
@ -76,10 +87,6 @@ defp filter_blocked_domains(query, %User{info: %{domain_blocks: domain_blocks}})
defp filter_blocked_domains(query, _), do: query
defp paginate(query, limit, offset) do
from(q in query, limit: ^limit, offset: ^offset)
end
defp union_subqueries({fts_subquery, trigram_subquery}) do
from(s in trigram_subquery, union_all: ^fts_subquery)
end
@ -151,7 +158,7 @@ defp boost_search_rank_query(query, for_user) do
defp fts_search_subquery(query, term) do
processed_query =
String.trim_trailing(term, "@" <> local_domain())
|> String.replace(~r/\W+/, " ")
|> String.replace(~r/[!-\/|@|[-`|{-~|:-?]+/, " ")
|> String.trim()
|> String.split()
|> Enum.map(&(&1 <> ":*"))

View File

@ -1,60 +0,0 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.User.Synchronization do
alias Pleroma.HTTP
alias Pleroma.User
@spec call([User.t()], map(), keyword()) :: {User.t(), map()}
def call(users, errors, opts \\ []) do
do_call(users, errors, opts)
end
defp do_call([user | []], errors, opts) do
updated = fetch_counters(user, errors, opts)
{user, updated}
end
defp do_call([user | others], errors, opts) do
updated = fetch_counters(user, errors, opts)
do_call(others, updated, opts)
end
defp fetch_counters(user, errors, opts) do
%{host: host} = URI.parse(user.ap_id)
info = %{}
{following, errors} = fetch_counter(user.ap_id <> "/following", host, errors, opts)
info = if following, do: Map.put(info, :following_count, following), else: info
{followers, errors} = fetch_counter(user.ap_id <> "/followers", host, errors, opts)
info = if followers, do: Map.put(info, :follower_count, followers), else: info
User.set_info_cache(user, info)
errors
end
defp available_domain?(domain, errors, opts) do
max_retries = Keyword.get(opts, :max_retries, 3)
not (Map.has_key?(errors, domain) && errors[domain] >= max_retries)
end
defp fetch_counter(url, host, errors, opts) do
with true <- available_domain?(host, errors, opts),
{:ok, %{body: body, status: code}} when code in 200..299 <-
HTTP.get(
url,
[{:Accept, "application/activity+json"}]
),
{:ok, data} <- Jason.decode(body) do
{data["totalItems"], errors}
else
false ->
{nil, errors}
_ ->
{nil, Map.update(errors, host, 1, &(&1 + 1))}
end
end
end

View File

@ -1,32 +0,0 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-onl
defmodule Pleroma.User.SynchronizationWorker do
use GenServer
def start_link do
config = Pleroma.Config.get([:instance, :external_user_synchronization])
if config[:enabled] do
GenServer.start_link(__MODULE__, interval: config[:interval])
else
:ignore
end
end
def init(opts) do
schedule_next(opts)
{:ok, opts}
end
def handle_info(:sync_follow_counters, opts) do
Pleroma.User.sync_follow_counter()
schedule_next(opts)
{:noreply, opts}
end
defp schedule_next(opts) do
Process.send_after(self(), :sync_follow_counters, opts[:interval])
end
end

View File

@ -1,3 +1,7 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.User.WelcomeMessage do
alias Pleroma.User
alias Pleroma.Web.CommonAPI

View File

@ -405,6 +405,19 @@ def unfollow(follower, followed, activity_id \\ nil, local \\ true) do
end
end
def delete(%User{ap_id: ap_id, follower_address: follower_address} = user) do
with data <- %{
"to" => [follower_address],
"type" => "Delete",
"actor" => ap_id,
"object" => %{"type" => "Person", "id" => ap_id}
},
{:ok, activity} <- insert(data, true, true),
:ok <- maybe_federate(activity) do
{:ok, user}
end
end
def delete(%Object{data: %{"id" => id, "actor" => actor}} = object, local \\ true) do
user = User.get_cached_by_ap_id(actor)
to = (object.data["to"] || []) ++ (object.data["cc"] || [])
@ -981,6 +994,7 @@ defp object_to_user_data(data) do
avatar: avatar,
name: data["name"],
follower_address: data["followers"],
following_address: data["following"],
bio: data["summary"]
}

View File

@ -31,9 +31,8 @@ def relay_active?(conn, _) do
conn
else
conn
|> put_status(404)
|> json(%{error: "not found"})
|> halt
|> render_error(:not_found, "not found")
|> halt()
end
end
@ -190,7 +189,7 @@ def inbox(conn, params) do
Logger.info(inspect(conn.req_headers))
end
json(conn, "error")
json(conn, dgettext("errors", "error"))
end
def relay(conn, _params) do
@ -218,9 +217,15 @@ def read_inbox(%{assigns: %{user: user}} = conn, %{"nickname" => nickname} = par
|> put_resp_header("content-type", "application/activity+json")
|> json(UserView.render("inbox.json", %{user: user, max_id: params["max_id"]}))
else
err =
dgettext("errors", "can't read inbox of %{nickname} as %{as_nickname}",
nickname: nickname,
as_nickname: user.nickname
)
conn
|> put_status(:forbidden)
|> json("can't read inbox of #{nickname} as #{user.nickname}")
|> json(err)
end
end
@ -246,7 +251,7 @@ def handle_user_activity(user, %{"type" => "Delete"} = params) do
{:ok, delete} <- ActivityPub.delete(object) do
{:ok, delete}
else
_ -> {:error, "Can't delete object"}
_ -> {:error, dgettext("errors", "Can't delete object")}
end
end
@ -255,12 +260,12 @@ def handle_user_activity(user, %{"type" => "Like"} = params) do
{:ok, activity, _object} <- ActivityPub.like(user, object) do
{:ok, activity}
else
_ -> {:error, "Can't like object"}
_ -> {:error, dgettext("errors", "Can't like object")}
end
end
def handle_user_activity(_, _) do
{:error, "Unhandled activity type"}
{:error, dgettext("errors", "Unhandled activity type")}
end
def update_outbox(
@ -288,22 +293,28 @@ def update_outbox(
|> json(message)
end
else
err =
dgettext("errors", "can't update outbox of %{nickname} as %{as_nickname}",
nickname: nickname,
as_nickname: user.nickname
)
conn
|> put_status(:forbidden)
|> json("can't update outbox of #{nickname} as #{user.nickname}")
|> json(err)
end
end
def errors(conn, {:error, :not_found}) do
conn
|> put_status(404)
|> json("Not found")
|> put_status(:not_found)
|> json(dgettext("errors", "Not found"))
end
def errors(conn, _e) do
conn
|> put_status(500)
|> json("error")
|> put_status(:internal_server_error)
|> json(dgettext("errors", "error"))
end
defp set_requester_reachable(%Plug.Conn{} = conn, _) do

View File

@ -9,8 +9,9 @@ defmodule Pleroma.Web.ActivityPub.MRF.EnsureRePrepended do
@behaviour Pleroma.Web.ActivityPub.MRF
@reply_prefix Regex.compile!("^re:[[:space:]]*", [:caseless])
def filter_by_summary(
%{"summary" => parent_summary} = _parent,
%{data: %{"summary" => parent_summary}} = _in_reply_to,
%{"summary" => child_summary} = child
)
when not is_nil(child_summary) and byte_size(child_summary) > 0 and
@ -24,17 +25,13 @@ def filter_by_summary(
end
end
def filter_by_summary(_parent, child), do: child
def filter(%{"type" => activity_type} = object) when activity_type == "Create" do
child = object["object"]
in_reply_to = Object.normalize(child["inReplyTo"])
def filter_by_summary(_in_reply_to, child), do: child
def filter(%{"type" => "Create", "object" => child_object} = object) do
child =
if(in_reply_to,
do: filter_by_summary(in_reply_to.data, child),
else: child
)
child_object["inReplyTo"]
|> Object.normalize(child_object["inReplyTo"])
|> filter_by_summary(child_object)
object = Map.put(object, "object", child)

View File

@ -10,19 +10,11 @@ defmodule Pleroma.Web.ActivityPub.MRF.NoPlaceholderTextPolicy do
def filter(
%{
"type" => "Create",
"object" => %{"content" => content, "attachment" => _attachment} = child_object
"object" => %{"content" => content, "attachment" => _} = _child_object
} = object
)
when content in [".", "<p>.</p>"] do
child_object =
child_object
|> Map.put("content", "")
object =
object
|> Map.put("object", child_object)
{:ok, object}
{:ok, put_in(object, ["object", "content"], "")}
end
@impl true

View File

@ -8,18 +8,14 @@ defmodule Pleroma.Web.ActivityPub.MRF.NormalizeMarkup do
@behaviour Pleroma.Web.ActivityPub.MRF
def filter(%{"type" => activity_type} = object) when activity_type == "Create" do
def filter(%{"type" => "Create", "object" => child_object} = object) do
scrub_policy = Pleroma.Config.get([:mrf_normalize_markup, :scrub_policy])
child = object["object"]
content =
child["content"]
child_object["content"]
|> HTML.filter_tags(scrub_policy)
child = Map.put(child, "content", content)
object = Map.put(object, "object", child)
object = put_in(object, ["object", "content"], content)
{:ok, object}
end

View File

@ -3,46 +3,42 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF.RejectNonPublic do
alias Pleroma.User
@moduledoc "Rejects non-public (followers-only, direct) activities"
alias Pleroma.Config
alias Pleroma.User
@behaviour Pleroma.Web.ActivityPub.MRF
@public "https://www.w3.org/ns/activitystreams#Public"
@impl true
def filter(%{"type" => "Create"} = object) do
user = User.get_cached_by_ap_id(object["actor"])
public = "https://www.w3.org/ns/activitystreams#Public"
# Determine visibility
visibility =
cond do
public in object["to"] -> "public"
public in object["cc"] -> "unlisted"
@public in object["to"] -> "public"
@public in object["cc"] -> "unlisted"
user.follower_address in object["to"] -> "followers"
true -> "direct"
end
policy = Pleroma.Config.get(:mrf_rejectnonpublic)
policy = Config.get(:mrf_rejectnonpublic)
case visibility do
"public" ->
cond do
visibility in ["public", "unlisted"] ->
{:ok, object}
"unlisted" ->
visibility == "followers" and Keyword.get(policy, :allow_followersonly) ->
{:ok, object}
"followers" ->
with true <- Keyword.get(policy, :allow_followersonly) do
{:ok, object}
else
_e -> {:reject, nil}
end
visibility == "direct" and Keyword.get(policy, :allow_direct) ->
{:ok, object}
"direct" ->
with true <- Keyword.get(policy, :allow_direct) do
{:ok, object}
else
_e -> {:reject, nil}
end
true ->
{:reject, nil}
end
end

View File

@ -19,12 +19,17 @@ defmodule Pleroma.Web.ActivityPub.MRF.TagPolicy do
- `mrf_tag:disable-any-subscription`: Reject any follow requests
"""
@public "https://www.w3.org/ns/activitystreams#Public"
defp get_tags(%User{tags: tags}) when is_list(tags), do: tags
defp get_tags(_), do: []
defp process_tag(
"mrf_tag:media-force-nsfw",
%{"type" => "Create", "object" => %{"attachment" => child_attachment} = object} = message
%{
"type" => "Create",
"object" => %{"attachment" => child_attachment} = object
} = message
)
when length(child_attachment) > 0 do
tags = (object["tag"] || []) ++ ["nsfw"]
@ -41,7 +46,10 @@ defp process_tag(
defp process_tag(
"mrf_tag:media-strip",
%{"type" => "Create", "object" => %{"attachment" => child_attachment} = object} = message
%{
"type" => "Create",
"object" => %{"attachment" => child_attachment} = object
} = message
)
when length(child_attachment) > 0 do
object = Map.delete(object, "attachment")
@ -52,19 +60,22 @@ defp process_tag(
defp process_tag(
"mrf_tag:force-unlisted",
%{"type" => "Create", "to" => to, "cc" => cc, "actor" => actor} = message
%{
"type" => "Create",
"to" => to,
"cc" => cc,
"actor" => actor,
"object" => object
} = message
) do
user = User.get_cached_by_ap_id(actor)
if Enum.member?(to, "https://www.w3.org/ns/activitystreams#Public") do
to =
List.delete(to, "https://www.w3.org/ns/activitystreams#Public") ++ [user.follower_address]
cc =
List.delete(cc, user.follower_address) ++ ["https://www.w3.org/ns/activitystreams#Public"]
if Enum.member?(to, @public) do
to = List.delete(to, @public) ++ [user.follower_address]
cc = List.delete(cc, user.follower_address) ++ [@public]
object =
message["object"]
object
|> Map.put("to", to)
|> Map.put("cc", cc)
@ -82,19 +93,22 @@ defp process_tag(
defp process_tag(
"mrf_tag:sandbox",
%{"type" => "Create", "to" => to, "cc" => cc, "actor" => actor} = message
%{
"type" => "Create",
"to" => to,
"cc" => cc,
"actor" => actor,
"object" => object
} = message
) do
user = User.get_cached_by_ap_id(actor)
if Enum.member?(to, "https://www.w3.org/ns/activitystreams#Public") or
Enum.member?(cc, "https://www.w3.org/ns/activitystreams#Public") do
to =
List.delete(to, "https://www.w3.org/ns/activitystreams#Public") ++ [user.follower_address]
cc = List.delete(cc, "https://www.w3.org/ns/activitystreams#Public")
if Enum.member?(to, @public) or Enum.member?(cc, @public) do
to = List.delete(to, @public) ++ [user.follower_address]
cc = List.delete(cc, @public)
object =
message["object"]
object
|> Map.put("to", to)
|> Map.put("cc", cc)
@ -123,7 +137,8 @@ defp process_tag(
end
end
defp process_tag("mrf_tag:disable-any-subscription", %{"type" => "Follow"}), do: {:reject, nil}
defp process_tag("mrf_tag:disable-any-subscription", %{"type" => "Follow"}),
do: {:reject, nil}
defp process_tag(_, message), do: {:ok, message}

View File

@ -21,7 +21,12 @@ defp filter_by_list(%{"actor" => actor} = object, allow_list) do
@impl true
def filter(%{"actor" => actor} = object) do
actor_info = URI.parse(actor)
allow_list = Config.get([:mrf_user_allowlist, String.to_atom(actor_info.host)], [])
allow_list =
Config.get(
[:mrf_user_allowlist, String.to_atom(actor_info.host)],
[]
)
filter_by_list(object, allow_list)
end

View File

@ -641,7 +641,7 @@ def handle_incoming(
# an error or a tombstone. This would allow us to verify that a deletion actually took
# place.
def handle_incoming(
%{"type" => "Delete", "object" => object_id, "actor" => _actor, "id" => _id} = data,
%{"type" => "Delete", "object" => object_id, "actor" => actor, "id" => _id} = data,
_options
) do
object_id = Utils.get_ap_id(object_id)
@ -653,7 +653,30 @@ def handle_incoming(
{:ok, activity} <- ActivityPub.delete(object, false) do
{:ok, activity}
else
_e -> :error
nil ->
case User.get_cached_by_ap_id(object_id) do
%User{ap_id: ^actor} = user ->
{:ok, followers} = User.get_followers(user)
Enum.each(followers, fn follower ->
User.unfollow(follower, user)
end)
{:ok, friends} = User.get_friends(user)
Enum.each(friends, fn followed ->
User.unfollow(user, followed)
end)
User.invalidate_cache(user)
Repo.delete(user)
nil ->
:error
end
_e ->
:error
end
end
@ -1064,6 +1087,10 @@ def upgrade_user_from_ap_id(ap_id) do
PleromaJobQueue.enqueue(:transmogrifier, __MODULE__, [:user_upgrade, user])
end
if Pleroma.Config.get([:instance, :external_user_synchronization]) do
update_following_followers_counters(user)
end
{:ok, user}
else
%User{} = user -> {:ok, user}
@ -1096,4 +1123,27 @@ def maybe_fix_user_object(data) do
data
|> maybe_fix_user_url
end
def update_following_followers_counters(user) do
info = %{}
following = fetch_counter(user.following_address)
info = if following, do: Map.put(info, :following_count, following), else: info
followers = fetch_counter(user.follower_address)
info = if followers, do: Map.put(info, :follower_count, followers), else: info
User.set_info_cache(user, info)
end
defp fetch_counter(url) do
with {:ok, %{body: body, status: code}} when code in 200..299 <-
Pleroma.HTTP.get(
url,
[{:Accept, "application/activity+json"}]
),
{:ok, data} <- Jason.decode(body) do
data["totalItems"]
end
end
end

View File

@ -1,3 +1,7 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.Visibility do
alias Pleroma.Activity
alias Pleroma.Object

View File

@ -160,9 +160,7 @@ def right_add(conn, %{"permission_group" => permission_group, "nickname" => nick
end
def right_add(conn, _) do
conn
|> put_status(404)
|> json(%{error: "No such permission_group"})
render_error(conn, :not_found, "No such permission_group")
end
def right_get(conn, %{"nickname" => nickname}) do
@ -184,9 +182,7 @@ def right_delete(
)
when permission_group in ["moderator", "admin"] do
if admin_nickname == nickname do
conn
|> put_status(403)
|> json(%{error: "You can't revoke your own admin status."})
render_error(conn, :forbidden, "You can't revoke your own admin status.")
else
user = User.get_cached_by_nickname(nickname)
@ -207,9 +203,7 @@ def right_delete(
end
def right_delete(conn, _) do
conn
|> put_status(404)
|> json(%{error: "No such permission_group"})
render_error(conn, :not_found, "No such permission_group")
end
def set_activation_status(conn, %{"nickname" => nickname, "status" => status}) do
@ -377,13 +371,13 @@ def config_update(conn, %{"configs" => configs}) do
if Pleroma.Config.get([:instance, :dynamic_configuration]) do
updated =
Enum.map(configs, fn
%{"group" => group, "key" => key, "value" => value} ->
{:ok, config} = Config.update_or_create(%{group: group, key: key, value: value})
config
%{"group" => group, "key" => key, "delete" => "true"} ->
{:ok, _} = Config.delete(%{group: group, key: key})
nil
%{"group" => group, "key" => key, "value" => value} ->
{:ok, config} = Config.update_or_create(%{group: group, key: key, value: value})
config
end)
|> Enum.reject(&is_nil(&1))
@ -401,26 +395,26 @@ def config_update(conn, %{"configs" => configs}) do
def errors(conn, {:error, :not_found}) do
conn
|> put_status(404)
|> json("Not found")
|> put_status(:not_found)
|> json(dgettext("errors", "Not found"))
end
def errors(conn, {:error, reason}) do
conn
|> put_status(400)
|> put_status(:bad_request)
|> json(reason)
end
def errors(conn, {:param_cast, _}) do
conn
|> put_status(400)
|> json("Invalid parameters")
|> put_status(:bad_request)
|> json(dgettext("errors", "Invalid parameters"))
end
def errors(conn, _) do
conn
|> put_status(500)
|> json("Something went wrong")
|> put_status(:internal_server_error)
|> json(dgettext("errors", "Something went wrong"))
end
defp page_params(params) do

View File

@ -5,6 +5,7 @@
defmodule Pleroma.Web.AdminAPI.Config do
use Ecto.Schema
import Ecto.Changeset
import Pleroma.Web.Gettext
alias __MODULE__
alias Pleroma.Repo
@ -57,104 +58,95 @@ def delete(params) do
with %Config{} = config <- Config.get_by_params(params) do
Repo.delete(config)
else
nil -> {:error, "Config with params #{inspect(params)} not found"}
nil ->
err =
dgettext("errors", "Config with params %{params} not found", params: inspect(params))
{:error, err}
end
end
@spec from_binary(binary()) :: term()
def from_binary(value), do: :erlang.binary_to_term(value)
def from_binary(binary), do: :erlang.binary_to_term(binary)
@spec from_binary_to_map(binary()) :: any()
def from_binary_to_map(binary) do
@spec from_binary_with_convert(binary()) :: any()
def from_binary_with_convert(binary) do
from_binary(binary)
|> do_convert()
end
defp do_convert([{k, v}] = value) when is_list(value) and length(value) == 1,
do: %{k => do_convert(v)}
defp do_convert(entity) when is_list(entity) do
for v <- entity, into: [], do: do_convert(v)
end
defp do_convert(values) when is_list(values), do: for(val <- values, do: do_convert(val))
defp do_convert(entity) when is_map(entity) do
for {k, v} <- entity, into: %{}, do: {do_convert(k), do_convert(v)}
end
defp do_convert({k, v} = value) when is_tuple(value),
do: %{k => do_convert(v)}
defp do_convert({:dispatch, [entity]}), do: %{"tuple" => [":dispatch", [inspect(entity)]]}
defp do_convert(value) when is_tuple(value), do: %{"tuple" => do_convert(Tuple.to_list(value))}
defp do_convert(entity) when is_tuple(entity),
do: %{"tuple" => do_convert(Tuple.to_list(entity))}
defp do_convert(value) when is_binary(value) or is_map(value) or is_number(value), do: value
defp do_convert(entity) when is_boolean(entity) or is_number(entity) or is_nil(entity),
do: entity
defp do_convert(value) when is_atom(value) do
string = to_string(value)
defp do_convert(entity) when is_atom(entity) do
string = to_string(entity)
if String.starts_with?(string, "Elixir."),
do: String.trim_leading(string, "Elixir."),
else: value
do: do_convert(string),
else: ":" <> string
end
defp do_convert("Elixir." <> module_name), do: module_name
defp do_convert(entity) when is_binary(entity), do: entity
@spec transform(any()) :: binary()
def transform(%{"tuple" => _} = entity), do: :erlang.term_to_binary(do_transform(entity))
def transform(entity) when is_map(entity) do
tuples =
for {k, v} <- entity,
into: [],
do: {if(is_atom(k), do: k, else: String.to_atom(k)), do_transform(v)}
Enum.reject(tuples, fn {_k, v} -> is_nil(v) end)
|> Enum.sort()
|> :erlang.term_to_binary()
end
def transform(entity) when is_list(entity) do
list = Enum.map(entity, &do_transform(&1))
:erlang.term_to_binary(list)
def transform(entity) when is_binary(entity) or is_map(entity) or is_list(entity) do
:erlang.term_to_binary(do_transform(entity))
end
def transform(entity), do: :erlang.term_to_binary(entity)
defp do_transform(%Regex{} = value) when is_map(value), do: value
defp do_transform(%Regex{} = entity) when is_map(entity), do: entity
defp do_transform(%{"tuple" => [k, values] = entity}) when length(entity) == 2 do
{do_transform(k), do_transform(values)}
defp do_transform(%{"tuple" => [":dispatch", [entity]]}) do
cleaned_string = String.replace(entity, ~r/[^\w|^{:,[|^,|^[|^\]^}|^\/|^\.|^"]^\s/, "")
{dispatch_settings, []} = Code.eval_string(cleaned_string, [], requires: [], macros: [])
{:dispatch, [dispatch_settings]}
end
defp do_transform(%{"tuple" => values}) do
Enum.reduce(values, {}, fn val, acc -> Tuple.append(acc, do_transform(val)) end)
defp do_transform(%{"tuple" => entity}) do
Enum.reduce(entity, {}, fn val, acc -> Tuple.append(acc, do_transform(val)) end)
end
defp do_transform(value) when is_map(value) do
values = for {key, val} <- value, into: [], do: {String.to_atom(key), do_transform(val)}
Enum.sort(values)
defp do_transform(entity) when is_map(entity) do
for {k, v} <- entity, into: %{}, do: {do_transform(k), do_transform(v)}
end
defp do_transform(value) when is_list(value) do
Enum.map(value, &do_transform(&1))
defp do_transform(entity) when is_list(entity) do
for v <- entity, into: [], do: do_transform(v)
end
defp do_transform(entity) when is_list(entity) and length(entity) == 1, do: hd(entity)
defp do_transform(value) when is_binary(value) do
String.trim(value)
defp do_transform(entity) when is_binary(entity) do
String.trim(entity)
|> do_transform_string()
end
defp do_transform(value), do: value
defp do_transform(entity), do: entity
defp do_transform_string(value) when byte_size(value) == 0, do: nil
defp do_transform_string("~r/" <> pattern) do
pattern = String.trim_trailing(pattern, "/")
~r/#{pattern}/
end
defp do_transform_string(":" <> atom), do: String.to_atom(atom)
defp do_transform_string(value) do
cond do
String.starts_with?(value, "Pleroma") or String.starts_with?(value, "Phoenix") ->
String.to_existing_atom("Elixir." <> value)
String.starts_with?(value, ":") ->
String.replace(value, ":", "") |> String.to_existing_atom()
String.starts_with?(value, "i:") ->
String.replace(value, "i:", "") |> String.to_integer()
true ->
value
end
if String.starts_with?(value, "Pleroma") or String.starts_with?(value, "Phoenix"),
do: String.to_existing_atom("Elixir." <> value),
else: value
end
end

View File

@ -1,3 +1,7 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.AdminAPI.ConfigView do
use Pleroma.Web, :view
@ -11,7 +15,7 @@ def render("show.json", %{config: config}) do
%{
key: config.key,
group: config.group,
value: Pleroma.Web.AdminAPI.Config.from_binary_to_map(config.value)
value: Pleroma.Web.AdminAPI.Config.from_binary_with_convert(config.value)
}
end
end

View File

@ -13,6 +13,7 @@ defmodule Pleroma.Web.CommonAPI do
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.ActivityPub.Visibility
import Pleroma.Web.Gettext
import Pleroma.Web.CommonAPI.Utils
def follow(follower, followed) do
@ -74,7 +75,7 @@ def delete(activity_id, user) do
{:ok, delete}
else
_ ->
{:error, "Could not delete"}
{:error, dgettext("errors", "Could not delete")}
end
end
@ -85,7 +86,7 @@ def repeat(id_or_ap_id, user) do
ActivityPub.announce(user, object)
else
_ ->
{:error, "Could not repeat"}
{:error, dgettext("errors", "Could not repeat")}
end
end
@ -95,7 +96,7 @@ def unrepeat(id_or_ap_id, user) do
ActivityPub.unannounce(user, object)
else
_ ->
{:error, "Could not unrepeat"}
{:error, dgettext("errors", "Could not unrepeat")}
end
end
@ -106,7 +107,7 @@ def favorite(id_or_ap_id, user) do
ActivityPub.like(user, object)
else
_ ->
{:error, "Could not favorite"}
{:error, dgettext("errors", "Could not favorite")}
end
end
@ -116,7 +117,7 @@ def unfavorite(id_or_ap_id, user) do
ActivityPub.unlike(user, object)
else
_ ->
{:error, "Could not unfavorite"}
{:error, dgettext("errors", "Could not unfavorite")}
end
end
@ -148,10 +149,10 @@ def vote(user, object, choices) do
object = Object.get_cached_by_ap_id(object.data["id"])
{:ok, answer_activities, object}
else
{:author, _} -> {:error, "Poll's author can't vote"}
{:existing_votes, _} -> {:error, "Already voted"}
{:choice_check, {_, false}} -> {:error, "Invalid indices"}
{:count_check, false} -> {:error, "Too many choices"}
{:author, _} -> {:error, dgettext("errors", "Poll's author can't vote")}
{:existing_votes, _} -> {:error, dgettext("errors", "Already voted")}
{:choice_check, {_, false}} -> {:error, dgettext("errors", "Invalid indices")}
{:count_check, false} -> {:error, dgettext("errors", "Too many choices")}
end
end
@ -248,9 +249,14 @@ def post(user, %{"status" => status} = data) do
res
else
{:private_to_public, true} -> {:error, "The message visibility must be direct"}
{:error, _} = e -> e
e -> {:error, e}
{:private_to_public, true} ->
{:error, dgettext("errors", "The message visibility must be direct")}
{:error, _} = e ->
e
e ->
{:error, e}
end
end
@ -301,7 +307,7 @@ def pin(id_or_ap_id, %{ap_id: user_ap_id} = user) do
{:error, err}
_ ->
{:error, "Could not pin"}
{:error, dgettext("errors", "Could not pin")}
end
end
@ -318,7 +324,7 @@ def unpin(id_or_ap_id, user) do
{:error, err}
_ ->
{:error, "Could not unpin"}
{:error, dgettext("errors", "Could not unpin")}
end
end
@ -326,7 +332,7 @@ def add_mute(user, activity) do
with {:ok, _} <- ThreadMute.add_mute(user.id, activity.data["context"]) do
{:ok, activity}
else
{:error, _} -> {:error, "conversation is already muted"}
{:error, _} -> {:error, dgettext("errors", "conversation is already muted")}
end
end
@ -371,8 +377,8 @@ def report(user, data) do
{:ok, activity}
else
{:error, err} -> {:error, err}
{:account_id, %{}} -> {:error, "Valid `account_id` required"}
{:account, nil} -> {:error, "Account not found"}
{:account_id, %{}} -> {:error, dgettext("errors", "Valid `account_id` required")}
{:account, nil} -> {:error, dgettext("errors", "Account not found")}
end
end
@ -381,14 +387,9 @@ def update_report_state(activity_id, state) do
{:ok, activity} <- Utils.update_report_state(activity, state) do
{:ok, activity}
else
nil ->
{:error, :not_found}
{:error, reason} ->
{:error, reason}
_ ->
{:error, "Could not update state"}
nil -> {:error, :not_found}
{:error, reason} -> {:error, reason}
_ -> {:error, dgettext("errors", "Could not update state")}
end
end
@ -398,11 +399,8 @@ def update_activity_scope(activity_id, opts \\ %{}) do
{:ok, activity} <- set_visibility(activity, opts) do
{:ok, activity}
else
nil ->
{:error, :not_found}
{:error, reason} ->
{:error, reason}
nil -> {:error, :not_found}
{:error, reason} -> {:error, reason}
end
end

View File

@ -3,6 +3,8 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.CommonAPI.Utils do
import Pleroma.Web.Gettext
alias Calendar.Strftime
alias Comeonin.Pbkdf2
alias Pleroma.Activity
@ -372,7 +374,7 @@ def confirm_current_password(user, password) do
true <- Pbkdf2.checkpw(password, db_user.password_hash) do
{:ok, db_user}
else
_ -> {:error, "Invalid password."}
_ -> {:error, dgettext("errors", "Invalid password.")}
end
end
@ -455,7 +457,8 @@ def make_report_content_html(comment) do
if String.length(comment) <= max_size do
{:ok, format_input(comment, "text/plain")}
else
{:error, "Comment must be up to #{max_size} characters"}
{:error,
dgettext("errors", "Comment must be up to %{max_size} characters", max_size: max_size)}
end
end
@ -490,7 +493,7 @@ def conversation_id_to_context(id) do
context
else
_e ->
{:error, "No such conversation"}
{:error, dgettext("errors", "No such conversation")}
end
end
@ -512,10 +515,10 @@ def validate_character_limit(full_payload, attachments, limit) do
if length > 0 or Enum.count(attachments) > 0 do
:ok
else
{:error, "Cannot post an empty status without attachments"}
{:error, dgettext("errors", "Cannot post an empty status without attachments")}
end
else
{:error, "The status is over the character limit"}
{:error, dgettext("errors", "The status is over the character limit")}
end
end
end

View File

@ -7,13 +7,9 @@ defmodule Pleroma.Web.Endpoint do
socket("/socket", Pleroma.Web.UserSocket)
# Serve at "/" the static files from "priv/static" directory.
#
# You should set gzip to true if you are running phoenix.digest
# when deploying your static files in production.
plug(Pleroma.Plugs.SetLocalePlug)
plug(CORSPlug)
plug(Pleroma.Plugs.HTTPSecurityPlug)
plug(Pleroma.Plugs.UploadedMedia)
@static_cache_control "public, no-cache"
@ -30,6 +26,10 @@ defmodule Pleroma.Web.Endpoint do
}
)
# Serve at "/" the static files from "priv/static" directory.
#
# You should set gzip to true if you are running phoenix.digest
# when deploying your static files in production.
plug(
Plug.Static,
at: "/",

View File

@ -1,3 +1,7 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.MastodonAPI do
import Ecto.Query
import Ecto.Changeset

View File

@ -160,10 +160,7 @@ def update_credentials(%{assigns: %{user: user}} = conn, params) do
AccountView.render("account.json", %{user: user, for: user, with_pleroma_settings: true})
)
else
_e ->
conn
|> put_status(403)
|> json(%{error: "Invalid request"})
_e -> render_error(conn, :forbidden, "Invalid request")
end
end
@ -258,10 +255,7 @@ def user(%{assigns: %{user: for_user}} = conn, %{"id" => nickname_or_id}) do
account = AccountView.render("account.json", %{user: user, for: for_user})
json(conn, account)
else
_e ->
conn
|> put_status(404)
|> json(%{error: "Can't find user"})
_e -> render_error(conn, :not_found, "Can't find user")
end
end
@ -305,7 +299,9 @@ defp mastodonized_emoji do
"static_url" => url,
"visible_in_picker" => true,
"url" => url,
"tags" => tags
"tags" => tags,
# Assuming that a comma is authorized in the category name
"category" => (tags -- ["Custom"]) |> Enum.join(",")
}
end)
end
@ -509,15 +505,8 @@ def get_poll(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|> put_view(StatusView)
|> try_render("poll.json", %{object: object, for: user})
else
nil ->
conn
|> put_status(404)
|> json(%{error: "Record not found"})
false ->
conn
|> put_status(404)
|> json(%{error: "Record not found"})
nil -> render_error(conn, :not_found, "Record not found")
false -> render_error(conn, :not_found, "Record not found")
end
end
@ -546,18 +535,14 @@ def poll_vote(%{assigns: %{user: user}} = conn, %{"id" => id, "choices" => choic
|> try_render("poll.json", %{object: object, for: user})
else
nil ->
conn
|> put_status(404)
|> json(%{error: "Record not found"})
render_error(conn, :not_found, "Record not found")
false ->
conn
|> put_status(404)
|> json(%{error: "Record not found"})
render_error(conn, :not_found, "Record not found")
{:error, message} ->
conn
|> put_status(422)
|> put_status(:unprocessable_entity)
|> json(%{error: message})
end
end
@ -646,10 +631,7 @@ def delete_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do
json(conn, %{})
else
_e ->
conn
|> put_status(403)
|> json(%{error: "Can't delete this post"})
_e -> render_error(conn, :forbidden, "Can't delete this post")
end
end
@ -697,8 +679,8 @@ def pin_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
else
{:error, reason} ->
conn
|> put_resp_content_type("application/json")
|> send_resp(:bad_request, Jason.encode!(%{"error" => reason}))
|> put_status(:bad_request)
|> json(%{"error" => reason})
end
end
@ -774,8 +756,8 @@ def get_notification(%{assigns: %{user: user}} = conn, %{"id" => id} = _params)
else
{:error, reason} ->
conn
|> put_resp_content_type("application/json")
|> send_resp(403, Jason.encode!(%{"error" => reason}))
|> put_status(:forbidden)
|> json(%{"error" => reason})
end
end
@ -790,8 +772,8 @@ def dismiss_notification(%{assigns: %{user: user}} = conn, %{"id" => id} = _para
else
{:error, reason} ->
conn
|> put_resp_content_type("application/json")
|> send_resp(403, Jason.encode!(%{"error" => reason}))
|> put_status(:forbidden)
|> json(%{"error" => reason})
end
end
@ -869,9 +851,7 @@ def set_mascot(%{assigns: %{user: user}} = conn, %{"file" => file}) do
conn
|> json(rendered)
else
conn
|> put_resp_content_type("application/json")
|> send_resp(415, Jason.encode!(%{"error" => "mascots can only be images"}))
render_error(conn, :unsupported_media_type, "mascots can only be images")
end
end
end
@ -1000,8 +980,8 @@ def authorize_follow_request(%{assigns: %{user: followed}} = conn, %{"id" => id}
else
{:error, message} ->
conn
|> put_resp_content_type("application/json")
|> send_resp(403, Jason.encode!(%{"error" => message}))
|> put_status(:forbidden)
|> json(%{error: message})
end
end
@ -1014,8 +994,8 @@ def reject_follow_request(%{assigns: %{user: followed}} = conn, %{"id" => id}) d
else
{:error, message} ->
conn
|> put_resp_content_type("application/json")
|> send_resp(403, Jason.encode!(%{"error" => message}))
|> put_status(:forbidden)
|> json(%{error: message})
end
end
@ -1032,8 +1012,8 @@ def follow(%{assigns: %{user: follower}} = conn, %{"id" => id}) do
{:error, message} ->
conn
|> put_resp_content_type("application/json")
|> send_resp(403, Jason.encode!(%{"error" => message}))
|> put_status(:forbidden)
|> json(%{error: message})
end
end
@ -1050,8 +1030,8 @@ def follow(%{assigns: %{user: follower}} = conn, %{"uri" => uri}) do
{:error, message} ->
conn
|> put_resp_content_type("application/json")
|> send_resp(403, Jason.encode!(%{"error" => message}))
|> put_status(:forbidden)
|> json(%{error: message})
end
end
@ -1080,8 +1060,8 @@ def mute(%{assigns: %{user: muter}} = conn, %{"id" => id}) do
else
{:error, message} ->
conn
|> put_resp_content_type("application/json")
|> send_resp(403, Jason.encode!(%{"error" => message}))
|> put_status(:forbidden)
|> json(%{error: message})
end
end
@ -1094,8 +1074,8 @@ def unmute(%{assigns: %{user: muter}} = conn, %{"id" => id}) do
else
{:error, message} ->
conn
|> put_resp_content_type("application/json")
|> send_resp(403, Jason.encode!(%{"error" => message}))
|> put_status(:forbidden)
|> json(%{error: message})
end
end
@ -1116,8 +1096,8 @@ def block(%{assigns: %{user: blocker}} = conn, %{"id" => id}) do
else
{:error, message} ->
conn
|> put_resp_content_type("application/json")
|> send_resp(403, Jason.encode!(%{"error" => message}))
|> put_status(:forbidden)
|> json(%{error: message})
end
end
@ -1131,8 +1111,8 @@ def unblock(%{assigns: %{user: blocker}} = conn, %{"id" => id}) do
else
{:error, message} ->
conn
|> put_resp_content_type("application/json")
|> send_resp(403, Jason.encode!(%{"error" => message}))
|> put_status(:forbidden)
|> json(%{error: message})
end
end
@ -1166,8 +1146,8 @@ def subscribe(%{assigns: %{user: user}} = conn, %{"id" => id}) do
else
{:error, message} ->
conn
|> put_resp_content_type("application/json")
|> send_resp(403, Jason.encode!(%{"error" => message}))
|> put_status(:forbidden)
|> json(%{error: message})
end
end
@ -1180,8 +1160,8 @@ def unsubscribe(%{assigns: %{user: user}} = conn, %{"id" => id}) do
else
{:error, message} ->
conn
|> put_resp_content_type("application/json")
|> send_resp(403, Jason.encode!(%{"error" => message}))
|> put_status(:forbidden)
|> json(%{error: message})
end
end
@ -1229,13 +1209,8 @@ def user_favourites(%{assigns: %{user: for_user}} = conn, %{"id" => id} = params
|> put_view(StatusView)
|> render("index.json", %{activities: activities, for: for_user, as: :activity})
else
nil ->
{:error, :not_found}
true ->
conn
|> put_status(403)
|> json(%{error: "Can't get favorites"})
nil -> {:error, :not_found}
true -> render_error(conn, :forbidden, "Can't get favorites")
end
end
@ -1267,10 +1242,7 @@ def get_list(%{assigns: %{user: user}} = conn, %{"id" => id}) do
res = ListView.render("list.json", list: list)
json(conn, res)
else
_e ->
conn
|> put_status(404)
|> json(%{error: "Record not found"})
_e -> render_error(conn, :not_found, "Record not found")
end
end
@ -1286,7 +1258,7 @@ def delete_list(%{assigns: %{user: user}} = conn, %{"id" => id}) do
json(conn, %{})
else
_e ->
json(conn, "error")
json(conn, dgettext("errors", "error"))
end
end
@ -1337,7 +1309,7 @@ def rename_list(%{assigns: %{user: user}} = conn, %{"id" => id, "title" => title
json(conn, res)
else
_e ->
json(conn, "error")
json(conn, dgettext("errors", "error"))
end
end
@ -1361,10 +1333,7 @@ def list_timeline(%{assigns: %{user: user}} = conn, %{"list_id" => id} = params)
|> put_view(StatusView)
|> render("index.json", %{activities: activities, for: user, as: :activity})
else
_e ->
conn
|> put_status(403)
|> json(%{error: "Error."})
_e -> render_error(conn, :forbidden, "Error.")
end
end
@ -1483,8 +1452,8 @@ def put_settings(%{assigns: %{user: user}} = conn, %{"data" => settings} = _para
else
e ->
conn
|> put_resp_content_type("application/json")
|> send_resp(500, Jason.encode!(%{"error" => inspect(e)}))
|> put_status(:internal_server_error)
|> json(%{error: inspect(e)})
end
end
@ -1652,20 +1621,18 @@ def errors(conn, {:error, %Changeset{} = changeset}) do
|> Enum.map_join(", ", fn {_k, v} -> v end)
conn
|> put_status(422)
|> put_status(:unprocessable_entity)
|> json(%{error: error_message})
end
def errors(conn, {:error, :not_found}) do
conn
|> put_status(404)
|> json(%{error: "Record not found"})
render_error(conn, :not_found, "Record not found")
end
def errors(conn, _) do
conn
|> put_status(500)
|> json("Something went wrong")
|> put_status(:internal_server_error)
|> json(dgettext("errors", "Something went wrong"))
end
def suggestions(%{assigns: %{user: user}} = conn, _) do
@ -1785,21 +1752,17 @@ def account_register(
else
{:error, errors} ->
conn
|> put_status(400)
|> json(Jason.encode!(errors))
|> put_status(:bad_request)
|> json(errors)
end
end
def account_register(%{assigns: %{app: _app}} = conn, _params) do
conn
|> put_status(400)
|> json(%{error: "Missing parameters"})
render_error(conn, :bad_request, "Missing parameters")
end
def account_register(conn, _) do
conn
|> put_status(403)
|> json(%{error: "Invalid credentials"})
render_error(conn, :forbidden, "Invalid credentials")
end
def conversations(%{assigns: %{user: user}} = conn, params) do
@ -1829,21 +1792,14 @@ def conversation_read(%{assigns: %{user: user}} = conn, %{"id" => participation_
def try_render(conn, target, params)
when is_binary(target) do
res = render(conn, target, params)
if res == nil do
conn
|> put_status(501)
|> json(%{error: "Can't display this activity"})
else
res
case render(conn, target, params) do
nil -> render_error(conn, :not_implemented, "Can't display this activity")
res -> res
end
end
def try_render(conn, _, _) do
conn
|> put_status(501)
|> json(%{error: "Can't display this activity"})
render_error(conn, :not_implemented, "Can't display this activity")
end
defp present?(nil), do: false

View File

@ -4,61 +4,18 @@
defmodule Pleroma.Web.MastodonAPI.SearchController do
use Pleroma.Web, :controller
alias Pleroma.Activity
alias Pleroma.Plugs.RateLimiter
alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.Web
alias Pleroma.Web.ControllerHelper
alias Pleroma.Web.MastodonAPI.AccountView
alias Pleroma.Web.MastodonAPI.StatusView
alias Pleroma.Web.ControllerHelper
require Logger
plug(Pleroma.Plugs.RateLimiter, :search when action in [:search, :search2, :account_search])
def search2(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do
accounts = with_fallback(fn -> User.search(query, search_options(params, user)) end, [])
statuses = with_fallback(fn -> Activity.search(user, query) end, [])
tags_path = Web.base_url() <> "/tag/"
tags =
query
|> String.split()
|> Enum.uniq()
|> Enum.filter(fn tag -> String.starts_with?(tag, "#") end)
|> Enum.map(fn tag -> String.slice(tag, 1..-1) end)
|> Enum.map(fn tag -> %{name: tag, url: tags_path <> tag} end)
res = %{
"accounts" => AccountView.render("accounts.json", users: accounts, for: user, as: :user),
"statuses" =>
StatusView.render("index.json", activities: statuses, for: user, as: :activity),
"hashtags" => tags
}
json(conn, res)
end
def search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do
accounts = with_fallback(fn -> User.search(query, search_options(params, user)) end, [])
statuses = with_fallback(fn -> Activity.search(user, query) end, [])
tags =
query
|> String.split()
|> Enum.uniq()
|> Enum.filter(fn tag -> String.starts_with?(tag, "#") end)
|> Enum.map(fn tag -> String.slice(tag, 1..-1) end)
res = %{
"accounts" => AccountView.render("accounts.json", users: accounts, for: user, as: :user),
"statuses" =>
StatusView.render("index.json", activities: statuses, for: user, as: :activity),
"hashtags" => tags
}
json(conn, res)
end
plug(RateLimiter, :search when action in [:search, :search2, :account_search])
def account_search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do
accounts = User.search(query, search_options(params, user))
@ -67,17 +24,86 @@ def account_search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) d
json(conn, res)
end
def search2(conn, params), do: do_search(:v2, conn, params)
def search(conn, params), do: do_search(:v1, conn, params)
defp do_search(version, %{assigns: %{user: user}} = conn, %{"q" => query} = params) do
options = search_options(params, user)
timeout = Keyword.get(Repo.config(), :timeout, 15_000)
default_values = %{"statuses" => [], "accounts" => [], "hashtags" => []}
result =
default_values
|> Enum.map(fn {resource, default_value} ->
if params["type"] == nil or params["type"] == resource do
{resource, fn -> resource_search(version, resource, query, options) end}
else
{resource, fn -> default_value end}
end
end)
|> Task.async_stream(fn {resource, f} -> {resource, with_fallback(f)} end,
timeout: timeout,
on_timeout: :kill_task
)
|> Enum.reduce(default_values, fn
{:ok, {resource, result}}, acc ->
Map.put(acc, resource, result)
_error, acc ->
acc
end)
json(conn, result)
end
defp search_options(params, user) do
[
resolve: params["resolve"] == "true",
following: params["following"] == "true",
limit: ControllerHelper.fetch_integer_param(params, "limit"),
offset: ControllerHelper.fetch_integer_param(params, "offset"),
type: params["type"],
author: get_author(params),
for_user: user
]
|> Enum.filter(&elem(&1, 1))
end
defp with_fallback(f, fallback) do
defp resource_search(_, "accounts", query, options) do
accounts = with_fallback(fn -> User.search(query, options) end)
AccountView.render("accounts.json", users: accounts, for: options[:for_user], as: :user)
end
defp resource_search(_, "statuses", query, options) do
statuses = with_fallback(fn -> Activity.search(options[:for_user], query, options) end)
StatusView.render("index.json", activities: statuses, for: options[:for_user], as: :activity)
end
defp resource_search(:v2, "hashtags", query, _options) do
tags_path = Web.base_url() <> "/tag/"
query
|> prepare_tags()
|> Enum.map(fn tag ->
tag = String.trim_leading(tag, "#")
%{name: tag, url: tags_path <> tag}
end)
end
defp resource_search(:v1, "hashtags", query, _options) do
query
|> prepare_tags()
|> Enum.map(fn tag -> String.trim_leading(tag, "#") end)
end
defp prepare_tags(query) do
query
|> String.split()
|> Enum.uniq()
|> Enum.filter(fn tag -> String.starts_with?(tag, "#") end)
end
defp with_fallback(f, fallback \\ []) do
try do
f.()
rescue
@ -86,4 +112,9 @@ defp with_fallback(f, fallback) do
fallback
end
end
defp get_author(%{"account_id" => account_id}) when is_binary(account_id),
do: User.get_cached_by_id(account_id)
defp get_author(_params), do: nil
end

View File

@ -59,13 +59,13 @@ def delete(%{assigns: %{user: user, token: token}} = conn, _params) do
#
def errors(conn, {:error, :not_found}) do
conn
|> put_status(404)
|> json("Not found")
|> put_status(:not_found)
|> json(dgettext("errors", "Not found"))
end
def errors(conn, _) do
conn
|> put_status(500)
|> json("Something went wrong")
|> put_status(:internal_server_error)
|> json(dgettext("errors", "Something went wrong"))
end
end

View File

@ -1,3 +1,7 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.ConversationView do
use Pleroma.Web, :view

View File

@ -19,6 +19,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
import Pleroma.Web.ActivityPub.Visibility, only: [get_visibility: 1]
# TODO: Add cached version.
defp get_replied_to_activities([]), do: %{}
defp get_replied_to_activities(activities) do
activities
|> Enum.map(fn
@ -147,8 +149,14 @@ def render("status.json", %{activity: %{data: %{"object" => _object}} = activity
tags = object.data["tag"] || []
sensitive = object.data["sensitive"] || Enum.member?(tags, "nsfw")
tag_mentions =
tags
|> Enum.filter(fn tag -> is_map(tag) and tag["type"] == "Mention" end)
|> Enum.map(fn tag -> tag["href"] end)
mentions =
activity.recipients
(object.data["to"] ++ tag_mentions)
|> Enum.uniq()
|> Enum.map(fn ap_id -> User.get_cached_by_ap_id(ap_id) end)
|> Enum.filter(& &1)
|> Enum.map(fn user -> AccountView.render("mention.json", %{user: user}) end)

View File

@ -1,3 +1,7 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Metadata.PlayerView do
use Pleroma.Web, :view
import Phoenix.HTML.Tag, only: [content_tag: 3, tag: 2]

View File

@ -1,3 +1,7 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Metadata.Providers.RelMe do
alias Pleroma.Web.Metadata.Providers.Provider
@behaviour Provider

View File

@ -29,7 +29,7 @@ def check_password(conn, %{"user" => username, "pass" => password}) do
else
false ->
conn
|> put_status(403)
|> put_status(:forbidden)
|> json(false)
_ ->

View File

@ -201,8 +201,6 @@ def nodeinfo(conn, %{"version" => "2.1"}) do
end
def nodeinfo(conn, _) do
conn
|> put_status(404)
|> json(%{error: "Nodeinfo schema version not handled"})
render_error(conn, :not_found, "Nodeinfo schema version not handled")
end
end

View File

@ -9,21 +9,24 @@ defmodule Pleroma.Web.OAuth.FallbackController do
def call(conn, {:register, :generic_error}) do
conn
|> put_status(:internal_server_error)
|> put_flash(:error, "Unknown error, please check the details and try again.")
|> put_flash(
:error,
dgettext("errors", "Unknown error, please check the details and try again.")
)
|> OAuthController.registration_details(conn.params)
end
def call(conn, {:register, _error}) do
conn
|> put_status(:unauthorized)
|> put_flash(:error, "Invalid Username/Password")
|> put_flash(:error, dgettext("errors", "Invalid Username/Password"))
|> OAuthController.registration_details(conn.params)
end
def call(conn, _error) do
conn
|> put_status(:unauthorized)
|> put_flash(:error, "Invalid Username/Password")
|> put_flash(:error, dgettext("errors", "Invalid Username/Password"))
|> OAuthController.authorize(conn.params)
end
end

View File

@ -90,7 +90,7 @@ defp handle_existing_authorization(
redirect(conn, external: url)
else
conn
|> put_flash(:error, "Unlisted redirect_uri.")
|> put_flash(:error, dgettext("errors", "Unlisted redirect_uri."))
|> redirect(external: redirect_uri(conn, redirect_uri))
end
end
@ -128,7 +128,7 @@ def after_create_authorization(%Plug.Conn{} = conn, %Authorization{} = auth, %{
redirect(conn, external: url)
else
conn
|> put_flash(:error, "Unlisted redirect_uri.")
|> put_flash(:error, dgettext("errors", "Unlisted redirect_uri."))
|> redirect(external: redirect_uri(conn, redirect_uri))
end
end
@ -142,7 +142,7 @@ defp handle_create_authorization_error(
# Per https://github.com/tootsuite/mastodon/blob/
# 51e154f5e87968d6bb115e053689767ab33e80cd/app/controllers/api/base_controller.rb#L39
conn
|> put_flash(:error, "This action is outside the authorized scopes")
|> put_flash(:error, dgettext("errors", "This action is outside the authorized scopes"))
|> put_status(:unauthorized)
|> authorize(params)
end
@ -155,7 +155,7 @@ defp handle_create_authorization_error(
# Per https://github.com/tootsuite/mastodon/blob/
# 51e154f5e87968d6bb115e053689767ab33e80cd/app/controllers/api/base_controller.rb#L76
conn
|> put_flash(:error, "Your login is missing a confirmed e-mail address")
|> put_flash(:error, dgettext("errors", "Your login is missing a confirmed e-mail address"))
|> put_status(:forbidden)
|> authorize(params)
end
@ -176,9 +176,7 @@ def token_exchange(
json(conn, Token.Response.build(user, token, response_attrs))
else
_error ->
put_status(conn, 400)
|> json(%{error: "Invalid credentials"})
_error -> render_invalid_credentials_error(conn)
end
end
@ -192,9 +190,7 @@ def token_exchange(%Plug.Conn{} = conn, %{"grant_type" => "authorization_code"}
json(conn, Token.Response.build(user, token, response_attrs))
else
_error ->
put_status(conn, 400)
|> json(%{error: "Invalid credentials"})
_error -> render_invalid_credentials_error(conn)
end
end
@ -214,18 +210,13 @@ def token_exchange(
{:auth_active, false} ->
# Per https://github.com/tootsuite/mastodon/blob/
# 51e154f5e87968d6bb115e053689767ab33e80cd/app/controllers/api/base_controller.rb#L76
conn
|> put_status(:forbidden)
|> json(%{error: "Your login is missing a confirmed e-mail address"})
render_error(conn, :forbidden, "Your login is missing a confirmed e-mail address")
{:user_active, false} ->
conn
|> put_status(:forbidden)
|> json(%{error: "Your account is currently disabled"})
render_error(conn, :forbidden, "Your account is currently disabled")
_error ->
put_status(conn, 400)
|> json(%{error: "Invalid credentials"})
render_invalid_credentials_error(conn)
end
end
@ -247,9 +238,7 @@ def token_exchange(%Plug.Conn{} = conn, %{"grant_type" => "client_credentials"}
{:ok, token} <- Token.exchange_token(app, auth) do
json(conn, Token.Response.build_for_client_credentials(token))
else
_error ->
put_status(conn, 400)
|> json(%{error: "Invalid credentials"})
_error -> render_invalid_credentials_error(conn)
end
end
@ -271,9 +260,7 @@ def token_revoke(%Plug.Conn{} = conn, params), do: bad_request(conn, params)
# Response for bad request
defp bad_request(%Plug.Conn{} = conn, _) do
conn
|> put_status(500)
|> json(%{error: "Bad request"})
render_error(conn, :internal_server_error, "Bad request")
end
@doc "Prepares OAuth request to provider for Ueberauth"
@ -304,9 +291,11 @@ def prepare_request(%Plug.Conn{} = conn, %{
def request(%Plug.Conn{} = conn, params) do
message =
if params["provider"] do
"Unsupported OAuth provider: #{params["provider"]}."
dgettext("errors", "Unsupported OAuth provider: %{provider}.",
provider: params["provider"]
)
else
"Bad OAuth request."
dgettext("errors", "Bad OAuth request.")
end
conn
@ -320,7 +309,10 @@ def callback(%Plug.Conn{assigns: %{ueberauth_failure: failure}} = conn, params)
message = Enum.join(messages, "; ")
conn
|> put_flash(:error, "Failed to authenticate: #{message}.")
|> put_flash(
:error,
dgettext("errors", "Failed to authenticate: %{message}.", message: message)
)
|> redirect(external: redirect_uri(conn, params["redirect_uri"]))
end
@ -350,7 +342,7 @@ def callback(%Plug.Conn{} = conn, params) do
Logger.debug(inspect(["OAUTH_ERROR", error, conn.assigns]))
conn
|> put_flash(:error, "Failed to set up user account.")
|> put_flash(:error, dgettext("errors", "Failed to set up user account."))
|> redirect(external: redirect_uri(conn, params["redirect_uri"]))
end
end
@ -468,4 +460,8 @@ def default_redirect_uri(%App{} = app) do
|> String.split()
|> Enum.at(0)
end
defp render_invalid_credentials_error(conn) do
render_error(conn, :bad_request, "Invalid credentials")
end
end

View File

@ -1,3 +1,7 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.OAuth.Token.Response do
@moduledoc false

View File

@ -1,3 +1,7 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.OAuth.Token.Strategy.RefreshToken do
@moduledoc """
Functions for dealing with refresh token strategy.

View File

@ -1,3 +1,7 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.OAuth.Token.Strategy.Revoke do
@moduledoc """
Functions for dealing with revocation.

View File

@ -1,3 +1,7 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.OAuth.Token.Utils do
@moduledoc """
Auxiliary functions for dealing with tokens.

View File

@ -245,14 +245,10 @@ defp represent_activity(conn, _, activity, user) do
end
def errors(conn, {:error, :not_found}) do
conn
|> put_status(404)
|> text("Not found")
render_error(conn, :not_found, "Not found")
end
def errors(conn, _) do
conn
|> put_status(500)
|> text("Something went wrong")
render_error(conn, :internal_server_error, "Something went wrong")
end
end

View File

@ -1,3 +1,7 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.RichMedia.Parsers.MetaTagsParser do
def parse(html, data, prefix, error_message, key_name, value_name \\ "content") do
meta_data =

View File

@ -1,3 +1,7 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.RichMedia.Parsers.OEmbed do
def parse(html, _data) do
with elements = [_ | _] <- get_discovery_data(html),

View File

@ -1,3 +1,7 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.RichMedia.Parsers.OGP do
def parse(html, data) do
Pleroma.Web.RichMedia.Parsers.MetaTagsParser.parse(

View File

@ -1,3 +1,7 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.RichMedia.Parsers.TwitterCard do
def parse(html, data) do
Pleroma.Web.RichMedia.Parsers.MetaTagsParser.parse(

View File

@ -0,0 +1,17 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.TranslationHelpers do
defmacro render_error(conn, status, msgid, bindings \\ Macro.escape(%{})) do
quote do
require Pleroma.Web.Gettext
unquote(conn)
|> Plug.Conn.put_status(unquote(status))
|> Phoenix.Controller.json(%{
error: Pleroma.Web.Gettext.dgettext("errors", unquote(msgid), unquote(bindings))
})
end
end
end

View File

@ -1,3 +1,7 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.UploaderController do
use Pleroma.Web, :controller
@ -8,7 +12,7 @@ def callback(conn, %{"upload_path" => upload_path} = params) do
end
def callbacks(conn, _) do
send_resp(conn, 400, "bad request")
render_error(conn, :bad_request, "bad request")
end
defp process_callback(conn, pid, params) when is_pid(pid) do
@ -20,6 +24,6 @@ defp process_callback(conn, pid, params) when is_pid(pid) do
end
defp process_callback(conn, _, _) do
send_resp(conn, 400, "bad request")
render_error(conn, :bad_request, "bad request")
end
end

View File

@ -23,9 +23,11 @@ defmodule Pleroma.Web do
def controller do
quote do
use Phoenix.Controller, namespace: Pleroma.Web
import Plug.Conn
import Pleroma.Web.Gettext
import Pleroma.Web.Router.Helpers
import Pleroma.Web.TranslationHelpers
plug(:set_put_layout)

View File

@ -1,3 +1,7 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Phoenix.Transports.WebSocket.Raw do
import Plug.Conn,
only: [

View File

@ -1,3 +1,7 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.XmlBuilder do
def to_xml({tag, attributes, content}) do
open_tag = make_open_tag(tag, attributes)

View File

@ -125,7 +125,7 @@ defp deps do
{:cors_plug, "~> 1.5"},
{:ex_doc, "~> 0.20.2", only: :dev, runtime: false},
{:web_push_encryption, "~> 0.2.1"},
{:swoosh, "~> 0.20"},
{:swoosh, "~> 0.23.2"},
{:phoenix_swoosh, "~> 0.2"},
{:gen_smtp, "~> 0.13"},
{:websocket_client, git: "https://github.com/jeremyong/websocket_client.git", only: :test},

View File

@ -18,7 +18,7 @@
"crontab": {:hex, :crontab, "1.1.5", "2c9439506ceb0e9045de75879e994b88d6f0be88bfe017d58cb356c66c4a5482", [:mix], [{:ecto, "~> 1.0 or ~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm"},
"crypt": {:git, "https://github.com/msantos/crypt", "1f2b58927ab57e72910191a7ebaeff984382a1d3", [ref: "1f2b58927ab57e72910191a7ebaeff984382a1d3"]},
"db_connection": {:hex, :db_connection, "2.0.6", "bde2f85d047969c5b5800cb8f4b3ed6316c8cb11487afedac4aa5f93fd39abfa", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm"},
"decimal": {:hex, :decimal, "1.7.0", "30d6b52c88541f9a66637359ddf85016df9eb266170d53105f02e4a67e00c5aa", [:mix], [], "hexpm"},
"decimal": {:hex, :decimal, "1.8.0", "ca462e0d885f09a1c5a342dbd7c1dcf27ea63548c65a65e67334f4b61803822e", [:mix], [], "hexpm"},
"deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm"},
"earmark": {:hex, :earmark, "1.3.2", "b840562ea3d67795ffbb5bd88940b1bed0ed9fa32834915125ea7d02e35888a5", [:mix], [], "hexpm"},
"ecto": {:hex, :ecto, "3.1.4", "69d852da7a9f04ede725855a35ede48d158ca11a404fe94f8b2fb3b2162cd3c9", [:mix], [{:decimal, "~> 1.6", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"},
@ -34,10 +34,8 @@
"ex_syslogger": {:git, "https://github.com/slashmili/ex_syslogger.git", "f3963399047af17e038897c69e20d552e6899e1d", [tag: "1.4.0"]},
"excoveralls": {:hex, :excoveralls, "0.11.1", "dd677fbdd49114fdbdbf445540ec735808250d56b011077798316505064edb2c", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm"},
"floki": {:hex, :floki, "0.20.4", "be42ac911fece24b4c72f3b5846774b6e61b83fe685c2fc9d62093277fb3bc86", [:mix], [{:html_entities, "~> 0.4.0", [hex: :html_entities, repo: "hexpm", optional: false]}, {:mochiweb, "~> 2.15", [hex: :mochiweb, repo: "hexpm", optional: false]}], "hexpm"},
"gen_smtp": {:hex, :gen_smtp, "0.13.0", "11f08504c4bdd831dc520b8f84a1dce5ce624474a797394e7aafd3c29f5dcd25", [:rebar3], [], "hexpm"},
"gen_stage": {:hex, :gen_stage, "0.14.1", "9d46723fda072d4f4bb31a102560013f7960f5d80ea44dcb96fd6304ed61e7a4", [:mix], [], "hexpm"},
"gen_state_machine": {:hex, :gen_state_machine, "2.0.5", "9ac15ec6e66acac994cc442dcc2c6f9796cf380ec4b08267223014be1c728a95", [:mix], [], "hexpm"},
"gettext": {:hex, :gettext, "0.16.1", "e2130b25eebcbe02bb343b119a07ae2c7e28bd4b146c4a154da2ffb2b3507af2", [:mix], [], "hexpm"},
"gen_smtp": {:hex, :gen_smtp, "0.14.0", "39846a03522456077c6429b4badfd1d55e5e7d0fdfb65e935b7c5e38549d9202", [:rebar3], [], "hexpm"},
"gettext": {:hex, :gettext, "0.17.0", "abe21542c831887a2b16f4c94556db9c421ab301aee417b7c4fbde7fbdbe01ec", [:mix], [], "hexpm"},
"hackney": {:hex, :hackney, "1.15.1", "9f8f471c844b8ce395f7b6d8398139e26ddca9ebc171a8b91342ee15a19963f4", [:rebar3], [{:certifi, "2.5.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.4", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"},
"html_entities": {:hex, :html_entities, "0.4.0", "f2fee876858cf6aaa9db608820a3209e45a087c5177332799592142b50e89a6b", [:mix], [], "hexpm"},
"html_sanitize_ex": {:hex, :html_sanitize_ex, "1.3.0", "f005ad692b717691203f940c686208aa3d8ffd9dd4bb3699240096a51fa9564e", [:mix], [{:mochiweb, "~> 2.15", [hex: :mochiweb, repo: "hexpm", optional: false]}], "hexpm"},
@ -72,19 +70,20 @@
"plug_crypto": {:hex, :plug_crypto, "1.0.0", "18e49317d3fa343f24620ed22795ec29d4a5e602d52d1513ccea0b07d8ea7d4d", [:mix], [], "hexpm"},
"plug_static_index_html": {:hex, :plug_static_index_html, "1.0.0", "840123d4d3975585133485ea86af73cb2600afd7f2a976f9f5fd8b3808e636a0", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
"poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"},
"poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm"},
"postgrex": {:hex, :postgrex, "0.14.3", "5754dee2fdf6e9e508cbf49ab138df964278700b764177e8f3871e658b345a1e", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.0", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"},
"prometheus": {:hex, :prometheus, "4.2.2", "a830e77b79dc6d28183f4db050a7cac926a6c58f1872f9ef94a35cd989aceef8", [:mix, :rebar3], [], "hexpm"},
"prometheus_ecto": {:hex, :prometheus_ecto, "1.4.1", "6c768ea9654de871e5b32fab2eac348467b3021604ebebbcbd8bcbe806a65ed5", [:mix], [{:ecto, "~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.1 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}], "hexpm"},
"prometheus_ex": {:hex, :prometheus_ex, "3.0.5", "fa58cfd983487fc5ead331e9a3e0aa622c67232b3ec71710ced122c4c453a02f", [:mix], [{:prometheus, "~> 4.0", [hex: :prometheus, repo: "hexpm", optional: false]}], "hexpm"},
"prometheus_phoenix": {:hex, :prometheus_phoenix, "1.2.1", "964a74dfbc055f781d3a75631e06ce3816a2913976d1df7830283aa3118a797a", [:mix], [{:phoenix, "~> 1.3", [hex: :phoenix, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.3 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}], "hexpm"},
"prometheus_plugs": {:hex, :prometheus_plugs, "1.1.5", "25933d48f8af3a5941dd7b621c889749894d8a1082a6ff7c67cc99dec26377c5", [:mix], [{:accept, "~> 0.1", [hex: :accept, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.1 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}, {:prometheus_process_collector, "~> 1.1", [hex: :prometheus_process_collector, repo: "hexpm", optional: true]}], "hexpm"},
"prometheus_process_collector": {:hex, :prometheus_process_collector, "1.4.0", "6dbd39e3165b9ef1c94a7a820e9ffe08479f949dcdd431ed4aaea7b250eebfde", [:rebar3], [{:prometheus, "~> 4.0", [hex: :prometheus, repo: "hexpm", optional: false]}], "hexpm"},
"quack": {:hex, :quack, "0.1.1", "cca7b4da1a233757fdb44b3334fce80c94785b3ad5a602053b7a002b5a8967bf", [:mix], [{:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: false]}, {:tesla, "~> 1.2.0", [hex: :tesla, repo: "hexpm", optional: false]}], "hexpm"},
"quantum": {:hex, :quantum, "2.3.4", "72a0e8855e2adc101459eac8454787cb74ab4169de6ca50f670e72142d4960e9", [:mix], [{:calendar, "~> 0.17", [hex: :calendar, repo: "hexpm", optional: true]}, {:crontab, "~> 1.1", [hex: :crontab, repo: "hexpm", optional: false]}, {:gen_stage, "~> 0.12", [hex: :gen_stage, repo: "hexpm", optional: false]}, {:swarm, "~> 3.3", [hex: :swarm, repo: "hexpm", optional: false]}, {:timex, "~> 3.1", [hex: :timex, repo: "hexpm", optional: true]}], "hexpm"},
"ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm"},
"recon": {:git, "https://github.com/ferd/recon.git", "75d70c7c08926d2f24f1ee6de14ee50fe8a52763", [tag: "2.4.0"]},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.4", "f0eafff810d2041e93f915ef59899c923f4568f4585904d010387ed74988e77b", [:make, :mix, :rebar3], [], "hexpm"},
"swarm": {:hex, :swarm, "3.4.0", "64f8b30055d74640d2186c66354b33b999438692a91be275bb89cdc7e401f448", [:mix], [{:gen_state_machine, "~> 2.0", [hex: :gen_state_machine, repo: "hexpm", optional: false]}, {:libring, "~> 1.0", [hex: :libring, repo: "hexpm", optional: false]}], "hexpm"},
"swoosh": {:hex, :swoosh, "0.23.1", "209b7cc6d862c09d2a064c16caa4d4d1c9c936285476459e16591e0065f8432b", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.12", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}], "hexpm"},
"swoosh": {:hex, :swoosh, "0.23.2", "7dda95ff0bf54a2298328d6899c74dae1223777b43563ccebebb4b5d2b61df38", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}], "hexpm"},
"syslog": {:git, "https://github.com/Vagabond/erlang-syslog.git", "4a6c6f2c996483e86c1320e9553f91d337bcb6aa", [tag: "1.0.5"]},
"telemetry": {:hex, :telemetry, "0.4.0", "8339bee3fa8b91cb84d14c2935f8ecf399ccd87301ad6da6b71c09553834b2ab", [:rebar3], [], "hexpm"},
"tesla": {:hex, :tesla, "1.2.1", "864783cc27f71dd8c8969163704752476cec0f3a51eb3b06393b3971dc9733ff", [:mix], [{:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "~> 4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm"},

View File

@ -91,3 +91,375 @@ msgstr ""
msgid "must be equal to %{number}"
msgstr ""
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:381
msgid "Account not found"
msgstr ""
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:153
msgid "Already voted"
msgstr ""
#, elixir-format
#: lib/pleroma/web/oauth/oauth_controller.ex:263
msgid "Bad request"
msgstr ""
#, elixir-format
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:254
msgid "Can't delete object"
msgstr ""
#, elixir-format
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:569
msgid "Can't delete this post"
msgstr ""
#, elixir-format
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1731
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1737
msgid "Can't display this activity"
msgstr ""
#, elixir-format
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:195
msgid "Can't find user"
msgstr ""
#, elixir-format
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1148
msgid "Can't get favorites"
msgstr ""
#, elixir-format
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:263
msgid "Can't like object"
msgstr ""
#, elixir-format
#: lib/pleroma/web/common_api/utils.ex:518
msgid "Cannot post an empty status without attachments"
msgstr ""
#, elixir-format
#: lib/pleroma/web/common_api/utils.ex:461
msgid "Comment must be up to %{max_size} characters"
msgstr ""
#, elixir-format
#: lib/pleroma/web/admin_api/config.ex:63
msgid "Config with params %{params} not found"
msgstr ""
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:78
msgid "Could not delete"
msgstr ""
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:110
msgid "Could not favorite"
msgstr ""
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:310
msgid "Could not pin"
msgstr ""
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:89
msgid "Could not repeat"
msgstr ""
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:120
msgid "Could not unfavorite"
msgstr ""
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:327
msgid "Could not unpin"
msgstr ""
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:99
msgid "Could not unrepeat"
msgstr ""
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:392
msgid "Could not update state"
msgstr ""
#, elixir-format
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1271
msgid "Error."
msgstr ""
#, elixir-format
#: lib/pleroma/captcha/kocaptcha.ex:36
msgid "Invalid CAPTCHA"
msgstr ""
#, elixir-format
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1700
#: lib/pleroma/web/oauth/oauth_controller.ex:465
msgid "Invalid credentials"
msgstr ""
#, elixir-format
#: lib/pleroma/plugs/ensure_authenticated_plug.ex:20
msgid "Invalid credentials."
msgstr ""
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:154
msgid "Invalid indices"
msgstr ""
#, elixir-format
#: lib/pleroma/web/admin_api/admin_api_controller.ex:411
msgid "Invalid parameters"
msgstr ""
#, elixir-format
#: lib/pleroma/web/common_api/utils.ex:377
msgid "Invalid password."
msgstr ""
#, elixir-format
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:163
msgid "Invalid request"
msgstr ""
#, elixir-format
#: lib/pleroma/captcha/kocaptcha.ex:16
msgid "Kocaptcha service unavailable"
msgstr ""
#, elixir-format
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1696
msgid "Missing parameters"
msgstr ""
#, elixir-format
#: lib/pleroma/web/common_api/utils.ex:496
msgid "No such conversation"
msgstr ""
#, elixir-format
#: lib/pleroma/web/admin_api/admin_api_controller.ex:163
#: lib/pleroma/web/admin_api/admin_api_controller.ex:206
msgid "No such permission_group"
msgstr ""
#, elixir-format
#: lib/pleroma/plugs/uploaded_media.ex:69
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:311
#: lib/pleroma/web/admin_api/admin_api_controller.ex:399
#: lib/pleroma/web/mastodon_api/subscription_controller.ex:63
#: lib/pleroma/web/ostatus/ostatus_controller.ex:248
msgid "Not found"
msgstr ""
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:152
msgid "Poll's author can't vote"
msgstr ""
#, elixir-format
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:443
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:444
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:473
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:476
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1180
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1564
msgid "Record not found"
msgstr ""
#, elixir-format
#: lib/pleroma/web/admin_api/admin_api_controller.ex:417
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1570
#: lib/pleroma/web/mastodon_api/subscription_controller.ex:69
#: lib/pleroma/web/ostatus/ostatus_controller.ex:252
msgid "Something went wrong"
msgstr ""
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:253
msgid "The message visibility must be direct"
msgstr ""
#, elixir-format
#: lib/pleroma/web/common_api/utils.ex:521
msgid "The status is over the character limit"
msgstr ""
#, elixir-format
#: lib/pleroma/plugs/ensure_public_or_authenticated_plug.ex:27
msgid "This resource requires authentication."
msgstr ""
#, elixir-format
#: lib/pleroma/plugs/rate_limiter.ex:89
msgid "Throttled"
msgstr ""
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:155
msgid "Too many choices"
msgstr ""
#, elixir-format
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:268
msgid "Unhandled activity type"
msgstr ""
#, elixir-format
#: lib/pleroma/plugs/user_is_admin_plug.ex:20
msgid "User is not admin."
msgstr ""
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:380
msgid "Valid `account_id` required"
msgstr ""
#, elixir-format
#: lib/pleroma/web/admin_api/admin_api_controller.ex:185
msgid "You can't revoke your own admin status."
msgstr ""
#, elixir-format
#: lib/pleroma/web/oauth/oauth_controller.ex:216
msgid "Your account is currently disabled"
msgstr ""
#, elixir-format
#: lib/pleroma/web/oauth/oauth_controller.ex:158
#: lib/pleroma/web/oauth/oauth_controller.ex:213
msgid "Your login is missing a confirmed e-mail address"
msgstr ""
#, elixir-format
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:221
msgid "can't read inbox of %{nickname} as %{as_nickname}"
msgstr ""
#, elixir-format
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:297
msgid "can't update outbox of %{nickname} as %{as_nickname}"
msgstr ""
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:335
msgid "conversation is already muted"
msgstr ""
#, elixir-format
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:192
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:317
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1196
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1247
msgid "error"
msgstr ""
#, elixir-format
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:789
msgid "mascots can only be images"
msgstr ""
#, elixir-format
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:34
msgid "not found"
msgstr ""
#, elixir-format
#: lib/pleroma/web/oauth/oauth_controller.ex:298
msgid "Bad OAuth request."
msgstr ""
#, elixir-format
#: lib/pleroma/captcha/captcha.ex:92
msgid "CAPTCHA already used"
msgstr ""
#, elixir-format
#: lib/pleroma/captcha/captcha.ex:89
msgid "CAPTCHA expired"
msgstr ""
#, elixir-format
#: lib/pleroma/plugs/uploaded_media.ex:50
msgid "Failed"
msgstr ""
#, elixir-format
#: lib/pleroma/web/oauth/oauth_controller.ex:314
msgid "Failed to authenticate: %{message}."
msgstr ""
#, elixir-format
#: lib/pleroma/web/oauth/oauth_controller.ex:345
msgid "Failed to set up user account."
msgstr ""
#, elixir-format
#: lib/pleroma/plugs/oauth_scopes_plug.ex:37
msgid "Insufficient permissions: %{permissions}."
msgstr ""
#, elixir-format
#: lib/pleroma/plugs/uploaded_media.ex:89
msgid "Internal Error"
msgstr ""
#, elixir-format
#: lib/pleroma/web/oauth/fallback_controller.ex:22
#: lib/pleroma/web/oauth/fallback_controller.ex:29
msgid "Invalid Username/Password"
msgstr ""
#, elixir-format
#: lib/pleroma/captcha/captcha.ex:107
msgid "Invalid answer data"
msgstr ""
#, elixir-format
#: lib/pleroma/web/nodeinfo/nodeinfo_controller.ex:204
msgid "Nodeinfo schema version not handled"
msgstr ""
#, elixir-format
#: lib/pleroma/web/oauth/oauth_controller.ex:145
msgid "This action is outside the authorized scopes"
msgstr ""
#, elixir-format
#: lib/pleroma/web/oauth/fallback_controller.ex:14
msgid "Unknown error, please check the details and try again."
msgstr ""
#, elixir-format
#: lib/pleroma/web/oauth/oauth_controller.ex:93
#: lib/pleroma/web/oauth/oauth_controller.ex:131
msgid "Unlisted redirect_uri."
msgstr ""
#, elixir-format
#: lib/pleroma/web/oauth/oauth_controller.ex:294
msgid "Unsupported OAuth provider: %{provider}."
msgstr ""
#, elixir-format
#: lib/pleroma/uploaders/uploader.ex:71
msgid "Uploader callback timeout"
msgstr ""
#, elixir-format
#: lib/pleroma/web/uploader_controller.ex:11
#: lib/pleroma/web/uploader_controller.ex:23
msgid "bad request"
msgstr ""

View File

@ -7,7 +7,6 @@
## Run `mix gettext.extract` to bring this file up to
## date. Leave `msgstr`s empty as changing them here as no
## effect: edit them in PO (`.po`) files instead.
## From Ecto.Changeset.cast/4
msgid "can't be blank"
msgstr ""
@ -89,3 +88,375 @@ msgstr ""
msgid "must be equal to %{number}"
msgstr ""
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:381
msgid "Account not found"
msgstr ""
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:153
msgid "Already voted"
msgstr ""
#, elixir-format
#: lib/pleroma/web/oauth/oauth_controller.ex:263
msgid "Bad request"
msgstr ""
#, elixir-format
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:254
msgid "Can't delete object"
msgstr ""
#, elixir-format
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:569
msgid "Can't delete this post"
msgstr ""
#, elixir-format
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1731
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1737
msgid "Can't display this activity"
msgstr ""
#, elixir-format
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:195
msgid "Can't find user"
msgstr ""
#, elixir-format
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1148
msgid "Can't get favorites"
msgstr ""
#, elixir-format
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:263
msgid "Can't like object"
msgstr ""
#, elixir-format
#: lib/pleroma/web/common_api/utils.ex:518
msgid "Cannot post an empty status without attachments"
msgstr ""
#, elixir-format
#: lib/pleroma/web/common_api/utils.ex:461
msgid "Comment must be up to %{max_size} characters"
msgstr ""
#, elixir-format
#: lib/pleroma/web/admin_api/config.ex:63
msgid "Config with params %{params} not found"
msgstr ""
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:78
msgid "Could not delete"
msgstr ""
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:110
msgid "Could not favorite"
msgstr ""
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:310
msgid "Could not pin"
msgstr ""
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:89
msgid "Could not repeat"
msgstr ""
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:120
msgid "Could not unfavorite"
msgstr ""
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:327
msgid "Could not unpin"
msgstr ""
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:99
msgid "Could not unrepeat"
msgstr ""
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:392
msgid "Could not update state"
msgstr ""
#, elixir-format
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1271
msgid "Error."
msgstr ""
#, elixir-format
#: lib/pleroma/captcha/kocaptcha.ex:36
msgid "Invalid CAPTCHA"
msgstr ""
#, elixir-format
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1700
#: lib/pleroma/web/oauth/oauth_controller.ex:465
msgid "Invalid credentials"
msgstr ""
#, elixir-format
#: lib/pleroma/plugs/ensure_authenticated_plug.ex:20
msgid "Invalid credentials."
msgstr ""
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:154
msgid "Invalid indices"
msgstr ""
#, elixir-format
#: lib/pleroma/web/admin_api/admin_api_controller.ex:411
msgid "Invalid parameters"
msgstr ""
#, elixir-format
#: lib/pleroma/web/common_api/utils.ex:377
msgid "Invalid password."
msgstr ""
#, elixir-format
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:163
msgid "Invalid request"
msgstr ""
#, elixir-format
#: lib/pleroma/captcha/kocaptcha.ex:16
msgid "Kocaptcha service unavailable"
msgstr ""
#, elixir-format
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1696
msgid "Missing parameters"
msgstr ""
#, elixir-format
#: lib/pleroma/web/common_api/utils.ex:496
msgid "No such conversation"
msgstr ""
#, elixir-format
#: lib/pleroma/web/admin_api/admin_api_controller.ex:163
#: lib/pleroma/web/admin_api/admin_api_controller.ex:206
msgid "No such permission_group"
msgstr ""
#, elixir-format
#: lib/pleroma/plugs/uploaded_media.ex:69
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:311
#: lib/pleroma/web/admin_api/admin_api_controller.ex:399
#: lib/pleroma/web/mastodon_api/subscription_controller.ex:63
#: lib/pleroma/web/ostatus/ostatus_controller.ex:248
msgid "Not found"
msgstr ""
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:152
msgid "Poll's author can't vote"
msgstr ""
#, elixir-format
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:443
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:444
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:473
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:476
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1180
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1564
msgid "Record not found"
msgstr ""
#, elixir-format
#: lib/pleroma/web/admin_api/admin_api_controller.ex:417
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1570
#: lib/pleroma/web/mastodon_api/subscription_controller.ex:69
#: lib/pleroma/web/ostatus/ostatus_controller.ex:252
msgid "Something went wrong"
msgstr ""
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:253
msgid "The message visibility must be direct"
msgstr ""
#, elixir-format
#: lib/pleroma/web/common_api/utils.ex:521
msgid "The status is over the character limit"
msgstr ""
#, elixir-format
#: lib/pleroma/plugs/ensure_public_or_authenticated_plug.ex:27
msgid "This resource requires authentication."
msgstr ""
#, elixir-format
#: lib/pleroma/plugs/rate_limiter.ex:89
msgid "Throttled"
msgstr ""
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:155
msgid "Too many choices"
msgstr ""
#, elixir-format
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:268
msgid "Unhandled activity type"
msgstr ""
#, elixir-format
#: lib/pleroma/plugs/user_is_admin_plug.ex:20
msgid "User is not admin."
msgstr ""
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:380
msgid "Valid `account_id` required"
msgstr ""
#, elixir-format
#: lib/pleroma/web/admin_api/admin_api_controller.ex:185
msgid "You can't revoke your own admin status."
msgstr ""
#, elixir-format
#: lib/pleroma/web/oauth/oauth_controller.ex:216
msgid "Your account is currently disabled"
msgstr ""
#, elixir-format
#: lib/pleroma/web/oauth/oauth_controller.ex:158
#: lib/pleroma/web/oauth/oauth_controller.ex:213
msgid "Your login is missing a confirmed e-mail address"
msgstr ""
#, elixir-format
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:221
msgid "can't read inbox of %{nickname} as %{as_nickname}"
msgstr ""
#, elixir-format
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:297
msgid "can't update outbox of %{nickname} as %{as_nickname}"
msgstr ""
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:335
msgid "conversation is already muted"
msgstr ""
#, elixir-format
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:192
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:317
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1196
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1247
msgid "error"
msgstr ""
#, elixir-format
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:789
msgid "mascots can only be images"
msgstr ""
#, elixir-format
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:34
msgid "not found"
msgstr ""
#, elixir-format
#: lib/pleroma/web/oauth/oauth_controller.ex:298
msgid "Bad OAuth request."
msgstr ""
#, elixir-format
#: lib/pleroma/captcha/captcha.ex:92
msgid "CAPTCHA already used"
msgstr ""
#, elixir-format
#: lib/pleroma/captcha/captcha.ex:89
msgid "CAPTCHA expired"
msgstr ""
#, elixir-format
#: lib/pleroma/plugs/uploaded_media.ex:50
msgid "Failed"
msgstr ""
#, elixir-format
#: lib/pleroma/web/oauth/oauth_controller.ex:314
msgid "Failed to authenticate: %{message}."
msgstr ""
#, elixir-format
#: lib/pleroma/web/oauth/oauth_controller.ex:345
msgid "Failed to set up user account."
msgstr ""
#, elixir-format
#: lib/pleroma/plugs/oauth_scopes_plug.ex:37
msgid "Insufficient permissions: %{permissions}."
msgstr ""
#, elixir-format
#: lib/pleroma/plugs/uploaded_media.ex:89
msgid "Internal Error"
msgstr ""
#, elixir-format
#: lib/pleroma/web/oauth/fallback_controller.ex:22
#: lib/pleroma/web/oauth/fallback_controller.ex:29
msgid "Invalid Username/Password"
msgstr ""
#, elixir-format
#: lib/pleroma/captcha/captcha.ex:107
msgid "Invalid answer data"
msgstr ""
#, elixir-format
#: lib/pleroma/web/nodeinfo/nodeinfo_controller.ex:204
msgid "Nodeinfo schema version not handled"
msgstr ""
#, elixir-format
#: lib/pleroma/web/oauth/oauth_controller.ex:145
msgid "This action is outside the authorized scopes"
msgstr ""
#, elixir-format
#: lib/pleroma/web/oauth/fallback_controller.ex:14
msgid "Unknown error, please check the details and try again."
msgstr ""
#, elixir-format
#: lib/pleroma/web/oauth/oauth_controller.ex:93
#: lib/pleroma/web/oauth/oauth_controller.ex:131
msgid "Unlisted redirect_uri."
msgstr ""
#, elixir-format
#: lib/pleroma/web/oauth/oauth_controller.ex:294
msgid "Unsupported OAuth provider: %{provider}."
msgstr ""
#, elixir-format
#: lib/pleroma/uploaders/uploader.ex:71
msgid "Uploader callback timeout"
msgstr ""
#, elixir-format
#: lib/pleroma/web/uploader_controller.ex:11
#: lib/pleroma/web/uploader_controller.ex:23
msgid "bad request"
msgstr ""

View File

@ -0,0 +1,463 @@
## `msgid`s in this file come from POT (.pot) files.
##
## Do not add, change, or remove `msgid`s manually here as
## they're tied to the ones in the corresponding POT file
## (with the same domain).
##
## Use `mix gettext.extract --merge` or `mix gettext.merge`
## to merge POT files into PO files.
msgid ""
msgstr ""
"Language: ru\n"
"Plural-Forms: nplurals=3\n"
msgid "can't be blank"
msgstr "не может быть пустым"
msgid "has already been taken"
msgstr "уже занято"
msgid "is invalid"
msgstr "неверный"
msgid "has invalid format"
msgstr "неверный формат"
msgid "has an invalid entry"
msgstr "содержит неверную запись"
msgid "is reserved"
msgstr "занято"
msgid "does not match confirmation"
msgstr "не совпадает"
msgid "is still associated with this entry"
msgstr "по прежнему связан с этой записью"
msgid "are still associated with this entry"
msgstr "по прежнему связаны с этой записью"
msgid "should be %{count} character(s)"
msgid_plural "should be %{count} character(s)"
msgstr[0] "должен состоять из %{count} символа"
msgstr[1] "должен состоять из %{count} символов"
msgstr[2] "должен состоять из %{count} символов"
msgid "should have %{count} item(s)"
msgid_plural "should have %{count} item(s)"
msgstr[0] "должен содержать %{count} элемент"
msgstr[1] "должен содержать %{count} элемента"
msgstr[2] "должен содержать %{count} элементов"
msgid "should be at least %{count} character(s)"
msgid_plural "should be at least %{count} character(s)"
msgstr[0] "должен быть не менее чем %{count} символа"
msgstr[1] "должен быть не менее чем %{count} символов"
msgstr[2] "должен быть не менее чем %{count} символов"
msgid "should have at least %{count} item(s)"
msgid_plural "should have at least %{count} item(s)"
msgstr[0] "должен быть не менее %{count} элемента"
msgstr[1] "должен быть не менее %{count} элементов"
msgstr[2] "должен быть не менее %{count} элементов"
msgid "should be at most %{count} character(s)"
msgid_plural "should be at most %{count} character(s)"
msgstr[0] "должен быть не более %{count} символа"
msgstr[1] "должен быть не более %{count} символов"
msgstr[2] "должен быть не более %{count} символов"
msgid "should have at most %{count} item(s)"
msgid_plural "should have at most %{count} item(s)"
msgstr[0] "должен содержать не менее %{count} элемента"
msgstr[1] "должен содержать не менее %{count} элемента"
msgstr[2] "должен содержать не менее %{count} элементов"
msgid "must be less than %{number}"
msgstr "должен быть меньше %{number}"
msgid "must be greater than %{number}"
msgstr "должен быть больше %{number}"
msgid "must be less than or equal to %{number}"
msgstr "должен быть меньше или равен %{number}"
msgid "must be greater than or equal to %{number}"
msgstr "должен быть больше или равен %{number}"
msgid "must be equal to %{number}"
msgstr "должен быть равным %{number}"
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:381
msgid "Account not found"
msgstr "Учетная запись не найдена"
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:153
msgid "Already voted"
msgstr "Уже проголосовал(а)"
#, elixir-format
#: lib/pleroma/web/oauth/oauth_controller.ex:263
msgid "Bad request"
msgstr "Неверный запрос"
#, elixir-format
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:254
msgid "Can't delete object"
msgstr "Произошла ошибка при удалении объекта"
#, elixir-format
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:569
msgid "Can't delete this post"
msgstr "Произошла ошибка при удалении этой записи"
#, elixir-format
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1731
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1737
msgid "Can't display this activity"
msgstr "Произошла ошибка при показе этой записи"
#, elixir-format
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:195
msgid "Can't find user"
msgstr "Пользователь не найден"
#, elixir-format
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1148
msgid "Can't get favorites"
msgstr "Не в состоянии получить избранное"
#, elixir-format
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:263
msgid "Can't like object"
msgstr "Не могу поставить лайк"
#, elixir-format
#: lib/pleroma/web/common_api/utils.ex:518
msgid "Cannot post an empty status without attachments"
msgstr "Нельзя отправить пустой статус без приложений"
#, elixir-format
#: lib/pleroma/web/common_api/utils.ex:461
msgid "Comment must be up to %{max_size} characters"
msgstr "Комментарий должен быть не более %{max_size} символов"
#, elixir-format
#: lib/pleroma/web/admin_api/config.ex:63
msgid "Config with params %{params} not found"
msgstr "Параметры конфигурации %{params} не найдены"
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:78
msgid "Could not delete"
msgstr "Не в силах удалить"
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:110
msgid "Could not favorite"
msgstr "Не в силах добавить в избранное"
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:310
msgid "Could not pin"
msgstr "Не в силах прикрепить"
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:89
msgid "Could not repeat"
msgstr "Не в силах повторить"
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:120
msgid "Could not unfavorite"
msgstr "Не в силах удалить из избранного"
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:327
msgid "Could not unpin"
msgstr "Не в силах открепить"
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:99
msgid "Could not unrepeat"
msgstr "Не в силах отменить повтор"
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:392
msgid "Could not update state"
msgstr "Не в силах обновить состояние"
#, elixir-format
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1271
msgid "Error."
msgstr "Ошибка"
#, elixir-format
#: lib/pleroma/captcha/kocaptcha.ex:36
msgid "Invalid CAPTCHA"
msgstr "Неверная CAPTCHA"
#, elixir-format
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1700
#: lib/pleroma/web/oauth/oauth_controller.ex:465
msgid "Invalid credentials"
msgstr "Неверные учетные данные"
#, elixir-format
#: lib/pleroma/plugs/ensure_authenticated_plug.ex:20
msgid "Invalid credentials."
msgstr "Неверные учетные данные"
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:154
msgid "Invalid indices"
msgstr "Неверные индексы"
#, elixir-format
#: lib/pleroma/web/admin_api/admin_api_controller.ex:411
msgid "Invalid parameters"
msgstr "Неверны параметры"
#, elixir-format
#: lib/pleroma/web/common_api/utils.ex:377
msgid "Invalid password."
msgstr "Неверный пароль"
#, elixir-format
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:163
msgid "Invalid request"
msgstr "Неверный запрос"
#, elixir-format
#: lib/pleroma/captcha/kocaptcha.ex:16
msgid "Kocaptcha service unavailable"
msgstr "Kocaptcha недоступен"
#, elixir-format
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1696
msgid "Missing parameters"
msgstr "Не хватает параметров"
#, elixir-format
#: lib/pleroma/web/common_api/utils.ex:496
msgid "No such conversation"
msgstr "Разговор не найден"
#, elixir-format
#: lib/pleroma/web/admin_api/admin_api_controller.ex:163
#: lib/pleroma/web/admin_api/admin_api_controller.ex:206
msgid "No such permission_group"
msgstr "Такой группы полномочий не существует"
#, elixir-format
#: lib/pleroma/plugs/uploaded_media.ex:69
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:311
#: lib/pleroma/web/admin_api/admin_api_controller.ex:399
#: lib/pleroma/web/mastodon_api/subscription_controller.ex:63
#: lib/pleroma/web/ostatus/ostatus_controller.ex:248
msgid "Not found"
msgstr "Не найден"
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:152
msgid "Poll's author can't vote"
msgstr "Автор опроса не может голосовать"
#, elixir-format
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:443
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:444
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:473
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:476
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1180
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1564
msgid "Record not found"
msgstr "Запись не найдена"
#, elixir-format
#: lib/pleroma/web/admin_api/admin_api_controller.ex:417
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1570
#: lib/pleroma/web/mastodon_api/subscription_controller.ex:69
#: lib/pleroma/web/ostatus/ostatus_controller.ex:252
msgid "Something went wrong"
msgstr "Что-то пошло не так"
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:253
msgid "The message visibility must be direct"
msgstr "Видимость у сообщения должна быть `Личное`"
#, elixir-format
#: lib/pleroma/web/common_api/utils.ex:521
msgid "The status is over the character limit"
msgstr "Превышена длина статуса"
#, elixir-format
#: lib/pleroma/plugs/ensure_public_or_authenticated_plug.ex:27
msgid "This resource requires authentication."
msgstr "Для этого ресурса требуется аутентификация"
#, elixir-format
#: lib/pleroma/plugs/rate_limiter.ex:89
msgid "Throttled"
msgstr "Ограничено. Превышен лимит запросов."
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:155
msgid "Too many choices"
msgstr "Слишком много ответов"
#, elixir-format
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:268
msgid "Unhandled activity type"
msgstr "Неизвестный тип activity"
#, elixir-format
#: lib/pleroma/plugs/user_is_admin_plug.ex:20
msgid "User is not admin."
msgstr "Пользователь не обладает правами администратора"
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:380
msgid "Valid `account_id` required"
msgstr "Требуется корректный `account_id`"
#, elixir-format
#: lib/pleroma/web/admin_api/admin_api_controller.ex:185
msgid "You can't revoke your own admin status."
msgstr "Вы не можете отозвать статус администратора у вашей учетной записи"
#, elixir-format
#: lib/pleroma/web/oauth/oauth_controller.ex:216
msgid "Your account is currently disabled"
msgstr "Ваша учетная запись отключена"
#, elixir-format
#: lib/pleroma/web/oauth/oauth_controller.ex:158
#: lib/pleroma/web/oauth/oauth_controller.ex:213
msgid "Your login is missing a confirmed e-mail address"
msgstr "Ваш e-mail адрес не подтвержден"
#, elixir-format
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:221
msgid "can't read inbox of %{nickname} as %{as_nickname}"
msgstr ""
#, elixir-format
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:297
msgid "can't update outbox of %{nickname} as %{as_nickname}"
msgstr ""
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:335
msgid "conversation is already muted"
msgstr "разговор уже игнорируется"
#, elixir-format
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:192
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:317
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1196
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1247
msgid "error"
msgstr "ошибка"
#, elixir-format
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:789
msgid "mascots can only be images"
msgstr "маскоты должны быть картинками"
#, elixir-format
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:34
msgid "not found"
msgstr "не найдено"
#, elixir-format
#: lib/pleroma/web/oauth/oauth_controller.ex:298
msgid "Bad OAuth request."
msgstr "Неверный OAuth запрос"
#, elixir-format
#: lib/pleroma/captcha/captcha.ex:92
msgid "CAPTCHA already used"
msgstr "CAPTCHA уже использована"
#, elixir-format
#: lib/pleroma/captcha/captcha.ex:89
msgid "CAPTCHA expired"
msgstr "CAPTCHA устарела"
#, elixir-format
#: lib/pleroma/plugs/uploaded_media.ex:50
msgid "Failed"
msgstr "Ошибка"
#, elixir-format
#: lib/pleroma/web/oauth/oauth_controller.ex:314
msgid "Failed to authenticate: %{message}."
msgstr "Ошибка при входе: %{message}"
#, elixir-format
#: lib/pleroma/web/oauth/oauth_controller.ex:345
msgid "Failed to set up user account."
msgstr "Ошибка при создании учетной записи"
#, elixir-format
#: lib/pleroma/plugs/oauth_scopes_plug.ex:37
msgid "Insufficient permissions: %{permissions}."
msgstr "Недостаточно полномочий: %{permissions}"
#, elixir-format
#: lib/pleroma/plugs/uploaded_media.ex:89
msgid "Internal Error"
msgstr "Внутренняя ошибка"
#, elixir-format
#: lib/pleroma/web/oauth/fallback_controller.ex:22
#: lib/pleroma/web/oauth/fallback_controller.ex:29
msgid "Invalid Username/Password"
msgstr "Неверное имя пользователя или пароль"
#, elixir-format
#: lib/pleroma/captcha/captcha.ex:107
msgid "Invalid answer data"
msgstr "Неверный ответ"
#, elixir-format
#: lib/pleroma/web/nodeinfo/nodeinfo_controller.ex:204
msgid "Nodeinfo schema version not handled"
msgstr "Версия схемы Nodeinfo не учитывается"
#, elixir-format
#: lib/pleroma/web/oauth/oauth_controller.ex:145
msgid "This action is outside the authorized scopes"
msgstr "Это действие выходит за рамки доступных полномочий"
#, elixir-format
#: lib/pleroma/web/oauth/fallback_controller.ex:14
msgid "Unknown error, please check the details and try again."
msgstr "Неизвестная ошибка. Пожалуйста, проверьте данные и попробуйте снова."
#, elixir-format
#: lib/pleroma/web/oauth/oauth_controller.ex:93
#: lib/pleroma/web/oauth/oauth_controller.ex:131
msgid "Unlisted redirect_uri."
msgstr "Неизвестный redirect_uri"
#, elixir-format
#: lib/pleroma/web/oauth/oauth_controller.ex:294
msgid "Unsupported OAuth provider: %{provider}."
msgstr "Неизвестный OAuth провайдер: %{provider}"
#, elixir-format
#: lib/pleroma/uploaders/uploader.ex:71
msgid "Uploader callback timeout"
msgstr "Тайм-аут при загрузке"
#, elixir-format
#: lib/pleroma/web/uploader_controller.ex:11
#: lib/pleroma/web/uploader_controller.ex:23
msgid "bad request"
msgstr "неправильный запрос"

View File

@ -0,0 +1,9 @@
defmodule Pleroma.Repo.Migrations.AddFollowingAddressToUser do
use Ecto.Migration
def change do
alter table(:users) do
add(:following_address, :string, unique: true)
end
end
end

View File

@ -0,0 +1,8 @@
defmodule Pleroma.Repo.Migrations.AddFollowingAddressIndexToUser do
use Ecto.Migration
@disable_ddl_transaction true
def change do
create(index(:users, [:following_address], concurrently: true))
end
end

View File

@ -0,0 +1,20 @@
defmodule Pleroma.Repo.Migrations.AddFollowingAddressFromSourceData do
use Ecto.Migration
import Ecto.Query
alias Pleroma.User
def change do
query =
User.external_users_query()
|> select([u], struct(u, [:id, :ap_id, :info]))
Pleroma.Repo.stream(query)
|> Enum.each(fn
%{info: %{source_data: source_data}} = user ->
Ecto.Changeset.cast(user, %{following_address: source_data["following"]}, [
:following_address
])
|> Pleroma.Repo.update()
end)
end
end

View File

@ -11,6 +11,7 @@ end %>
config :pleroma, Pleroma.Web.Endpoint,
url: [host: "<%= domain %>", scheme: "https", port: <%= port %>],
http: [ip: {<%= String.replace(listen_ip, ".", ", ") %>}, port: <%= listen_port %>],
secret_key_base: "<%= secret %>",
signing_salt: "<%= signing_salt %>"

View File

@ -6,6 +6,7 @@ defmodule Pleroma.ActivityTest do
use Pleroma.DataCase
alias Pleroma.Activity
alias Pleroma.Bookmark
alias Pleroma.Object
alias Pleroma.ThreadMute
import Pleroma.Factory
@ -18,15 +19,18 @@ test "returns an activity by it's AP id" do
test "returns activities by it's objects AP ids" do
activity = insert(:note_activity)
[found_activity] = Activity.get_all_create_by_object_ap_id(activity.data["object"]["id"])
object_data = Object.normalize(activity).data
[found_activity] = Activity.get_all_create_by_object_ap_id(object_data["id"])
assert activity == found_activity
end
test "returns the activity that created an object" do
activity = insert(:note_activity)
object_data = Object.normalize(activity).data
found_activity = Activity.get_create_by_object_ap_id(activity.data["object"]["id"])
found_activity = Activity.get_create_by_object_ap_id(object_data["id"])
assert activity == found_activity
end

View File

@ -1,3 +1,7 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.BBS.HandlerTest do
use Pleroma.DataCase
alias Pleroma.Activity
@ -59,6 +63,7 @@ test "replying" do
another_user = insert(:user)
{:ok, activity} = CommonAPI.post(another_user, %{"status" => "this is a test post"})
activity_object = Object.normalize(activity)
output =
capture_io(fn ->
@ -76,8 +81,9 @@ test "replying" do
)
assert reply.actor == user.ap_id
object = Object.normalize(reply)
assert object.data["content"] == "this is a reply"
assert object.data["inReplyTo"] == activity.data["object"]
reply_object_data = Object.normalize(reply).data
assert reply_object_data["content"] == "this is a reply"
assert reply_object_data["inReplyTo"] == activity_object.data["id"]
end
end

View File

@ -1,3 +1,7 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.BookmarkTest do
use Pleroma.DataCase
import Pleroma.Factory

View File

@ -1,3 +1,7 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Config.TransferTaskTest do
use Pleroma.DataCase

View File

@ -0,0 +1,37 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Emails.AdminEmailTest do
use Pleroma.DataCase
import Pleroma.Factory
alias Pleroma.Emails.AdminEmail
alias Pleroma.Web.Router.Helpers
test "build report email" do
config = Pleroma.Config.get(:instance)
to_user = insert(:user)
reporter = insert(:user)
account = insert(:user)
res =
AdminEmail.report(to_user, reporter, account, [%{name: "Test", id: "12"}], "Test comment")
status_url = Helpers.o_status_url(Pleroma.Web.Endpoint, :notice, "12")
reporter_url = Helpers.o_status_url(Pleroma.Web.Endpoint, :feed_redirect, reporter.nickname)
account_url = Helpers.o_status_url(Pleroma.Web.Endpoint, :feed_redirect, account.nickname)
assert res.to == [{to_user.name, to_user.email}]
assert res.from == {config[:name], config[:notify_email]}
assert res.reply_to == {reporter.name, reporter.email}
assert res.subject == "#{config[:name]} Report"
assert res.html_body ==
"<p>Reported by: <a href=\"#{reporter_url}\">#{reporter.nickname}</a></p>\n<p>Reported Account: <a href=\"#{
account_url
}\">#{account.nickname}</a></p>\n<p>Comment: Test comment\n<p> Statuses:\n <ul>\n <li><a href=\"#{
status_url
}\">#{status_url}</li>\n </ul>\n</p>\n\n"
end
end

View File

@ -0,0 +1,57 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Emails.MailerTest do
use Pleroma.DataCase
alias Pleroma.Emails.Mailer
import Swoosh.TestAssertions
@email %Swoosh.Email{
from: {"Pleroma", "noreply@example.com"},
html_body: "Test email",
subject: "Pleroma test email",
to: [{"Test User", "user1@example.com"}]
}
setup do
value = Pleroma.Config.get([Pleroma.Emails.Mailer, :enabled])
on_exit(fn -> Pleroma.Config.put([Pleroma.Emails.Mailer, :enabled], value) end)
:ok
end
test "not send email when mailer is disabled" do
Pleroma.Config.put([Pleroma.Emails.Mailer, :enabled], false)
Mailer.deliver(@email)
refute_email_sent(
from: {"Pleroma", "noreply@example.com"},
to: [{"Test User", "user1@example.com"}],
html_body: "Test email",
subject: "Pleroma test email"
)
end
test "send email" do
Mailer.deliver(@email)
assert_email_sent(
from: {"Pleroma", "noreply@example.com"},
to: [{"Test User", "user1@example.com"}],
html_body: "Test email",
subject: "Pleroma test email"
)
end
test "perform" do
Mailer.perform(:deliver_async, @email, [])
assert_email_sent(
from: {"Pleroma", "noreply@example.com"},
to: [{"Test User", "user1@example.com"}],
html_body: "Test email",
subject: "Pleroma test email"
)
end
end

View File

@ -0,0 +1,48 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Emails.UserEmailTest do
use Pleroma.DataCase
alias Pleroma.Emails.UserEmail
alias Pleroma.Web.Endpoint
alias Pleroma.Web.Router
import Pleroma.Factory
test "build password reset email" do
config = Pleroma.Config.get(:instance)
user = insert(:user)
email = UserEmail.password_reset_email(user, "test_token")
assert email.from == {config[:name], config[:notify_email]}
assert email.to == [{user.name, user.email}]
assert email.subject == "Password reset"
assert email.html_body =~ Router.Helpers.reset_password_url(Endpoint, :reset, "test_token")
end
test "build user invitation email" do
config = Pleroma.Config.get(:instance)
user = insert(:user)
token = %Pleroma.UserInviteToken{token: "test-token"}
email = UserEmail.user_invitation_email(user, token, "test@test.com", "Jonh")
assert email.from == {config[:name], config[:notify_email]}
assert email.subject == "Invitation to Pleroma"
assert email.to == [{"Jonh", "test@test.com"}]
assert email.html_body =~
Router.Helpers.redirect_url(Endpoint, :registration_page, token.token)
end
test "build account confirmation email" do
config = Pleroma.Config.get(:instance)
user = insert(:user, info: %Pleroma.User.Info{confirmation_token: "conf-token"})
email = UserEmail.account_confirmation_email(user)
assert email.from == {config[:name], config[:notify_email]}
assert email.to == [{user.name, user.email}]
assert email.subject == "#{config[:name]} account confirmation"
assert email.html_body =~
Router.Helpers.confirm_email_url(Endpoint, :confirm_email, user.id, "conf-token")
end
end

Some files were not shown because too many files have changed in this diff Show More