Merge remote-tracking branch 'upstream/develop' into registration-workflow

This commit is contained in:
Alex Gleason 2020-12-17 09:04:43 -06:00
commit 80891e83d8
No known key found for this signature in database
GPG Key ID: 7211D1F99744FBB7
162 changed files with 8645 additions and 2668 deletions

8
.gitattributes vendored
View File

@ -1,8 +1,10 @@
*.ex diff=elixir *.ex diff=elixir
*.exs diff=elixir *.exs diff=elixir
# At the time of writing all js/css files included
# in the repo are minified bundles, and we don't want priv/static/instance/static.css diff=css
# to search/diff those as text files.
# Most of js/css files included in the repo are minified bundles,
# and we don't want to search/diff those as text files.
*.js binary *.js binary
*.js.map binary *.js.map binary
*.css binary *.css binary

View File

@ -57,7 +57,7 @@ unit-testing:
policy: pull policy: pull
services: services:
- name: postgres:9.6 - name: postgres:13
alias: postgres alias: postgres
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"] command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
script: script:
@ -228,7 +228,7 @@ arm:
artifacts: *release-artifacts artifacts: *release-artifacts
only: *release-only only: *release-only
tags: tags:
- arm32 - arm32-specified
image: arm32v7/elixir:1.10.3 image: arm32v7/elixir:1.10.3
cache: *release-cache cache: *release-cache
variables: *release-variables variables: *release-variables
@ -240,7 +240,7 @@ arm-musl:
artifacts: *release-artifacts artifacts: *release-artifacts
only: *release-only only: *release-only
tags: tags:
- arm32 - arm32-specified
image: arm32v7/elixir:1.10.3-alpine image: arm32v7/elixir:1.10.3-alpine
cache: *release-cache cache: *release-cache
variables: *release-variables variables: *release-variables

View File

@ -12,18 +12,23 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Admin Emails: The ap id is used as the user link in emails now. - Admin Emails: The ap id is used as the user link in emails now.
- Improved registration workflow for email confirmation and account approval modes. - Improved registration workflow for email confirmation and account approval modes.
- **Breaking:** Changed `mix pleroma.user toggle_confirmed` to `mix pleroma.user confirm` - **Breaking:** Changed `mix pleroma.user toggle_confirmed` to `mix pleroma.user confirm`
- Search: When using Postgres 11+, Pleroma will use the `websearch_to_tsvector` function to parse search queries.
- Emoji: Support the full Unicode 13.1 set of Emoji for reactions, plus regional indicators.
### Added ### Added
- Reports now generate notifications for admins and mods. - Reports now generate notifications for admins and mods.
- Experimental websocket-based federation between Pleroma instances. - Experimental websocket-based federation between Pleroma instances.
- Support for local-only statuses - Support for local-only statuses.
- Support pagination of blocks and mutes. - Support pagination of blocks and mutes.
- Account backup. - Account backup.
- Configuration: Add `:instance, autofollowing_nicknames` setting to provide a way to make accounts automatically follow new users that register on the local Pleroma instance. - Configuration: Add `:instance, autofollowing_nicknames` setting to provide a way to make accounts automatically follow new users that register on the local Pleroma instance.
- Ability to view remote timelines, with ex. `/api/v1/timelines/public?instance=lain.com` and streams `public:remote` and `public:remote:media`. - Ability to view remote timelines, with ex. `/api/v1/timelines/public?instance=lain.com` and streams `public:remote` and `public:remote:media`.
- The site title is now injected as a `title` tag like preloads or metadata. - The site title is now injected as a `title` tag like preloads or metadata.
- Password reset tokens now are not accepted after a certain age. - Password reset tokens now are not accepted after a certain age.
- Mix tasks to help with displaying and removing ConfigDB entries. See `mix pleroma.config`.
- OAuth form improvements: users are remembered by their cookie, the CSS is overridable by the admin, and the style has been improved.
- OAuth improvements and fixes: more secure session-based authentication (by token that could be revoked anytime), ability to revoke belonging OAuth token from any client etc.
<details> <details>
<summary>API Changes</summary> <summary>API Changes</summary>
@ -31,13 +36,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Pleroma API: Add `idempotency_key` to the chat message entity that can be used for optimistic message sending. - Pleroma API: Add `idempotency_key` to the chat message entity that can be used for optimistic message sending.
- Pleroma API: (`GET /api/v1/pleroma/federation_status`) Add a way to get a list of unreachable instances. - Pleroma API: (`GET /api/v1/pleroma/federation_status`) Add a way to get a list of unreachable instances.
- Mastodon API: User and conversation mutes can now auto-expire if `expires_in` parameter was given while adding the mute. - Mastodon API: User and conversation mutes can now auto-expire if `expires_in` parameter was given while adding the mute.
- Admin API: An endpoint to manage frontends - Admin API: An endpoint to manage frontends.
- Streaming API: Add follow relationships updates.
</details> </details>
### Fixed ### Fixed
- Users with `is_discoverable` field set to false (default value) will appear in in-service search results but be hidden from external services (search bots etc.). - Users with `is_discoverable` field set to false (default value) will appear in in-service search results but be hidden from external services (search bots etc.).
- Streaming API: Posts and notifications are not dropped, when CLI task is executing.
<details> <details>
<summary>API Changes</summary> <summary>API Changes</summary>
@ -58,6 +64,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Mix task pleroma.user delete_activities for source installations. - Mix task pleroma.user delete_activities for source installations.
- Fix ability to update Pleroma Chat push notifications with PUT /api/v1/push/subscription and alert type pleroma:chat_mention - Fix ability to update Pleroma Chat push notifications with PUT /api/v1/push/subscription and alert type pleroma:chat_mention
- Forwarded reports duplication from Pleroma instances. - Forwarded reports duplication from Pleroma instances.
- Rich Media Previews sometimes showed the wrong preview due to a bug following redirects.
<details> <details>
<summary>API</summary> <summary>API</summary>
@ -101,7 +108,7 @@ switched to a new configuration mechanism, however it was not officially removed
- Media preview proxy (requires `ffmpeg` and `ImageMagick` to be installed and media proxy to be enabled; see `:media_preview_proxy` config for more details). - Media preview proxy (requires `ffmpeg` and `ImageMagick` to be installed and media proxy to be enabled; see `:media_preview_proxy` config for more details).
- Mix tasks for controlling user account confirmation status in bulk (`mix pleroma.user confirm_all` and `mix pleroma.user unconfirm_all`) - Mix tasks for controlling user account confirmation status in bulk (`mix pleroma.user confirm_all` and `mix pleroma.user unconfirm_all`)
- Mix task for sending confirmation emails to all unconfirmed users (`mix pleroma.email send_confirmation_mails`) - Mix task for sending confirmation emails to all unconfirmed users (`mix pleroma.email resend_confirmation_emails`)
- Mix task option for force-unfollowing relays - Mix task option for force-unfollowing relays
- App metrics: ability to restrict access to specified IP whitelist. - App metrics: ability to restrict access to specified IP whitelist.

View File

@ -33,7 +33,7 @@ ARG DATA=/var/lib/pleroma
RUN echo "http://nl.alpinelinux.org/alpine/latest-stable/community" >> /etc/apk/repositories &&\ RUN echo "http://nl.alpinelinux.org/alpine/latest-stable/community" >> /etc/apk/repositories &&\
apk update &&\ apk update &&\
apk add exiftool imagemagick ncurses postgresql-client &&\ apk add exiftool imagemagick libmagic ncurses postgresql-client &&\
adduser --system --shell /bin/false --home ${HOME} pleroma &&\ adduser --system --shell /bin/false --home ${HOME} pleroma &&\
mkdir -p ${DATA}/uploads &&\ mkdir -p ${DATA}/uploads &&\
mkdir -p ${DATA}/static &&\ mkdir -p ${DATA}/static &&\

View File

@ -109,8 +109,8 @@ def make_friends(main_user, max) when is_integer(max) do
end end
def make_friends(%User{} = main_user, %User{} = user) do def make_friends(%User{} = main_user, %User{} = user) do
{:ok, _} = User.follow(main_user, user) {:ok, _, _} = User.follow(main_user, user)
{:ok, _} = User.follow(user, main_user) {:ok, _, _} = User.follow(user, main_user)
end end
@spec get_users(User.t(), keyword()) :: [User.t()] @spec get_users(User.t(), keyword()) :: [User.t()]

View File

@ -50,7 +50,7 @@ def run(_args) do
) )
users users
|> Enum.each(fn {:ok, follower} -> Pleroma.User.follow(follower, user) end) |> Enum.each(fn {:ok, follower, user} -> Pleroma.User.follow(follower, user) end)
Benchee.run( Benchee.run(
%{ %{

View File

@ -147,16 +147,6 @@
"SameSite=Lax" "SameSite=Lax"
] ]
config :pleroma, :fed_sockets,
enabled: false,
connection_duration: :timer.hours(8),
rejection_duration: :timer.minutes(15),
fed_socket_fetches: [
default: 12_000,
interval: 3_000,
lazy: false
]
# Configures Elixir's Logger # Configures Elixir's Logger
config :logger, :console, config :logger, :console,
level: :debug, level: :debug,
@ -316,7 +306,7 @@
hideSitename: false, hideSitename: false,
hideUserStats: false, hideUserStats: false,
loginMethod: "password", loginMethod: "password",
logo: "/static/logo.png", logo: "/static/logo.svg",
logoMargin: ".1em", logoMargin: ".1em",
logoMask: true, logoMask: true,
minimalScopesMode: false, minimalScopesMode: false,
@ -353,8 +343,8 @@
config :pleroma, :manifest, config :pleroma, :manifest,
icons: [ icons: [
%{ %{
src: "/static/logo.png", src: "/static/logo.svg",
type: "image/png" type: "image/svg+xml"
} }
], ],
theme_color: "#282c37", theme_color: "#282c37",
@ -658,7 +648,7 @@
} }
config :pleroma, :oauth2, config :pleroma, :oauth2,
token_expires_in: 600, token_expires_in: 3600 * 24 * 30,
issue_new_refresh_token: true, issue_new_refresh_token: true,
clean_expired_tokens: false clean_expired_tokens: false

View File

@ -1254,7 +1254,7 @@
hideSitename: false, hideSitename: false,
hideUserStats: false, hideUserStats: false,
loginMethod: "password", loginMethod: "password",
logo: "/static/logo.png", logo: "/static/logo.svg",
logoMargin: ".1em", logoMargin: ".1em",
logoMask: true, logoMask: true,
minimalScopesMode: false, minimalScopesMode: false,
@ -1340,7 +1340,7 @@
key: :logo, key: :logo,
type: {:string, :image}, type: {:string, :image},
description: "URL of the logo, defaults to Pleroma's logo", description: "URL of the logo, defaults to Pleroma's logo",
suggestions: ["/static/logo.png"] suggestions: ["/static/logo.svg"]
}, },
%{ %{
key: :logoMargin, key: :logoMargin,
@ -2540,7 +2540,7 @@
key: :token_expires_in, key: :token_expires_in,
type: :integer, type: :integer,
description: "The lifetime in seconds of the access token", description: "The lifetime in seconds of the access token",
suggestions: [600] suggestions: [2_592_000]
}, },
%{ %{
key: :issue_new_refresh_token, key: :issue_new_refresh_token,

View File

@ -1,31 +0,0 @@
import Config
config :pleroma, :instance, static_dir: "/var/lib/pleroma/static"
config :pleroma, Pleroma.Uploaders.Local, uploads: "/var/lib/pleroma/uploads"
config :pleroma, :modules, runtime_dir: "/var/lib/pleroma/modules"
config_path = System.get_env("PLEROMA_CONFIG_PATH") || "/etc/pleroma/config.exs"
config :pleroma, release: true, config_path: config_path
if File.exists?(config_path) do
import_config config_path
else
warning = [
IO.ANSI.red(),
IO.ANSI.bright(),
"!!! #{config_path} not found! Please ensure it exists and that PLEROMA_CONFIG_PATH is unset or points to an existing file",
IO.ANSI.reset()
]
IO.puts(warning)
end
exported_config =
config_path
|> Path.dirname()
|> Path.join("prod.exported_from_db.secret.exs")
if File.exists?(exported_config) do
import_config exported_config
end

View File

@ -19,11 +19,6 @@
level: :warn, level: :warn,
format: "\n[$level] $message\n" format: "\n[$level] $message\n"
config :pleroma, :fed_sockets,
enabled: false,
connection_duration: 5,
rejection_duration: 5
config :pleroma, :auth, oauth_consumer_strategies: [] config :pleroma, :auth, oauth_consumer_strategies: []
config :pleroma, Pleroma.Upload, config :pleroma, Pleroma.Upload,

View File

@ -4,7 +4,7 @@ A Pleroma instance can be identified by "<Mastodon version> (compatible; Pleroma
## Flake IDs ## Flake IDs
Pleroma uses 128-bit ids as opposed to Mastodon's 64 bits. However just like Mastodon's ids they are lexically sortable strings Pleroma uses 128-bit ids as opposed to Mastodon's 64 bits. However, just like Mastodon's ids, they are lexically sortable strings
## Timelines ## Timelines
@ -26,8 +26,8 @@ Has these additional fields under the `pleroma` object:
- `conversation_id`: the ID of the AP context the status is associated with (if any) - `conversation_id`: the ID of the AP context the status is associated with (if any)
- `direct_conversation_id`: the ID of the Mastodon direct message conversation the status is associated with (if any) - `direct_conversation_id`: the ID of the Mastodon direct message conversation the status is associated with (if any)
- `in_reply_to_account_acct`: the `acct` property of User entity for replied user (if any) - `in_reply_to_account_acct`: the `acct` property of User entity for replied user (if any)
- `content`: a map consisting of alternate representations of the `content` property with the key being it's mimetype. Currently the only alternate representation supported is `text/plain` - `content`: a map consisting of alternate representations of the `content` property with the key being its mimetype. Currently, the only alternate representation supported is `text/plain`
- `spoiler_text`: a map consisting of alternate representations of the `spoiler_text` property with the key being it's mimetype. Currently the only alternate representation supported is `text/plain` - `spoiler_text`: a map consisting of alternate representations of the `spoiler_text` property with the key being its mimetype. Currently, the only alternate representation supported is `text/plain`
- `expires_at`: a datetime (iso8601) that states when the post will expire (be deleted automatically), or empty if the post won't expire - `expires_at`: a datetime (iso8601) that states when the post will expire (be deleted automatically), or empty if the post won't expire
- `thread_muted`: true if the thread the post belongs to is muted - `thread_muted`: true if the thread the post belongs to is muted
- `emoji_reactions`: A list with emoji / reaction maps. The format is `{name: "☕", count: 1, me: true}`. Contains no information about the reacting users, for that use the `/statuses/:id/reactions` endpoint. - `emoji_reactions`: A list with emoji / reaction maps. The format is `{name: "☕", count: 1, me: true}`. Contains no information about the reacting users, for that use the `/statuses/:id/reactions` endpoint.
@ -170,9 +170,9 @@ Returns on success: 200 OK `{}`
Additional parameters can be added to the JSON body/Form data: Additional parameters can be added to the JSON body/Form data:
- `preview`: boolean, if set to `true` the post won't be actually posted, but the status entitiy would still be rendered back. This could be useful for previewing rich text/custom emoji, for example. - `preview`: boolean, if set to `true` the post won't be actually posted, but the status entity would still be rendered back. This could be useful for previewing rich text/custom emoji, for example.
- `content_type`: string, contain the MIME type of the status, it is transformed into HTML by the backend. You can get the list of the supported MIME types with the nodeinfo endpoint. - `content_type`: string, contain the MIME type of the status, it is transformed into HTML by the backend. You can get the list of the supported MIME types with the nodeinfo endpoint.
- `to`: A list of nicknames (like `lain@soykaf.club` or `lain` on the local server) that will be used to determine who is going to be addressed by this post. Using this will disable the implicit addressing by mentioned names in the `status` body, only the people in the `to` list will be addressed. The normal rules for for post visibility are not affected by this and will still apply. - `to`: A list of nicknames (like `lain@soykaf.club` or `lain` on the local server) that will be used to determine who is going to be addressed by this post. Using this will disable the implicit addressing by mentioned names in the `status` body, only the people in the `to` list will be addressed. The normal rules for post visibility are not affected by this and will still apply.
- `visibility`: string, besides standard MastoAPI values (`direct`, `private`, `unlisted`, `local` or `public`) it can be used to address a List by setting it to `list:LIST_ID`. - `visibility`: string, besides standard MastoAPI values (`direct`, `private`, `unlisted`, `local` or `public`) it can be used to address a List by setting it to `list:LIST_ID`.
- `expires_in`: The number of seconds the posted activity should expire in. When a posted activity expires it will be deleted from the server, and a delete request for it will be federated. This needs to be longer than an hour. - `expires_in`: The number of seconds the posted activity should expire in. When a posted activity expires it will be deleted from the server, and a delete request for it will be federated. This needs to be longer than an hour.
- `in_reply_to_conversation_id`: Will reply to a given conversation, addressing only the people who are part of the recipient set of that conversation. Sets the visibility to `direct`. - `in_reply_to_conversation_id`: Will reply to a given conversation, addressing only the people who are part of the recipient set of that conversation. Sets the visibility to `direct`.
@ -279,10 +279,27 @@ Has these additional fields under the `pleroma` object:
## Streaming ## Streaming
### Chats
There is an additional `user:pleroma_chat` stream. Incoming chat messages will make the current chat be sent to this `user` stream. The `event` of an incoming chat message is `pleroma:chat_update`. The payload is the updated chat with the incoming chat message in the `last_message` field. There is an additional `user:pleroma_chat` stream. Incoming chat messages will make the current chat be sent to this `user` stream. The `event` of an incoming chat message is `pleroma:chat_update`. The payload is the updated chat with the incoming chat message in the `last_message` field.
### Remote timelines
For viewing remote server timelines, there are `public:remote` and `public:remote:media` streams. Each of these accept a parameter like `?instance=lain.com`. For viewing remote server timelines, there are `public:remote` and `public:remote:media` streams. Each of these accept a parameter like `?instance=lain.com`.
### Follow relationships updates
Pleroma streams follow relationships updates as `pleroma:follow_relationships_update` events to the `user` stream.
The message payload consist of:
- `state`: a relationship state, one of `follow_pending`, `follow_accept` or `follow_reject`.
- `follower` and `following` maps with following fields:
- `id`: user ID
- `follower_count`: follower count
- `following_count`: following count
## User muting and thread muting ## User muting and thread muting
Both user muting and thread muting can be done for only a certain time by adding an `expires_in` parameter to the API calls and giving the expiration time in seconds. Both user muting and thread muting can be done for only a certain time by adding an `expires_in` parameter to the API calls and giving the expiration time in seconds.

View File

@ -579,14 +579,14 @@ Emoji reactions work a lot like favourites do. They make it possible to react to
### React to a post with a unicode emoji ### React to a post with a unicode emoji
* Method: `PUT` * Method: `PUT`
* Authentication: required * Authentication: required
* Params: `emoji`: A single character unicode emoji * Params: `emoji`: A unicode RGI emoji or a regional indicator
* Response: JSON, the status. * Response: JSON, the status.
## `DELETE /api/v1/pleroma/statuses/:id/reactions/:emoji` ## `DELETE /api/v1/pleroma/statuses/:id/reactions/:emoji`
### Remove a reaction to a post with a unicode emoji ### Remove a reaction to a post with a unicode emoji
* Method: `DELETE` * Method: `DELETE`
* Authentication: required * Authentication: required
* Params: `emoji`: A single character unicode emoji * Params: `emoji`: A unicode RGI emoji or a regional indicator
* Response: JSON, the status. * Response: JSON, the status.
## `GET /api/v1/pleroma/statuses/:id/reactions` ## `GET /api/v1/pleroma/statuses/:id/reactions`

View File

@ -32,7 +32,7 @@
config :pleroma, configurable_from_database: false config :pleroma, configurable_from_database: false
``` ```
To delete transfered settings from database optional flag `-d` can be used. `<env>` is `prod` by default. To delete transferred settings from database optional flag `-d` can be used. `<env>` is `prod` by default.
=== "OTP" === "OTP"
```sh ```sh
@ -43,3 +43,111 @@ To delete transfered settings from database optional flag `-d` can be used. `<en
```sh ```sh
mix pleroma.config migrate_from_db [--env=<env>] [-d] mix pleroma.config migrate_from_db [--env=<env>] [-d]
``` ```
## Dump all of the config settings defined in the database
=== "OTP"
```sh
./bin/pleroma_ctl config dump
```
=== "From Source"
```sh
mix pleroma.config dump
```
## List individual configuration groups in the database
=== "OTP"
```sh
./bin/pleroma_ctl config groups
```
=== "From Source"
```sh
mix pleroma.config groups
```
## Dump the saved configuration values for a specific group or key
e.g., this shows all the settings under `config :pleroma`
=== "OTP"
```sh
./bin/pleroma_ctl config dump pleroma
```
=== "From Source"
```sh
mix pleroma.config dump pleroma
```
To get values under a specific key:
e.g., this shows all the settings under `config :pleroma, :instance`
=== "OTP"
```sh
./bin/pleroma_ctl config dump pleroma instance
```
=== "From Source"
```sh
mix pleroma.config dump pleroma instance
```
## Delete the saved configuration values for a specific group or key
e.g., this deletes all the settings under `config :tesla`
=== "OTP"
```sh
./bin/pleroma_ctl config delete [--force] tesla
```
=== "From Source"
```sh
mix pleroma.config delete [--force] tesla
```
To delete values under a specific key:
e.g., this deletes all the settings under `config :phoenix, :stacktrace_depth`
=== "OTP"
```sh
./bin/pleroma_ctl config delete [--force] phoenix stacktrace_depth
```
=== "From Source"
```sh
mix pleroma.config delete [--force] phoenix stacktrace_depth
```
## Remove all settings from the database
This forcibly removes all saved values in the database.
=== "OTP"
```sh
./bin/pleroma_ctl config [--force] reset
```
=== "From Source"
```sh
mix pleroma.config [--force] reset
```

View File

@ -16,8 +16,7 @@
mix pleroma.email test [--to <destination email address>] mix pleroma.email test [--to <destination email address>]
``` ```
Example:
Example:
=== "OTP" === "OTP"
@ -36,11 +35,11 @@ Example:
=== "OTP" === "OTP"
```sh ```sh
./bin/pleroma_ctl email send_confirmation_mails ./bin/pleroma_ctl email resend_confirmation_emails
``` ```
=== "From Source" === "From Source"
```sh ```sh
mix pleroma.email send_confirmation_mails mix pleroma.email resend_confirmation_emails
``` ```

View File

@ -5,50 +5,37 @@ The configuration of Pleroma has traditionally been managed with a config file,
## Migration to database config ## Migration to database config
1. Run the mix task to migrate to the database. You'll receive some debugging output and a few messages informing you of what happened. 1. Run the mix task to migrate to the database.
**Source:** **Source:**
``` ```
$ mix pleroma.config migrate_to_db $ mix pleroma.config migrate_to_db
``` ```
or or
**OTP:** **OTP:**
*Note: OTP users need Pleroma to be running for `pleroma_ctl` commands to work* *Note: OTP users need Pleroma to be running for `pleroma_ctl` commands to work*
``` ```
$ ./bin/pleroma_ctl config migrate_to_db $ ./bin/pleroma_ctl config migrate_to_db
``` ```
``` ```
10:04:34.155 [debug] QUERY OK source="config" db=1.6ms decode=2.0ms queue=33.5ms idle=0.0ms
SELECT c0."id", c0."key", c0."group", c0."value", c0."inserted_at", c0."updated_at" FROM "config" AS c0 []
Migrating settings from file: /home/pleroma/config/dev.secret.exs Migrating settings from file: /home/pleroma/config/dev.secret.exs
10:04:34.240 [debug] QUERY OK db=4.5ms queue=0.3ms idle=92.2ms
TRUNCATE config; []
10:04:34.244 [debug] QUERY OK db=2.8ms queue=0.3ms idle=97.2ms
ALTER SEQUENCE config_id_seq RESTART; []
10:04:34.256 [debug] QUERY OK source="config" db=0.8ms queue=1.4ms idle=109.8ms
SELECT c0."id", c0."key", c0."group", c0."value", c0."inserted_at", c0."updated_at" FROM "config" AS c0 WHERE ((c0."group" = $1) AND (c0."key" = $2)) [":pleroma", ":instance"]
10:04:34.292 [debug] QUERY OK db=2.6ms queue=1.7ms idle=137.7ms
INSERT INTO "config" ("group","key","value","inserted_at","updated_at") VALUES ($1,$2,$3,$4,$5) RETURNING "id" [":pleroma", ":instance", <<131, 108, 0, 0, 0, 1, 104, 2, 100, 0, 4, 110, 97, 109, 101, 109, 0, 0, 0, 7, 66, 108, 101, 114, 111, 109, 97, 106>>, ~N[2020-07-12 15:04:34], ~N[2020-07-12 15:04:34]]
Settings for key instance migrated. Settings for key instance migrated.
Settings for group :pleroma migrated. Settings for group :pleroma migrated.
``` ```
2. It is recommended to backup your config file now. 2. It is recommended to backup your config file now.
``` ```
cp config/dev.secret.exs config/dev.secret.exs.orig cp config/dev.secret.exs config/dev.secret.exs.orig
``` ```
3. Edit your Pleroma config to enable database configuration: 3. Edit your Pleroma config to enable database configuration:
``` ```
@ -76,17 +63,17 @@ The configuration of Pleroma has traditionally been managed with a config file,
config :pleroma, Pleroma.Web.Endpoint, config :pleroma, Pleroma.Web.Endpoint,
url: [host: "cool.pleroma.site", scheme: "https", port: 443] url: [host: "cool.pleroma.site", scheme: "https", port: 443]
config :pleroma, Pleroma.Repo, config :pleroma, Pleroma.Repo,
adapter: Ecto.Adapters.Postgres, adapter: Ecto.Adapters.Postgres,
username: "pleroma", username: "pleroma",
password: "MySecretPassword", password: "MySecretPassword",
database: "pleroma_prod", database: "pleroma_prod",
hostname: "localhost" hostname: "localhost"
config :pleroma, configurable_from_database: true config :pleroma, configurable_from_database: true
``` ```
5. Restart your instance and you can now access the Settings tab in AdminFE. 5. Restart your instance and you can now access the Settings tab in AdminFE.
@ -95,15 +82,15 @@ The configuration of Pleroma has traditionally been managed with a config file,
1. Run the mix task to migrate back from the database. You'll receive some debugging output and a few messages informing you of what happened. 1. Run the mix task to migrate back from the database. You'll receive some debugging output and a few messages informing you of what happened.
**Source:** **Source:**
``` ```
$ mix pleroma.config migrate_from_db $ mix pleroma.config migrate_from_db
``` ```
or or
**OTP:** **OTP:**
``` ```
$ ./bin/pleroma_ctl config migrate_from_db $ ./bin/pleroma_ctl config migrate_from_db
``` ```
@ -111,7 +98,7 @@ The configuration of Pleroma has traditionally been managed with a config file,
``` ```
10:26:30.593 [debug] QUERY OK source="config" db=9.8ms decode=1.2ms queue=26.0ms idle=0.0ms 10:26:30.593 [debug] QUERY OK source="config" db=9.8ms decode=1.2ms queue=26.0ms idle=0.0ms
SELECT c0."id", c0."key", c0."group", c0."value", c0."inserted_at", c0."updated_at" FROM "config" AS c0 [] SELECT c0."id", c0."key", c0."group", c0."value", c0."inserted_at", c0."updated_at" FROM "config" AS c0 []
10:26:30.659 [debug] QUERY OK source="config" db=1.1ms idle=80.7ms 10:26:30.659 [debug] QUERY OK source="config" db=1.1ms idle=80.7ms
SELECT c0."id", c0."key", c0."group", c0."value", c0."inserted_at", c0."updated_at" FROM "config" AS c0 [] SELECT c0."id", c0."key", c0."group", c0."value", c0."inserted_at", c0."updated_at" FROM "config" AS c0 []
Database configuration settings have been saved to config/dev.exported_from_db.secret.exs Database configuration settings have been saved to config/dev.exported_from_db.secret.exs
@ -124,30 +111,45 @@ The configuration of Pleroma has traditionally been managed with a config file,
## Debugging ## Debugging
### Clearing database config ### Clearing database config
You can clear the database config by truncating the `config` table in the database. e.g., You can clear the database config with the following command:
``` **Source:**
psql -d pleroma_dev
pleroma_dev=# TRUNCATE config; ```
TRUNCATE TABLE $ mix pleroma.config reset
``` ```
or
**OTP:**
```
$ ./bin/pleroma_ctl config reset
```
Additionally, every time you migrate the configuration to the database the config table is automatically truncated to ensure a clean migration. Additionally, every time you migrate the configuration to the database the config table is automatically truncated to ensure a clean migration.
### Manually removing a setting ### Manually removing a setting
If you encounter a situation where the server cannot run properly because of an invalid setting in the database and this is preventing you from accessing AdminFE, you can manually remove the offending setting if you know which one it is. If you encounter a situation where the server cannot run properly because of an invalid setting in the database and this is preventing you from accessing AdminFE, you can manually remove the offending setting if you know which one it is.
e.g., here is an example showing a minimal configuration in the database. Only the `config :pleroma, :instance` settings are in the table: e.g., here is an example showing a the removal of the `config :pleroma, :instance` settings:
``` **Source:**
psql -d pleroma_dev
pleroma_dev=# select * from config; ```
id | key | value | inserted_at | updated_at | group $ mix pleroma.config delete pleroma instance
----+-----------+------------------------------------------------------------+---------------------+---------------------+---------- Are you sure you want to continue? [n] y
1 | :instance | \x836c0000000168026400046e616d656d00000007426c65726f6d616a | 2020-07-12 15:33:29 | 2020-07-12 15:33:29 | :pleroma config :pleroma, :instance deleted from the ConfigDB.
(1 row) ```
pleroma_dev=# delete from config where key = ':instance' and group = ':pleroma';
DELETE 1 or
```
**OTP:**
```
$ ./bin/pleroma_ctl config delete pleroma instance
Are you sure you want to continue? [n] y
config :pleroma, :instance deleted from the ConfigDB.
```
Now the `config :pleroma, :instance` settings have been removed from the database. Now the `config :pleroma, :instance` settings have been removed from the database.

View File

@ -88,3 +88,8 @@ config :pleroma, :frontend_configurations,
Note the extra `static` folder for the terms-of-service.html Note the extra `static` folder for the terms-of-service.html
Terms of Service will be shown to all users on the registration page. It's the best place where to write down the rules for your instance. You can modify the rules by adding and changing `$static_dir/static/terms-of-service.html`. Terms of Service will be shown to all users on the registration page. It's the best place where to write down the rules for your instance. You can modify the rules by adding and changing `$static_dir/static/terms-of-service.html`.
## Styling rendered pages
To overwrite the CSS stylesheet of the OAuth form and other static pages, you can upload your own CSS file to `instance/static/static.css`. This will completely replace the CSS used by those pages, so it might be a good idea to copy the one from `priv/static/instance/static.css` and make your changes.

View File

@ -14,9 +14,9 @@ This document contains notes and guidelines for Pleroma developers.
For `:api` pipeline routes, it'll be verified whether `OAuthScopesPlug` was called or explicitly skipped, and if it was not then auth information will be dropped for request. Then `EnsurePublicOrAuthenticatedPlug` will be called to ensure that either the instance is not private or user is authenticated (unless explicitly skipped). Such automated checks help to prevent human errors and result in higher security / privacy for users. For `:api` pipeline routes, it'll be verified whether `OAuthScopesPlug` was called or explicitly skipped, and if it was not then auth information will be dropped for request. Then `EnsurePublicOrAuthenticatedPlug` will be called to ensure that either the instance is not private or user is authenticated (unless explicitly skipped). Such automated checks help to prevent human errors and result in higher security / privacy for users.
## [HTTP Basic Authentication](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization) ## Non-OAuth authentication
* With HTTP Basic Auth, OAuth scopes check is _not_ performed for any action (since password is provided during the auth, requester is able to obtain a token with full permissions anyways). `Pleroma.Web.Plugs.AuthenticationPlug` and `Pleroma.Web.Plugs.LegacyAuthenticationPlug` both call `Pleroma.Web.Plugs.OAuthScopesPlug.skip_plug(conn)` when password is provided. * With non-OAuth authentication ([HTTP Basic Authentication](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization) or HTTP header- or params-provided auth), OAuth scopes check is _not_ performed for any action (since password is provided during the auth, requester is able to obtain a token with full permissions anyways); auth plugs invoke `Pleroma.Helpers.AuthHelper.skip_oauth(conn)` in this case.
## Auth-related configuration, OAuth consumer mode etc. ## Auth-related configuration, OAuth consumer mode etc.

View File

@ -12,7 +12,8 @@ defmodule Mix.Pleroma do
:cachex, :cachex,
:flake_id, :flake_id,
:swoosh, :swoosh,
:timex :timex,
:fast_html
] ]
@cachex_children ["object", "user", "scrubber", "web_resp"] @cachex_children ["object", "user", "scrubber", "web_resp"]
@doc "Common functions to be reused in mix tasks" @doc "Common functions to be reused in mix tasks"
@ -22,8 +23,8 @@ def start_pleroma do
Pleroma.Application.limiters_setup() Pleroma.Application.limiters_setup()
Application.put_env(:phoenix, :serve_endpoints, false, persistent: true) Application.put_env(:phoenix, :serve_endpoints, false, persistent: true)
if Pleroma.Config.get(:env) != :test do unless System.get_env("DEBUG") do
Application.put_env(:logger, :console, level: :debug) Logger.remove_backend(:console)
end end
adapter = Application.get_env(:tesla, :adapter) adapter = Application.get_env(:tesla, :adapter)
@ -37,12 +38,23 @@ def start_pleroma do
Enum.each(apps, &Application.ensure_all_started/1) Enum.each(apps, &Application.ensure_all_started/1)
oban_config = [
crontab: [],
repo: Pleroma.Repo,
log: false,
queues: [],
plugins: []
]
children = children =
[ [
Pleroma.Repo, Pleroma.Repo,
Pleroma.Emoji,
{Pleroma.Config.TransferTask, false}, {Pleroma.Config.TransferTask, false},
Pleroma.Web.Endpoint, Pleroma.Web.Endpoint,
{Oban, Pleroma.Config.get(Oban)} {Oban, oban_config},
{Majic.Pool,
[name: Pleroma.MajicPool, pool_size: Pleroma.Config.get([:majic_pool, :size], 2)]}
] ++ ] ++
http_children(adapter) http_children(adapter)
@ -98,12 +110,6 @@ def shell_prompt(prompt, defval \\ nil, defname \\ nil) do
end end
end end
def shell_yes?(message) do
if mix_shell?(),
do: Mix.shell().yes?("Continue?"),
else: shell_prompt(message, "Continue?") in ~w(Yn Y y)
end
def shell_info(message) do def shell_info(message) do
if mix_shell?(), if mix_shell?(),
do: Mix.shell().info(message), do: Mix.shell().info(message),

View File

@ -5,6 +5,7 @@
defmodule Mix.Tasks.Pleroma.Config do defmodule Mix.Tasks.Pleroma.Config do
use Mix.Task use Mix.Task
import Ecto.Query
import Mix.Pleroma import Mix.Pleroma
alias Pleroma.ConfigDB alias Pleroma.ConfigDB
@ -14,26 +15,199 @@ defmodule Mix.Tasks.Pleroma.Config do
@moduledoc File.read!("docs/administration/CLI_tasks/config.md") @moduledoc File.read!("docs/administration/CLI_tasks/config.md")
def run(["migrate_to_db"]) do def run(["migrate_to_db"]) do
start_pleroma() check_configdb(fn ->
migrate_to_db() start_pleroma()
migrate_to_db()
end)
end end
def run(["migrate_from_db" | options]) do def run(["migrate_from_db" | options]) do
check_configdb(fn ->
start_pleroma()
{opts, _} =
OptionParser.parse!(options,
strict: [env: :string, delete: :boolean],
aliases: [d: :delete]
)
migrate_from_db(opts)
end)
end
def run(["dump"]) do
check_configdb(fn ->
start_pleroma()
header = config_header()
settings =
ConfigDB
|> Repo.all()
|> Enum.sort()
unless settings == [] do
shell_info("#{header}")
Enum.each(settings, &dump(&1))
else
shell_error("No settings in ConfigDB.")
end
end)
end
def run(["dump", group, key]) do
check_configdb(fn ->
start_pleroma()
group = maybe_atomize(group)
key = maybe_atomize(key)
group
|> ConfigDB.get_by_group_and_key(key)
|> dump()
end)
end
def run(["dump", group]) do
check_configdb(fn ->
start_pleroma()
group = maybe_atomize(group)
dump_group(group)
end)
end
def run(["groups"]) do
check_configdb(fn ->
start_pleroma()
groups =
ConfigDB
|> distinct([c], true)
|> select([c], c.group)
|> Repo.all()
if length(groups) > 0 do
shell_info("The following configuration groups are set in ConfigDB:\r\n")
groups |> Enum.each(fn x -> shell_info("- #{x}") end)
shell_info("\r\n")
end
end)
end
def run(["reset", "--force"]) do
check_configdb(fn ->
start_pleroma()
truncatedb()
shell_info("The ConfigDB settings have been removed from the database.")
end)
end
def run(["reset"]) do
check_configdb(fn ->
start_pleroma()
shell_info("The following settings will be permanently removed:")
ConfigDB
|> Repo.all()
|> Enum.sort()
|> Enum.each(&dump(&1))
shell_error("\nTHIS CANNOT BE UNDONE!")
if shell_prompt("Are you sure you want to continue?", "n") in ~w(Yn Y y) do
truncatedb()
shell_info("The ConfigDB settings have been removed from the database.")
else
shell_error("No changes made.")
end
end)
end
def run(["delete", "--force", group, key]) do
start_pleroma() start_pleroma()
{opts, _} = group = maybe_atomize(group)
OptionParser.parse!(options, key = maybe_atomize(key)
strict: [env: :string, delete: :boolean],
aliases: [d: :delete]
)
migrate_from_db(opts) with true <- key_exists?(group, key) do
shell_info("The following settings will be removed from ConfigDB:\n")
group
|> ConfigDB.get_by_group_and_key(key)
|> dump()
delete_key(group, key)
else
_ ->
shell_error("No settings in ConfigDB for #{inspect(group)}, #{inspect(key)}. Aborting.")
end
end
def run(["delete", "--force", group]) do
start_pleroma()
group = maybe_atomize(group)
with true <- group_exists?(group) do
shell_info("The following settings will be removed from ConfigDB:\n")
dump_group(group)
delete_group(group)
else
_ -> shell_error("No settings in ConfigDB for #{inspect(group)}. Aborting.")
end
end
def run(["delete", group, key]) do
start_pleroma()
group = maybe_atomize(group)
key = maybe_atomize(key)
with true <- key_exists?(group, key) do
shell_info("The following settings will be removed from ConfigDB:\n")
group
|> ConfigDB.get_by_group_and_key(key)
|> dump()
if shell_prompt("Are you sure you want to continue?", "n") in ~w(Yn Y y) do
delete_key(group, key)
else
shell_error("No changes made.")
end
else
_ ->
shell_error("No settings in ConfigDB for #{inspect(group)}, #{inspect(key)}. Aborting.")
end
end
def run(["delete", group]) do
start_pleroma()
group = maybe_atomize(group)
with true <- group_exists?(group) do
shell_info("The following settings will be removed from ConfigDB:\n")
dump_group(group)
if shell_prompt("Are you sure you want to continue?", "n") in ~w(Yn Y y) do
delete_group(group)
else
shell_error("No changes made.")
end
else
_ -> shell_error("No settings in ConfigDB for #{inspect(group)}. Aborting.")
end
end end
@spec migrate_to_db(Path.t() | nil) :: any() @spec migrate_to_db(Path.t() | nil) :: any()
def migrate_to_db(file_path \\ nil) do def migrate_to_db(file_path \\ nil) do
with true <- Pleroma.Config.get([:configurable_from_database]), with :ok <- Pleroma.Config.DeprecationWarnings.warn() do
:ok <- Pleroma.Config.DeprecationWarnings.warn() do
config_file = config_file =
if file_path do if file_path do
file_path file_path
@ -47,16 +221,15 @@ def migrate_to_db(file_path \\ nil) do
do_migrate_to_db(config_file) do_migrate_to_db(config_file)
else else
:error -> deprecation_error() _ ->
_ -> migration_error() shell_error("Migration is not allowed until all deprecation warnings have been resolved.")
end end
end end
defp do_migrate_to_db(config_file) do defp do_migrate_to_db(config_file) do
if File.exists?(config_file) do if File.exists?(config_file) do
shell_info("Migrating settings from file: #{Path.expand(config_file)}") shell_info("Migrating settings from file: #{Path.expand(config_file)}")
Ecto.Adapters.SQL.query!(Repo, "TRUNCATE config;") truncatedb()
Ecto.Adapters.SQL.query!(Repo, "ALTER SEQUENCE config_id_seq RESTART;")
custom_config = custom_config =
config_file config_file
@ -80,52 +253,38 @@ defp create(group, settings) do
shell_info("Settings for key #{key} migrated.") shell_info("Settings for key #{key} migrated.")
end) end)
shell_info("Settings for group :#{group} migrated.") shell_info("Settings for group #{inspect(group)} migrated.")
end end
defp migrate_from_db(opts) do defp migrate_from_db(opts) do
if Pleroma.Config.get([:configurable_from_database]) do env = opts[:env] || Pleroma.Config.get(:env)
env = opts[:env] || Pleroma.Config.get(:env)
config_path = config_path =
if Pleroma.Config.get(:release) do if Pleroma.Config.get(:release) do
:config_path :config_path
|> Pleroma.Config.get() |> Pleroma.Config.get()
|> Path.dirname() |> Path.dirname()
else else
"config" "config"
end end
|> Path.join("#{env}.exported_from_db.secret.exs") |> Path.join("#{env}.exported_from_db.secret.exs")
file = File.open!(config_path, [:write, :utf8]) file = File.open!(config_path, [:write, :utf8])
IO.write(file, config_header()) IO.write(file, config_header())
ConfigDB ConfigDB
|> Repo.all() |> Repo.all()
|> Enum.each(&write_and_delete(&1, file, opts[:delete])) |> Enum.each(&write_and_delete(&1, file, opts[:delete]))
:ok = File.close(file) :ok = File.close(file)
System.cmd("mix", ["format", config_path]) System.cmd("mix", ["format", config_path])
shell_info( shell_info(
"Database configuration settings have been exported to config/#{env}.exported_from_db.secret.exs" "Database configuration settings have been exported to config/#{env}.exported_from_db.secret.exs"
)
else
migration_error()
end
end
defp migration_error do
shell_error(
"Migration is not allowed in config. You can change this behavior by setting `config :pleroma, configurable_from_database: true`"
) )
end end
defp deprecation_error do
shell_error("Migration is not allowed until all deprecation warnings have been resolved.")
end
if Code.ensure_loaded?(Config.Reader) do if Code.ensure_loaded?(Config.Reader) do
defp config_header, do: "import Config\r\n\r\n" defp config_header, do: "import Config\r\n\r\n"
defp read_file(config_file), do: Config.Reader.read_imports!(config_file) defp read_file(config_file), do: Config.Reader.read_imports!(config_file)
@ -150,8 +309,80 @@ defp write(config, file) do
defp delete(config, true) do defp delete(config, true) do
{:ok, _} = Repo.delete(config) {:ok, _} = Repo.delete(config)
shell_info("#{config.key} deleted from DB.")
shell_info(
"config #{inspect(config.group)}, #{inspect(config.key)} was deleted from the ConfigDB."
)
end end
defp delete(_config, _), do: :ok defp delete(_config, _), do: :ok
defp dump(%ConfigDB{} = config) do
value = inspect(config.value, limit: :infinity)
shell_info("config #{inspect(config.group)}, #{inspect(config.key)}, #{value}\r\n\r\n")
end
defp dump(_), do: :noop
defp dump_group(group) when is_atom(group) do
group
|> ConfigDB.get_all_by_group()
|> Enum.each(&dump/1)
end
defp group_exists?(group) do
group
|> ConfigDB.get_all_by_group()
|> Enum.any?()
end
defp key_exists?(group, key) do
group
|> ConfigDB.get_by_group_and_key(key)
|> is_nil
|> Kernel.!()
end
defp maybe_atomize(arg) when is_atom(arg), do: arg
defp maybe_atomize(":" <> arg), do: maybe_atomize(arg)
defp maybe_atomize(arg) when is_binary(arg) do
if ConfigDB.module_name?(arg) do
String.to_existing_atom("Elixir." <> arg)
else
String.to_atom(arg)
end
end
defp check_configdb(callback) do
with true <- Pleroma.Config.get([:configurable_from_database]) do
callback.()
else
_ ->
shell_error(
"ConfigDB not enabled. Please check the value of :configurable_from_database in your configuration."
)
end
end
defp delete_key(group, key) do
check_configdb(fn ->
ConfigDB.delete(%{group: group, key: key})
end)
end
defp delete_group(group) do
check_configdb(fn ->
group
|> ConfigDB.get_all_by_group()
|> Enum.each(&ConfigDB.delete/1)
end)
end
defp truncatedb do
Ecto.Adapters.SQL.query!(Repo, "TRUNCATE config;")
Ecto.Adapters.SQL.query!(Repo, "ALTER SEQUENCE config_id_seq RESTART;")
end
end end

View File

@ -48,9 +48,15 @@ def run(["bump_all_conversations"]) do
def run(["update_users_following_followers_counts"]) do def run(["update_users_following_followers_counts"]) do
start_pleroma() start_pleroma()
User Repo.transaction(
|> Repo.all() fn ->
|> Enum.each(&User.update_follower_count/1) from(u in User, select: u)
|> Repo.stream()
|> Stream.each(&User.update_follower_count/1)
|> Stream.run()
end,
timeout: :infinity
)
end end
def run(["prune_objects" | args]) do def run(["prune_objects" | args]) do

View File

@ -161,12 +161,21 @@ def run(["gen" | rest]) do
) )
|> Path.expand() |> Path.expand()
{strip_uploads_message, strip_uploads_default} =
if Pleroma.Utils.command_available?("exiftool") do
{"Do you want to strip location (GPS) data from uploaded images? This requires exiftool, it was detected as installed. (y/n)",
"y"}
else
{"Do you want to strip location (GPS) data from uploaded images? This requires exiftool, it was detected as not installed, please install it if you answer yes. (y/n)",
"n"}
end
strip_uploads = strip_uploads =
get_option( get_option(
options, options,
:strip_uploads, :strip_uploads,
"Do you want to strip location (GPS) data from uploaded images? (y/n)", strip_uploads_message,
"y" strip_uploads_default
) === "y" ) === "y"
anonymize_uploads = anonymize_uploads =
@ -253,7 +262,7 @@ def run(["gen" | rest]) do
else else
shell_error( shell_error(
"The task would have overwritten the following files:\n" <> "The task would have overwritten the following files:\n" <>
(Enum.map(paths, &"- #{&1}\n") |> Enum.join("")) <> (Enum.map(will_overwrite, &"- #{&1}\n") |> Enum.join("")) <>
"Rerun with `--force` to overwrite them." "Rerun with `--force` to overwrite them."
) )
end end

View File

@ -60,7 +60,7 @@ def run(["new", nickname, email | rest]) do
- admin: #{if(admin?, do: "true", else: "false")} - admin: #{if(admin?, do: "true", else: "false")}
""") """)
proceed? = assume_yes? or shell_yes?("Continue?") proceed? = assume_yes? or shell_prompt("Continue?", "n") in ~w(Yn Y y)
if proceed? do if proceed? do
start_pleroma() start_pleroma()

View File

@ -194,6 +194,19 @@ def get_by_id(id) do
end end
end end
def get_by_id_with_user_actor(id) do
case FlakeId.flake_id?(id) do
true ->
Activity
|> where([a], a.id == ^id)
|> with_preloaded_user_actor()
|> Repo.one()
_ ->
nil
end
end
def get_by_id_with_object(id) do def get_by_id_with_object(id) do
Activity Activity
|> where(id: ^id) |> where(id: ^id)

View File

@ -19,11 +19,18 @@ def search(user, search_query, options \\ []) do
offset = Keyword.get(options, :offset, 0) offset = Keyword.get(options, :offset, 0)
author = Keyword.get(options, :author) author = Keyword.get(options, :author)
search_function =
if :persistent_term.get({Pleroma.Repo, :postgres_version}) >= 11 do
:websearch
else
:plain
end
Activity Activity
|> Activity.with_preloaded_object() |> Activity.with_preloaded_object()
|> Activity.restrict_deactivated_users() |> Activity.restrict_deactivated_users()
|> restrict_public() |> restrict_public()
|> query_with(index_type, search_query) |> query_with(index_type, search_query, search_function)
|> maybe_restrict_local(user) |> maybe_restrict_local(user)
|> maybe_restrict_author(author) |> maybe_restrict_author(author)
|> maybe_restrict_blocked(user) |> maybe_restrict_blocked(user)
@ -53,7 +60,7 @@ defp restrict_public(q) do
) )
end end
defp query_with(q, :gin, search_query) do defp query_with(q, :gin, search_query, :plain) do
from([a, o] in q, from([a, o] in q,
where: where:
fragment( fragment(
@ -64,7 +71,18 @@ defp query_with(q, :gin, search_query) do
) )
end end
defp query_with(q, :rum, search_query) do defp query_with(q, :gin, search_query, :websearch) do
from([a, o] in q,
where:
fragment(
"to_tsvector('english', ?->>'content') @@ websearch_to_tsquery('english', ?)",
o.data,
^search_query
)
)
end
defp query_with(q, :rum, search_query, :plain) do
from([a, o] in q, from([a, o] in q,
where: where:
fragment( fragment(
@ -76,6 +94,18 @@ defp query_with(q, :rum, search_query) do
) )
end end
defp query_with(q, :rum, search_query, :websearch) do
from([a, o] in q,
where:
fragment(
"? @@ websearch_to_tsquery('english', ?)",
o.fts_content,
^search_query
),
order_by: [fragment("? <=> now()::date", o.inserted_at)]
)
end
defp maybe_restrict_local(q, user) do defp maybe_restrict_local(q, user) do
limit = Pleroma.Config.get([:instance, :limit_to_local_content], :unauthenticated) limit = Pleroma.Config.get([:instance, :limit_to_local_content], :unauthenticated)

View File

@ -110,7 +110,28 @@ def start(_type, _args) do
# See http://elixir-lang.org/docs/stable/elixir/Supervisor.html # See http://elixir-lang.org/docs/stable/elixir/Supervisor.html
# for other strategies and supported options # for other strategies and supported options
opts = [strategy: :one_for_one, name: Pleroma.Supervisor] opts = [strategy: :one_for_one, name: Pleroma.Supervisor]
Supervisor.start_link(children, opts) result = Supervisor.start_link(children, opts)
set_postgres_server_version()
result
end
defp set_postgres_server_version do
version =
with %{rows: [[version]]} <- Ecto.Adapters.SQL.query!(Pleroma.Repo, "show server_version"),
{num, _} <- Float.parse(version) do
num
else
e ->
Logger.warn(
"Could not get the postgres version: #{inspect(e)}.\nSetting the default value of 9.6"
)
9.6
end
:persistent_term.put({Pleroma.Repo, :postgres_version}, version)
end end
def load_custom_modules do def load_custom_modules do

View File

@ -24,6 +24,7 @@ def verify! do
|> check_migrations_applied!() |> check_migrations_applied!()
|> check_welcome_message_config!() |> check_welcome_message_config!()
|> check_rum!() |> check_rum!()
|> check_repo_pool_size!()
|> handle_result() |> handle_result()
end end
@ -188,6 +189,30 @@ defp check_system_commands!(:ok) do
defp check_system_commands!(result), do: result defp check_system_commands!(result), do: result
defp check_repo_pool_size!(:ok) do
if Pleroma.Config.get([Pleroma.Repo, :pool_size], 10) != 10 and
not Pleroma.Config.get([:dangerzone, :override_repo_pool_size], false) do
Logger.error("""
!!!CONFIG WARNING!!!
The database pool size has been altered from the recommended value of 10.
Please revert or ensure your database is tuned appropriately and then set
`config :pleroma, :dangerzone, override_repo_pool_size: true`.
If you are experiencing database timeouts, please check the "Optimizing
your PostgreSQL performance" section in the documentation. If you still
encounter issues after that, please open an issue on the tracker.
""")
{:error, "Repo.pool_size different than recommended value."}
else
:ok
end
end
defp check_repo_pool_size!(result), do: result
defp check_filter(filter, command_required) do defp check_filter(filter, command_required) do
filters = Config.get([Pleroma.Upload, :filters]) filters = Config.get([Pleroma.Upload, :filters])

View File

@ -9,12 +9,7 @@ defmodule Pleroma.Config.Holder do
def save_default do def save_default do
default_config = default_config =
if System.get_env("RELEASE_NAME") do if System.get_env("RELEASE_NAME") do
release_config = Pleroma.Config.Loader.merge(@config, release_defaults())
[:code.root_dir(), "releases", System.get_env("RELEASE_VSN"), "releases.exs"]
|> Path.join()
|> Pleroma.Config.Loader.read()
Pleroma.Config.Loader.merge(@config, release_config)
else else
@config @config
end end
@ -32,4 +27,16 @@ def default_config(group), do: Keyword.get(get_default(), group)
def default_config(group, key), do: get_in(get_default(), [group, key]) def default_config(group, key), do: get_in(get_default(), [group, key])
defp get_default, do: Pleroma.Config.get(:default_config) defp get_default, do: Pleroma.Config.get(:default_config)
@spec release_defaults() :: keyword()
def release_defaults do
[
pleroma: [
{:instance, [static_dir: "/var/lib/pleroma/static"]},
{Pleroma.Uploaders.Local, [uploads: "/var/lib/pleroma/uploads"]},
{:modules, [runtime_dir: "/var/lib/pleroma/modules"]},
{:release, true}
]
]
end
end end

View File

@ -0,0 +1,50 @@
defmodule Pleroma.Config.ReleaseRuntimeProvider do
@moduledoc """
Imports `runtime.exs` and `{env}.exported_from_db.secret.exs` for elixir releases.
"""
@behaviour Config.Provider
@impl true
def init(opts), do: opts
@impl true
def load(config, _opts) do
with_defaults = Config.Reader.merge(config, Pleroma.Config.Holder.release_defaults())
config_path = System.get_env("PLEROMA_CONFIG_PATH") || "/etc/pleroma/config.exs"
with_runtime_config =
if File.exists?(config_path) do
runtime_config = Config.Reader.read!(config_path)
with_defaults
|> Config.Reader.merge(pleroma: [config_path: config_path])
|> Config.Reader.merge(runtime_config)
else
warning = [
IO.ANSI.red(),
IO.ANSI.bright(),
"!!! #{config_path} not found! Please ensure it exists and that PLEROMA_CONFIG_PATH is unset or points to an existing file",
IO.ANSI.reset()
]
IO.puts(warning)
with_defaults
end
exported_config_path =
config_path
|> Path.dirname()
|> Path.join("prod.exported_from_db.secret.exs")
with_exported =
if File.exists?(exported_config_path) do
exported_config = Config.Reader.read!(with_runtime_config)
Config.Reader.merge(with_runtime_config, exported_config)
else
with_runtime_config
end
with_exported
end
end

View File

@ -6,7 +6,7 @@ defmodule Pleroma.ConfigDB do
use Ecto.Schema use Ecto.Schema
import Ecto.Changeset import Ecto.Changeset
import Ecto.Query, only: [select: 3] import Ecto.Query, only: [select: 3, from: 2]
import Pleroma.Web.Gettext import Pleroma.Web.Gettext
alias __MODULE__ alias __MODULE__
@ -41,8 +41,18 @@ def get_all_as_keyword do
end) end)
end end
@spec get_all_by_group(atom() | String.t()) :: [t()]
def get_all_by_group(group) do
from(c in ConfigDB, where: c.group == ^group) |> Repo.all()
end
@spec get_by_group_and_key(atom() | String.t(), atom() | String.t()) :: t() | nil
def get_by_group_and_key(group, key) do
get_by_params(%{group: group, key: key})
end
@spec get_by_params(map()) :: ConfigDB.t() | nil @spec get_by_params(map()) :: ConfigDB.t() | nil
def get_by_params(params), do: Repo.get_by(ConfigDB, params) def get_by_params(%{group: _, key: _} = params), do: Repo.get_by(ConfigDB, params)
@spec changeset(ConfigDB.t(), map()) :: Changeset.t() @spec changeset(ConfigDB.t(), map()) :: Changeset.t()
def changeset(config, params \\ %{}) do def changeset(config, params \\ %{}) do

View File

@ -164,7 +164,7 @@ def digest_email(user) do
logo_path = logo_path =
if is_nil(logo) do if is_nil(logo) do
Path.join(:code.priv_dir(:pleroma), "static/static/logo.png") Path.join(:code.priv_dir(:pleroma), "static/static/logo.svg")
else else
Path.join(Config.get([:instance, :static_dir]), logo) Path.join(Config.get([:instance, :static_dir]), logo)
end end
@ -175,7 +175,7 @@ def digest_email(user) do
|> subject("Your digest from #{instance_name()}") |> subject("Your digest from #{instance_name()}")
|> put_layout(false) |> put_layout(false)
|> render_body("digest.html", html_data) |> render_body("digest.html", html_data)
|> attachment(Swoosh.Attachment.new(logo_path, filename: "logo.png", type: :inline)) |> attachment(Swoosh.Attachment.new(logo_path, filename: "logo.svg", type: :inline))
end end
end end

View File

@ -1,769 +0,0 @@
# emoji-data.txt
# Date: 2019-01-15, 12:10:05 GMT
# © 2019 Unicode®, Inc.
# Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries.
# For terms of use, see http://www.unicode.org/terms_of_use.html
#
# Emoji Data for UTS #51
# Version: 12.0
#
# For documentation and usage, see http://www.unicode.org/reports/tr51
#
# Format:
# <codepoint(s)> ; <property> # <comments>
# Note: there is no guarantee as to the structure of whitespace or comments
#
# Characters and sequences are listed in code point order. Users should be shown a more natural order.
# See the CLDR collation order for Emoji.
# ================================================
# All omitted code points have Emoji=No
# @missing: 0000..10FFFF ; Emoji ; No
0023 ; Emoji # 1.1 [1] (#) number sign
002A ; Emoji # 1.1 [1] (*) asterisk
0030..0039 ; Emoji # 1.1 [10] (0..9) digit zero..digit nine
00A9 ; Emoji # 1.1 [1] (©️) copyright
00AE ; Emoji # 1.1 [1] (®️) registered
203C ; Emoji # 1.1 [1] (‼️) double exclamation mark
2049 ; Emoji # 3.0 [1] (⁉️) exclamation question mark
2122 ; Emoji # 1.1 [1] (™️) trade mark
2139 ; Emoji # 3.0 [1] () information
2194..2199 ; Emoji # 1.1 [6] (↔️..↙️) left-right arrow..down-left arrow
21A9..21AA ; Emoji # 1.1 [2] (↩️..↪️) right arrow curving left..left arrow curving right
231A..231B ; Emoji # 1.1 [2] (⌚..⌛) watch..hourglass done
2328 ; Emoji # 1.1 [1] (⌨️) keyboard
23CF ; Emoji # 4.0 [1] (⏏️) eject button
23E9..23F3 ; Emoji # 6.0 [11] (⏩..⏳) fast-forward button..hourglass not done
23F8..23FA ; Emoji # 7.0 [3] (⏸️..⏺️) pause button..record button
24C2 ; Emoji # 1.1 [1] (Ⓜ️) circled M
25AA..25AB ; Emoji # 1.1 [2] (▪️..▫️) black small square..white small square
25B6 ; Emoji # 1.1 [1] (▶️) play button
25C0 ; Emoji # 1.1 [1] (◀️) reverse button
25FB..25FE ; Emoji # 3.2 [4] (◻️..◾) white medium square..black medium-small square
2600..2604 ; Emoji # 1.1 [5] (☀️..☄️) sun..comet
260E ; Emoji # 1.1 [1] (☎️) telephone
2611 ; Emoji # 1.1 [1] (☑️) check box with check
2614..2615 ; Emoji # 4.0 [2] (☔..☕) umbrella with rain drops..hot beverage
2618 ; Emoji # 4.1 [1] (☘️) shamrock
261D ; Emoji # 1.1 [1] (☝️) index pointing up
2620 ; Emoji # 1.1 [1] (☠️) skull and crossbones
2622..2623 ; Emoji # 1.1 [2] (☢️..☣️) radioactive..biohazard
2626 ; Emoji # 1.1 [1] (☦️) orthodox cross
262A ; Emoji # 1.1 [1] (☪️) star and crescent
262E..262F ; Emoji # 1.1 [2] (☮️..☯️) peace symbol..yin yang
2638..263A ; Emoji # 1.1 [3] (☸️..☺️) wheel of dharma..smiling face
2640 ; Emoji # 1.1 [1] (♀️) female sign
2642 ; Emoji # 1.1 [1] (♂️) male sign
2648..2653 ; Emoji # 1.1 [12] (♈..♓) Aries..Pisces
265F..2660 ; Emoji # 1.1 [2] (♟️..♠️) chess pawn..spade suit
2663 ; Emoji # 1.1 [1] (♣️) club suit
2665..2666 ; Emoji # 1.1 [2] (♥️..♦️) heart suit..diamond suit
2668 ; Emoji # 1.1 [1] (♨️) hot springs
267B ; Emoji # 3.2 [1] (♻️) recycling symbol
267E..267F ; Emoji # 4.1 [2] (♾️..♿) infinity..wheelchair symbol
2692..2697 ; Emoji # 4.1 [6] (⚒️..⚗️) hammer and pick..alembic
2699 ; Emoji # 4.1 [1] (⚙️) gear
269B..269C ; Emoji # 4.1 [2] (⚛️..⚜️) atom symbol..fleur-de-lis
26A0..26A1 ; Emoji # 4.0 [2] (⚠️..⚡) warning..high voltage
26AA..26AB ; Emoji # 4.1 [2] (⚪..⚫) white circle..black circle
26B0..26B1 ; Emoji # 4.1 [2] (⚰️..⚱️) coffin..funeral urn
26BD..26BE ; Emoji # 5.2 [2] (⚽..⚾) soccer ball..baseball
26C4..26C5 ; Emoji # 5.2 [2] (⛄..⛅) snowman without snow..sun behind cloud
26C8 ; Emoji # 5.2 [1] (⛈️) cloud with lightning and rain
26CE ; Emoji # 6.0 [1] (⛎) Ophiuchus
26CF ; Emoji # 5.2 [1] (⛏️) pick
26D1 ; Emoji # 5.2 [1] (⛑️) rescue workers helmet
26D3..26D4 ; Emoji # 5.2 [2] (⛓️..⛔) chains..no entry
26E9..26EA ; Emoji # 5.2 [2] (⛩️..⛪) shinto shrine..church
26F0..26F5 ; Emoji # 5.2 [6] (⛰️..⛵) mountain..sailboat
26F7..26FA ; Emoji # 5.2 [4] (⛷️..⛺) skier..tent
26FD ; Emoji # 5.2 [1] (⛽) fuel pump
2702 ; Emoji # 1.1 [1] (✂️) scissors
2705 ; Emoji # 6.0 [1] (✅) check mark button
2708..2709 ; Emoji # 1.1 [2] (✈️..✉️) airplane..envelope
270A..270B ; Emoji # 6.0 [2] (✊..✋) raised fist..raised hand
270C..270D ; Emoji # 1.1 [2] (✌️..✍️) victory hand..writing hand
270F ; Emoji # 1.1 [1] (✏️) pencil
2712 ; Emoji # 1.1 [1] (✒️) black nib
2714 ; Emoji # 1.1 [1] (✔️) check mark
2716 ; Emoji # 1.1 [1] (✖️) multiplication sign
271D ; Emoji # 1.1 [1] (✝️) latin cross
2721 ; Emoji # 1.1 [1] (✡️) star of David
2728 ; Emoji # 6.0 [1] (✨) sparkles
2733..2734 ; Emoji # 1.1 [2] (✳️..✴️) eight-spoked asterisk..eight-pointed star
2744 ; Emoji # 1.1 [1] (❄️) snowflake
2747 ; Emoji # 1.1 [1] (❇️) sparkle
274C ; Emoji # 6.0 [1] (❌) cross mark
274E ; Emoji # 6.0 [1] (❎) cross mark button
2753..2755 ; Emoji # 6.0 [3] (❓..❕) question mark..white exclamation mark
2757 ; Emoji # 5.2 [1] (❗) exclamation mark
2763..2764 ; Emoji # 1.1 [2] (❣️..❤️) heart exclamation..red heart
2795..2797 ; Emoji # 6.0 [3] (..➗) plus sign..division sign
27A1 ; Emoji # 1.1 [1] (➡️) right arrow
27B0 ; Emoji # 6.0 [1] (➰) curly loop
27BF ; Emoji # 6.0 [1] (➿) double curly loop
2934..2935 ; Emoji # 3.2 [2] (⤴️..⤵️) right arrow curving up..right arrow curving down
2B05..2B07 ; Emoji # 4.0 [3] (⬅️..⬇️) left arrow..down arrow
2B1B..2B1C ; Emoji # 5.1 [2] (⬛..⬜) black large square..white large square
2B50 ; Emoji # 5.1 [1] (⭐) star
2B55 ; Emoji # 5.2 [1] (⭕) hollow red circle
3030 ; Emoji # 1.1 [1] (〰️) wavy dash
303D ; Emoji # 3.2 [1] (〽️) part alternation mark
3297 ; Emoji # 1.1 [1] (㊗️) Japanese “congratulations” button
3299 ; Emoji # 1.1 [1] (㊙️) Japanese “secret” button
1F004 ; Emoji # 5.1 [1] (🀄) mahjong red dragon
1F0CF ; Emoji # 6.0 [1] (🃏) joker
1F170..1F171 ; Emoji # 6.0 [2] (🅰️..🅱️) A button (blood type)..B button (blood type)
1F17E ; Emoji # 6.0 [1] (🅾️) O button (blood type)
1F17F ; Emoji # 5.2 [1] (🅿️) P button
1F18E ; Emoji # 6.0 [1] (🆎) AB button (blood type)
1F191..1F19A ; Emoji # 6.0 [10] (🆑..🆚) CL button..VS button
1F1E6..1F1FF ; Emoji # 6.0 [26] (🇦..🇿) regional indicator symbol letter a..regional indicator symbol letter z
1F201..1F202 ; Emoji # 6.0 [2] (🈁..🈂️) Japanese “here” button..Japanese “service charge” button
1F21A ; Emoji # 5.2 [1] (🈚) Japanese “free of charge” button
1F22F ; Emoji # 5.2 [1] (🈯) Japanese “reserved” button
1F232..1F23A ; Emoji # 6.0 [9] (🈲..🈺) Japanese “prohibited” button..Japanese “open for business” button
1F250..1F251 ; Emoji # 6.0 [2] (🉐..🉑) Japanese “bargain” button..Japanese “acceptable” button
1F300..1F320 ; Emoji # 6.0 [33] (🌀..🌠) cyclone..shooting star
1F321 ; Emoji # 7.0 [1] (🌡️) thermometer
1F324..1F32C ; Emoji # 7.0 [9] (🌤️..🌬️) sun behind small cloud..wind face
1F32D..1F32F ; Emoji # 8.0 [3] (🌭..🌯) hot dog..burrito
1F330..1F335 ; Emoji # 6.0 [6] (🌰..🌵) chestnut..cactus
1F336 ; Emoji # 7.0 [1] (🌶️) hot pepper
1F337..1F37C ; Emoji # 6.0 [70] (🌷..🍼) tulip..baby bottle
1F37D ; Emoji # 7.0 [1] (🍽️) fork and knife with plate
1F37E..1F37F ; Emoji # 8.0 [2] (🍾..🍿) bottle with popping cork..popcorn
1F380..1F393 ; Emoji # 6.0 [20] (🎀..🎓) ribbon..graduation cap
1F396..1F397 ; Emoji # 7.0 [2] (🎖️..🎗️) military medal..reminder ribbon
1F399..1F39B ; Emoji # 7.0 [3] (🎙️..🎛️) studio microphone..control knobs
1F39E..1F39F ; Emoji # 7.0 [2] (🎞️..🎟️) film frames..admission tickets
1F3A0..1F3C4 ; Emoji # 6.0 [37] (🎠..🏄) carousel horse..person surfing
1F3C5 ; Emoji # 7.0 [1] (🏅) sports medal
1F3C6..1F3CA ; Emoji # 6.0 [5] (🏆..🏊) trophy..person swimming
1F3CB..1F3CE ; Emoji # 7.0 [4] (🏋️..🏎️) person lifting weights..racing car
1F3CF..1F3D3 ; Emoji # 8.0 [5] (🏏..🏓) cricket game..ping pong
1F3D4..1F3DF ; Emoji # 7.0 [12] (🏔️..🏟️) snow-capped mountain..stadium
1F3E0..1F3F0 ; Emoji # 6.0 [17] (🏠..🏰) house..castle
1F3F3..1F3F5 ; Emoji # 7.0 [3] (🏳️..🏵️) white flag..rosette
1F3F7 ; Emoji # 7.0 [1] (🏷️) label
1F3F8..1F3FF ; Emoji # 8.0 [8] (🏸..🏿) badminton..dark skin tone
1F400..1F43E ; Emoji # 6.0 [63] (🐀..🐾) rat..paw prints
1F43F ; Emoji # 7.0 [1] (🐿️) chipmunk
1F440 ; Emoji # 6.0 [1] (👀) eyes
1F441 ; Emoji # 7.0 [1] (👁️) eye
1F442..1F4F7 ; Emoji # 6.0[182] (👂..📷) ear..camera
1F4F8 ; Emoji # 7.0 [1] (📸) camera with flash
1F4F9..1F4FC ; Emoji # 6.0 [4] (📹..📼) video camera..videocassette
1F4FD ; Emoji # 7.0 [1] (📽️) film projector
1F4FF ; Emoji # 8.0 [1] (📿) prayer beads
1F500..1F53D ; Emoji # 6.0 [62] (🔀..🔽) shuffle tracks button..downwards button
1F549..1F54A ; Emoji # 7.0 [2] (🕉️..🕊️) om..dove
1F54B..1F54E ; Emoji # 8.0 [4] (🕋..🕎) kaaba..menorah
1F550..1F567 ; Emoji # 6.0 [24] (🕐..🕧) one oclock..twelve-thirty
1F56F..1F570 ; Emoji # 7.0 [2] (🕯️..🕰️) candle..mantelpiece clock
1F573..1F579 ; Emoji # 7.0 [7] (🕳️..🕹️) hole..joystick
1F57A ; Emoji # 9.0 [1] (🕺) man dancing
1F587 ; Emoji # 7.0 [1] (🖇️) linked paperclips
1F58A..1F58D ; Emoji # 7.0 [4] (🖊️..🖍️) pen..crayon
1F590 ; Emoji # 7.0 [1] (🖐️) hand with fingers splayed
1F595..1F596 ; Emoji # 7.0 [2] (🖕..🖖) middle finger..vulcan salute
1F5A4 ; Emoji # 9.0 [1] (🖤) black heart
1F5A5 ; Emoji # 7.0 [1] (🖥️) desktop computer
1F5A8 ; Emoji # 7.0 [1] (🖨️) printer
1F5B1..1F5B2 ; Emoji # 7.0 [2] (🖱️..🖲️) computer mouse..trackball
1F5BC ; Emoji # 7.0 [1] (🖼️) framed picture
1F5C2..1F5C4 ; Emoji # 7.0 [3] (🗂️..🗄️) card index dividers..file cabinet
1F5D1..1F5D3 ; Emoji # 7.0 [3] (🗑️..🗓️) wastebasket..spiral calendar
1F5DC..1F5DE ; Emoji # 7.0 [3] (🗜️..🗞️) clamp..rolled-up newspaper
1F5E1 ; Emoji # 7.0 [1] (🗡️) dagger
1F5E3 ; Emoji # 7.0 [1] (🗣️) speaking head
1F5E8 ; Emoji # 7.0 [1] (🗨️) left speech bubble
1F5EF ; Emoji # 7.0 [1] (🗯️) right anger bubble
1F5F3 ; Emoji # 7.0 [1] (🗳️) ballot box with ballot
1F5FA ; Emoji # 7.0 [1] (🗺️) world map
1F5FB..1F5FF ; Emoji # 6.0 [5] (🗻..🗿) mount fuji..moai
1F600 ; Emoji # 6.1 [1] (😀) grinning face
1F601..1F610 ; Emoji # 6.0 [16] (😁..😐) beaming face with smiling eyes..neutral face
1F611 ; Emoji # 6.1 [1] (😑) expressionless face
1F612..1F614 ; Emoji # 6.0 [3] (😒..😔) unamused face..pensive face
1F615 ; Emoji # 6.1 [1] (😕) confused face
1F616 ; Emoji # 6.0 [1] (😖) confounded face
1F617 ; Emoji # 6.1 [1] (😗) kissing face
1F618 ; Emoji # 6.0 [1] (😘) face blowing a kiss
1F619 ; Emoji # 6.1 [1] (😙) kissing face with smiling eyes
1F61A ; Emoji # 6.0 [1] (😚) kissing face with closed eyes
1F61B ; Emoji # 6.1 [1] (😛) face with tongue
1F61C..1F61E ; Emoji # 6.0 [3] (😜..😞) winking face with tongue..disappointed face
1F61F ; Emoji # 6.1 [1] (😟) worried face
1F620..1F625 ; Emoji # 6.0 [6] (😠..😥) angry face..sad but relieved face
1F626..1F627 ; Emoji # 6.1 [2] (😦..😧) frowning face with open mouth..anguished face
1F628..1F62B ; Emoji # 6.0 [4] (😨..😫) fearful face..tired face
1F62C ; Emoji # 6.1 [1] (😬) grimacing face
1F62D ; Emoji # 6.0 [1] (😭) loudly crying face
1F62E..1F62F ; Emoji # 6.1 [2] (😮..😯) face with open mouth..hushed face
1F630..1F633 ; Emoji # 6.0 [4] (😰..😳) anxious face with sweat..flushed face
1F634 ; Emoji # 6.1 [1] (😴) sleeping face
1F635..1F640 ; Emoji # 6.0 [12] (😵..🙀) dizzy face..weary cat
1F641..1F642 ; Emoji # 7.0 [2] (🙁..🙂) slightly frowning face..slightly smiling face
1F643..1F644 ; Emoji # 8.0 [2] (🙃..🙄) upside-down face..face with rolling eyes
1F645..1F64F ; Emoji # 6.0 [11] (🙅..🙏) person gesturing NO..folded hands
1F680..1F6C5 ; Emoji # 6.0 [70] (🚀..🛅) rocket..left luggage
1F6CB..1F6CF ; Emoji # 7.0 [5] (🛋️..🛏️) couch and lamp..bed
1F6D0 ; Emoji # 8.0 [1] (🛐) place of worship
1F6D1..1F6D2 ; Emoji # 9.0 [2] (🛑..🛒) stop sign..shopping cart
1F6D5 ; Emoji # 12.0 [1] (🛕) hindu temple
1F6E0..1F6E5 ; Emoji # 7.0 [6] (🛠️..🛥️) hammer and wrench..motor boat
1F6E9 ; Emoji # 7.0 [1] (🛩️) small airplane
1F6EB..1F6EC ; Emoji # 7.0 [2] (🛫..🛬) airplane departure..airplane arrival
1F6F0 ; Emoji # 7.0 [1] (🛰️) satellite
1F6F3 ; Emoji # 7.0 [1] (🛳️) passenger ship
1F6F4..1F6F6 ; Emoji # 9.0 [3] (🛴..🛶) kick scooter..canoe
1F6F7..1F6F8 ; Emoji # 10.0 [2] (🛷..🛸) sled..flying saucer
1F6F9 ; Emoji # 11.0 [1] (🛹) skateboard
1F6FA ; Emoji # 12.0 [1] (🛺) auto rickshaw
1F7E0..1F7EB ; Emoji # 12.0 [12] (🟠..🟫) orange circle..brown square
1F90D..1F90F ; Emoji # 12.0 [3] (🤍..🤏) white heart..pinching hand
1F910..1F918 ; Emoji # 8.0 [9] (🤐..🤘) zipper-mouth face..sign of the horns
1F919..1F91E ; Emoji # 9.0 [6] (🤙..🤞) call me hand..crossed fingers
1F91F ; Emoji # 10.0 [1] (🤟) love-you gesture
1F920..1F927 ; Emoji # 9.0 [8] (🤠..🤧) cowboy hat face..sneezing face
1F928..1F92F ; Emoji # 10.0 [8] (🤨..🤯) face with raised eyebrow..exploding head
1F930 ; Emoji # 9.0 [1] (🤰) pregnant woman
1F931..1F932 ; Emoji # 10.0 [2] (🤱..🤲) breast-feeding..palms up together
1F933..1F93A ; Emoji # 9.0 [8] (🤳..🤺) selfie..person fencing
1F93C..1F93E ; Emoji # 9.0 [3] (🤼..🤾) people wrestling..person playing handball
1F93F ; Emoji # 12.0 [1] (🤿) diving mask
1F940..1F945 ; Emoji # 9.0 [6] (🥀..🥅) wilted flower..goal net
1F947..1F94B ; Emoji # 9.0 [5] (🥇..🥋) 1st place medal..martial arts uniform
1F94C ; Emoji # 10.0 [1] (🥌) curling stone
1F94D..1F94F ; Emoji # 11.0 [3] (🥍..🥏) lacrosse..flying disc
1F950..1F95E ; Emoji # 9.0 [15] (🥐..🥞) croissant..pancakes
1F95F..1F96B ; Emoji # 10.0 [13] (🥟..🥫) dumpling..canned food
1F96C..1F970 ; Emoji # 11.0 [5] (🥬..🥰) leafy green..smiling face with hearts
1F971 ; Emoji # 12.0 [1] (🥱) yawning face
1F973..1F976 ; Emoji # 11.0 [4] (🥳..🥶) partying face..cold face
1F97A ; Emoji # 11.0 [1] (🥺) pleading face
1F97B ; Emoji # 12.0 [1] (🥻) sari
1F97C..1F97F ; Emoji # 11.0 [4] (🥼..🥿) lab coat..flat shoe
1F980..1F984 ; Emoji # 8.0 [5] (🦀..🦄) crab..unicorn
1F985..1F991 ; Emoji # 9.0 [13] (🦅..🦑) eagle..squid
1F992..1F997 ; Emoji # 10.0 [6] (🦒..🦗) giraffe..cricket
1F998..1F9A2 ; Emoji # 11.0 [11] (🦘..🦢) kangaroo..swan
1F9A5..1F9AA ; Emoji # 12.0 [6] (🦥..🦪) sloth..oyster
1F9AE..1F9AF ; Emoji # 12.0 [2] (🦮..🦯) guide dog..probing cane
1F9B0..1F9B9 ; Emoji # 11.0 [10] (🦰..🦹) red hair..supervillain
1F9BA..1F9BF ; Emoji # 12.0 [6] (🦺..🦿) safety vest..mechanical leg
1F9C0 ; Emoji # 8.0 [1] (🧀) cheese wedge
1F9C1..1F9C2 ; Emoji # 11.0 [2] (🧁..🧂) cupcake..salt
1F9C3..1F9CA ; Emoji # 12.0 [8] (🧃..🧊) beverage box..ice cube
1F9CD..1F9CF ; Emoji # 12.0 [3] (🧍..🧏) person standing..deaf person
1F9D0..1F9E6 ; Emoji # 10.0 [23] (🧐..🧦) face with monocle..socks
1F9E7..1F9FF ; Emoji # 11.0 [25] (🧧..🧿) red envelope..nazar amulet
1FA70..1FA73 ; Emoji # 12.0 [4] (🩰..🩳) ballet shoes..shorts
1FA78..1FA7A ; Emoji # 12.0 [3] (🩸..🩺) drop of blood..stethoscope
1FA80..1FA82 ; Emoji # 12.0 [3] (🪀..🪂) yo-yo..parachute
1FA90..1FA95 ; Emoji # 12.0 [6] (🪐..🪕) ringed planet..banjo
# Total elements: 1311
# ================================================
# All omitted code points have Emoji_Presentation=No
# @missing: 0000..10FFFF ; Emoji_Presentation ; No
231A..231B ; Emoji_Presentation # 1.1 [2] (⌚..⌛) watch..hourglass done
23E9..23EC ; Emoji_Presentation # 6.0 [4] (⏩..⏬) fast-forward button..fast down button
23F0 ; Emoji_Presentation # 6.0 [1] (⏰) alarm clock
23F3 ; Emoji_Presentation # 6.0 [1] (⏳) hourglass not done
25FD..25FE ; Emoji_Presentation # 3.2 [2] (◽..◾) white medium-small square..black medium-small square
2614..2615 ; Emoji_Presentation # 4.0 [2] (☔..☕) umbrella with rain drops..hot beverage
2648..2653 ; Emoji_Presentation # 1.1 [12] (♈..♓) Aries..Pisces
267F ; Emoji_Presentation # 4.1 [1] (♿) wheelchair symbol
2693 ; Emoji_Presentation # 4.1 [1] (⚓) anchor
26A1 ; Emoji_Presentation # 4.0 [1] (⚡) high voltage
26AA..26AB ; Emoji_Presentation # 4.1 [2] (⚪..⚫) white circle..black circle
26BD..26BE ; Emoji_Presentation # 5.2 [2] (⚽..⚾) soccer ball..baseball
26C4..26C5 ; Emoji_Presentation # 5.2 [2] (⛄..⛅) snowman without snow..sun behind cloud
26CE ; Emoji_Presentation # 6.0 [1] (⛎) Ophiuchus
26D4 ; Emoji_Presentation # 5.2 [1] (⛔) no entry
26EA ; Emoji_Presentation # 5.2 [1] (⛪) church
26F2..26F3 ; Emoji_Presentation # 5.2 [2] (⛲..⛳) fountain..flag in hole
26F5 ; Emoji_Presentation # 5.2 [1] (⛵) sailboat
26FA ; Emoji_Presentation # 5.2 [1] (⛺) tent
26FD ; Emoji_Presentation # 5.2 [1] (⛽) fuel pump
2705 ; Emoji_Presentation # 6.0 [1] (✅) check mark button
270A..270B ; Emoji_Presentation # 6.0 [2] (✊..✋) raised fist..raised hand
2728 ; Emoji_Presentation # 6.0 [1] (✨) sparkles
274C ; Emoji_Presentation # 6.0 [1] (❌) cross mark
274E ; Emoji_Presentation # 6.0 [1] (❎) cross mark button
2753..2755 ; Emoji_Presentation # 6.0 [3] (❓..❕) question mark..white exclamation mark
2757 ; Emoji_Presentation # 5.2 [1] (❗) exclamation mark
2795..2797 ; Emoji_Presentation # 6.0 [3] (..➗) plus sign..division sign
27B0 ; Emoji_Presentation # 6.0 [1] (➰) curly loop
27BF ; Emoji_Presentation # 6.0 [1] (➿) double curly loop
2B1B..2B1C ; Emoji_Presentation # 5.1 [2] (⬛..⬜) black large square..white large square
2B50 ; Emoji_Presentation # 5.1 [1] (⭐) star
2B55 ; Emoji_Presentation # 5.2 [1] (⭕) hollow red circle
1F004 ; Emoji_Presentation # 5.1 [1] (🀄) mahjong red dragon
1F0CF ; Emoji_Presentation # 6.0 [1] (🃏) joker
1F18E ; Emoji_Presentation # 6.0 [1] (🆎) AB button (blood type)
1F191..1F19A ; Emoji_Presentation # 6.0 [10] (🆑..🆚) CL button..VS button
1F1E6..1F1FF ; Emoji_Presentation # 6.0 [26] (🇦..🇿) regional indicator symbol letter a..regional indicator symbol letter z
1F201 ; Emoji_Presentation # 6.0 [1] (🈁) Japanese “here” button
1F21A ; Emoji_Presentation # 5.2 [1] (🈚) Japanese “free of charge” button
1F22F ; Emoji_Presentation # 5.2 [1] (🈯) Japanese “reserved” button
1F232..1F236 ; Emoji_Presentation # 6.0 [5] (🈲..🈶) Japanese “prohibited” button..Japanese “not free of charge” button
1F238..1F23A ; Emoji_Presentation # 6.0 [3] (🈸..🈺) Japanese “application” button..Japanese “open for business” button
1F250..1F251 ; Emoji_Presentation # 6.0 [2] (🉐..🉑) Japanese “bargain” button..Japanese “acceptable” button
1F300..1F320 ; Emoji_Presentation # 6.0 [33] (🌀..🌠) cyclone..shooting star
1F32D..1F32F ; Emoji_Presentation # 8.0 [3] (🌭..🌯) hot dog..burrito
1F330..1F335 ; Emoji_Presentation # 6.0 [6] (🌰..🌵) chestnut..cactus
1F337..1F37C ; Emoji_Presentation # 6.0 [70] (🌷..🍼) tulip..baby bottle
1F37E..1F37F ; Emoji_Presentation # 8.0 [2] (🍾..🍿) bottle with popping cork..popcorn
1F380..1F393 ; Emoji_Presentation # 6.0 [20] (🎀..🎓) ribbon..graduation cap
1F3A0..1F3C4 ; Emoji_Presentation # 6.0 [37] (🎠..🏄) carousel horse..person surfing
1F3C5 ; Emoji_Presentation # 7.0 [1] (🏅) sports medal
1F3C6..1F3CA ; Emoji_Presentation # 6.0 [5] (🏆..🏊) trophy..person swimming
1F3CF..1F3D3 ; Emoji_Presentation # 8.0 [5] (🏏..🏓) cricket game..ping pong
1F3E0..1F3F0 ; Emoji_Presentation # 6.0 [17] (🏠..🏰) house..castle
1F3F4 ; Emoji_Presentation # 7.0 [1] (🏴) black flag
1F3F8..1F3FF ; Emoji_Presentation # 8.0 [8] (🏸..🏿) badminton..dark skin tone
1F400..1F43E ; Emoji_Presentation # 6.0 [63] (🐀..🐾) rat..paw prints
1F440 ; Emoji_Presentation # 6.0 [1] (👀) eyes
1F442..1F4F7 ; Emoji_Presentation # 6.0[182] (👂..📷) ear..camera
1F4F8 ; Emoji_Presentation # 7.0 [1] (📸) camera with flash
1F4F9..1F4FC ; Emoji_Presentation # 6.0 [4] (📹..📼) video camera..videocassette
1F4FF ; Emoji_Presentation # 8.0 [1] (📿) prayer beads
1F500..1F53D ; Emoji_Presentation # 6.0 [62] (🔀..🔽) shuffle tracks button..downwards button
1F54B..1F54E ; Emoji_Presentation # 8.0 [4] (🕋..🕎) kaaba..menorah
1F550..1F567 ; Emoji_Presentation # 6.0 [24] (🕐..🕧) one oclock..twelve-thirty
1F57A ; Emoji_Presentation # 9.0 [1] (🕺) man dancing
1F595..1F596 ; Emoji_Presentation # 7.0 [2] (🖕..🖖) middle finger..vulcan salute
1F5A4 ; Emoji_Presentation # 9.0 [1] (🖤) black heart
1F5FB..1F5FF ; Emoji_Presentation # 6.0 [5] (🗻..🗿) mount fuji..moai
1F600 ; Emoji_Presentation # 6.1 [1] (😀) grinning face
1F601..1F610 ; Emoji_Presentation # 6.0 [16] (😁..😐) beaming face with smiling eyes..neutral face
1F611 ; Emoji_Presentation # 6.1 [1] (😑) expressionless face
1F612..1F614 ; Emoji_Presentation # 6.0 [3] (😒..😔) unamused face..pensive face
1F615 ; Emoji_Presentation # 6.1 [1] (😕) confused face
1F616 ; Emoji_Presentation # 6.0 [1] (😖) confounded face
1F617 ; Emoji_Presentation # 6.1 [1] (😗) kissing face
1F618 ; Emoji_Presentation # 6.0 [1] (😘) face blowing a kiss
1F619 ; Emoji_Presentation # 6.1 [1] (😙) kissing face with smiling eyes
1F61A ; Emoji_Presentation # 6.0 [1] (😚) kissing face with closed eyes
1F61B ; Emoji_Presentation # 6.1 [1] (😛) face with tongue
1F61C..1F61E ; Emoji_Presentation # 6.0 [3] (😜..😞) winking face with tongue..disappointed face
1F61F ; Emoji_Presentation # 6.1 [1] (😟) worried face
1F620..1F625 ; Emoji_Presentation # 6.0 [6] (😠..😥) angry face..sad but relieved face
1F626..1F627 ; Emoji_Presentation # 6.1 [2] (😦..😧) frowning face with open mouth..anguished face
1F628..1F62B ; Emoji_Presentation # 6.0 [4] (😨..😫) fearful face..tired face
1F62C ; Emoji_Presentation # 6.1 [1] (😬) grimacing face
1F62D ; Emoji_Presentation # 6.0 [1] (😭) loudly crying face
1F62E..1F62F ; Emoji_Presentation # 6.1 [2] (😮..😯) face with open mouth..hushed face
1F630..1F633 ; Emoji_Presentation # 6.0 [4] (😰..😳) anxious face with sweat..flushed face
1F634 ; Emoji_Presentation # 6.1 [1] (😴) sleeping face
1F635..1F640 ; Emoji_Presentation # 6.0 [12] (😵..🙀) dizzy face..weary cat
1F641..1F642 ; Emoji_Presentation # 7.0 [2] (🙁..🙂) slightly frowning face..slightly smiling face
1F643..1F644 ; Emoji_Presentation # 8.0 [2] (🙃..🙄) upside-down face..face with rolling eyes
1F645..1F64F ; Emoji_Presentation # 6.0 [11] (🙅..🙏) person gesturing NO..folded hands
1F680..1F6C5 ; Emoji_Presentation # 6.0 [70] (🚀..🛅) rocket..left luggage
1F6CC ; Emoji_Presentation # 7.0 [1] (🛌) person in bed
1F6D0 ; Emoji_Presentation # 8.0 [1] (🛐) place of worship
1F6D1..1F6D2 ; Emoji_Presentation # 9.0 [2] (🛑..🛒) stop sign..shopping cart
1F6D5 ; Emoji_Presentation # 12.0 [1] (🛕) hindu temple
1F6EB..1F6EC ; Emoji_Presentation # 7.0 [2] (🛫..🛬) airplane departure..airplane arrival
1F6F4..1F6F6 ; Emoji_Presentation # 9.0 [3] (🛴..🛶) kick scooter..canoe
1F6F7..1F6F8 ; Emoji_Presentation # 10.0 [2] (🛷..🛸) sled..flying saucer
1F6F9 ; Emoji_Presentation # 11.0 [1] (🛹) skateboard
1F6FA ; Emoji_Presentation # 12.0 [1] (🛺) auto rickshaw
1F7E0..1F7EB ; Emoji_Presentation # 12.0 [12] (🟠..🟫) orange circle..brown square
1F90D..1F90F ; Emoji_Presentation # 12.0 [3] (🤍..🤏) white heart..pinching hand
1F910..1F918 ; Emoji_Presentation # 8.0 [9] (🤐..🤘) zipper-mouth face..sign of the horns
1F919..1F91E ; Emoji_Presentation # 9.0 [6] (🤙..🤞) call me hand..crossed fingers
1F91F ; Emoji_Presentation # 10.0 [1] (🤟) love-you gesture
1F920..1F927 ; Emoji_Presentation # 9.0 [8] (🤠..🤧) cowboy hat face..sneezing face
1F928..1F92F ; Emoji_Presentation # 10.0 [8] (🤨..🤯) face with raised eyebrow..exploding head
1F930 ; Emoji_Presentation # 9.0 [1] (🤰) pregnant woman
1F931..1F932 ; Emoji_Presentation # 10.0 [2] (🤱..🤲) breast-feeding..palms up together
1F933..1F93A ; Emoji_Presentation # 9.0 [8] (🤳..🤺) selfie..person fencing
1F93C..1F93E ; Emoji_Presentation # 9.0 [3] (🤼..🤾) people wrestling..person playing handball
1F93F ; Emoji_Presentation # 12.0 [1] (🤿) diving mask
1F940..1F945 ; Emoji_Presentation # 9.0 [6] (🥀..🥅) wilted flower..goal net
1F947..1F94B ; Emoji_Presentation # 9.0 [5] (🥇..🥋) 1st place medal..martial arts uniform
1F94C ; Emoji_Presentation # 10.0 [1] (🥌) curling stone
1F94D..1F94F ; Emoji_Presentation # 11.0 [3] (🥍..🥏) lacrosse..flying disc
1F950..1F95E ; Emoji_Presentation # 9.0 [15] (🥐..🥞) croissant..pancakes
1F95F..1F96B ; Emoji_Presentation # 10.0 [13] (🥟..🥫) dumpling..canned food
1F96C..1F970 ; Emoji_Presentation # 11.0 [5] (🥬..🥰) leafy green..smiling face with hearts
1F971 ; Emoji_Presentation # 12.0 [1] (🥱) yawning face
1F973..1F976 ; Emoji_Presentation # 11.0 [4] (🥳..🥶) partying face..cold face
1F97A ; Emoji_Presentation # 11.0 [1] (🥺) pleading face
1F97B ; Emoji_Presentation # 12.0 [1] (🥻) sari
1F97C..1F97F ; Emoji_Presentation # 11.0 [4] (🥼..🥿) lab coat..flat shoe
1F980..1F984 ; Emoji_Presentation # 8.0 [5] (🦀..🦄) crab..unicorn
1F985..1F991 ; Emoji_Presentation # 9.0 [13] (🦅..🦑) eagle..squid
1F992..1F997 ; Emoji_Presentation # 10.0 [6] (🦒..🦗) giraffe..cricket
1F998..1F9A2 ; Emoji_Presentation # 11.0 [11] (🦘..🦢) kangaroo..swan
1F9A5..1F9AA ; Emoji_Presentation # 12.0 [6] (🦥..🦪) sloth..oyster
1F9AE..1F9AF ; Emoji_Presentation # 12.0 [2] (🦮..🦯) guide dog..probing cane
1F9B0..1F9B9 ; Emoji_Presentation # 11.0 [10] (🦰..🦹) red hair..supervillain
1F9BA..1F9BF ; Emoji_Presentation # 12.0 [6] (🦺..🦿) safety vest..mechanical leg
1F9C0 ; Emoji_Presentation # 8.0 [1] (🧀) cheese wedge
1F9C1..1F9C2 ; Emoji_Presentation # 11.0 [2] (🧁..🧂) cupcake..salt
1F9C3..1F9CA ; Emoji_Presentation # 12.0 [8] (🧃..🧊) beverage box..ice cube
1F9CD..1F9CF ; Emoji_Presentation # 12.0 [3] (🧍..🧏) person standing..deaf person
1F9D0..1F9E6 ; Emoji_Presentation # 10.0 [23] (🧐..🧦) face with monocle..socks
1F9E7..1F9FF ; Emoji_Presentation # 11.0 [25] (🧧..🧿) red envelope..nazar amulet
1FA70..1FA73 ; Emoji_Presentation # 12.0 [4] (🩰..🩳) ballet shoes..shorts
1FA78..1FA7A ; Emoji_Presentation # 12.0 [3] (🩸..🩺) drop of blood..stethoscope
1FA80..1FA82 ; Emoji_Presentation # 12.0 [3] (🪀..🪂) yo-yo..parachute
1FA90..1FA95 ; Emoji_Presentation # 12.0 [6] (🪐..🪕) ringed planet..banjo
# Total elements: 1093
# ================================================
# All omitted code points have Emoji_Modifier=No
# @missing: 0000..10FFFF ; Emoji_Modifier ; No
1F3FB..1F3FF ; Emoji_Modifier # 8.0 [5] (🏻..🏿) light skin tone..dark skin tone
# Total elements: 5
# ================================================
# All omitted code points have Emoji_Modifier_Base=No
# @missing: 0000..10FFFF ; Emoji_Modifier_Base ; No
261D ; Emoji_Modifier_Base # 1.1 [1] (☝️) index pointing up
26F9 ; Emoji_Modifier_Base # 5.2 [1] (⛹️) person bouncing ball
270A..270B ; Emoji_Modifier_Base # 6.0 [2] (✊..✋) raised fist..raised hand
270C..270D ; Emoji_Modifier_Base # 1.1 [2] (✌️..✍️) victory hand..writing hand
1F385 ; Emoji_Modifier_Base # 6.0 [1] (🎅) Santa Claus
1F3C2..1F3C4 ; Emoji_Modifier_Base # 6.0 [3] (🏂..🏄) snowboarder..person surfing
1F3C7 ; Emoji_Modifier_Base # 6.0 [1] (🏇) horse racing
1F3CA ; Emoji_Modifier_Base # 6.0 [1] (🏊) person swimming
1F3CB..1F3CC ; Emoji_Modifier_Base # 7.0 [2] (🏋️..🏌️) person lifting weights..person golfing
1F442..1F443 ; Emoji_Modifier_Base # 6.0 [2] (👂..👃) ear..nose
1F446..1F450 ; Emoji_Modifier_Base # 6.0 [11] (👆..👐) backhand index pointing up..open hands
1F466..1F478 ; Emoji_Modifier_Base # 6.0 [19] (👦..👸) boy..princess
1F47C ; Emoji_Modifier_Base # 6.0 [1] (👼) baby angel
1F481..1F483 ; Emoji_Modifier_Base # 6.0 [3] (💁..💃) person tipping hand..woman dancing
1F485..1F487 ; Emoji_Modifier_Base # 6.0 [3] (💅..💇) nail polish..person getting haircut
1F48F ; Emoji_Modifier_Base # 6.0 [1] (💏) kiss
1F491 ; Emoji_Modifier_Base # 6.0 [1] (💑) couple with heart
1F4AA ; Emoji_Modifier_Base # 6.0 [1] (💪) flexed biceps
1F574..1F575 ; Emoji_Modifier_Base # 7.0 [2] (🕴️..🕵️) man in suit levitating..detective
1F57A ; Emoji_Modifier_Base # 9.0 [1] (🕺) man dancing
1F590 ; Emoji_Modifier_Base # 7.0 [1] (🖐️) hand with fingers splayed
1F595..1F596 ; Emoji_Modifier_Base # 7.0 [2] (🖕..🖖) middle finger..vulcan salute
1F645..1F647 ; Emoji_Modifier_Base # 6.0 [3] (🙅..🙇) person gesturing NO..person bowing
1F64B..1F64F ; Emoji_Modifier_Base # 6.0 [5] (🙋..🙏) person raising hand..folded hands
1F6A3 ; Emoji_Modifier_Base # 6.0 [1] (🚣) person rowing boat
1F6B4..1F6B6 ; Emoji_Modifier_Base # 6.0 [3] (🚴..🚶) person biking..person walking
1F6C0 ; Emoji_Modifier_Base # 6.0 [1] (🛀) person taking bath
1F6CC ; Emoji_Modifier_Base # 7.0 [1] (🛌) person in bed
1F90F ; Emoji_Modifier_Base # 12.0 [1] (🤏) pinching hand
1F918 ; Emoji_Modifier_Base # 8.0 [1] (🤘) sign of the horns
1F919..1F91E ; Emoji_Modifier_Base # 9.0 [6] (🤙..🤞) call me hand..crossed fingers
1F91F ; Emoji_Modifier_Base # 10.0 [1] (🤟) love-you gesture
1F926 ; Emoji_Modifier_Base # 9.0 [1] (🤦) person facepalming
1F930 ; Emoji_Modifier_Base # 9.0 [1] (🤰) pregnant woman
1F931..1F932 ; Emoji_Modifier_Base # 10.0 [2] (🤱..🤲) breast-feeding..palms up together
1F933..1F939 ; Emoji_Modifier_Base # 9.0 [7] (🤳..🤹) selfie..person juggling
1F93C..1F93E ; Emoji_Modifier_Base # 9.0 [3] (🤼..🤾) people wrestling..person playing handball
1F9B5..1F9B6 ; Emoji_Modifier_Base # 11.0 [2] (🦵..🦶) leg..foot
1F9B8..1F9B9 ; Emoji_Modifier_Base # 11.0 [2] (🦸..🦹) superhero..supervillain
1F9BB ; Emoji_Modifier_Base # 12.0 [1] (🦻) ear with hearing aid
1F9CD..1F9CF ; Emoji_Modifier_Base # 12.0 [3] (🧍..🧏) person standing..deaf person
1F9D1..1F9DD ; Emoji_Modifier_Base # 10.0 [13] (🧑..🧝) person..elf
# Total elements: 120
# ================================================
# All omitted code points have Emoji_Component=No
# @missing: 0000..10FFFF ; Emoji_Component ; No
0023 ; Emoji_Component # 1.1 [1] (#) number sign
002A ; Emoji_Component # 1.1 [1] (*) asterisk
0030..0039 ; Emoji_Component # 1.1 [10] (0..9) digit zero..digit nine
200D ; Emoji_Component # 1.1 [1] () zero width joiner
20E3 ; Emoji_Component # 3.0 [1] (⃣) combining enclosing keycap
FE0F ; Emoji_Component # 3.2 [1] () VARIATION SELECTOR-16
1F1E6..1F1FF ; Emoji_Component # 6.0 [26] (🇦..🇿) regional indicator symbol letter a..regional indicator symbol letter z
1F3FB..1F3FF ; Emoji_Component # 8.0 [5] (🏻..🏿) light skin tone..dark skin tone
1F9B0..1F9B3 ; Emoji_Component # 11.0 [4] (🦰..🦳) red hair..white hair
E0020..E007F ; Emoji_Component # 3.1 [96] (󠀠..󠁿) tag space..cancel tag
# Total elements: 146
# ================================================
# All omitted code points have Extended_Pictographic=No
# @missing: 0000..10FFFF ; Extended_Pictographic ; No
00A9 ; Extended_Pictographic# 1.1 [1] (©️) copyright
00AE ; Extended_Pictographic# 1.1 [1] (®️) registered
203C ; Extended_Pictographic# 1.1 [1] (‼️) double exclamation mark
2049 ; Extended_Pictographic# 3.0 [1] (⁉️) exclamation question mark
2122 ; Extended_Pictographic# 1.1 [1] (™️) trade mark
2139 ; Extended_Pictographic# 3.0 [1] () information
2194..2199 ; Extended_Pictographic# 1.1 [6] (↔️..↙️) left-right arrow..down-left arrow
21A9..21AA ; Extended_Pictographic# 1.1 [2] (↩️..↪️) right arrow curving left..left arrow curving right
231A..231B ; Extended_Pictographic# 1.1 [2] (⌚..⌛) watch..hourglass done
2328 ; Extended_Pictographic# 1.1 [1] (⌨️) keyboard
2388 ; Extended_Pictographic# 3.0 [1] (⎈) HELM SYMBOL
23CF ; Extended_Pictographic# 4.0 [1] (⏏️) eject button
23E9..23F3 ; Extended_Pictographic# 6.0 [11] (⏩..⏳) fast-forward button..hourglass not done
23F8..23FA ; Extended_Pictographic# 7.0 [3] (⏸️..⏺️) pause button..record button
24C2 ; Extended_Pictographic# 1.1 [1] (Ⓜ️) circled M
25AA..25AB ; Extended_Pictographic# 1.1 [2] (▪️..▫️) black small square..white small square
25B6 ; Extended_Pictographic# 1.1 [1] (▶️) play button
25C0 ; Extended_Pictographic# 1.1 [1] (◀️) reverse button
25FB..25FE ; Extended_Pictographic# 3.2 [4] (◻️..◾) white medium square..black medium-small square
2600..2605 ; Extended_Pictographic# 1.1 [6] (☀️..★) sun..BLACK STAR
2607..2612 ; Extended_Pictographic# 1.1 [12] (☇..☒) LIGHTNING..BALLOT BOX WITH X
2614..2615 ; Extended_Pictographic# 4.0 [2] (☔..☕) umbrella with rain drops..hot beverage
2616..2617 ; Extended_Pictographic# 3.2 [2] (☖..☗) WHITE SHOGI PIECE..BLACK SHOGI PIECE
2618 ; Extended_Pictographic# 4.1 [1] (☘️) shamrock
2619 ; Extended_Pictographic# 3.0 [1] (☙) REVERSED ROTATED FLORAL HEART BULLET
261A..266F ; Extended_Pictographic# 1.1 [86] (☚..♯) BLACK LEFT POINTING INDEX..MUSIC SHARP SIGN
2670..2671 ; Extended_Pictographic# 3.0 [2] (♰..♱) WEST SYRIAC CROSS..EAST SYRIAC CROSS
2672..267D ; Extended_Pictographic# 3.2 [12] (♲..♽) UNIVERSAL RECYCLING SYMBOL..PARTIALLY-RECYCLED PAPER SYMBOL
267E..267F ; Extended_Pictographic# 4.1 [2] (♾️..♿) infinity..wheelchair symbol
2680..2685 ; Extended_Pictographic# 3.2 [6] (⚀..⚅) DIE FACE-1..DIE FACE-6
2690..2691 ; Extended_Pictographic# 4.0 [2] (⚐..⚑) WHITE FLAG..BLACK FLAG
2692..269C ; Extended_Pictographic# 4.1 [11] (⚒️..⚜️) hammer and pick..fleur-de-lis
269D ; Extended_Pictographic# 5.1 [1] (⚝) OUTLINED WHITE STAR
269E..269F ; Extended_Pictographic# 5.2 [2] (⚞..⚟) THREE LINES CONVERGING RIGHT..THREE LINES CONVERGING LEFT
26A0..26A1 ; Extended_Pictographic# 4.0 [2] (⚠️..⚡) warning..high voltage
26A2..26B1 ; Extended_Pictographic# 4.1 [16] (⚢..⚱️) DOUBLED FEMALE SIGN..funeral urn
26B2 ; Extended_Pictographic# 5.0 [1] (⚲) NEUTER
26B3..26BC ; Extended_Pictographic# 5.1 [10] (⚳..⚼) CERES..SESQUIQUADRATE
26BD..26BF ; Extended_Pictographic# 5.2 [3] (⚽..⚿) soccer ball..SQUARED KEY
26C0..26C3 ; Extended_Pictographic# 5.1 [4] (⛀..⛃) WHITE DRAUGHTS MAN..BLACK DRAUGHTS KING
26C4..26CD ; Extended_Pictographic# 5.2 [10] (⛄..⛍) snowman without snow..DISABLED CAR
26CE ; Extended_Pictographic# 6.0 [1] (⛎) Ophiuchus
26CF..26E1 ; Extended_Pictographic# 5.2 [19] (⛏️..⛡) pick..RESTRICTED LEFT ENTRY-2
26E2 ; Extended_Pictographic# 6.0 [1] (⛢) ASTRONOMICAL SYMBOL FOR URANUS
26E3 ; Extended_Pictographic# 5.2 [1] (⛣) HEAVY CIRCLE WITH STROKE AND TWO DOTS ABOVE
26E4..26E7 ; Extended_Pictographic# 6.0 [4] (⛤..⛧) PENTAGRAM..INVERTED PENTAGRAM
26E8..26FF ; Extended_Pictographic# 5.2 [24] (⛨..⛿) BLACK CROSS ON SHIELD..WHITE FLAG WITH HORIZONTAL MIDDLE BLACK STRIPE
2700 ; Extended_Pictographic# 7.0 [1] (✀) BLACK SAFETY SCISSORS
2701..2704 ; Extended_Pictographic# 1.1 [4] (✁..✄) UPPER BLADE SCISSORS..WHITE SCISSORS
2705 ; Extended_Pictographic# 6.0 [1] (✅) check mark button
2708..2709 ; Extended_Pictographic# 1.1 [2] (✈️..✉️) airplane..envelope
270A..270B ; Extended_Pictographic# 6.0 [2] (✊..✋) raised fist..raised hand
270C..2712 ; Extended_Pictographic# 1.1 [7] (✌️..✒️) victory hand..black nib
2714 ; Extended_Pictographic# 1.1 [1] (✔️) check mark
2716 ; Extended_Pictographic# 1.1 [1] (✖️) multiplication sign
271D ; Extended_Pictographic# 1.1 [1] (✝️) latin cross
2721 ; Extended_Pictographic# 1.1 [1] (✡️) star of David
2728 ; Extended_Pictographic# 6.0 [1] (✨) sparkles
2733..2734 ; Extended_Pictographic# 1.1 [2] (✳️..✴️) eight-spoked asterisk..eight-pointed star
2744 ; Extended_Pictographic# 1.1 [1] (❄️) snowflake
2747 ; Extended_Pictographic# 1.1 [1] (❇️) sparkle
274C ; Extended_Pictographic# 6.0 [1] (❌) cross mark
274E ; Extended_Pictographic# 6.0 [1] (❎) cross mark button
2753..2755 ; Extended_Pictographic# 6.0 [3] (❓..❕) question mark..white exclamation mark
2757 ; Extended_Pictographic# 5.2 [1] (❗) exclamation mark
2763..2767 ; Extended_Pictographic# 1.1 [5] (❣️..❧) heart exclamation..ROTATED FLORAL HEART BULLET
2795..2797 ; Extended_Pictographic# 6.0 [3] (..➗) plus sign..division sign
27A1 ; Extended_Pictographic# 1.1 [1] (➡️) right arrow
27B0 ; Extended_Pictographic# 6.0 [1] (➰) curly loop
27BF ; Extended_Pictographic# 6.0 [1] (➿) double curly loop
2934..2935 ; Extended_Pictographic# 3.2 [2] (⤴️..⤵️) right arrow curving up..right arrow curving down
2B05..2B07 ; Extended_Pictographic# 4.0 [3] (⬅️..⬇️) left arrow..down arrow
2B1B..2B1C ; Extended_Pictographic# 5.1 [2] (⬛..⬜) black large square..white large square
2B50 ; Extended_Pictographic# 5.1 [1] (⭐) star
2B55 ; Extended_Pictographic# 5.2 [1] (⭕) hollow red circle
3030 ; Extended_Pictographic# 1.1 [1] (〰️) wavy dash
303D ; Extended_Pictographic# 3.2 [1] (〽️) part alternation mark
3297 ; Extended_Pictographic# 1.1 [1] (㊗️) Japanese “congratulations” button
3299 ; Extended_Pictographic# 1.1 [1] (㊙️) Japanese “secret” button
1F000..1F02B ; Extended_Pictographic# 5.1 [44] (🀀..🀫) MAHJONG TILE EAST WIND..MAHJONG TILE BACK
1F02C..1F02F ; Extended_Pictographic# NA [4] (🀬..🀯) <reserved-1F02C>..<reserved-1F02F>
1F030..1F093 ; Extended_Pictographic# 5.1[100] (🀰..🂓) DOMINO TILE HORIZONTAL BACK..DOMINO TILE VERTICAL-06-06
1F094..1F09F ; Extended_Pictographic# NA [12] (🂔..🂟) <reserved-1F094>..<reserved-1F09F>
1F0A0..1F0AE ; Extended_Pictographic# 6.0 [15] (🂠..🂮) PLAYING CARD BACK..PLAYING CARD KING OF SPADES
1F0AF..1F0B0 ; Extended_Pictographic# NA [2] (🂯..🂰) <reserved-1F0AF>..<reserved-1F0B0>
1F0B1..1F0BE ; Extended_Pictographic# 6.0 [14] (🂱..🂾) PLAYING CARD ACE OF HEARTS..PLAYING CARD KING OF HEARTS
1F0BF ; Extended_Pictographic# 7.0 [1] (🂿) PLAYING CARD RED JOKER
1F0C0 ; Extended_Pictographic# NA [1] (🃀) <reserved-1F0C0>
1F0C1..1F0CF ; Extended_Pictographic# 6.0 [15] (🃁..🃏) PLAYING CARD ACE OF DIAMONDS..joker
1F0D0 ; Extended_Pictographic# NA [1] (🃐) <reserved-1F0D0>
1F0D1..1F0DF ; Extended_Pictographic# 6.0 [15] (🃑..🃟) PLAYING CARD ACE OF CLUBS..PLAYING CARD WHITE JOKER
1F0E0..1F0F5 ; Extended_Pictographic# 7.0 [22] (🃠..🃵) PLAYING CARD FOOL..PLAYING CARD TRUMP-21
1F0F6..1F0FF ; Extended_Pictographic# NA [10] (🃶..🃿) <reserved-1F0F6>..<reserved-1F0FF>
1F10D..1F10F ; Extended_Pictographic# NA [3] (🄍..🄏) <reserved-1F10D>..<reserved-1F10F>
1F12F ; Extended_Pictographic# 11.0 [1] (🄯) COPYLEFT SYMBOL
1F16C ; Extended_Pictographic# 12.0 [1] (🅬) RAISED MR SIGN
1F16D..1F16F ; Extended_Pictographic# NA [3] (🅭..🅯) <reserved-1F16D>..<reserved-1F16F>
1F170..1F171 ; Extended_Pictographic# 6.0 [2] (🅰️..🅱️) A button (blood type)..B button (blood type)
1F17E ; Extended_Pictographic# 6.0 [1] (🅾️) O button (blood type)
1F17F ; Extended_Pictographic# 5.2 [1] (🅿️) P button
1F18E ; Extended_Pictographic# 6.0 [1] (🆎) AB button (blood type)
1F191..1F19A ; Extended_Pictographic# 6.0 [10] (🆑..🆚) CL button..VS button
1F1AD..1F1E5 ; Extended_Pictographic# NA [57] (🆭..🇥) <reserved-1F1AD>..<reserved-1F1E5>
1F201..1F202 ; Extended_Pictographic# 6.0 [2] (🈁..🈂️) Japanese “here” button..Japanese “service charge” button
1F203..1F20F ; Extended_Pictographic# NA [13] (🈃..🈏) <reserved-1F203>..<reserved-1F20F>
1F21A ; Extended_Pictographic# 5.2 [1] (🈚) Japanese “free of charge” button
1F22F ; Extended_Pictographic# 5.2 [1] (🈯) Japanese “reserved” button
1F232..1F23A ; Extended_Pictographic# 6.0 [9] (🈲..🈺) Japanese “prohibited” button..Japanese “open for business” button
1F23C..1F23F ; Extended_Pictographic# NA [4] (🈼..🈿) <reserved-1F23C>..<reserved-1F23F>
1F249..1F24F ; Extended_Pictographic# NA [7] (🉉..🉏) <reserved-1F249>..<reserved-1F24F>
1F250..1F251 ; Extended_Pictographic# 6.0 [2] (🉐..🉑) Japanese “bargain” button..Japanese “acceptable” button
1F252..1F25F ; Extended_Pictographic# NA [14] (🉒..🉟) <reserved-1F252>..<reserved-1F25F>
1F260..1F265 ; Extended_Pictographic# 10.0 [6] (🉠..🉥) ROUNDED SYMBOL FOR FU..ROUNDED SYMBOL FOR CAI
1F266..1F2FF ; Extended_Pictographic# NA[154] (🉦..🋿) <reserved-1F266>..<reserved-1F2FF>
1F300..1F320 ; Extended_Pictographic# 6.0 [33] (🌀..🌠) cyclone..shooting star
1F321..1F32C ; Extended_Pictographic# 7.0 [12] (🌡️..🌬️) thermometer..wind face
1F32D..1F32F ; Extended_Pictographic# 8.0 [3] (🌭..🌯) hot dog..burrito
1F330..1F335 ; Extended_Pictographic# 6.0 [6] (🌰..🌵) chestnut..cactus
1F336 ; Extended_Pictographic# 7.0 [1] (🌶️) hot pepper
1F337..1F37C ; Extended_Pictographic# 6.0 [70] (🌷..🍼) tulip..baby bottle
1F37D ; Extended_Pictographic# 7.0 [1] (🍽️) fork and knife with plate
1F37E..1F37F ; Extended_Pictographic# 8.0 [2] (🍾..🍿) bottle with popping cork..popcorn
1F380..1F393 ; Extended_Pictographic# 6.0 [20] (🎀..🎓) ribbon..graduation cap
1F394..1F39F ; Extended_Pictographic# 7.0 [12] (🎔..🎟️) HEART WITH TIP ON THE LEFT..admission tickets
1F3A0..1F3C4 ; Extended_Pictographic# 6.0 [37] (🎠..🏄) carousel horse..person surfing
1F3C5 ; Extended_Pictographic# 7.0 [1] (🏅) sports medal
1F3C6..1F3CA ; Extended_Pictographic# 6.0 [5] (🏆..🏊) trophy..person swimming
1F3CB..1F3CE ; Extended_Pictographic# 7.0 [4] (🏋️..🏎️) person lifting weights..racing car
1F3CF..1F3D3 ; Extended_Pictographic# 8.0 [5] (🏏..🏓) cricket game..ping pong
1F3D4..1F3DF ; Extended_Pictographic# 7.0 [12] (🏔️..🏟️) snow-capped mountain..stadium
1F3E0..1F3F0 ; Extended_Pictographic# 6.0 [17] (🏠..🏰) house..castle
1F3F1..1F3F7 ; Extended_Pictographic# 7.0 [7] (🏱..🏷️) WHITE PENNANT..label
1F3F8..1F3FA ; Extended_Pictographic# 8.0 [3] (🏸..🏺) badminton..amphora
1F400..1F43E ; Extended_Pictographic# 6.0 [63] (🐀..🐾) rat..paw prints
1F43F ; Extended_Pictographic# 7.0 [1] (🐿️) chipmunk
1F440 ; Extended_Pictographic# 6.0 [1] (👀) eyes
1F441 ; Extended_Pictographic# 7.0 [1] (👁️) eye
1F442..1F4F7 ; Extended_Pictographic# 6.0[182] (👂..📷) ear..camera
1F4F8 ; Extended_Pictographic# 7.0 [1] (📸) camera with flash
1F4F9..1F4FC ; Extended_Pictographic# 6.0 [4] (📹..📼) video camera..videocassette
1F4FD..1F4FE ; Extended_Pictographic# 7.0 [2] (📽️..📾) film projector..PORTABLE STEREO
1F4FF ; Extended_Pictographic# 8.0 [1] (📿) prayer beads
1F500..1F53D ; Extended_Pictographic# 6.0 [62] (🔀..🔽) shuffle tracks button..downwards button
1F546..1F54A ; Extended_Pictographic# 7.0 [5] (🕆..🕊️) WHITE LATIN CROSS..dove
1F54B..1F54F ; Extended_Pictographic# 8.0 [5] (🕋..🕏) kaaba..BOWL OF HYGIEIA
1F550..1F567 ; Extended_Pictographic# 6.0 [24] (🕐..🕧) one oclock..twelve-thirty
1F568..1F579 ; Extended_Pictographic# 7.0 [18] (🕨..🕹️) RIGHT SPEAKER..joystick
1F57A ; Extended_Pictographic# 9.0 [1] (🕺) man dancing
1F57B..1F5A3 ; Extended_Pictographic# 7.0 [41] (🕻..🖣) LEFT HAND TELEPHONE RECEIVER..BLACK DOWN POINTING BACKHAND INDEX
1F5A4 ; Extended_Pictographic# 9.0 [1] (🖤) black heart
1F5A5..1F5FA ; Extended_Pictographic# 7.0 [86] (🖥️..🗺️) desktop computer..world map
1F5FB..1F5FF ; Extended_Pictographic# 6.0 [5] (🗻..🗿) mount fuji..moai
1F600 ; Extended_Pictographic# 6.1 [1] (😀) grinning face
1F601..1F610 ; Extended_Pictographic# 6.0 [16] (😁..😐) beaming face with smiling eyes..neutral face
1F611 ; Extended_Pictographic# 6.1 [1] (😑) expressionless face
1F612..1F614 ; Extended_Pictographic# 6.0 [3] (😒..😔) unamused face..pensive face
1F615 ; Extended_Pictographic# 6.1 [1] (😕) confused face
1F616 ; Extended_Pictographic# 6.0 [1] (😖) confounded face
1F617 ; Extended_Pictographic# 6.1 [1] (😗) kissing face
1F618 ; Extended_Pictographic# 6.0 [1] (😘) face blowing a kiss
1F619 ; Extended_Pictographic# 6.1 [1] (😙) kissing face with smiling eyes
1F61A ; Extended_Pictographic# 6.0 [1] (😚) kissing face with closed eyes
1F61B ; Extended_Pictographic# 6.1 [1] (😛) face with tongue
1F61C..1F61E ; Extended_Pictographic# 6.0 [3] (😜..😞) winking face with tongue..disappointed face
1F61F ; Extended_Pictographic# 6.1 [1] (😟) worried face
1F620..1F625 ; Extended_Pictographic# 6.0 [6] (😠..😥) angry face..sad but relieved face
1F626..1F627 ; Extended_Pictographic# 6.1 [2] (😦..😧) frowning face with open mouth..anguished face
1F628..1F62B ; Extended_Pictographic# 6.0 [4] (😨..😫) fearful face..tired face
1F62C ; Extended_Pictographic# 6.1 [1] (😬) grimacing face
1F62D ; Extended_Pictographic# 6.0 [1] (😭) loudly crying face
1F62E..1F62F ; Extended_Pictographic# 6.1 [2] (😮..😯) face with open mouth..hushed face
1F630..1F633 ; Extended_Pictographic# 6.0 [4] (😰..😳) anxious face with sweat..flushed face
1F634 ; Extended_Pictographic# 6.1 [1] (😴) sleeping face
1F635..1F640 ; Extended_Pictographic# 6.0 [12] (😵..🙀) dizzy face..weary cat
1F641..1F642 ; Extended_Pictographic# 7.0 [2] (🙁..🙂) slightly frowning face..slightly smiling face
1F643..1F644 ; Extended_Pictographic# 8.0 [2] (🙃..🙄) upside-down face..face with rolling eyes
1F645..1F64F ; Extended_Pictographic# 6.0 [11] (🙅..🙏) person gesturing NO..folded hands
1F680..1F6C5 ; Extended_Pictographic# 6.0 [70] (🚀..🛅) rocket..left luggage
1F6C6..1F6CF ; Extended_Pictographic# 7.0 [10] (🛆..🛏️) TRIANGLE WITH ROUNDED CORNERS..bed
1F6D0 ; Extended_Pictographic# 8.0 [1] (🛐) place of worship
1F6D1..1F6D2 ; Extended_Pictographic# 9.0 [2] (🛑..🛒) stop sign..shopping cart
1F6D3..1F6D4 ; Extended_Pictographic# 10.0 [2] (🛓..🛔) STUPA..PAGODA
1F6D5 ; Extended_Pictographic# 12.0 [1] (🛕) hindu temple
1F6D6..1F6DF ; Extended_Pictographic# NA [10] (🛖..🛟) <reserved-1F6D6>..<reserved-1F6DF>
1F6E0..1F6EC ; Extended_Pictographic# 7.0 [13] (🛠️..🛬) hammer and wrench..airplane arrival
1F6ED..1F6EF ; Extended_Pictographic# NA [3] (🛭..🛯) <reserved-1F6ED>..<reserved-1F6EF>
1F6F0..1F6F3 ; Extended_Pictographic# 7.0 [4] (🛰️..🛳️) satellite..passenger ship
1F6F4..1F6F6 ; Extended_Pictographic# 9.0 [3] (🛴..🛶) kick scooter..canoe
1F6F7..1F6F8 ; Extended_Pictographic# 10.0 [2] (🛷..🛸) sled..flying saucer
1F6F9 ; Extended_Pictographic# 11.0 [1] (🛹) skateboard
1F6FA ; Extended_Pictographic# 12.0 [1] (🛺) auto rickshaw
1F6FB..1F6FF ; Extended_Pictographic# NA [5] (🛻..🛿) <reserved-1F6FB>..<reserved-1F6FF>
1F774..1F77F ; Extended_Pictographic# NA [12] (🝴..🝿) <reserved-1F774>..<reserved-1F77F>
1F7D5..1F7D8 ; Extended_Pictographic# 11.0 [4] (🟕..🟘) CIRCLED TRIANGLE..NEGATIVE CIRCLED SQUARE
1F7D9..1F7DF ; Extended_Pictographic# NA [7] (🟙..🟟) <reserved-1F7D9>..<reserved-1F7DF>
1F7E0..1F7EB ; Extended_Pictographic# 12.0 [12] (🟠..🟫) orange circle..brown square
1F7EC..1F7FF ; Extended_Pictographic# NA [20] (🟬..🟿) <reserved-1F7EC>..<reserved-1F7FF>
1F80C..1F80F ; Extended_Pictographic# NA [4] (🠌..🠏) <reserved-1F80C>..<reserved-1F80F>
1F848..1F84F ; Extended_Pictographic# NA [8] (🡈..🡏) <reserved-1F848>..<reserved-1F84F>
1F85A..1F85F ; Extended_Pictographic# NA [6] (🡚..🡟) <reserved-1F85A>..<reserved-1F85F>
1F888..1F88F ; Extended_Pictographic# NA [8] (🢈..🢏) <reserved-1F888>..<reserved-1F88F>
1F8AE..1F8FF ; Extended_Pictographic# NA [82] (🢮..🣿) <reserved-1F8AE>..<reserved-1F8FF>
1F90C ; Extended_Pictographic# NA [1] (🤌) <reserved-1F90C>
1F90D..1F90F ; Extended_Pictographic# 12.0 [3] (🤍..🤏) white heart..pinching hand
1F910..1F918 ; Extended_Pictographic# 8.0 [9] (🤐..🤘) zipper-mouth face..sign of the horns
1F919..1F91E ; Extended_Pictographic# 9.0 [6] (🤙..🤞) call me hand..crossed fingers
1F91F ; Extended_Pictographic# 10.0 [1] (🤟) love-you gesture
1F920..1F927 ; Extended_Pictographic# 9.0 [8] (🤠..🤧) cowboy hat face..sneezing face
1F928..1F92F ; Extended_Pictographic# 10.0 [8] (🤨..🤯) face with raised eyebrow..exploding head
1F930 ; Extended_Pictographic# 9.0 [1] (🤰) pregnant woman
1F931..1F932 ; Extended_Pictographic# 10.0 [2] (🤱..🤲) breast-feeding..palms up together
1F933..1F93A ; Extended_Pictographic# 9.0 [8] (🤳..🤺) selfie..person fencing
1F93C..1F93E ; Extended_Pictographic# 9.0 [3] (🤼..🤾) people wrestling..person playing handball
1F93F ; Extended_Pictographic# 12.0 [1] (🤿) diving mask
1F940..1F945 ; Extended_Pictographic# 9.0 [6] (🥀..🥅) wilted flower..goal net
1F947..1F94B ; Extended_Pictographic# 9.0 [5] (🥇..🥋) 1st place medal..martial arts uniform
1F94C ; Extended_Pictographic# 10.0 [1] (🥌) curling stone
1F94D..1F94F ; Extended_Pictographic# 11.0 [3] (🥍..🥏) lacrosse..flying disc
1F950..1F95E ; Extended_Pictographic# 9.0 [15] (🥐..🥞) croissant..pancakes
1F95F..1F96B ; Extended_Pictographic# 10.0 [13] (🥟..🥫) dumpling..canned food
1F96C..1F970 ; Extended_Pictographic# 11.0 [5] (🥬..🥰) leafy green..smiling face with hearts
1F971 ; Extended_Pictographic# 12.0 [1] (🥱) yawning face
1F972 ; Extended_Pictographic# NA [1] (🥲) <reserved-1F972>
1F973..1F976 ; Extended_Pictographic# 11.0 [4] (🥳..🥶) partying face..cold face
1F977..1F979 ; Extended_Pictographic# NA [3] (🥷..🥹) <reserved-1F977>..<reserved-1F979>
1F97A ; Extended_Pictographic# 11.0 [1] (🥺) pleading face
1F97B ; Extended_Pictographic# 12.0 [1] (🥻) sari
1F97C..1F97F ; Extended_Pictographic# 11.0 [4] (🥼..🥿) lab coat..flat shoe
1F980..1F984 ; Extended_Pictographic# 8.0 [5] (🦀..🦄) crab..unicorn
1F985..1F991 ; Extended_Pictographic# 9.0 [13] (🦅..🦑) eagle..squid
1F992..1F997 ; Extended_Pictographic# 10.0 [6] (🦒..🦗) giraffe..cricket
1F998..1F9A2 ; Extended_Pictographic# 11.0 [11] (🦘..🦢) kangaroo..swan
1F9A3..1F9A4 ; Extended_Pictographic# NA [2] (🦣..🦤) <reserved-1F9A3>..<reserved-1F9A4>
1F9A5..1F9AA ; Extended_Pictographic# 12.0 [6] (🦥..🦪) sloth..oyster
1F9AB..1F9AD ; Extended_Pictographic# NA [3] (🦫..🦭) <reserved-1F9AB>..<reserved-1F9AD>
1F9AE..1F9AF ; Extended_Pictographic# 12.0 [2] (🦮..🦯) guide dog..probing cane
1F9B0..1F9B9 ; Extended_Pictographic# 11.0 [10] (🦰..🦹) red hair..supervillain
1F9BA..1F9BF ; Extended_Pictographic# 12.0 [6] (🦺..🦿) safety vest..mechanical leg
1F9C0 ; Extended_Pictographic# 8.0 [1] (🧀) cheese wedge
1F9C1..1F9C2 ; Extended_Pictographic# 11.0 [2] (🧁..🧂) cupcake..salt
1F9C3..1F9CA ; Extended_Pictographic# 12.0 [8] (🧃..🧊) beverage box..ice cube
1F9CB..1F9CC ; Extended_Pictographic# NA [2] (🧋..🧌) <reserved-1F9CB>..<reserved-1F9CC>
1F9CD..1F9CF ; Extended_Pictographic# 12.0 [3] (🧍..🧏) person standing..deaf person
1F9D0..1F9E6 ; Extended_Pictographic# 10.0 [23] (🧐..🧦) face with monocle..socks
1F9E7..1F9FF ; Extended_Pictographic# 11.0 [25] (🧧..🧿) red envelope..nazar amulet
1FA00..1FA53 ; Extended_Pictographic# 12.0 [84] (🨀..🩓) NEUTRAL CHESS KING..BLACK CHESS KNIGHT-BISHOP
1FA54..1FA5F ; Extended_Pictographic# NA [12] (🩔..🩟) <reserved-1FA54>..<reserved-1FA5F>
1FA60..1FA6D ; Extended_Pictographic# 11.0 [14] (🩠..🩭) XIANGQI RED GENERAL..XIANGQI BLACK SOLDIER
1FA6E..1FA6F ; Extended_Pictographic# NA [2] (🩮..🩯) <reserved-1FA6E>..<reserved-1FA6F>
1FA70..1FA73 ; Extended_Pictographic# 12.0 [4] (🩰..🩳) ballet shoes..shorts
1FA74..1FA77 ; Extended_Pictographic# NA [4] (🩴..🩷) <reserved-1FA74>..<reserved-1FA77>
1FA78..1FA7A ; Extended_Pictographic# 12.0 [3] (🩸..🩺) drop of blood..stethoscope
1FA7B..1FA7F ; Extended_Pictographic# NA [5] (🩻..🩿) <reserved-1FA7B>..<reserved-1FA7F>
1FA80..1FA82 ; Extended_Pictographic# 12.0 [3] (🪀..🪂) yo-yo..parachute
1FA83..1FA8F ; Extended_Pictographic# NA [13] (🪃..🪏) <reserved-1FA83>..<reserved-1FA8F>
1FA90..1FA95 ; Extended_Pictographic# 12.0 [6] (🪐..🪕) ringed planet..banjo
1FA96..1FFFD ; Extended_Pictographic# NA[1384] (🪖..🿽) <reserved-1FA96>..<reserved-1FFFD>
# Total elements: 3793
#EOF

4879
lib/pleroma/emoji-test.txt Normal file

File diff suppressed because it is too large Load Diff

View File

@ -102,31 +102,36 @@ defp update_emojis(emojis) do
:ets.insert(@ets, emojis) :ets.insert(@ets, emojis)
end end
@external_resource "lib/pleroma/emoji-data.txt" @external_resource "lib/pleroma/emoji-test.txt"
regional_indicators =
Enum.map(127_462..127_487, fn codepoint ->
<<codepoint::utf8>>
end)
emojis = emojis =
@external_resource @external_resource
|> File.read!() |> File.read!()
|> String.split("\n") |> String.split("\n")
|> Enum.filter(fn line -> line != "" and not String.starts_with?(line, "#") end) |> Enum.filter(fn line ->
line != "" and not String.starts_with?(line, "#") and
String.contains?(line, "fully-qualified")
end)
|> Enum.map(fn line -> |> Enum.map(fn line ->
line line
|> String.split(";", parts: 2) |> String.split(";", parts: 2)
|> hd() |> hd()
|> String.trim() |> String.trim()
|> String.split("..") |> String.split()
|> case do |> Enum.map(fn codepoint ->
[number] -> <<String.to_integer(codepoint, 16)::utf8>>
<<String.to_integer(number, 16)::utf8>> end)
|> Enum.join()
[first, last] ->
String.to_integer(first, 16)..String.to_integer(last, 16)
|> Enum.map(&<<&1::utf8>>)
end
end) end)
|> List.flatten()
|> Enum.uniq() |> Enum.uniq()
emojis = emojis ++ regional_indicators
for emoji <- emojis do for emoji <- emojis do
def is_unicode_emoji?(unquote(emoji)), do: true def is_unicode_emoji?(unquote(emoji)), do: true
end end

View File

@ -22,14 +22,14 @@ defmodule Pleroma.Emoji.Pack do
alias Pleroma.Emoji alias Pleroma.Emoji
alias Pleroma.Emoji.Pack alias Pleroma.Emoji.Pack
alias Pleroma.Utils
@spec create(String.t()) :: {:ok, t()} | {:error, File.posix()} | {:error, :empty_values} @spec create(String.t()) :: {:ok, t()} | {:error, File.posix()} | {:error, :empty_values}
def create(name) do def create(name) do
with :ok <- validate_not_empty([name]), with :ok <- validate_not_empty([name]),
dir <- Path.join(emoji_path(), name), dir <- Path.join(emoji_path(), name),
:ok <- File.mkdir(dir) do :ok <- File.mkdir(dir) do
%__MODULE__{pack_file: Path.join(dir, "pack.json")} save_pack(%__MODULE__{pack_file: Path.join(dir, "pack.json")})
|> save_pack()
end end
end end
@ -62,10 +62,9 @@ def show(opts) do
@spec delete(String.t()) :: @spec delete(String.t()) ::
{:ok, [binary()]} | {:error, File.posix(), binary()} | {:error, :empty_values} {:ok, [binary()]} | {:error, File.posix(), binary()} | {:error, :empty_values}
def delete(name) do def delete(name) do
with :ok <- validate_not_empty([name]) do with :ok <- validate_not_empty([name]),
emoji_path() pack_path <- Path.join(emoji_path(), name) do
|> Path.join(name) File.rm_rf(pack_path)
|> File.rm_rf()
end end
end end
@ -94,7 +93,7 @@ defp unpack_zip_emojies(zip_files) do
def add_file(%Pack{} = pack, _, _, %Plug.Upload{content_type: "application/zip"} = file) do def add_file(%Pack{} = pack, _, _, %Plug.Upload{content_type: "application/zip"} = file) do
with {:ok, zip_files} <- :zip.table(to_charlist(file.path)), with {:ok, zip_files} <- :zip.table(to_charlist(file.path)),
[_ | _] = emojies <- unpack_zip_emojies(zip_files), [_ | _] = emojies <- unpack_zip_emojies(zip_files),
{:ok, tmp_dir} <- Pleroma.Utils.tmp_dir("emoji") do {:ok, tmp_dir} <- Utils.tmp_dir("emoji") do
try do try do
{:ok, _emoji_files} = {:ok, _emoji_files} =
:zip.unzip( :zip.unzip(
@ -282,18 +281,21 @@ def update_metadata(name, data) do
end end
end end
@spec load_pack(String.t()) :: {:ok, t()} | {:error, :not_found} @spec load_pack(String.t()) :: {:ok, t()} | {:error, :file.posix()}
def load_pack(name) do def load_pack(name) do
pack_file = Path.join([emoji_path(), name, "pack.json"]) pack_file = Path.join([emoji_path(), name, "pack.json"])
if File.exists?(pack_file) do with {:ok, _} <- File.stat(pack_file),
{:ok, pack_data} <- File.read(pack_file) do
pack = pack =
pack_file from_json(
|> File.read!() pack_data,
|> from_json() %{
|> Map.put(:pack_file, pack_file) pack_file: pack_file,
|> Map.put(:path, Path.dirname(pack_file)) path: Path.dirname(pack_file),
|> Map.put(:name, name) name: name
}
)
files_count = files_count =
pack.files pack.files
@ -301,8 +303,6 @@ def load_pack(name) do
|> length() |> length()
{:ok, Map.put(pack, :files_count, files_count)} {:ok, Map.put(pack, :files_count, files_count)}
else
{:error, :not_found}
end end
end end
@ -434,10 +434,17 @@ defp save_pack(pack) do
end end
end end
defp from_json(json) do defp from_json(json, attrs) do
map = Jason.decode!(json) map = Jason.decode!(json)
struct(__MODULE__, %{files: map["files"], pack: map["pack"]}) pack_attrs =
attrs
|> Map.merge(%{
files: map["files"],
pack: map["pack"]
})
struct(__MODULE__, pack_attrs)
end end
defp validate_shareable_packs_available(uri) do defp validate_shareable_packs_available(uri) do
@ -491,10 +498,10 @@ defp rename_file(pack, filename, new_filename) do
end end
defp create_subdirs(file_path) do defp create_subdirs(file_path) do
if String.contains?(file_path, "/") do with true <- String.contains?(file_path, "/"),
file_path path <- Path.dirname(file_path),
|> Path.dirname() false <- File.exists?(path) do
|> File.mkdir_p!() File.mkdir_p!(path)
end end
end end
@ -518,10 +525,15 @@ defp remove_dir_if_empty(emoji, filename) do
defp get_filename(pack, shortcode) do defp get_filename(pack, shortcode) do
with %{^shortcode => filename} when is_binary(filename) <- pack.files, with %{^shortcode => filename} when is_binary(filename) <- pack.files,
true <- pack.path |> Path.join(filename) |> File.exists?() do file_path <- Path.join(pack.path, filename),
{:ok, _} <- File.stat(file_path) do
{:ok, filename} {:ok, filename}
else else
_ -> {:error, :doesnt_exist} {:error, _} = error ->
error
_ ->
{:error, :doesnt_exist}
end end
end end

View File

@ -62,23 +62,47 @@ def update(%User{} = follower, %User{} = following, state) do
follow(follower, following, state) follow(follower, following, state)
following_relationship -> following_relationship ->
following_relationship with {:ok, _following_relationship} <-
|> cast(%{state: state}, [:state]) following_relationship
|> validate_required([:state]) |> cast(%{state: state}, [:state])
|> Repo.update() |> validate_required([:state])
|> Repo.update() do
after_update(state, follower, following)
end
end end
end end
def follow(%User{} = follower, %User{} = following, state \\ :follow_accept) do def follow(%User{} = follower, %User{} = following, state \\ :follow_accept) do
%__MODULE__{} with {:ok, _following_relationship} <-
|> changeset(%{follower: follower, following: following, state: state}) %__MODULE__{}
|> Repo.insert(on_conflict: :nothing) |> changeset(%{follower: follower, following: following, state: state})
|> Repo.insert(on_conflict: :nothing) do
after_update(state, follower, following)
end
end end
def unfollow(%User{} = follower, %User{} = following) do def unfollow(%User{} = follower, %User{} = following) do
case get(follower, following) do case get(follower, following) do
%__MODULE__{} = following_relationship -> Repo.delete(following_relationship) %__MODULE__{} = following_relationship ->
_ -> {:ok, nil} with {:ok, _following_relationship} <- Repo.delete(following_relationship) do
after_update(:unfollow, follower, following)
end
_ ->
{:ok, nil}
end
end
defp after_update(state, %User{} = follower, %User{} = following) do
with {:ok, following} <- User.update_follower_count(following),
{:ok, follower} <- User.update_following_count(follower) do
Pleroma.Web.Streamer.stream("follow_relationship", %{
state: state,
following: following,
follower: follower
})
{:ok, follower, following}
end end
end end

View File

@ -0,0 +1,46 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Helpers.AuthHelper do
alias Pleroma.Web.Plugs.OAuthScopesPlug
alias Plug.Conn
import Plug.Conn
@oauth_token_session_key :oauth_token
@doc """
Skips OAuth permissions (scopes) checks, assigns nil `:token`.
Intended to be used with explicit authentication and only when OAuth token cannot be determined.
"""
def skip_oauth(conn) do
conn
|> assign(:token, nil)
|> OAuthScopesPlug.skip_plug()
end
@doc "Drops authentication info from connection"
def drop_auth_info(conn) do
# To simplify debugging, setting a private variable on `conn` if auth info is dropped
conn
|> assign(:user, nil)
|> assign(:token, nil)
|> put_private(:authentication_ignored, true)
end
@doc "Gets OAuth token string from session"
def get_session_token(%Conn{} = conn) do
get_session(conn, @oauth_token_session_key)
end
@doc "Updates OAuth token string in session"
def put_session_token(%Conn{} = conn, token) when is_binary(token) do
put_session(conn, @oauth_token_session_key, token)
end
@doc "Deletes OAuth token string from session"
def delete_session_token(%Conn{} = conn) do
delete_session(conn, @oauth_token_session_key)
end
end

View File

@ -77,7 +77,7 @@ def reachable?(url_or_host) when is_binary(url_or_host) do
) )
end end
def reachable?(_), do: true def reachable?(url_or_host) when is_binary(url_or_host), do: true
def set_reachable(url_or_host) when is_binary(url_or_host) do def set_reachable(url_or_host) when is_binary(url_or_host) do
with host <- host(url_or_host), with host <- host(url_or_host),
@ -166,7 +166,8 @@ def get_or_update_favicon(%URI{host: host} = instance_uri) do
defp scrape_favicon(%URI{} = instance_uri) do defp scrape_favicon(%URI{} = instance_uri) do
try do try do
with {:ok, %Tesla.Env{body: html}} <- with {_, true} <- {:reachable, reachable?(instance_uri.host)},
{:ok, %Tesla.Env{body: html}} <-
Pleroma.HTTP.get(to_string(instance_uri), [{"accept", "text/html"}], pool: :media), Pleroma.HTTP.get(to_string(instance_uri), [{"accept", "text/html"}], pool: :media),
{_, [favicon_rel | _]} when is_binary(favicon_rel) <- {_, [favicon_rel | _]} when is_binary(favicon_rel) <-
{:parse, {:parse,
@ -175,7 +176,15 @@ defp scrape_favicon(%URI{} = instance_uri) do
{:merge, URI.merge(instance_uri, favicon_rel) |> to_string()} do {:merge, URI.merge(instance_uri, favicon_rel) |> to_string()} do
favicon favicon
else else
_ -> nil {:reachable, false} ->
Logger.debug(
"Instance.scrape_favicon(\"#{to_string(instance_uri)}\") ignored unreachable host"
)
nil
_ ->
nil
end end
rescue rescue
e -> e ->

View File

@ -12,6 +12,26 @@ defmodule Pleroma.ModerationLog do
import Ecto.Query import Ecto.Query
@type t :: %__MODULE__{}
@type log_subject :: Activity.t() | User.t() | list(User.t())
@type log_params :: %{
required(:actor) => User.t(),
required(:action) => String.t(),
optional(:subject) => log_subject(),
optional(:subject_actor) => User.t(),
optional(:subject_id) => String.t(),
optional(:subjects) => list(User.t()),
optional(:permission) => String.t(),
optional(:text) => String.t(),
optional(:sensitive) => String.t(),
optional(:visibility) => String.t(),
optional(:followed) => User.t(),
optional(:follower) => User.t(),
optional(:nicknames) => list(String.t()),
optional(:tags) => list(String.t()),
optional(:target) => String.t()
}
schema "moderation_log" do schema "moderation_log" do
field(:data, :map) field(:data, :map)
@ -90,203 +110,105 @@ defp parse_datetime(datetime) do
parsed_datetime parsed_datetime
end end
@spec insert_log(%{actor: User, subject: [User], action: String.t(), permission: String.t()}) :: defp prepare_log_data(%{actor: actor, action: action} = attrs) do
{:ok, ModerationLog} | {:error, any} %{
def insert_log(%{ "actor" => user_to_map(actor),
actor: %User{} = actor, "action" => action,
subject: subjects, "message" => ""
action: action,
permission: permission
}) do
%ModerationLog{
data: %{
"actor" => user_to_map(actor),
"subject" => user_to_map(subjects),
"action" => action,
"permission" => permission,
"message" => ""
}
} }
|> insert_log_entry_with_message() |> Pleroma.Maps.put_if_present("subject_actor", user_to_map(attrs[:subject_actor]))
end end
@spec insert_log(%{actor: User, subject: User, action: String.t()}) :: defp prepare_log_data(attrs), do: attrs
{:ok, ModerationLog} | {:error, any}
def insert_log(%{ @spec insert_log(log_params()) :: {:ok, ModerationLog} | {:error, any}
actor: %User{} = actor, def insert_log(%{actor: %User{}, subject: subjects, permission: permission} = attrs) do
action: "report_update", data =
subject: %Activity{data: %{"type" => "Flag"}} = subject attrs
}) do |> prepare_log_data
%ModerationLog{ |> Map.merge(%{"subject" => user_to_map(subjects), "permission" => permission})
data: %{
"actor" => user_to_map(actor), insert_log_entry_with_message(%ModerationLog{data: data})
"action" => "report_update",
"subject" => report_to_map(subject),
"message" => ""
}
}
|> insert_log_entry_with_message()
end end
@spec insert_log(%{actor: User, subject: Activity, action: String.t(), text: String.t()}) :: def insert_log(%{actor: %User{}, action: action, subject: %Activity{} = subject} = attrs)
{:ok, ModerationLog} | {:error, any} when action in ["report_note_delete", "report_update", "report_note"] do
def insert_log(%{ data =
actor: %User{} = actor, attrs
action: "report_note", |> prepare_log_data
subject: %Activity{} = subject, |> Pleroma.Maps.put_if_present("text", attrs[:text])
text: text |> Map.merge(%{"subject" => report_to_map(subject)})
}) do
%ModerationLog{ insert_log_entry_with_message(%ModerationLog{data: data})
data: %{
"actor" => user_to_map(actor),
"action" => "report_note",
"subject" => report_to_map(subject),
"text" => text
}
}
|> insert_log_entry_with_message()
end end
@spec insert_log(%{actor: User, subject: Activity, action: String.t(), text: String.t()}) :: def insert_log(
{:ok, ModerationLog} | {:error, any} %{
def insert_log(%{ actor: %User{},
actor: %User{} = actor, action: action,
action: "report_note_delete", subject: %Activity{} = subject,
subject: %Activity{} = subject, sensitive: sensitive,
text: text visibility: visibility
}) do } = attrs
%ModerationLog{ )
data: %{ when action == "status_update" do
"actor" => user_to_map(actor), data =
"action" => "report_note_delete", attrs
"subject" => report_to_map(subject), |> prepare_log_data
"text" => text |> Map.merge(%{
}
}
|> insert_log_entry_with_message()
end
@spec insert_log(%{
actor: User,
subject: Activity,
action: String.t(),
sensitive: String.t(),
visibility: String.t()
}) :: {:ok, ModerationLog} | {:error, any}
def insert_log(%{
actor: %User{} = actor,
action: "status_update",
subject: %Activity{} = subject,
sensitive: sensitive,
visibility: visibility
}) do
%ModerationLog{
data: %{
"actor" => user_to_map(actor),
"action" => "status_update",
"subject" => status_to_map(subject), "subject" => status_to_map(subject),
"sensitive" => sensitive, "sensitive" => sensitive,
"visibility" => visibility, "visibility" => visibility
"message" => "" })
}
} insert_log_entry_with_message(%ModerationLog{data: data})
|> insert_log_entry_with_message()
end end
@spec insert_log(%{actor: User, action: String.t(), subject_id: String.t()}) :: def insert_log(%{actor: %User{}, action: action, subject_id: subject_id} = attrs)
{:ok, ModerationLog} | {:error, any} when action == "status_delete" do
def insert_log(%{ data =
actor: %User{} = actor, attrs
action: "status_delete", |> prepare_log_data
subject_id: subject_id |> Map.merge(%{"subject_id" => subject_id})
}) do
%ModerationLog{ insert_log_entry_with_message(%ModerationLog{data: data})
data: %{
"actor" => user_to_map(actor),
"action" => "status_delete",
"subject_id" => subject_id,
"message" => ""
}
}
|> insert_log_entry_with_message()
end end
@spec insert_log(%{actor: User, subject: User, action: String.t()}) :: def insert_log(%{actor: %User{}, subject: subject, action: _action} = attrs) do
{:ok, ModerationLog} | {:error, any} data =
def insert_log(%{actor: %User{} = actor, subject: subject, action: action}) do attrs
%ModerationLog{ |> prepare_log_data
data: %{ |> Map.merge(%{"subject" => user_to_map(subject)})
"actor" => user_to_map(actor),
"action" => action, insert_log_entry_with_message(%ModerationLog{data: data})
"subject" => user_to_map(subject),
"message" => ""
}
}
|> insert_log_entry_with_message()
end end
@spec insert_log(%{actor: User, subjects: [User], action: String.t()}) :: def insert_log(%{actor: %User{}, subjects: subjects, action: _action} = attrs) do
{:ok, ModerationLog} | {:error, any} data =
def insert_log(%{actor: %User{} = actor, subjects: subjects, action: action}) do attrs
subjects = Enum.map(subjects, &user_to_map/1) |> prepare_log_data
|> Map.merge(%{"subjects" => user_to_map(subjects)})
%ModerationLog{ insert_log_entry_with_message(%ModerationLog{data: data})
data: %{
"actor" => user_to_map(actor),
"action" => action,
"subjects" => subjects,
"message" => ""
}
}
|> insert_log_entry_with_message()
end end
@spec insert_log(%{actor: User, action: String.t(), followed: User, follower: User}) :: def insert_log(
{:ok, ModerationLog} | {:error, any} %{
def insert_log(%{ actor: %User{},
actor: %User{} = actor, followed: %User{} = followed,
followed: %User{} = followed, follower: %User{} = follower,
follower: %User{} = follower, action: action
action: "follow" } = attrs
}) do )
%ModerationLog{ when action in ["unfollow", "follow"] do
data: %{ data =
"actor" => user_to_map(actor), attrs
"action" => "follow", |> prepare_log_data
"followed" => user_to_map(followed), |> Map.merge(%{"followed" => user_to_map(followed), "follower" => user_to_map(follower)})
"follower" => user_to_map(follower),
"message" => "" insert_log_entry_with_message(%ModerationLog{data: data})
}
}
|> insert_log_entry_with_message()
end end
@spec insert_log(%{actor: User, action: String.t(), followed: User, follower: User}) ::
{:ok, ModerationLog} | {:error, any}
def insert_log(%{
actor: %User{} = actor,
followed: %User{} = followed,
follower: %User{} = follower,
action: "unfollow"
}) do
%ModerationLog{
data: %{
"actor" => user_to_map(actor),
"action" => "unfollow",
"followed" => user_to_map(followed),
"follower" => user_to_map(follower),
"message" => ""
}
}
|> insert_log_entry_with_message()
end
@spec insert_log(%{
actor: User,
action: String.t(),
nicknames: [String.t()],
tags: [String.t()]
}) :: {:ok, ModerationLog} | {:error, any}
def insert_log(%{ def insert_log(%{
actor: %User{} = actor, actor: %User{} = actor,
nicknames: nicknames, nicknames: nicknames,
@ -305,27 +227,16 @@ def insert_log(%{
|> insert_log_entry_with_message() |> insert_log_entry_with_message()
end end
@spec insert_log(%{actor: User, action: String.t(), target: String.t()}) :: def insert_log(%{actor: %User{}, action: action, target: target} = attrs)
{:ok, ModerationLog} | {:error, any}
def insert_log(%{
actor: %User{} = actor,
action: action,
target: target
})
when action in ["relay_follow", "relay_unfollow"] do when action in ["relay_follow", "relay_unfollow"] do
%ModerationLog{ data =
data: %{ attrs
"actor" => user_to_map(actor), |> prepare_log_data
"action" => action, |> Map.merge(%{"target" => target})
"target" => target,
"message" => "" insert_log_entry_with_message(%ModerationLog{data: data})
}
}
|> insert_log_entry_with_message()
end end
@spec insert_log(%{actor: User, action: String.t(), subject_id: String.t()}) ::
{:ok, ModerationLog} | {:error, any}
def insert_log(%{actor: %User{} = actor, action: "chat_message_delete", subject_id: subject_id}) do def insert_log(%{actor: %User{} = actor, action: "chat_message_delete", subject_id: subject_id}) do
%ModerationLog{ %ModerationLog{
data: %{ data: %{
@ -345,32 +256,27 @@ defp insert_log_entry_with_message(entry) do
end end
defp user_to_map(users) when is_list(users) do defp user_to_map(users) when is_list(users) do
users |> Enum.map(&user_to_map/1) Enum.map(users, &user_to_map/1)
end end
defp user_to_map(%User{} = user) do defp user_to_map(%User{} = user) do
user user
|> Map.from_struct()
|> Map.take([:id, :nickname]) |> Map.take([:id, :nickname])
|> Map.new(fn {k, v} -> {Atom.to_string(k), v} end) |> Map.new(fn {k, v} -> {Atom.to_string(k), v} end)
|> Map.put("type", "user") |> Map.put("type", "user")
end end
defp user_to_map(_), do: nil
defp report_to_map(%Activity{} = report) do defp report_to_map(%Activity{} = report) do
%{ %{"type" => "report", "id" => report.id, "state" => report.data["state"]}
"type" => "report",
"id" => report.id,
"state" => report.data["state"]
}
end end
defp status_to_map(%Activity{} = status) do defp status_to_map(%Activity{} = status) do
%{ %{"type" => "status", "id" => status.id}
"type" => "status",
"id" => status.id
}
end end
@spec get_log_entry_message(ModerationLog.t()) :: String.t()
def get_log_entry_message(%ModerationLog{ def get_log_entry_message(%ModerationLog{
data: %{ data: %{
"actor" => %{"nickname" => actor_nickname}, "actor" => %{"nickname" => actor_nickname},
@ -382,7 +288,6 @@ def get_log_entry_message(%ModerationLog{
"@#{actor_nickname} made @#{follower_nickname} #{action} @#{followed_nickname}" "@#{actor_nickname} made @#{follower_nickname} #{action} @#{followed_nickname}"
end end
@spec get_log_entry_message(ModerationLog) :: String.t()
def get_log_entry_message(%ModerationLog{ def get_log_entry_message(%ModerationLog{
data: %{ data: %{
"actor" => %{"nickname" => actor_nickname}, "actor" => %{"nickname" => actor_nickname},
@ -393,7 +298,6 @@ def get_log_entry_message(%ModerationLog{
"@#{actor_nickname} deleted users: #{users_to_nicknames_string(subjects)}" "@#{actor_nickname} deleted users: #{users_to_nicknames_string(subjects)}"
end end
@spec get_log_entry_message(ModerationLog) :: String.t()
def get_log_entry_message(%ModerationLog{ def get_log_entry_message(%ModerationLog{
data: %{ data: %{
"actor" => %{"nickname" => actor_nickname}, "actor" => %{"nickname" => actor_nickname},
@ -404,7 +308,6 @@ def get_log_entry_message(%ModerationLog{
"@#{actor_nickname} created users: #{users_to_nicknames_string(subjects)}" "@#{actor_nickname} created users: #{users_to_nicknames_string(subjects)}"
end end
@spec get_log_entry_message(ModerationLog) :: String.t()
def get_log_entry_message(%ModerationLog{ def get_log_entry_message(%ModerationLog{
data: %{ data: %{
"actor" => %{"nickname" => actor_nickname}, "actor" => %{"nickname" => actor_nickname},
@ -415,7 +318,6 @@ def get_log_entry_message(%ModerationLog{
"@#{actor_nickname} activated users: #{users_to_nicknames_string(users)}" "@#{actor_nickname} activated users: #{users_to_nicknames_string(users)}"
end end
@spec get_log_entry_message(ModerationLog) :: String.t()
def get_log_entry_message(%ModerationLog{ def get_log_entry_message(%ModerationLog{
data: %{ data: %{
"actor" => %{"nickname" => actor_nickname}, "actor" => %{"nickname" => actor_nickname},
@ -426,7 +328,6 @@ def get_log_entry_message(%ModerationLog{
"@#{actor_nickname} deactivated users: #{users_to_nicknames_string(users)}" "@#{actor_nickname} deactivated users: #{users_to_nicknames_string(users)}"
end end
@spec get_log_entry_message(ModerationLog) :: String.t()
def get_log_entry_message(%ModerationLog{ def get_log_entry_message(%ModerationLog{
data: %{ data: %{
"actor" => %{"nickname" => actor_nickname}, "actor" => %{"nickname" => actor_nickname},
@ -437,7 +338,6 @@ def get_log_entry_message(%ModerationLog{
"@#{actor_nickname} approved users: #{users_to_nicknames_string(users)}" "@#{actor_nickname} approved users: #{users_to_nicknames_string(users)}"
end end
@spec get_log_entry_message(ModerationLog) :: String.t()
def get_log_entry_message(%ModerationLog{ def get_log_entry_message(%ModerationLog{
data: %{ data: %{
"actor" => %{"nickname" => actor_nickname}, "actor" => %{"nickname" => actor_nickname},
@ -451,7 +351,6 @@ def get_log_entry_message(%ModerationLog{
"@#{actor_nickname} added tags: #{tags_string} to users: #{nicknames_to_string(nicknames)}" "@#{actor_nickname} added tags: #{tags_string} to users: #{nicknames_to_string(nicknames)}"
end end
@spec get_log_entry_message(ModerationLog) :: String.t()
def get_log_entry_message(%ModerationLog{ def get_log_entry_message(%ModerationLog{
data: %{ data: %{
"actor" => %{"nickname" => actor_nickname}, "actor" => %{"nickname" => actor_nickname},
@ -465,7 +364,6 @@ def get_log_entry_message(%ModerationLog{
"@#{actor_nickname} removed tags: #{tags_string} from users: #{nicknames_to_string(nicknames)}" "@#{actor_nickname} removed tags: #{tags_string} from users: #{nicknames_to_string(nicknames)}"
end end
@spec get_log_entry_message(ModerationLog) :: String.t()
def get_log_entry_message(%ModerationLog{ def get_log_entry_message(%ModerationLog{
data: %{ data: %{
"actor" => %{"nickname" => actor_nickname}, "actor" => %{"nickname" => actor_nickname},
@ -477,7 +375,6 @@ def get_log_entry_message(%ModerationLog{
"@#{actor_nickname} made #{users_to_nicknames_string(users)} #{permission}" "@#{actor_nickname} made #{users_to_nicknames_string(users)} #{permission}"
end end
@spec get_log_entry_message(ModerationLog) :: String.t()
def get_log_entry_message(%ModerationLog{ def get_log_entry_message(%ModerationLog{
data: %{ data: %{
"actor" => %{"nickname" => actor_nickname}, "actor" => %{"nickname" => actor_nickname},
@ -489,7 +386,6 @@ def get_log_entry_message(%ModerationLog{
"@#{actor_nickname} revoked #{permission} role from #{users_to_nicknames_string(users)}" "@#{actor_nickname} revoked #{permission} role from #{users_to_nicknames_string(users)}"
end end
@spec get_log_entry_message(ModerationLog) :: String.t()
def get_log_entry_message(%ModerationLog{ def get_log_entry_message(%ModerationLog{
data: %{ data: %{
"actor" => %{"nickname" => actor_nickname}, "actor" => %{"nickname" => actor_nickname},
@ -500,7 +396,6 @@ def get_log_entry_message(%ModerationLog{
"@#{actor_nickname} followed relay: #{target}" "@#{actor_nickname} followed relay: #{target}"
end end
@spec get_log_entry_message(ModerationLog) :: String.t()
def get_log_entry_message(%ModerationLog{ def get_log_entry_message(%ModerationLog{
data: %{ data: %{
"actor" => %{"nickname" => actor_nickname}, "actor" => %{"nickname" => actor_nickname},
@ -511,42 +406,48 @@ def get_log_entry_message(%ModerationLog{
"@#{actor_nickname} unfollowed relay: #{target}" "@#{actor_nickname} unfollowed relay: #{target}"
end end
@spec get_log_entry_message(ModerationLog) :: String.t() def get_log_entry_message(
def get_log_entry_message(%ModerationLog{ %ModerationLog{
data: %{ data: %{
"actor" => %{"nickname" => actor_nickname}, "actor" => %{"nickname" => actor_nickname},
"action" => "report_update", "action" => "report_update",
"subject" => %{"id" => subject_id, "state" => state, "type" => "report"} "subject" => %{"id" => subject_id, "state" => state, "type" => "report"}
} }
}) do } = log
"@#{actor_nickname} updated report ##{subject_id} with '#{state}' state" ) do
"@#{actor_nickname} updated report ##{subject_id}" <>
subject_actor_nickname(log, " (on user ", ")") <>
" with '#{state}' state"
end end
@spec get_log_entry_message(ModerationLog) :: String.t() def get_log_entry_message(
def get_log_entry_message(%ModerationLog{ %ModerationLog{
data: %{ data: %{
"actor" => %{"nickname" => actor_nickname}, "actor" => %{"nickname" => actor_nickname},
"action" => "report_note", "action" => "report_note",
"subject" => %{"id" => subject_id, "type" => "report"}, "subject" => %{"id" => subject_id, "type" => "report"},
"text" => text "text" => text
} }
}) do } = log
"@#{actor_nickname} added note '#{text}' to report ##{subject_id}" ) do
"@#{actor_nickname} added note '#{text}' to report ##{subject_id}" <>
subject_actor_nickname(log, " on user ")
end end
@spec get_log_entry_message(ModerationLog) :: String.t() def get_log_entry_message(
def get_log_entry_message(%ModerationLog{ %ModerationLog{
data: %{ data: %{
"actor" => %{"nickname" => actor_nickname}, "actor" => %{"nickname" => actor_nickname},
"action" => "report_note_delete", "action" => "report_note_delete",
"subject" => %{"id" => subject_id, "type" => "report"}, "subject" => %{"id" => subject_id, "type" => "report"},
"text" => text "text" => text
} }
}) do } = log
"@#{actor_nickname} deleted note '#{text}' from report ##{subject_id}" ) do
"@#{actor_nickname} deleted note '#{text}' from report ##{subject_id}" <>
subject_actor_nickname(log, " on user ")
end end
@spec get_log_entry_message(ModerationLog) :: String.t()
def get_log_entry_message(%ModerationLog{ def get_log_entry_message(%ModerationLog{
data: %{ data: %{
"actor" => %{"nickname" => actor_nickname}, "actor" => %{"nickname" => actor_nickname},
@ -559,7 +460,6 @@ def get_log_entry_message(%ModerationLog{
"@#{actor_nickname} updated status ##{subject_id}, set visibility: '#{visibility}'" "@#{actor_nickname} updated status ##{subject_id}, set visibility: '#{visibility}'"
end end
@spec get_log_entry_message(ModerationLog) :: String.t()
def get_log_entry_message(%ModerationLog{ def get_log_entry_message(%ModerationLog{
data: %{ data: %{
"actor" => %{"nickname" => actor_nickname}, "actor" => %{"nickname" => actor_nickname},
@ -572,7 +472,6 @@ def get_log_entry_message(%ModerationLog{
"@#{actor_nickname} updated status ##{subject_id}, set sensitive: '#{sensitive}'" "@#{actor_nickname} updated status ##{subject_id}, set sensitive: '#{sensitive}'"
end end
@spec get_log_entry_message(ModerationLog) :: String.t()
def get_log_entry_message(%ModerationLog{ def get_log_entry_message(%ModerationLog{
data: %{ data: %{
"actor" => %{"nickname" => actor_nickname}, "actor" => %{"nickname" => actor_nickname},
@ -587,7 +486,6 @@ def get_log_entry_message(%ModerationLog{
}'" }'"
end end
@spec get_log_entry_message(ModerationLog) :: String.t()
def get_log_entry_message(%ModerationLog{ def get_log_entry_message(%ModerationLog{
data: %{ data: %{
"actor" => %{"nickname" => actor_nickname}, "actor" => %{"nickname" => actor_nickname},
@ -598,7 +496,6 @@ def get_log_entry_message(%ModerationLog{
"@#{actor_nickname} deleted status ##{subject_id}" "@#{actor_nickname} deleted status ##{subject_id}"
end end
@spec get_log_entry_message(ModerationLog) :: String.t()
def get_log_entry_message(%ModerationLog{ def get_log_entry_message(%ModerationLog{
data: %{ data: %{
"actor" => %{"nickname" => actor_nickname}, "actor" => %{"nickname" => actor_nickname},
@ -609,7 +506,6 @@ def get_log_entry_message(%ModerationLog{
"@#{actor_nickname} forced password reset for users: #{users_to_nicknames_string(subjects)}" "@#{actor_nickname} forced password reset for users: #{users_to_nicknames_string(subjects)}"
end end
@spec get_log_entry_message(ModerationLog) :: String.t()
def get_log_entry_message(%ModerationLog{ def get_log_entry_message(%ModerationLog{
data: %{ data: %{
"actor" => %{"nickname" => actor_nickname}, "actor" => %{"nickname" => actor_nickname},
@ -620,7 +516,6 @@ def get_log_entry_message(%ModerationLog{
"@#{actor_nickname} confirmed email for users: #{users_to_nicknames_string(subjects)}" "@#{actor_nickname} confirmed email for users: #{users_to_nicknames_string(subjects)}"
end end
@spec get_log_entry_message(ModerationLog) :: String.t()
def get_log_entry_message(%ModerationLog{ def get_log_entry_message(%ModerationLog{
data: %{ data: %{
"actor" => %{"nickname" => actor_nickname}, "actor" => %{"nickname" => actor_nickname},
@ -633,7 +528,6 @@ def get_log_entry_message(%ModerationLog{
}" }"
end end
@spec get_log_entry_message(ModerationLog) :: String.t()
def get_log_entry_message(%ModerationLog{ def get_log_entry_message(%ModerationLog{
data: %{ data: %{
"actor" => %{"nickname" => actor_nickname}, "actor" => %{"nickname" => actor_nickname},
@ -644,7 +538,6 @@ def get_log_entry_message(%ModerationLog{
"@#{actor_nickname} updated users: #{users_to_nicknames_string(subjects)}" "@#{actor_nickname} updated users: #{users_to_nicknames_string(subjects)}"
end end
@spec get_log_entry_message(ModerationLog) :: String.t()
def get_log_entry_message(%ModerationLog{ def get_log_entry_message(%ModerationLog{
data: %{ data: %{
"actor" => %{"nickname" => actor_nickname}, "actor" => %{"nickname" => actor_nickname},
@ -676,4 +569,16 @@ defp users_to_nicknames_string(users) do
|> Enum.map(&"@#{&1["nickname"]}") |> Enum.map(&"@#{&1["nickname"]}")
|> Enum.join(", ") |> Enum.join(", ")
end end
defp subject_actor_nickname(%ModerationLog{data: data}, prefix_msg, postfix_msg \\ "") do
case data do
%{"subject_actor" => %{"nickname" => subject_actor}} ->
[prefix_msg, "@#{subject_actor}", postfix_msg]
|> Enum.reject(&(&1 == ""))
|> Enum.join()
_ ->
""
end
end
end end

View File

@ -473,6 +473,18 @@ def remote_user_changeset(struct \\ %User{local: false}, params) do
|> validate_length(:bio, max: bio_limit) |> validate_length(:bio, max: bio_limit)
|> validate_length(:name, max: name_limit) |> validate_length(:name, max: name_limit)
|> validate_fields(true) |> validate_fields(true)
|> validate_non_local()
end
defp validate_non_local(cng) do
local? = get_field(cng, :local)
if local? do
cng
|> add_error(:local, "User is local, can't update with this changeset.")
else
cng
end
end end
def update_changeset(struct, params \\ %{}) do def update_changeset(struct, params \\ %{}) do
@ -914,7 +926,7 @@ def maybe_direct_follow(%User{} = follower, %User{} = followed) do
if not ap_enabled?(followed) do if not ap_enabled?(followed) do
follow(follower, followed) follow(follower, followed)
else else
{:ok, follower} {:ok, follower, followed}
end end
end end
@ -940,11 +952,6 @@ def follow(%User{} = follower, %User{} = followed, state \\ :follow_accept) do
true -> true ->
FollowingRelationship.follow(follower, followed, state) FollowingRelationship.follow(follower, followed, state)
{:ok, _} = update_follower_count(followed)
follower
|> update_following_count()
end end
end end
@ -968,11 +975,6 @@ defp do_unfollow(%User{} = follower, %User{} = followed) do
case get_follow_state(follower, followed) do case get_follow_state(follower, followed) do
state when state in [:follow_pending, :follow_accept] -> state when state in [:follow_pending, :follow_accept] ->
FollowingRelationship.unfollow(follower, followed) FollowingRelationship.unfollow(follower, followed)
{:ok, followed} = update_follower_count(followed)
{:ok, follower} = update_following_count(follower)
{:ok, follower, followed}
nil -> nil ->
{:error, "Not subscribed!"} {:error, "Not subscribed!"}
@ -2449,4 +2451,8 @@ def sanitize_html(%User{} = user, filter) do
|> Map.put(:bio, HTML.filter_tags(user.bio, filter)) |> Map.put(:bio, HTML.filter_tags(user.bio, filter))
|> Map.put(:fields, fields) |> Map.put(:fields, fields)
end end
def get_host(%User{ap_id: ap_id} = _user) do
URI.parse(ap_id).host
end
end end

View File

@ -45,7 +45,7 @@ def perform(:follow_import, %User{} = follower, [_ | _] = identifiers) do
identifiers, identifiers,
fn identifier -> fn identifier ->
with {:ok, %User{} = followed} <- User.get_or_fetch(identifier), with {:ok, %User{} = followed} <- User.get_or_fetch(identifier),
{:ok, follower} <- User.maybe_direct_follow(follower, followed), {:ok, follower, followed} <- User.maybe_direct_follow(follower, followed),
{:ok, _, _, _} <- CommonAPI.follow(follower, followed) do {:ok, _, _, _} <- CommonAPI.follow(follower, followed) do
followed followed
else else

View File

@ -3,6 +3,14 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Utils do defmodule Pleroma.Utils do
@posix_error_codes ~w(
eacces eagain ebadf ebadmsg ebusy edeadlk edeadlock edquot eexist efault
efbig eftype eintr einval eio eisdir eloop emfile emlink emultihop
enametoolong enfile enobufs enodev enolck enolink enoent enomem enospc
enosr enostr enosys enotblk enotdir enotsup enxio eopnotsupp eoverflow
eperm epipe erange erofs espipe esrch estale etxtbsy exdev
)a
def compile_dir(dir) when is_binary(dir) do def compile_dir(dir) when is_binary(dir) do
dir dir
|> File.ls!() |> File.ls!()
@ -44,4 +52,12 @@ def tmp_dir(prefix \\ "") do
error -> error error -> error
end end
end end
@spec posix_error_message(atom()) :: binary()
def posix_error_message(code) when code in @posix_error_codes do
error_message = Gettext.dgettext(Pleroma.Web.Gettext, "posix_errors", "#{code}")
"(POSIX error: #{error_message})"
end
def posix_error_message(_), do: ""
end end

View File

@ -20,6 +20,7 @@ defmodule Pleroma.Web do
below. below.
""" """
alias Pleroma.Helpers.AuthHelper
alias Pleroma.Web.Plugs.EnsureAuthenticatedPlug alias Pleroma.Web.Plugs.EnsureAuthenticatedPlug
alias Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug alias Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug
alias Pleroma.Web.Plugs.ExpectAuthenticatedCheckPlug alias Pleroma.Web.Plugs.ExpectAuthenticatedCheckPlug
@ -75,7 +76,7 @@ defp action(conn, params) do
defp maybe_drop_authentication_if_oauth_check_ignored(conn) do defp maybe_drop_authentication_if_oauth_check_ignored(conn) do
if PlugHelper.plug_called?(conn, ExpectPublicOrAuthenticatedCheckPlug) and if PlugHelper.plug_called?(conn, ExpectPublicOrAuthenticatedCheckPlug) and
not PlugHelper.plug_called_or_skipped?(conn, OAuthScopesPlug) do not PlugHelper.plug_called_or_skipped?(conn, OAuthScopesPlug) do
OAuthScopesPlug.drop_auth_info(conn) AuthHelper.drop_auth_info(conn)
else else
conn conn
end end

View File

@ -47,10 +47,9 @@ def handle(
%User{} = followed <- User.get_cached_by_ap_id(actor), %User{} = followed <- User.get_cached_by_ap_id(actor),
%User{} = follower <- User.get_cached_by_ap_id(follower_id), %User{} = follower <- User.get_cached_by_ap_id(follower_id),
{:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "accept"), {:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "accept"),
{:ok, _relationship} <- FollowingRelationship.update(follower, followed, :follow_accept) do {:ok, _follower, followed} <-
FollowingRelationship.update(follower, followed, :follow_accept) do
Notification.update_notification_type(followed, follow_activity) Notification.update_notification_type(followed, follow_activity)
User.update_follower_count(followed)
User.update_following_count(follower)
end end
{:ok, object, meta} {:ok, object, meta}
@ -99,7 +98,7 @@ def handle(
) do ) do
with %User{} = follower <- User.get_cached_by_ap_id(following_user), with %User{} = follower <- User.get_cached_by_ap_id(following_user),
%User{} = followed <- User.get_cached_by_ap_id(followed_user), %User{} = followed <- User.get_cached_by_ap_id(followed_user),
{_, {:ok, _}, _, _} <- {_, {:ok, _, _}, _, _} <-
{:following, User.follow(follower, followed, :follow_pending), follower, followed} do {:following, User.follow(follower, followed, :follow_pending), follower, followed} do
if followed.local && !followed.is_locked do if followed.local && !followed.is_locked do
{:ok, accept_data, _} = Builder.accept(followed, object) {:ok, accept_data, _} = Builder.accept(followed, object)

View File

@ -50,10 +50,13 @@ def update(%{assigns: %{user: admin}, body_params: %{reports: reports}} = conn,
Enum.map(reports, fn report -> Enum.map(reports, fn report ->
case CommonAPI.update_report_state(report.id, report.state) do case CommonAPI.update_report_state(report.id, report.state) do
{:ok, activity} -> {:ok, activity} ->
report = Activity.get_by_id_with_user_actor(activity.id)
ModerationLog.insert_log(%{ ModerationLog.insert_log(%{
action: "report_update", action: "report_update",
actor: admin, actor: admin,
subject: activity subject: activity,
subject_actor: report.user_actor
}) })
activity activity
@ -73,11 +76,13 @@ def update(%{assigns: %{user: admin}, body_params: %{reports: reports}} = conn,
def notes_create(%{assigns: %{user: user}, body_params: %{content: content}} = conn, %{ def notes_create(%{assigns: %{user: user}, body_params: %{content: content}} = conn, %{
id: report_id id: report_id
}) do }) do
with {:ok, _} <- ReportNote.create(user.id, report_id, content) do with {:ok, _} <- ReportNote.create(user.id, report_id, content),
report <- Activity.get_by_id_with_user_actor(report_id) do
ModerationLog.insert_log(%{ ModerationLog.insert_log(%{
action: "report_note", action: "report_note",
actor: user, actor: user,
subject: Activity.get_by_id(report_id), subject: report,
subject_actor: report.user_actor,
text: content text: content
}) })
@ -91,11 +96,13 @@ def notes_delete(%{assigns: %{user: user}} = conn, %{
id: note_id, id: note_id,
report_id: report_id report_id: report_id
}) do }) do
with {:ok, note} <- ReportNote.destroy(note_id) do with {:ok, note} <- ReportNote.destroy(note_id),
report <- Activity.get_by_id_with_user_actor(report_id) do
ModerationLog.insert_log(%{ ModerationLog.insert_log(%{
action: "report_note_delete", action: "report_note_delete",
actor: user, actor: user,
subject: Activity.get_by_id(report_id), subject: report,
subject_actor: report.user_actor,
text: note.content text: note.content
}) })

View File

@ -27,7 +27,8 @@ def create_operation do
422 => Operation.response("Unprocessable Entity", "application/json", ApiError), 422 => Operation.response("Unprocessable Entity", "application/json", ApiError),
404 => Operation.response("Not Found", "application/json", ApiError), 404 => Operation.response("Not Found", "application/json", ApiError),
400 => Operation.response("Bad Request", "application/json", ApiError), 400 => Operation.response("Bad Request", "application/json", ApiError),
409 => Operation.response("Conflict", "application/json", ApiError) 409 => Operation.response("Conflict", "application/json", ApiError),
500 => Operation.response("Error", "application/json", ApiError)
} }
} }
end end

View File

@ -169,7 +169,8 @@ def delete_operation do
responses: %{ responses: %{
200 => ok_response(), 200 => ok_response(),
400 => Operation.response("Bad Request", "application/json", ApiError), 400 => Operation.response("Bad Request", "application/json", ApiError),
404 => Operation.response("Not Found", "application/json", ApiError) 404 => Operation.response("Not Found", "application/json", ApiError),
500 => Operation.response("Error", "application/json", ApiError)
} }
} }
end end
@ -184,7 +185,8 @@ def update_operation do
parameters: [name_param()], parameters: [name_param()],
responses: %{ responses: %{
200 => Operation.response("Metadata", "application/json", metadata()), 200 => Operation.response("Metadata", "application/json", metadata()),
400 => Operation.response("Bad Request", "application/json", ApiError) 400 => Operation.response("Bad Request", "application/json", ApiError),
500 => Operation.response("Error", "application/json", ApiError)
} }
} }
end end

View File

@ -51,7 +51,7 @@ def most_recent_update(activities, user) do
def feed_logo do def feed_logo do
case Pleroma.Config.get([:feed, :logo]) do case Pleroma.Config.get([:feed, :logo]) do
nil -> nil ->
"#{Pleroma.Web.base_url()}/static/logo.png" "#{Pleroma.Web.base_url()}/static/logo.svg"
logo -> logo ->
"#{Pleroma.Web.base_url()}#{logo}" "#{Pleroma.Web.base_url()}#{logo}"

View File

@ -6,6 +6,8 @@ defmodule Pleroma.Web.MastoFEController do
use Pleroma.Web, :controller use Pleroma.Web, :controller
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.MastodonAPI.AuthController
alias Pleroma.Web.OAuth.Token
alias Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug alias Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug
alias Pleroma.Web.Plugs.OAuthScopesPlug alias Pleroma.Web.Plugs.OAuthScopesPlug
@ -26,27 +28,27 @@ defmodule Pleroma.Web.MastoFEController do
) )
@doc "GET /web/*path" @doc "GET /web/*path"
def index(%{assigns: %{user: user, token: token}} = conn, _params)
when not is_nil(user) and not is_nil(token) do
conn
|> put_layout(false)
|> render("index.html",
token: token.token,
user: user,
custom_emojis: Pleroma.Emoji.get_all()
)
end
def index(conn, _params) do def index(conn, _params) do
conn with %{assigns: %{user: %User{} = user, token: %Token{app_id: token_app_id} = token}} <- conn,
|> put_session(:return_to, conn.request_path) {:ok, %{id: ^token_app_id}} <- AuthController.local_mastofe_app() do
|> redirect(to: "/web/login") conn
|> put_layout(false)
|> render("index.html",
token: token.token,
user: user,
custom_emojis: Pleroma.Emoji.get_all()
)
else
_ ->
conn
|> put_session(:return_to, conn.request_path)
|> redirect(to: "/web/login")
end
end end
@doc "GET /web/manifest.json" @doc "GET /web/manifest.json"
def manifest(conn, _params) do def manifest(conn, _params) do
conn render(conn, "manifest.json")
|> render("manifest.json")
end end
@doc "PUT /api/web/settings: Backend-obscure settings blob for MastoFE, don't parse/reuse elsewhere" @doc "PUT /api/web/settings: Backend-obscure settings blob for MastoFE, don't parse/reuse elsewhere"

View File

@ -25,7 +25,6 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
alias Pleroma.Web.MastodonAPI.MastodonAPIController alias Pleroma.Web.MastodonAPI.MastodonAPIController
alias Pleroma.Web.MastodonAPI.StatusView alias Pleroma.Web.MastodonAPI.StatusView
alias Pleroma.Web.OAuth.OAuthController alias Pleroma.Web.OAuth.OAuthController
alias Pleroma.Web.OAuth.OAuthView
alias Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug alias Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug
alias Pleroma.Web.Plugs.OAuthScopesPlug alias Pleroma.Web.Plugs.OAuthScopesPlug
alias Pleroma.Web.Plugs.RateLimiter alias Pleroma.Web.Plugs.RateLimiter
@ -103,7 +102,7 @@ def create(%{assigns: %{app: app}, body_params: params} = conn, _params) do
{:ok, user} <- TwitterAPI.register_user(params), {:ok, user} <- TwitterAPI.register_user(params),
{_, {:ok, token}} <- {_, {:ok, token}} <-
{:login, OAuthController.login(user, app, app.scopes)} do {:login, OAuthController.login(user, app, app.scopes)} do
json(conn, OAuthView.render("token.json", %{user: user, token: token})) OAuthController.after_token_exchange(conn, %{user: user, token: token})
else else
{:login, {:account_status, :confirmation_pending}} -> {:login, {:account_status, :confirmation_pending}} ->
json_response(conn, :ok, %{ json_response(conn, :ok, %{

View File

@ -7,10 +7,13 @@ defmodule Pleroma.Web.MastodonAPI.AuthController do
import Pleroma.Web.ControllerHelper, only: [json_response: 3] import Pleroma.Web.ControllerHelper, only: [json_response: 3]
alias Pleroma.Helpers.AuthHelper
alias Pleroma.Helpers.UriHelper
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.OAuth.App alias Pleroma.Web.OAuth.App
alias Pleroma.Web.OAuth.Authorization alias Pleroma.Web.OAuth.Authorization
alias Pleroma.Web.OAuth.Token alias Pleroma.Web.OAuth.Token
alias Pleroma.Web.OAuth.Token.Strategy.Revoke, as: RevokeToken
alias Pleroma.Web.TwitterAPI.TwitterAPI alias Pleroma.Web.TwitterAPI.TwitterAPI
action_fallback(Pleroma.Web.MastodonAPI.FallbackController) action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
@ -20,24 +23,35 @@ defmodule Pleroma.Web.MastodonAPI.AuthController do
@local_mastodon_name "Mastodon-Local" @local_mastodon_name "Mastodon-Local"
@doc "GET /web/login" @doc "GET /web/login"
def login(%{assigns: %{user: %User{}}} = conn, _params) do # Local Mastodon FE login callback action
redirect(conn, to: local_mastodon_root_path(conn)) def login(conn, %{"code" => auth_token} = params) do
end with {:ok, app} <- local_mastofe_app(),
# Local Mastodon FE login init action
def login(conn, %{"code" => auth_token}) do
with {:ok, app} <- get_or_make_app(),
{:ok, auth} <- Authorization.get_by_token(app, auth_token), {:ok, auth} <- Authorization.get_by_token(app, auth_token),
{:ok, token} <- Token.exchange_token(app, auth) do {:ok, oauth_token} <- Token.exchange_token(app, auth) do
redirect_to =
conn
|> local_mastodon_post_login_path()
|> UriHelper.modify_uri_params(%{"access_token" => oauth_token.token})
conn conn
|> put_session(:oauth_token, token.token) |> AuthHelper.put_session_token(oauth_token.token)
|> redirect(to: local_mastodon_root_path(conn)) |> redirect(to: redirect_to)
else
_ -> redirect_to_oauth_form(conn, params)
end end
end end
# Local Mastodon FE callback action def login(conn, params) do
def login(conn, _) do with %{assigns: %{user: %User{}, token: %Token{app_id: app_id}}} <- conn,
with {:ok, app} <- get_or_make_app() do {:ok, %{id: ^app_id}} <- local_mastofe_app() do
redirect(conn, to: local_mastodon_post_login_path(conn))
else
_ -> redirect_to_oauth_form(conn, params)
end
end
defp redirect_to_oauth_form(conn, _params) do
with {:ok, app} <- local_mastofe_app() do
path = path =
o_auth_path(conn, :authorize, o_auth_path(conn, :authorize,
response_type: "code", response_type: "code",
@ -52,9 +66,16 @@ def login(conn, _) do
@doc "DELETE /auth/sign_out" @doc "DELETE /auth/sign_out"
def logout(conn, _) do def logout(conn, _) do
conn conn =
|> clear_session with %{assigns: %{token: %Token{} = oauth_token}} <- conn,
|> redirect(to: "/") session_token = AuthHelper.get_session_token(conn),
{:ok, %Token{token: ^session_token}} <- RevokeToken.revoke(oauth_token) do
AuthHelper.delete_session_token(conn)
else
_ -> conn
end
redirect(conn, to: "/")
end end
@doc "POST /auth/password" @doc "POST /auth/password"
@ -66,7 +87,7 @@ def password_reset(conn, params) do
json_response(conn, :no_content, "") json_response(conn, :no_content, "")
end end
defp local_mastodon_root_path(conn) do defp local_mastodon_post_login_path(conn) do
case get_session(conn, :return_to) do case get_session(conn, :return_to) do
nil -> nil ->
masto_fe_path(conn, :index, ["getting-started"]) masto_fe_path(conn, :index, ["getting-started"])
@ -77,9 +98,11 @@ defp local_mastodon_root_path(conn) do
end end
end end
@spec get_or_make_app() :: {:ok, App.t()} | {:error, Ecto.Changeset.t()} @spec local_mastofe_app() :: {:ok, App.t()} | {:error, Ecto.Changeset.t()}
defp get_or_make_app do def local_mastofe_app do
%{client_name: @local_mastodon_name, redirect_uris: "."} App.get_or_make(
|> App.get_or_make(["read", "write", "follow", "push", "admin"]) %{client_name: @local_mastodon_name, redirect_uris: "."},
["read", "write", "follow", "push", "admin"]
)
end end
end end

View File

@ -187,18 +187,14 @@ defp do_render("show.json", %{user: user} = opts) do
header_static = User.banner_url(user) |> MediaProxy.preview_url(static: true) header_static = User.banner_url(user) |> MediaProxy.preview_url(static: true)
following_count = following_count =
if !user.hide_follows_count or !user.hide_follows or opts[:for] == user do if !user.hide_follows_count or !user.hide_follows or opts[:for] == user,
user.following_count || 0 do: user.following_count,
else else: 0
0
end
followers_count = followers_count =
if !user.hide_followers_count or !user.hide_followers or opts[:for] == user do if !user.hide_followers_count or !user.hide_followers or opts[:for] == user,
user.follower_count || 0 do: user.follower_count,
else else: 0
0
end
bot = user.actor_type == "Service" bot = user.actor_type == "Service"

View File

@ -9,6 +9,7 @@ defmodule Pleroma.Web.OAuth.Authorization do
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.OAuth.App alias Pleroma.Web.OAuth.App
alias Pleroma.Web.OAuth.Authorization alias Pleroma.Web.OAuth.Authorization
alias Pleroma.Web.OAuth.Token
import Ecto.Changeset import Ecto.Changeset
import Ecto.Query import Ecto.Query
@ -53,7 +54,8 @@ defp add_token(changeset) do
end end
defp add_lifetime(changeset) do defp add_lifetime(changeset) do
put_change(changeset, :valid_until, NaiveDateTime.add(NaiveDateTime.utc_now(), 60 * 10)) lifespan = Token.lifespan()
put_change(changeset, :valid_until, NaiveDateTime.add(NaiveDateTime.utc_now(), lifespan))
end end
@spec use_changeset(Authtorizatiton.t(), map()) :: Changeset.t() @spec use_changeset(Authtorizatiton.t(), map()) :: Changeset.t()

View File

@ -13,7 +13,6 @@ defmodule Pleroma.Web.OAuth.MFAController do
alias Pleroma.Web.Auth.TOTPAuthenticator alias Pleroma.Web.Auth.TOTPAuthenticator
alias Pleroma.Web.OAuth.MFAView, as: View alias Pleroma.Web.OAuth.MFAView, as: View
alias Pleroma.Web.OAuth.OAuthController alias Pleroma.Web.OAuth.OAuthController
alias Pleroma.Web.OAuth.OAuthView
alias Pleroma.Web.OAuth.Token alias Pleroma.Web.OAuth.Token
plug(:fetch_session when action in [:show, :verify]) plug(:fetch_session when action in [:show, :verify])
@ -75,7 +74,7 @@ def challenge(conn, %{"mfa_token" => mfa_token} = params) do
{:ok, %{user: user, authorization: auth}} <- MFA.Token.validate(mfa_token), {:ok, %{user: user, authorization: auth}} <- MFA.Token.validate(mfa_token),
{:ok, _} <- validates_challenge(user, params), {:ok, _} <- validates_challenge(user, params),
{:ok, token} <- Token.exchange_token(app, auth) do {:ok, token} <- Token.exchange_token(app, auth) do
json(conn, OAuthView.render("token.json", %{user: user, token: token})) OAuthController.after_token_exchange(conn, %{user: user, token: token})
else else
_error -> _error ->
conn conn

View File

@ -5,6 +5,7 @@
defmodule Pleroma.Web.OAuth.OAuthController do defmodule Pleroma.Web.OAuth.OAuthController do
use Pleroma.Web, :controller use Pleroma.Web, :controller
alias Pleroma.Helpers.AuthHelper
alias Pleroma.Helpers.UriHelper alias Pleroma.Helpers.UriHelper
alias Pleroma.Maps alias Pleroma.Maps
alias Pleroma.MFA alias Pleroma.MFA
@ -79,6 +80,13 @@ defp do_authorize(%Plug.Conn{} = conn, params) do
available_scopes = (app && app.scopes) || [] available_scopes = (app && app.scopes) || []
scopes = Scopes.fetch_scopes(params, available_scopes) scopes = Scopes.fetch_scopes(params, available_scopes)
user =
with %{assigns: %{user: %User{} = user}} <- conn do
user
else
_ -> nil
end
scopes = scopes =
if scopes == [] do if scopes == [] do
available_scopes available_scopes
@ -88,6 +96,8 @@ defp do_authorize(%Plug.Conn{} = conn, params) do
# Note: `params` might differ from `conn.params`; use `@params` not `@conn.params` in template # Note: `params` might differ from `conn.params`; use `@params` not `@conn.params` in template
render(conn, Authenticator.auth_template(), %{ render(conn, Authenticator.auth_template(), %{
user: user,
app: app && Map.delete(app, :client_secret),
response_type: params["response_type"], response_type: params["response_type"],
client_id: params["client_id"], client_id: params["client_id"],
available_scopes: available_scopes, available_scopes: available_scopes,
@ -131,11 +141,13 @@ defp handle_existing_authorization(
end end
end end
def create_authorization( def create_authorization(_, _, opts \\ [])
%Plug.Conn{} = conn,
%{"authorization" => _} = params, def create_authorization(%Plug.Conn{assigns: %{user: %User{} = user}} = conn, params, []) do
opts \\ [] create_authorization(conn, params, user: user)
) do end
def create_authorization(%Plug.Conn{} = conn, %{"authorization" => _} = params, opts) do
with {:ok, auth, user} <- do_create_authorization(conn, params, opts[:user]), with {:ok, auth, user} <- do_create_authorization(conn, params, opts[:user]),
{:mfa_required, _, _, false} <- {:mfa_required, user, auth, MFA.require?(user)} do {:mfa_required, _, _, false} <- {:mfa_required, user, auth, MFA.require?(user)} do
after_create_authorization(conn, auth, params) after_create_authorization(conn, auth, params)
@ -248,7 +260,7 @@ def token_exchange(
with {:ok, app} <- Token.Utils.fetch_app(conn), with {:ok, app} <- Token.Utils.fetch_app(conn),
{:ok, %{user: user} = token} <- Token.get_by_refresh_token(app, token), {:ok, %{user: user} = token} <- Token.get_by_refresh_token(app, token),
{:ok, token} <- RefreshToken.grant(token) do {:ok, token} <- RefreshToken.grant(token) do
json(conn, OAuthView.render("token.json", %{user: user, token: token})) after_token_exchange(conn, %{user: user, token: token})
else else
_error -> render_invalid_credentials_error(conn) _error -> render_invalid_credentials_error(conn)
end end
@ -260,7 +272,7 @@ def token_exchange(%Plug.Conn{} = conn, %{"grant_type" => "authorization_code"}
{:ok, auth} <- Authorization.get_by_token(app, fixed_token), {:ok, auth} <- Authorization.get_by_token(app, fixed_token),
%User{} = user <- User.get_cached_by_id(auth.user_id), %User{} = user <- User.get_cached_by_id(auth.user_id),
{:ok, token} <- Token.exchange_token(app, auth) do {:ok, token} <- Token.exchange_token(app, auth) do
json(conn, OAuthView.render("token.json", %{user: user, token: token})) after_token_exchange(conn, %{user: user, token: token})
else else
error -> error ->
handle_token_exchange_error(conn, error) handle_token_exchange_error(conn, error)
@ -275,7 +287,7 @@ def token_exchange(
{:ok, app} <- Token.Utils.fetch_app(conn), {:ok, app} <- Token.Utils.fetch_app(conn),
requested_scopes <- Scopes.fetch_scopes(params, app.scopes), requested_scopes <- Scopes.fetch_scopes(params, app.scopes),
{:ok, token} <- login(user, app, requested_scopes) do {:ok, token} <- login(user, app, requested_scopes) do
json(conn, OAuthView.render("token.json", %{user: user, token: token})) after_token_exchange(conn, %{user: user, token: token})
else else
error -> error ->
handle_token_exchange_error(conn, error) handle_token_exchange_error(conn, error)
@ -298,7 +310,7 @@ def token_exchange(%Plug.Conn{} = conn, %{"grant_type" => "client_credentials"}
with {:ok, app} <- Token.Utils.fetch_app(conn), with {:ok, app} <- Token.Utils.fetch_app(conn),
{:ok, auth} <- Authorization.create_authorization(app, %User{}), {:ok, auth} <- Authorization.create_authorization(app, %User{}),
{:ok, token} <- Token.exchange_token(app, auth) do {:ok, token} <- Token.exchange_token(app, auth) do
json(conn, OAuthView.render("token.json", %{token: token})) after_token_exchange(conn, %{token: token})
else else
_error -> _error ->
handle_token_exchange_error(conn, :invalid_credentails) handle_token_exchange_error(conn, :invalid_credentails)
@ -308,6 +320,12 @@ def token_exchange(%Plug.Conn{} = conn, %{"grant_type" => "client_credentials"}
# Bad request # Bad request
def token_exchange(%Plug.Conn{} = conn, params), do: bad_request(conn, params) def token_exchange(%Plug.Conn{} = conn, params), do: bad_request(conn, params)
def after_token_exchange(%Plug.Conn{} = conn, %{token: token} = view_params) do
conn
|> AuthHelper.put_session_token(token.token)
|> json(OAuthView.render("token.json", view_params))
end
defp handle_token_exchange_error(%Plug.Conn{} = conn, {:mfa_required, user, auth, _}) do defp handle_token_exchange_error(%Plug.Conn{} = conn, {:mfa_required, user, auth, _}) do
conn conn
|> put_status(:forbidden) |> put_status(:forbidden)
@ -361,9 +379,17 @@ defp handle_token_exchange_error(%Plug.Conn{} = conn, _error) do
render_invalid_credentials_error(conn) render_invalid_credentials_error(conn)
end end
def token_revoke(%Plug.Conn{} = conn, %{"token" => _token} = params) do def token_revoke(%Plug.Conn{} = conn, %{"token" => token}) do
with {:ok, app} <- Token.Utils.fetch_app(conn), with {:ok, %Token{} = oauth_token} <- Token.get_by_token(token),
{:ok, _token} <- RevokeToken.revoke(app, params) do {:ok, oauth_token} <- RevokeToken.revoke(oauth_token) do
conn =
with session_token = AuthHelper.get_session_token(conn),
%Token{token: ^session_token} <- oauth_token do
AuthHelper.delete_session_token(conn)
else
_ -> conn
end
json(conn, %{}) json(conn, %{})
else else
_error -> _error ->

View File

@ -13,7 +13,7 @@ def render("token.json", %{token: token} = opts) do
token_type: "Bearer", token_type: "Bearer",
access_token: token.token, access_token: token.token,
refresh_token: token.refresh_token, refresh_token: token.refresh_token,
expires_in: expires_in(), expires_in: NaiveDateTime.diff(token.valid_until, NaiveDateTime.utc_now()),
scope: Enum.join(token.scopes, " "), scope: Enum.join(token.scopes, " "),
created_at: Utils.format_created_at(token) created_at: Utils.format_created_at(token)
} }
@ -25,6 +25,4 @@ def render("token.json", %{token: token} = opts) do
response response
end end
end end
defp expires_in, do: Pleroma.Config.get([:oauth2, :token_expires_in], 600)
end end

View File

@ -27,6 +27,18 @@ defmodule Pleroma.Web.OAuth.Token do
timestamps() timestamps()
end end
def lifespan do
Pleroma.Config.get!([:oauth2, :token_expires_in])
end
@doc "Gets token by unique access token"
@spec get_by_token(String.t()) :: {:ok, t()} | {:error, :not_found}
def get_by_token(token) do
token
|> Query.get_by_token()
|> Repo.find_resource()
end
@doc "Gets token for app by access token" @doc "Gets token for app by access token"
@spec get_by_token(App.t(), String.t()) :: {:ok, t()} | {:error, :not_found} @spec get_by_token(App.t(), String.t()) :: {:ok, t()} | {:error, :not_found}
def get_by_token(%App{id: app_id} = _app, token) do def get_by_token(%App{id: app_id} = _app, token) do
@ -75,11 +87,11 @@ defp put_refresh_token(changeset, attrs) do
end end
defp put_valid_until(changeset, attrs) do defp put_valid_until(changeset, attrs) do
expires_in = valid_until =
Map.get(attrs, :valid_until, NaiveDateTime.add(NaiveDateTime.utc_now(), expires_in())) Map.get(attrs, :valid_until, NaiveDateTime.add(NaiveDateTime.utc_now(), lifespan()))
changeset changeset
|> change(%{valid_until: expires_in}) |> change(%{valid_until: valid_until})
|> validate_required([:valid_until]) |> validate_required([:valid_until])
end end
@ -130,6 +142,4 @@ def is_expired?(%__MODULE__{valid_until: valid_until}) do
end end
def is_expired?(_), do: false def is_expired?(_), do: false
defp expires_in, do: Pleroma.Config.get([:oauth2, :token_expires_in], 600)
end end

View File

@ -42,7 +42,10 @@ def create(%{body_params: params} = conn, %{name: pack_name}) do
|> json(%{error: "pack name, shortcode or filename cannot be empty"}) |> json(%{error: "pack name, shortcode or filename cannot be empty"})
{:error, _} = error -> {:error, _} = error ->
handle_error(conn, error, %{pack_name: pack_name}) handle_error(conn, error, %{
pack_name: pack_name,
message: "Unexpected error occurred while adding file to pack."
})
end end
end end
@ -69,7 +72,11 @@ def update(%{body_params: %{shortcode: shortcode} = params} = conn, %{name: pack
|> json(%{error: "new_shortcode or new_filename cannot be empty"}) |> json(%{error: "new_shortcode or new_filename cannot be empty"})
{:error, _} = error -> {:error, _} = error ->
handle_error(conn, error, %{pack_name: pack_name, code: shortcode}) handle_error(conn, error, %{
pack_name: pack_name,
code: shortcode,
message: "Unexpected error occurred while updating."
})
end end
end end
@ -84,7 +91,11 @@ def delete(conn, %{name: pack_name, shortcode: shortcode}) do
|> json(%{error: "pack name or shortcode cannot be empty"}) |> json(%{error: "pack name or shortcode cannot be empty"})
{:error, _} = error -> {:error, _} = error ->
handle_error(conn, error, %{pack_name: pack_name, code: shortcode}) handle_error(conn, error, %{
pack_name: pack_name,
code: shortcode,
message: "Unexpected error occurred while deleting emoji file."
})
end end
end end
@ -94,18 +105,24 @@ defp handle_error(conn, {:error, :doesnt_exist}, %{code: emoji_code}) do
|> json(%{error: "Emoji \"#{emoji_code}\" does not exist"}) |> json(%{error: "Emoji \"#{emoji_code}\" does not exist"})
end end
defp handle_error(conn, {:error, :not_found}, %{pack_name: pack_name}) do defp handle_error(conn, {:error, :enoent}, %{pack_name: pack_name}) do
conn conn
|> put_status(:not_found) |> put_status(:not_found)
|> json(%{error: "pack \"#{pack_name}\" is not found"}) |> json(%{error: "pack \"#{pack_name}\" is not found"})
end end
defp handle_error(conn, {:error, _}, _) do defp handle_error(conn, {:error, error}, opts) do
render_error( message =
conn, [
:internal_server_error, Map.get(opts, :message, "Unexpected error occurred."),
"Unexpected error occurred while adding file to pack." Pleroma.Utils.posix_error_message(error)
) ]
|> Enum.join(" ")
|> String.trim()
conn
|> put_status(:internal_server_error)
|> json(%{error: message})
end end
defp get_filename(%Plug.Upload{filename: filename}), do: filename defp get_filename(%Plug.Upload{filename: filename}), do: filename

View File

@ -71,7 +71,7 @@ def show(conn, %{name: name, page: page, page_size: page_size}) do
with {:ok, pack} <- Pack.show(name: name, page: page, page_size: page_size) do with {:ok, pack} <- Pack.show(name: name, page: page, page_size: page_size) do
json(conn, pack) json(conn, pack)
else else
{:error, :not_found} -> {:error, :enoent} ->
conn conn
|> put_status(:not_found) |> put_status(:not_found)
|> json(%{error: "Pack #{name} does not exist"}) |> json(%{error: "Pack #{name} does not exist"})
@ -80,6 +80,17 @@ def show(conn, %{name: name, page: page, page_size: page_size}) do
conn conn
|> put_status(:bad_request) |> put_status(:bad_request)
|> json(%{error: "pack name cannot be empty"}) |> json(%{error: "pack name cannot be empty"})
{:error, error} ->
error_message =
add_posix_error(
"Failed to get the contents of the `#{name}` pack.",
error
)
conn
|> put_status(:internal_server_error)
|> json(%{error: error_message})
end end
end end
@ -95,7 +106,7 @@ def archive(conn, %{name: name}) do
"Pack #{name} cannot be downloaded from this instance, either pack sharing was disabled for this pack or some files are missing" "Pack #{name} cannot be downloaded from this instance, either pack sharing was disabled for this pack or some files are missing"
}) })
{:error, :not_found} -> {:error, :enoent} ->
conn conn
|> put_status(:not_found) |> put_status(:not_found)
|> json(%{error: "Pack #{name} does not exist"}) |> json(%{error: "Pack #{name} does not exist"})
@ -116,10 +127,10 @@ def download(%{body_params: %{url: url, name: name} = params} = conn, _) do
|> put_status(:internal_server_error) |> put_status(:internal_server_error)
|> json(%{error: "SHA256 for the pack doesn't match the one sent by the server"}) |> json(%{error: "SHA256 for the pack doesn't match the one sent by the server"})
{:error, e} -> {:error, error} ->
conn conn
|> put_status(:internal_server_error) |> put_status(:internal_server_error)
|> json(%{error: e}) |> json(%{error: error})
end end
end end
@ -139,12 +150,16 @@ def create(conn, %{name: name}) do
|> put_status(:bad_request) |> put_status(:bad_request)
|> json(%{error: "pack name cannot be empty"}) |> json(%{error: "pack name cannot be empty"})
{:error, _} -> {:error, error} ->
render_error( error_message =
conn, add_posix_error(
:internal_server_error, "Unexpected error occurred while creating pack.",
"Unexpected error occurred while creating pack." error
) )
conn
|> put_status(:internal_server_error)
|> json(%{error: error_message})
end end
end end
@ -164,10 +179,12 @@ def delete(conn, %{name: name}) do
|> put_status(:bad_request) |> put_status(:bad_request)
|> json(%{error: "pack name cannot be empty"}) |> json(%{error: "pack name cannot be empty"})
{:error, _, _} -> {:error, error, _} ->
error_message = add_posix_error("Couldn't delete the `#{name}` pack", error)
conn conn
|> put_status(:internal_server_error) |> put_status(:internal_server_error)
|> json(%{error: "Couldn't delete the pack #{name}"}) |> json(%{error: error_message})
end end
end end
@ -180,12 +197,16 @@ def update(%{body_params: %{metadata: metadata}} = conn, %{name: name}) do
|> put_status(:bad_request) |> put_status(:bad_request)
|> json(%{error: "The fallback archive does not have all files specified in pack.json"}) |> json(%{error: "The fallback archive does not have all files specified in pack.json"})
{:error, _} -> {:error, error} ->
render_error( error_message =
conn, add_posix_error(
:internal_server_error, "Unexpected error occurred while updating pack metadata.",
"Unexpected error occurred while updating pack metadata." error
) )
conn
|> put_status(:internal_server_error)
|> json(%{error: error_message})
end end
end end
@ -204,4 +225,10 @@ def import_from_filesystem(conn, _params) do
|> json(%{error: "Error accessing emoji pack directory"}) |> json(%{error: "Error accessing emoji pack directory"})
end end
end end
defp add_posix_error(msg, error) do
[msg, Pleroma.Utils.posix_error_message(error)]
|> Enum.join(" ")
|> String.trim()
end
end end

View File

@ -5,21 +5,14 @@
defmodule Pleroma.Web.Plugs.AdminSecretAuthenticationPlug do defmodule Pleroma.Web.Plugs.AdminSecretAuthenticationPlug do
import Plug.Conn import Plug.Conn
alias Pleroma.Helpers.AuthHelper
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.Plugs.OAuthScopesPlug
alias Pleroma.Web.Plugs.RateLimiter alias Pleroma.Web.Plugs.RateLimiter
def init(options) do def init(options) do
options options
end end
def secret_token do
case Pleroma.Config.get(:admin_token) do
blank when blank in [nil, ""] -> nil
token -> token
end
end
def call(%{assigns: %{user: %User{}}} = conn, _), do: conn def call(%{assigns: %{user: %User{}}} = conn, _), do: conn
def call(conn, _) do def call(conn, _) do
@ -30,7 +23,7 @@ def call(conn, _) do
end end
end end
def authenticate(%{params: %{"admin_token" => admin_token}} = conn) do defp authenticate(%{params: %{"admin_token" => admin_token}} = conn) do
if admin_token == secret_token() do if admin_token == secret_token() do
assign_admin_user(conn) assign_admin_user(conn)
else else
@ -38,7 +31,7 @@ def authenticate(%{params: %{"admin_token" => admin_token}} = conn) do
end end
end end
def authenticate(conn) do defp authenticate(conn) do
token = secret_token() token = secret_token()
case get_req_header(conn, "x-admin-token") do case get_req_header(conn, "x-admin-token") do
@ -48,10 +41,17 @@ def authenticate(conn) do
end end
end end
defp secret_token do
case Pleroma.Config.get(:admin_token) do
blank when blank in [nil, ""] -> nil
token -> token
end
end
defp assign_admin_user(conn) do defp assign_admin_user(conn) do
conn conn
|> assign(:user, %User{is_admin: true}) |> assign(:user, %User{is_admin: true})
|> OAuthScopesPlug.skip_plug() |> AuthHelper.skip_oauth()
end end
defp handle_bad_token(conn) do defp handle_bad_token(conn) do

View File

@ -3,6 +3,9 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Plugs.AuthenticationPlug do defmodule Pleroma.Web.Plugs.AuthenticationPlug do
@moduledoc "Password authentication plug."
alias Pleroma.Helpers.AuthHelper
alias Pleroma.User alias Pleroma.User
import Plug.Conn import Plug.Conn
@ -11,6 +14,30 @@ defmodule Pleroma.Web.Plugs.AuthenticationPlug do
def init(options), do: options def init(options), do: options
def call(%{assigns: %{user: %User{}}} = conn, _), do: conn
def call(
%{
assigns: %{
auth_user: %{password_hash: password_hash} = auth_user,
auth_credentials: %{password: password}
}
} = conn,
_
) do
if checkpw(password, password_hash) do
{:ok, auth_user} = maybe_update_password(auth_user, password)
conn
|> assign(:user, auth_user)
|> AuthHelper.skip_oauth()
else
conn
end
end
def call(conn, _), do: conn
def checkpw(password, "$6" <> _ = password_hash) do def checkpw(password, "$6" <> _ = password_hash) do
:crypt.crypt(password, password_hash) == password_hash :crypt.crypt(password, password_hash) == password_hash
end end
@ -40,40 +67,6 @@ def maybe_update_password(%User{password_hash: "$6" <> _} = user, password) do
def maybe_update_password(user, _), do: {:ok, user} def maybe_update_password(user, _), do: {:ok, user}
defp do_update_password(user, password) do defp do_update_password(user, password) do
user User.reset_password(user, %{password: password, password_confirmation: password})
|> User.password_update_changeset(%{
"password" => password,
"password_confirmation" => password
})
|> Pleroma.Repo.update()
end end
def call(%{assigns: %{user: %User{}}} = conn, _), do: conn
def call(
%{
assigns: %{
auth_user: %{password_hash: password_hash} = auth_user,
auth_credentials: %{password: password}
}
} = conn,
_
) do
if checkpw(password, password_hash) do
{:ok, auth_user} = maybe_update_password(auth_user, password)
conn
|> assign(:user, auth_user)
|> Pleroma.Web.Plugs.OAuthScopesPlug.skip_plug()
else
conn
end
end
def call(%{assigns: %{auth_credentials: %{password: _}}} = conn, _) do
Pbkdf2.no_user_verify()
conn
end
def call(conn, _), do: conn
end end

View File

@ -3,6 +3,12 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Plugs.BasicAuthDecoderPlug do defmodule Pleroma.Web.Plugs.BasicAuthDecoderPlug do
@moduledoc """
Decodes HTTP Basic Auth information and assigns `:auth_credentials`.
NOTE: no checks are performed at this step, auth_credentials/username could be easily faked.
"""
import Plug.Conn import Plug.Conn
def init(options) do def init(options) do

View File

@ -7,8 +7,22 @@ defmodule Pleroma.Web.Plugs.DigestPlug do
require Logger require Logger
def read_body(conn, opts) do def read_body(conn, opts) do
digest_algorithm =
with [digest_header] <- Conn.get_req_header(conn, "digest") do
digest_header
|> String.split("=", parts: 2)
|> List.first()
else
_ -> "SHA-256"
end
unless String.downcase(digest_algorithm) == "sha-256" do
raise ArgumentError,
message: "invalid value for digest algorithm, got: #{digest_algorithm}"
end
{:ok, body, conn} = Conn.read_body(conn, opts) {:ok, body, conn} = Conn.read_body(conn, opts)
digest = "SHA-256=" <> (:crypto.hash(:sha256, body) |> Base.encode64()) encoded_digest = :crypto.hash(:sha256, body) |> Base.encode64()
{:ok, body, Conn.assign(conn, :digest, digest)} {:ok, body, Conn.assign(conn, :digest, "#{digest_algorithm}=#{encoded_digest}")}
end end
end end

View File

@ -1,18 +0,0 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Plugs.EnsureUserKeyPlug do
import Plug.Conn
def init(opts) do
opts
end
def call(%{assigns: %{user: _}} = conn, _), do: conn
def call(conn, _) do
conn
|> assign(:user, nil)
end
end

View File

@ -0,0 +1,36 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Plugs.EnsureUserTokenAssignsPlug do
import Plug.Conn
alias Pleroma.Helpers.AuthHelper
alias Pleroma.User
alias Pleroma.Web.OAuth.Token
@moduledoc "Ensures presence and consistency of :user and :token assigns."
def init(opts) do
opts
end
def call(%{assigns: %{user: %User{id: user_id}} = assigns} = conn, _) do
with %Token{user_id: ^user_id} <- assigns[:token] do
conn
else
%Token{} ->
# A safety net for abnormal (unexpected) scenario: :token belongs to another user
AuthHelper.drop_auth_info(conn)
_ ->
assign(conn, :token, nil)
end
end
def call(conn, _) do
conn
|> assign(:user, nil)
|> assign(:token, nil)
end
end

View File

@ -1,41 +0,0 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Plugs.LegacyAuthenticationPlug do
import Plug.Conn
alias Pleroma.User
def init(options) do
options
end
def call(%{assigns: %{user: %User{}}} = conn, _), do: conn
def call(
%{
assigns: %{
auth_user: %{password_hash: "$6$" <> _ = password_hash} = auth_user,
auth_credentials: %{password: password}
}
} = conn,
_
) do
with ^password_hash <- :crypt.crypt(password, password_hash),
{:ok, user} <-
User.reset_password(auth_user, %{password: password, password_confirmation: password}) do
conn
|> assign(:auth_user, user)
|> assign(:user, user)
|> Pleroma.Web.Plugs.OAuthScopesPlug.skip_plug()
else
_ ->
conn
end
end
def call(conn, _) do
conn
end
end

View File

@ -3,6 +3,7 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Plugs.MappedSignatureToIdentityPlug do defmodule Pleroma.Web.Plugs.MappedSignatureToIdentityPlug do
alias Pleroma.Helpers.AuthHelper
alias Pleroma.Signature alias Pleroma.Signature
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.ActivityPub.Utils
@ -12,6 +13,47 @@ defmodule Pleroma.Web.Plugs.MappedSignatureToIdentityPlug do
def init(options), do: options def init(options), do: options
def call(%{assigns: %{user: %User{}}} = conn, _opts), do: conn
# if this has payload make sure it is signed by the same actor that made it
def call(%{assigns: %{valid_signature: true}, params: %{"actor" => actor}} = conn, _opts) do
with actor_id <- Utils.get_ap_id(actor),
{:user, %User{} = user} <- {:user, user_from_key_id(conn)},
{:user_match, true} <- {:user_match, user.ap_id == actor_id} do
conn
|> assign(:user, user)
|> AuthHelper.skip_oauth()
else
{:user_match, false} ->
Logger.debug("Failed to map identity from signature (payload actor mismatch)")
Logger.debug("key_id=#{inspect(key_id_from_conn(conn))}, actor=#{inspect(actor)}")
assign(conn, :valid_signature, false)
# remove me once testsuite uses mapped capabilities instead of what we do now
{:user, nil} ->
Logger.debug("Failed to map identity from signature (lookup failure)")
Logger.debug("key_id=#{inspect(key_id_from_conn(conn))}, actor=#{actor}")
conn
end
end
# no payload, probably a signed fetch
def call(%{assigns: %{valid_signature: true}} = conn, _opts) do
with %User{} = user <- user_from_key_id(conn) do
conn
|> assign(:user, user)
|> AuthHelper.skip_oauth()
else
_ ->
Logger.debug("Failed to map identity from signature (no payload actor mismatch)")
Logger.debug("key_id=#{inspect(key_id_from_conn(conn))}")
assign(conn, :valid_signature, false)
end
end
# no signature at all
def call(conn, _opts), do: conn
defp key_id_from_conn(conn) do defp key_id_from_conn(conn) do
with %{"keyId" => key_id} <- HTTPSignatures.signature_for_conn(conn), with %{"keyId" => key_id} <- HTTPSignatures.signature_for_conn(conn),
{:ok, ap_id} <- Signature.key_id_to_actor_id(key_id) do {:ok, ap_id} <- Signature.key_id_to_actor_id(key_id) do
@ -31,41 +73,4 @@ defp user_from_key_id(conn) do
nil nil
end end
end end
def call(%{assigns: %{user: _}} = conn, _opts), do: conn
# if this has payload make sure it is signed by the same actor that made it
def call(%{assigns: %{valid_signature: true}, params: %{"actor" => actor}} = conn, _opts) do
with actor_id <- Utils.get_ap_id(actor),
{:user, %User{} = user} <- {:user, user_from_key_id(conn)},
{:user_match, true} <- {:user_match, user.ap_id == actor_id} do
assign(conn, :user, user)
else
{:user_match, false} ->
Logger.debug("Failed to map identity from signature (payload actor mismatch)")
Logger.debug("key_id=#{inspect(key_id_from_conn(conn))}, actor=#{inspect(actor)}")
assign(conn, :valid_signature, false)
# remove me once testsuite uses mapped capabilities instead of what we do now
{:user, nil} ->
Logger.debug("Failed to map identity from signature (lookup failure)")
Logger.debug("key_id=#{inspect(key_id_from_conn(conn))}, actor=#{actor}")
conn
end
end
# no payload, probably a signed fetch
def call(%{assigns: %{valid_signature: true}} = conn, _opts) do
with %User{} = user <- user_from_key_id(conn) do
assign(conn, :user, user)
else
_ ->
Logger.debug("Failed to map identity from signature (no payload actor mismatch)")
Logger.debug("key_id=#{inspect(key_id_from_conn(conn))}")
assign(conn, :valid_signature, false)
end
end
# no signature at all
def call(conn, _opts), do: conn
end end

View File

@ -3,9 +3,12 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Plugs.OAuthPlug do defmodule Pleroma.Web.Plugs.OAuthPlug do
@moduledoc "Performs OAuth authentication by token from params / headers / cookies."
import Plug.Conn import Plug.Conn
import Ecto.Query import Ecto.Query
alias Pleroma.Helpers.AuthHelper
alias Pleroma.Repo alias Pleroma.Repo
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.OAuth.App alias Pleroma.Web.OAuth.App
@ -17,45 +20,26 @@ def init(options), do: options
def call(%{assigns: %{user: %User{}}} = conn, _), do: conn def call(%{assigns: %{user: %User{}}} = conn, _), do: conn
def call(%{params: %{"access_token" => access_token}} = conn, _) do
with {:ok, user, token_record} <- fetch_user_and_token(access_token) do
conn
|> assign(:token, token_record)
|> assign(:user, user)
else
_ ->
# token found, but maybe only with app
with {:ok, app, token_record} <- fetch_app_and_token(access_token) do
conn
|> assign(:token, token_record)
|> assign(:app, app)
else
_ -> conn
end
end
end
def call(conn, _) do def call(conn, _) do
case fetch_token_str(conn) do with {:ok, token_str} <- fetch_token_str(conn) do
{:ok, token} -> with {:ok, user, user_token} <- fetch_user_and_token(token_str),
with {:ok, user, token_record} <- fetch_user_and_token(token) do false <- Token.is_expired?(user_token) do
conn
|> assign(:token, token_record)
|> assign(:user, user)
else
_ ->
# token found, but maybe only with app
with {:ok, app, token_record} <- fetch_app_and_token(token) do
conn
|> assign(:token, token_record)
|> assign(:app, app)
else
_ -> conn
end
end
_ ->
conn conn
|> assign(:token, user_token)
|> assign(:user, user)
else
_ ->
with {:ok, app, app_token} <- fetch_app_and_token(token_str),
false <- Token.is_expired?(app_token) do
conn
|> assign(:token, app_token)
|> assign(:app, app)
else
_ -> conn
end
end
else
_ -> conn
end end
end end
@ -70,7 +54,6 @@ defp fetch_user_and_token(token) do
preload: [user: user] preload: [user: user]
) )
# credo:disable-for-next-line Credo.Check.Readability.MaxLineLength
with %Token{user: user} = token_record <- Repo.one(query) do with %Token{user: user} = token_record <- Repo.one(query) do
{:ok, user, token_record} {:ok, user, token_record}
end end
@ -86,29 +69,23 @@ defp fetch_app_and_token(token) do
end end
end end
# Gets token from session by :oauth_token key # Gets token string from conn (in params / headers / session)
# #
@spec fetch_token_from_session(Plug.Conn.t()) :: :no_token_found | {:ok, String.t()} @spec fetch_token_str(Plug.Conn.t() | list(String.t())) :: :no_token_found | {:ok, String.t()}
defp fetch_token_from_session(conn) do defp fetch_token_str(%Plug.Conn{params: %{"access_token" => access_token}} = _conn) do
case get_session(conn, :oauth_token) do {:ok, access_token}
nil -> :no_token_found
token -> {:ok, token}
end
end end
# Gets token from headers
#
@spec fetch_token_str(Plug.Conn.t()) :: :no_token_found | {:ok, String.t()}
defp fetch_token_str(%Plug.Conn{} = conn) do defp fetch_token_str(%Plug.Conn{} = conn) do
headers = get_req_header(conn, "authorization") headers = get_req_header(conn, "authorization")
with :no_token_found <- fetch_token_str(headers), with {:ok, token} <- fetch_token_str(headers) do
do: fetch_token_from_session(conn) {:ok, token}
else
_ -> fetch_token_from_session(conn)
end
end end
@spec fetch_token_str(Keyword.t()) :: :no_token_found | {:ok, String.t()}
defp fetch_token_str([]), do: :no_token_found
defp fetch_token_str([token | tail]) do defp fetch_token_str([token | tail]) do
trimmed_token = String.trim(token) trimmed_token = String.trim(token)
@ -117,4 +94,14 @@ defp fetch_token_str([token | tail]) do
_ -> fetch_token_str(tail) _ -> fetch_token_str(tail)
end end
end end
defp fetch_token_str([]), do: :no_token_found
@spec fetch_token_from_session(Plug.Conn.t()) :: :no_token_found | {:ok, String.t()}
defp fetch_token_from_session(conn) do
case AuthHelper.get_session_token(conn) do
nil -> :no_token_found
token -> {:ok, token}
end
end
end end

View File

@ -7,6 +7,7 @@ defmodule Pleroma.Web.Plugs.OAuthScopesPlug do
import Pleroma.Web.Gettext import Pleroma.Web.Gettext
alias Pleroma.Config alias Pleroma.Config
alias Pleroma.Helpers.AuthHelper
use Pleroma.Web, :plug use Pleroma.Web, :plug
@ -28,7 +29,7 @@ def perform(%Plug.Conn{assigns: assigns} = conn, %{scopes: scopes} = options) do
conn conn
options[:fallback] == :proceed_unauthenticated -> options[:fallback] == :proceed_unauthenticated ->
drop_auth_info(conn) AuthHelper.drop_auth_info(conn)
true -> true ->
missing_scopes = scopes -- matched_scopes missing_scopes = scopes -- matched_scopes
@ -44,15 +45,6 @@ def perform(%Plug.Conn{assigns: assigns} = conn, %{scopes: scopes} = options) do
end end
end end
@doc "Drops authentication info from connection"
def drop_auth_info(conn) do
# To simplify debugging, setting a private variable on `conn` if auth info is dropped
conn
|> put_private(:authentication_ignored, true)
|> assign(:user, nil)
|> assign(:token, nil)
end
@doc "Keeps those of `scopes` which are descendants of `supported_scopes`" @doc "Keeps those of `scopes` which are descendants of `supported_scopes`"
def filter_descendants(scopes, supported_scopes) do def filter_descendants(scopes, supported_scopes) do
Enum.filter( Enum.filter(

View File

@ -1,21 +0,0 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Plugs.SessionAuthenticationPlug do
import Plug.Conn
def init(options) do
options
end
def call(conn, _) do
with saved_user_id <- get_session(conn, :user_id),
%{auth_user: %{id: ^saved_user_id}} <- conn.assigns do
conn
|> assign(:user, conn.assigns.auth_user)
else
_ -> conn
end
end
end

View File

@ -3,16 +3,15 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Plugs.SetUserSessionIdPlug do defmodule Pleroma.Web.Plugs.SetUserSessionIdPlug do
import Plug.Conn alias Pleroma.Helpers.AuthHelper
alias Pleroma.User alias Pleroma.Web.OAuth.Token
def init(opts) do def init(opts) do
opts opts
end end
def call(%{assigns: %{user: %User{id: id}}} = conn, _) do def call(%{assigns: %{token: %Token{} = oauth_token}} = conn, _) do
conn AuthHelper.put_session_token(conn, oauth_token.token)
|> put_session(:user_id, id)
end end
def call(conn, _), do: conn def call(conn, _), do: conn

View File

@ -3,7 +3,7 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Plugs.UserEnabledPlug do defmodule Pleroma.Web.Plugs.UserEnabledPlug do
import Plug.Conn alias Pleroma.Helpers.AuthHelper
alias Pleroma.User alias Pleroma.User
def init(options) do def init(options) do
@ -11,9 +11,10 @@ def init(options) do
end end
def call(%{assigns: %{user: %User{} = user}} = conn, _) do def call(%{assigns: %{user: %User{} = user}} = conn, _) do
case User.account_status(user) do if User.account_status(user) == :active do
:active -> conn conn
_ -> assign(conn, :user, nil) else
AuthHelper.drop_auth_info(conn)
end end
end end

View File

@ -3,6 +3,12 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Plugs.UserFetcherPlug do defmodule Pleroma.Web.Plugs.UserFetcherPlug do
@moduledoc """
Assigns `:auth_user` basing on `:auth_credentials`.
NOTE: no checks are performed at this step, auth_credentials/username could be easily faked.
"""
alias Pleroma.User alias Pleroma.User
import Plug.Conn import Plug.Conn

View File

@ -34,6 +34,7 @@ defmodule Pleroma.Web.Router do
plug(:fetch_session) plug(:fetch_session)
plug(Pleroma.Web.Plugs.OAuthPlug) plug(Pleroma.Web.Plugs.OAuthPlug)
plug(Pleroma.Web.Plugs.UserEnabledPlug) plug(Pleroma.Web.Plugs.UserEnabledPlug)
plug(Pleroma.Web.Plugs.EnsureUserTokenAssignsPlug)
end end
pipeline :expect_authentication do pipeline :expect_authentication do
@ -48,15 +49,13 @@ defmodule Pleroma.Web.Router do
plug(Pleroma.Web.Plugs.OAuthPlug) plug(Pleroma.Web.Plugs.OAuthPlug)
plug(Pleroma.Web.Plugs.BasicAuthDecoderPlug) plug(Pleroma.Web.Plugs.BasicAuthDecoderPlug)
plug(Pleroma.Web.Plugs.UserFetcherPlug) plug(Pleroma.Web.Plugs.UserFetcherPlug)
plug(Pleroma.Web.Plugs.SessionAuthenticationPlug)
plug(Pleroma.Web.Plugs.LegacyAuthenticationPlug)
plug(Pleroma.Web.Plugs.AuthenticationPlug) plug(Pleroma.Web.Plugs.AuthenticationPlug)
end end
pipeline :after_auth do pipeline :after_auth do
plug(Pleroma.Web.Plugs.UserEnabledPlug) plug(Pleroma.Web.Plugs.UserEnabledPlug)
plug(Pleroma.Web.Plugs.SetUserSessionIdPlug) plug(Pleroma.Web.Plugs.SetUserSessionIdPlug)
plug(Pleroma.Web.Plugs.EnsureUserKeyPlug) plug(Pleroma.Web.Plugs.EnsureUserTokenAssignsPlug)
end end
pipeline :base_api do pipeline :base_api do
@ -100,7 +99,7 @@ defmodule Pleroma.Web.Router do
pipeline :pleroma_html do pipeline :pleroma_html do
plug(:browser) plug(:browser)
plug(:authenticate) plug(:authenticate)
plug(Pleroma.Web.Plugs.EnsureUserKeyPlug) plug(Pleroma.Web.Plugs.EnsureUserTokenAssignsPlug)
end end
pipeline :well_known do pipeline :well_known do
@ -292,7 +291,6 @@ defmodule Pleroma.Web.Router do
post("/main/ostatus", UtilController, :remote_subscribe) post("/main/ostatus", UtilController, :remote_subscribe)
get("/ostatus_subscribe", RemoteFollowController, :follow) get("/ostatus_subscribe", RemoteFollowController, :follow)
post("/ostatus_subscribe", RemoteFollowController, :do_follow) post("/ostatus_subscribe", RemoteFollowController, :do_follow)
end end
@ -321,20 +319,26 @@ defmodule Pleroma.Web.Router do
end end
scope "/oauth", Pleroma.Web.OAuth do scope "/oauth", Pleroma.Web.OAuth do
scope [] do
pipe_through(:oauth)
get("/authorize", OAuthController, :authorize)
end
post("/authorize", OAuthController, :create_authorization)
post("/token", OAuthController, :token_exchange)
post("/revoke", OAuthController, :token_revoke)
get("/registration_details", OAuthController, :registration_details) get("/registration_details", OAuthController, :registration_details)
post("/mfa/challenge", MFAController, :challenge)
post("/mfa/verify", MFAController, :verify, as: :mfa_verify) post("/mfa/verify", MFAController, :verify, as: :mfa_verify)
get("/mfa", MFAController, :show) get("/mfa", MFAController, :show)
scope [] do
pipe_through(:oauth)
get("/authorize", OAuthController, :authorize)
post("/authorize", OAuthController, :create_authorization)
end
scope [] do
pipe_through(:fetch_session)
post("/token", OAuthController, :token_exchange)
post("/revoke", OAuthController, :token_revoke)
post("/mfa/challenge", MFAController, :challenge)
end
scope [] do scope [] do
pipe_through(:browser) pipe_through(:browser)

View File

@ -36,9 +36,8 @@ def registry, do: @registry
) :: ) ::
{:ok, topic :: String.t()} | {:error, :bad_topic} | {:error, :unauthorized} {:ok, topic :: String.t()} | {:error, :bad_topic} | {:error, :unauthorized}
def get_topic_and_add_socket(stream, user, oauth_token, params \\ %{}) do def get_topic_and_add_socket(stream, user, oauth_token, params \\ %{}) do
case get_topic(stream, user, oauth_token, params) do with {:ok, topic} <- get_topic(stream, user, oauth_token, params) do
{:ok, topic} -> add_socket(topic, user) add_socket(topic, user)
error -> error
end end
end end
@ -70,10 +69,10 @@ def get_topic("public:remote:media", _user, _oauth_token, %{"instance" => instan
def get_topic( def get_topic(
stream, stream,
%User{id: user_id} = user, %User{id: user_id} = user,
%Token{user_id: token_user_id} = oauth_token, %Token{user_id: user_id} = oauth_token,
_params _params
) )
when stream in @user_streams and user_id == token_user_id do when stream in @user_streams do
# Note: "read" works for all user streams (not mentioning it since it's an ancestor scope) # Note: "read" works for all user streams (not mentioning it since it's an ancestor scope)
required_scopes = required_scopes =
if stream == "user:notification" do if stream == "user:notification" do
@ -97,10 +96,9 @@ def get_topic(stream, _user, _oauth_token, _params) when stream in @user_streams
def get_topic( def get_topic(
"list", "list",
%User{id: user_id} = user, %User{id: user_id} = user,
%Token{user_id: token_user_id} = oauth_token, %Token{user_id: user_id} = oauth_token,
%{"list" => id} %{"list" => id}
) ) do
when user_id == token_user_id do
cond do cond do
OAuthScopesPlug.filter_descendants(["read", "read:lists"], oauth_token.scopes) == [] -> OAuthScopesPlug.filter_descendants(["read", "read:lists"], oauth_token.scopes) == [] ->
{:error, :unauthorized} {:error, :unauthorized}
@ -137,16 +135,10 @@ def remove_socket(topic) do
def stream(topics, items) do def stream(topics, items) do
if should_env_send?() do if should_env_send?() do
List.wrap(topics) for topic <- List.wrap(topics), item <- List.wrap(items) do
|> Enum.each(fn topic -> spawn(fn -> do_stream(topic, item) end)
List.wrap(items) end
|> Enum.each(fn item ->
spawn(fn -> do_stream(topic, item) end)
end)
end)
end end
:ok
end end
def filtered_by_user?(user, item, streamed_type \\ :activity) def filtered_by_user?(user, item, streamed_type \\ :activity)
@ -160,8 +152,7 @@ def filtered_by_user?(%User{} = user, %Activity{} = item, streamed_type) do
domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks) domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks)
with parent <- Object.normalize(item) || item, with parent <- Object.normalize(item) || item,
true <- true <- Enum.all?([blocked_ap_ids, muted_ap_ids], &(item.actor not in &1)),
Enum.all?([blocked_ap_ids, muted_ap_ids], &(item.actor not in &1)),
true <- item.data["type"] != "Announce" || item.actor not in reblog_muted_ap_ids, true <- item.data["type"] != "Announce" || item.actor not in reblog_muted_ap_ids,
true <- true <-
!(streamed_type == :activity && item.data["type"] == "Announce" && !(streamed_type == :activity && item.data["type"] == "Announce" &&
@ -195,6 +186,19 @@ defp do_stream("direct", item) do
end) end)
end end
defp do_stream("follow_relationship", item) do
text = StreamerView.render("follow_relationships_update.json", item)
user_topic = "user:#{item.follower.id}"
Logger.debug("Trying to push follow relationship update to #{user_topic}\n\n")
Registry.dispatch(@registry, user_topic, fn list ->
Enum.each(list, fn {pid, _auth} ->
send(pid, {:text, text})
end)
end)
end
defp do_stream("participation", participation) do defp do_stream("participation", participation) do
user_topic = "direct:#{participation.user_id}" user_topic = "direct:#{participation.user_id}"
Logger.debug("Trying to push a conversation participation to #{user_topic}\n\n") Logger.debug("Trying to push a conversation participation to #{user_topic}\n\n")

View File

@ -126,7 +126,7 @@
<div align="center" class="img-container center" <div align="center" class="img-container center"
style="padding-right: 0px;padding-left: 0px;"> style="padding-right: 0px;padding-left: 0px;">
<!--[if mso]><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr style="line-height:0px"><td style="padding-right: 0px;padding-left: 0px;" align="center"><![endif]--><img <!--[if mso]><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr style="line-height:0px"><td style="padding-right: 0px;padding-left: 0px;" align="center"><![endif]--><img
align="center" alt="Image" border="0" class="center" src="cid:logo.png" align="center" alt="Image" border="0" class="center" src="cid:logo.svg"
style="text-decoration: none; -ms-interpolation-mode: bicubic; border: 0; height: 80px; width: auto; max-height: 80px; display: block;" style="text-decoration: none; -ms-interpolation-mode: bicubic; border: 0; height: 80px; width: auto; max-height: 80px; display: block;"
title="Image" height="80" /> title="Image" height="80" />
<!--[if mso]></td></tr></table><![endif]--> <!--[if mso]></td></tr></table><![endif]-->

View File

@ -1,233 +1,19 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,minimal-ui" /> <meta name="viewport" content="width=device-width,initial-scale=1,minimal-ui">
<title> <title><%= Pleroma.Config.get([:instance, :name]) %></title>
<%= Pleroma.Config.get([:instance, :name]) %> <link rel="stylesheet" href="/instance/static.css">
</title>
<style>
body {
background-color: #121a24;
font-family: sans-serif;
color: #b9b9ba;
text-align: center;
}
.container {
max-width: 420px;
padding: 20px;
background-color: #182230;
border-radius: 4px;
margin: auto;
margin-top: 10vh;
box-shadow: 0 1px 4px 0px rgba(0, 0, 0, 0.5);
}
h1 {
margin: 0;
font-size: 24px;
}
h2 {
color: #b9b9ba;
font-weight: normal;
font-size: 18px;
margin-bottom: 20px;
}
a {
color: #d8a070;
text-decoration: none;
}
form {
width: 100%;
}
.input {
text-align: left;
color: #89898a;
display: flex;
flex-direction: column;
}
input {
box-sizing: content-box;
padding: 10px;
margin-top: 5px;
margin-bottom: 10px;
background-color: #121a24;
color: #b9b9ba;
border: 0;
transition-property: border-bottom;
transition-duration: 0.35s;
border-bottom: 2px solid #2a384a;
font-size: 14px;
}
.scopes-input {
display: flex;
flex-direction: column;
margin-top: 1em;
text-align: left;
color: #89898a;
}
.scopes-input label:first-child {
height: 2em;
}
.scopes {
display: flex;
flex-wrap: wrap;
text-align: left;
color: #b9b9ba;
}
.scope {
display: flex;
flex-basis: 100%;
height: 2em;
align-items: center;
}
.scope:before {
color: #b9b9ba;
content: "\fe0e";
margin-left: 1em;
margin-right: 1em;
}
[type="checkbox"] + label {
display: none;
cursor: pointer;
margin: 0.5em;
}
[type="checkbox"] {
display: none;
}
[type="checkbox"] + label:before {
cursor: pointer;
display: inline-block;
color: white;
background-color: #121a24;
border: 4px solid #121a24;
box-shadow: 0px 0px 1px 0 #d8a070;
box-sizing: border-box;
width: 1.2em;
height: 1.2em;
margin-right: 1.0em;
content: "";
transition-property: background-color;
transition-duration: 0.35s;
color: #121a24;
margin-bottom: -0.2em;
border-radius: 2px;
}
[type="checkbox"]:checked + label:before {
background-color: #d8a070;
}
input:focus {
outline: none;
border-bottom: 2px solid #d8a070;
}
button {
box-sizing: border-box;
width: 100%;
background-color: #1c2a3a;
color: #b9b9ba;
border-radius: 4px;
border: none;
padding: 10px;
margin-top: 20px;
margin-bottom: 20px;
text-transform: uppercase;
font-size: 16px;
box-shadow: 0px 0px 2px 0px black,
0px 1px 0px 0px rgba(255, 255, 255, 0.2) inset,
0px -1px 0px 0px rgba(0, 0, 0, 0.2) inset;
}
button:hover {
cursor: pointer;
box-shadow: 0px 0px 0px 1px #d8a070,
0px 1px 0px 0px rgba(255, 255, 255, 0.2) inset,
0px -1px 0px 0px rgba(0, 0, 0, 0.2) inset;
}
.alert-danger {
box-sizing: border-box;
width: 100%;
background-color: #931014;
border: 1px solid #a06060;
border-radius: 4px;
padding: 10px;
margin-top: 20px;
font-weight: 500;
font-size: 16px;
}
.alert-info {
box-sizing: border-box;
width: 100%;
border-radius: 4px;
border: 1px solid #7d796a;
padding: 10px;
margin-top: 20px;
font-weight: 500;
font-size: 16px;
}
@media all and (max-width: 440px) {
.container {
margin-top: 0
}
.scope {
flex-basis: 0%;
}
.scope:before {
content: "";
margin-left: 0em;
margin-right: 1em;
}
.scope:first-child:before {
margin-left: 1em;
content: "\fe0e";
}
.scope:after {
content: ",";
}
.scope:last-child:after {
content: "";
}
}
.form-row {
display: flex;
}
.form-row > label {
text-align: left;
line-height: 47px;
flex: 1;
}
.form-row > input {
flex: 2;
}
</style>
</head> </head>
<body> <body>
<div class="instance-header">
<a class="instance-header__content" href="/">
<img class="instance-header__thumbnail" src="<%= Pleroma.Config.get([:instance, :instance_thumbnail]) %>">
<h1 class="instance-header__title"><%= Pleroma.Config.get([:instance, :name]) %></h1>
</a>
</div>
<div class="container"> <div class="container">
<h1><%= Pleroma.Config.get([:instance, :name]) %></h1>
<%= @inner_content %> <%= @inner_content %>
</div> </div>
</body> </body>

View File

@ -5,32 +5,55 @@
<p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p> <p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p>
<% end %> <% end %>
<h2>OAuth Authorization</h2>
<%= form_for @conn, o_auth_path(@conn, :authorize), [as: "authorization"], fn f -> %> <%= form_for @conn, o_auth_path(@conn, :authorize), [as: "authorization"], fn f -> %>
<%= if @params["registration"] in ["true", true] do %> <%= if @user do %>
<h3>This is the first time you visit! Please enter your Pleroma handle.</h3> <div class="account-header">
<p>Choose carefully! You won't be able to change this later. You will be able to change your display name, though.</p> <div class="account-header__banner" style="background-image: url('<%= Pleroma.User.banner_url(@user) %>')"></div>
<div class="input"> <div class="account-header__avatar" style="background-image: url('<%= Pleroma.User.avatar_url(@user) %>')"></div>
<%= label f, :nickname, "Pleroma Handle" %> <div class="account-header__meta">
<%= text_input f, :nickname, placeholder: "lain" %> <div class="account-header__display-name"><%= @user.name %></div>
<div class="account-header__nickname">@<%= @user.nickname %>@<%= Pleroma.User.get_host(@user) %></div>
</div>
</div> </div>
<%= hidden_input f, :name, value: @params["name"] %>
<%= hidden_input f, :password, value: @params["password"] %>
<br>
<% else %>
<div class="input">
<%= label f, :name, "Username" %>
<%= text_input f, :name %>
</div>
<div class="input">
<%= label f, :password, "Password" %>
<%= password_input f, :password %>
</div>
<%= submit "Log In" %>
<%= render @view_module, "_scopes.html", Map.merge(assigns, %{form: f}) %>
<% end %> <% end %>
<div class="container__content">
<%= if @app do %>
<p>Application <strong><%= @app.client_name %></strong> is requesting access to your account.</p>
<%= render @view_module, "_scopes.html", Map.merge(assigns, %{form: f}) %>
<% end %>
<%= if @user do %>
<div class="actions">
<a class="button button--cancel" href="/">Cancel</a>
<%= submit "Approve", class: "button--approve" %>
</div>
<% else %>
<%= if @params["registration"] in ["true", true] do %>
<h3>This is the first time you visit! Please enter your Pleroma handle.</h3>
<p>Choose carefully! You won't be able to change this later. You will be able to change your display name, though.</p>
<div class="input">
<%= label f, :nickname, "Pleroma Handle" %>
<%= text_input f, :nickname, placeholder: "lain" %>
</div>
<%= hidden_input f, :name, value: @params["name"] %>
<%= hidden_input f, :password, value: @params["password"] %>
<br>
<% else %>
<div class="input">
<%= label f, :name, "Username" %>
<%= text_input f, :name %>
</div>
<div class="input">
<%= label f, :password, "Password" %>
<%= password_input f, :password %>
</div>
<%= submit "Log In" %>
<% end %>
<% end %>
</div>
<%= hidden_input f, :client_id, value: @client_id %> <%= hidden_input f, :client_id, value: @client_id %>
<%= hidden_input f, :response_type, value: @response_type %> <%= hidden_input f, :response_type, value: @response_type %>
<%= hidden_input f, :redirect_uri, value: @redirect_uri %> <%= hidden_input f, :redirect_uri, value: @redirect_uri %>
@ -40,4 +63,3 @@
<%= if Pleroma.Config.oauth_consumer_enabled?() do %> <%= if Pleroma.Config.oauth_consumer_enabled?() do %>
<%= render @view_module, Pleroma.Web.Auth.Authenticator.oauth_consumer_template(), assigns %> <%= render @view_module, Pleroma.Web.Auth.Authenticator.oauth_consumer_template(), assigns %>
<% end %> <% end %>

View File

@ -74,6 +74,28 @@ def render("chat_update.json", %{chat_message_reference: cm_ref}) do
|> Jason.encode!() |> Jason.encode!()
end end
def render("follow_relationships_update.json", item) do
%{
event: "pleroma:follow_relationships_update",
payload:
%{
state: item.state,
follower: %{
id: item.follower.id,
follower_count: item.follower.follower_count,
following_count: item.follower.following_count
},
following: %{
id: item.following.id,
follower_count: item.following.follower_count,
following_count: item.following.following_count
}
}
|> Jason.encode!()
}
|> Jason.encode!()
end
def render("conversation.json", %Participation{} = participation) do def render("conversation.json", %Participation{} = participation) do
%{ %{
event: "conversation", event: "conversation",

15
mix.exs
View File

@ -22,7 +22,7 @@ def project do
docs: [ docs: [
source_url_pattern: source_url_pattern:
"https://git.pleroma.social/pleroma/pleroma/blob/develop/%{path}#L%{line}", "https://git.pleroma.social/pleroma/pleroma/blob/develop/%{path}#L%{line}",
logo: "priv/static/static/logo.png", logo: "priv/static/images/logo.png",
extras: ["README.md", "CHANGELOG.md"] ++ Path.wildcard("docs/**/*.md"), extras: ["README.md", "CHANGELOG.md"] ++ Path.wildcard("docs/**/*.md"),
groups_for_extras: [ groups_for_extras: [
"Installation manuals": Path.wildcard("docs/installation/*.md"), "Installation manuals": Path.wildcard("docs/installation/*.md"),
@ -37,7 +37,8 @@ def project do
pleroma: [ pleroma: [
include_executables_for: [:unix], include_executables_for: [:unix],
applications: [ex_syslogger: :load, syslog: :load, eldap: :transient], applications: [ex_syslogger: :load, syslog: :load, eldap: :transient],
steps: [:assemble, &put_otp_version/1, &copy_files/1, &copy_nginx_config/1] steps: [:assemble, &put_otp_version/1, &copy_files/1, &copy_nginx_config/1],
config_providers: [{Pleroma.Config.ReleaseRuntimeProvider, nil}]
] ]
] ]
] ]
@ -157,7 +158,7 @@ defp deps do
{:floki, "~> 0.27"}, {:floki, "~> 0.27"},
{:timex, "~> 3.6"}, {:timex, "~> 3.6"},
{:ueberauth, "~> 0.4"}, {:ueberauth, "~> 0.4"},
{:linkify, "~> 0.2.0"}, {:linkify, "~> 0.4.0"},
{:http_signatures, "~> 0.1.0"}, {:http_signatures, "~> 0.1.0"},
{:telemetry, "~> 0.3"}, {:telemetry, "~> 0.3"},
{:poolboy, "~> 1.5"}, {:poolboy, "~> 1.5"},
@ -193,7 +194,8 @@ defp deps do
ref: "e0f16822d578866e186a0974d65ad58cddc1e2ab"}, ref: "e0f16822d578866e186a0974d65ad58cddc1e2ab"},
{:restarter, path: "./restarter"}, {:restarter, path: "./restarter"},
{:majic, {:majic,
git: "https://git.pleroma.social/pleroma/elixir-libraries/majic.git", branch: "develop"}, git: "https://git.pleroma.social/pleroma/elixir-libraries/majic.git",
ref: "4c692e544b28d1f5e543fb8a44be090f8cd96f80"},
{:open_api_spex, {:open_api_spex,
git: "https://git.pleroma.social/pleroma/elixir-libraries/open_api_spex.git", git: "https://git.pleroma.social/pleroma/elixir-libraries/open_api_spex.git",
ref: "f296ac0924ba3cf79c7a588c4c252889df4c2edd"}, ref: "f296ac0924ba3cf79c7a588c4c252889df4c2edd"},
@ -205,7 +207,10 @@ defp deps do
{:mock, "~> 0.3.5", only: :test}, {:mock, "~> 0.3.5", only: :test},
# temporary downgrade for excoveralls, hackney until hackney max_connections bug will be fixed # temporary downgrade for excoveralls, hackney until hackney max_connections bug will be fixed
{:excoveralls, "0.12.3", only: :test}, {:excoveralls, "0.12.3", only: :test},
{:hackney, "1.15.2", override: true}, {:hackney,
git: "https://git.pleroma.social/pleroma/elixir-libraries/hackney.git",
ref: "7d7119f0651515d6d7669c78393fd90950a3ec6e",
override: true},
{:mox, "~> 0.5", only: :test}, {:mox, "~> 0.5", only: :test},
{:websocket_client, git: "https://github.com/jeremyong/websocket_client.git", only: :test} {:websocket_client, git: "https://github.com/jeremyong/websocket_client.git", only: :test}
] ++ oauth_deps() ] ++ oauth_deps()

View File

@ -11,7 +11,7 @@
"calendar": {:hex, :calendar, "1.0.0", "f52073a708528482ec33d0a171954ca610fe2bd28f1e871f247dc7f1565fa807", [:mix], [{:tzdata, "~> 0.5.20 or ~> 0.1.201603 or ~> 1.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "990e9581920c82912a5ee50e62ff5ef96da6b15949a2ee4734f935fdef0f0a6f"}, "calendar": {:hex, :calendar, "1.0.0", "f52073a708528482ec33d0a171954ca610fe2bd28f1e871f247dc7f1565fa807", [:mix], [{:tzdata, "~> 0.5.20 or ~> 0.1.201603 or ~> 1.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "990e9581920c82912a5ee50e62ff5ef96da6b15949a2ee4734f935fdef0f0a6f"},
"captcha": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/elixir-captcha.git", "e0f16822d578866e186a0974d65ad58cddc1e2ab", [ref: "e0f16822d578866e186a0974d65ad58cddc1e2ab"]}, "captcha": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/elixir-captcha.git", "e0f16822d578866e186a0974d65ad58cddc1e2ab", [ref: "e0f16822d578866e186a0974d65ad58cddc1e2ab"]},
"castore": {:hex, :castore, "0.1.7", "1ca19eee705cde48c9e809e37fdd0730510752cc397745e550f6065a56a701e9", [:mix], [], "hexpm", "a2ae2c13d40e9c308387f1aceb14786dca019ebc2a11484fb2a9f797ea0aa0d8"}, "castore": {:hex, :castore, "0.1.7", "1ca19eee705cde48c9e809e37fdd0730510752cc397745e550f6065a56a701e9", [:mix], [], "hexpm", "a2ae2c13d40e9c308387f1aceb14786dca019ebc2a11484fb2a9f797ea0aa0d8"},
"certifi": {:hex, :certifi, "2.5.1", "867ce347f7c7d78563450a18a6a28a8090331e77fa02380b4a21962a65d36ee5", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm", "805abd97539caf89ec6d4732c91e62ba9da0cda51ac462380bbd28ee697a8c42"}, "certifi": {:git, "https://github.com/certifi/erlang-certifi", "e08b12e8993502240c25b78563993776f87ecd2a", [tag: "2.5.1"]},
"combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"}, "combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"},
"comeonin": {:hex, :comeonin, "5.3.1", "7fe612b739c78c9c1a75186ef2d322ce4d25032d119823269d0aa1e2f1e20025", [:mix], [], "hexpm", "d6222483060c17f0977fad1b7401ef0c5863c985a64352755f366aee3799c245"}, "comeonin": {:hex, :comeonin, "5.3.1", "7fe612b739c78c9c1a75186ef2d322ce4d25032d119823269d0aa1e2f1e20025", [:mix], [], "hexpm", "d6222483060c17f0977fad1b7401ef0c5863c985a64352755f366aee3799c245"},
"concurrent_limiter": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/concurrent_limiter.git", "d81be41024569330f296fc472e24198d7499ba78", [ref: "d81be41024569330f296fc472e24198d7499ba78"]}, "concurrent_limiter": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/concurrent_limiter.git", "d81be41024569330f296fc472e24198d7499ba78", [ref: "d81be41024569330f296fc472e24198d7499ba78"]},
@ -53,26 +53,26 @@
"gen_state_machine": {:hex, :gen_state_machine, "2.0.5", "9ac15ec6e66acac994cc442dcc2c6f9796cf380ec4b08267223014be1c728a95", [:mix], [], "hexpm"}, "gen_state_machine": {:hex, :gen_state_machine, "2.0.5", "9ac15ec6e66acac994cc442dcc2c6f9796cf380ec4b08267223014be1c728a95", [:mix], [], "hexpm"},
"gettext": {:hex, :gettext, "0.18.0", "406d6b9e0e3278162c2ae1de0a60270452c553536772167e2d701f028116f870", [:mix], [], "hexpm", "c3f850be6367ebe1a08616c2158affe4a23231c70391050bf359d5f92f66a571"}, "gettext": {:hex, :gettext, "0.18.0", "406d6b9e0e3278162c2ae1de0a60270452c553536772167e2d701f028116f870", [:mix], [], "hexpm", "c3f850be6367ebe1a08616c2158affe4a23231c70391050bf359d5f92f66a571"},
"gun": {:git, "https://github.com/ninenines/gun.git", "921c47146b2d9567eac7e9a4d2ccc60fffd4f327", [ref: "921c47146b2d9567eac7e9a4d2ccc60fffd4f327"]}, "gun": {:git, "https://github.com/ninenines/gun.git", "921c47146b2d9567eac7e9a4d2ccc60fffd4f327", [ref: "921c47146b2d9567eac7e9a4d2ccc60fffd4f327"]},
"hackney": {:hex, :hackney, "1.15.2", "07e33c794f8f8964ee86cebec1a8ed88db5070e52e904b8f12209773c1036085", [: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.5", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "e0100f8ef7d1124222c11ad362c857d3df7cb5f4204054f9f0f4a728666591fc"}, "hackney": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/hackney.git", "7d7119f0651515d6d7669c78393fd90950a3ec6e", [ref: "7d7119f0651515d6d7669c78393fd90950a3ec6e"]},
"html_entities": {:hex, :html_entities, "0.5.1", "1c9715058b42c35a2ab65edc5b36d0ea66dd083767bef6e3edb57870ef556549", [:mix], [], "hexpm", "30efab070904eb897ff05cd52fa61c1025d7f8ef3a9ca250bc4e6513d16c32de"}, "html_entities": {:hex, :html_entities, "0.5.1", "1c9715058b42c35a2ab65edc5b36d0ea66dd083767bef6e3edb57870ef556549", [:mix], [], "hexpm", "30efab070904eb897ff05cd52fa61c1025d7f8ef3a9ca250bc4e6513d16c32de"},
"html_sanitize_ex": {:hex, :html_sanitize_ex, "1.3.0", "f005ad692b717691203f940c686208aa3d8ffd9dd4bb3699240096a51fa9564e", [:mix], [{:mochiweb, "~> 2.15", [hex: :mochiweb, repo: "hexpm", optional: false]}], "hexpm"}, "html_sanitize_ex": {:hex, :html_sanitize_ex, "1.3.0", "f005ad692b717691203f940c686208aa3d8ffd9dd4bb3699240096a51fa9564e", [:mix], [{:mochiweb, "~> 2.15", [hex: :mochiweb, repo: "hexpm", optional: false]}], "hexpm"},
"http_signatures": {:hex, :http_signatures, "0.1.0", "4e4b501a936dbf4cb5222597038a89ea10781776770d2e185849fa829686b34c", [:mix], [], "hexpm", "f8a7b3731e3fd17d38fa6e343fcad7b03d6874a3b0a108c8568a71ed9c2cf824"}, "http_signatures": {:hex, :http_signatures, "0.1.0", "4e4b501a936dbf4cb5222597038a89ea10781776770d2e185849fa829686b34c", [:mix], [], "hexpm", "f8a7b3731e3fd17d38fa6e343fcad7b03d6874a3b0a108c8568a71ed9c2cf824"},
"httpoison": {:hex, :httpoison, "1.6.2", "ace7c8d3a361cebccbed19c283c349b3d26991eff73a1eaaa8abae2e3c8089b6", [:mix], [{:hackney, "~> 1.15 and >= 1.15.2", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "aa2c74bd271af34239a3948779612f87df2422c2fdcfdbcec28d9c105f0773fe"}, "httpoison": {:hex, :httpoison, "1.6.2", "ace7c8d3a361cebccbed19c283c349b3d26991eff73a1eaaa8abae2e3c8089b6", [:mix], [{:hackney, "~> 1.15 and >= 1.15.2", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "aa2c74bd271af34239a3948779612f87df2422c2fdcfdbcec28d9c105f0773fe"},
"idna": {:hex, :idna, "6.0.0", "689c46cbcdf3524c44d5f3dde8001f364cd7608a99556d8fbd8239a5798d4c10", [:rebar3], [{:unicode_util_compat, "0.4.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "4bdd305eb64e18b0273864920695cb18d7a2021f31a11b9c5fbcd9a253f936e2"}, "idna": {:git, "https://github.com/benoitc/erlang-idna", "6cff72747821110169ecfac871b0c69e5064afff", [tag: "6.0.0"]},
"inet_cidr": {:hex, :inet_cidr, "1.0.4", "a05744ab7c221ca8e395c926c3919a821eb512e8f36547c062f62c4ca0cf3d6e", [:mix], [], "hexpm", "64a2d30189704ae41ca7dbdd587f5291db5d1dda1414e0774c29ffc81088c1bc"}, "inet_cidr": {:hex, :inet_cidr, "1.0.4", "a05744ab7c221ca8e395c926c3919a821eb512e8f36547c062f62c4ca0cf3d6e", [:mix], [], "hexpm", "64a2d30189704ae41ca7dbdd587f5291db5d1dda1414e0774c29ffc81088c1bc"},
"jason": {:hex, :jason, "1.2.2", "ba43e3f2709fd1aa1dce90aaabfd039d000469c05c56f0b8e31978e03fa39052", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "18a228f5f0058ee183f29f9eae0805c6e59d61c3b006760668d8d18ff0d12179"}, "jason": {:hex, :jason, "1.2.2", "ba43e3f2709fd1aa1dce90aaabfd039d000469c05c56f0b8e31978e03fa39052", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "18a228f5f0058ee183f29f9eae0805c6e59d61c3b006760668d8d18ff0d12179"},
"joken": {:hex, :joken, "2.2.0", "2daa1b12be05184aff7b5ace1d43ca1f81345962285fff3f88db74927c954d3a", [:mix], [{:jose, "~> 1.9", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "b4f92e30388206f869dd25d1af628a1d99d7586e5cf0672f64d4df84c4d2f5e9"}, "joken": {:hex, :joken, "2.2.0", "2daa1b12be05184aff7b5ace1d43ca1f81345962285fff3f88db74927c954d3a", [:mix], [{:jose, "~> 1.9", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "b4f92e30388206f869dd25d1af628a1d99d7586e5cf0672f64d4df84c4d2f5e9"},
"jose": {:hex, :jose, "1.10.1", "16d8e460dae7203c6d1efa3f277e25b5af8b659febfc2f2eb4bacf87f128b80a", [:mix, :rebar3], [], "hexpm", "3c7ddc8a9394b92891db7c2771da94bf819834a1a4c92e30857b7d582e2f8257"}, "jose": {:hex, :jose, "1.10.1", "16d8e460dae7203c6d1efa3f277e25b5af8b659febfc2f2eb4bacf87f128b80a", [:mix, :rebar3], [], "hexpm", "3c7ddc8a9394b92891db7c2771da94bf819834a1a4c92e30857b7d582e2f8257"},
"jumper": {:hex, :jumper, "1.0.1", "3c00542ef1a83532b72269fab9f0f0c82bf23a35e27d278bfd9ed0865cecabff", [:mix], [], "hexpm", "318c59078ac220e966d27af3646026db9b5a5e6703cb2aa3e26bcfaba65b7433"}, "jumper": {:hex, :jumper, "1.0.1", "3c00542ef1a83532b72269fab9f0f0c82bf23a35e27d278bfd9ed0865cecabff", [:mix], [], "hexpm", "318c59078ac220e966d27af3646026db9b5a5e6703cb2aa3e26bcfaba65b7433"},
"libring": {:hex, :libring, "1.4.0", "41246ba2f3fbc76b3971f6bce83119dfec1eee17e977a48d8a9cfaaf58c2a8d6", [:mix], [], "hexpm"}, "libring": {:hex, :libring, "1.4.0", "41246ba2f3fbc76b3971f6bce83119dfec1eee17e977a48d8a9cfaaf58c2a8d6", [:mix], [], "hexpm"},
"linkify": {:hex, :linkify, "0.2.0", "2518bbbea21d2caa9d372424e1ad845b640c6630e2d016f1bd1f518f9ebcca28", [:mix], [], "hexpm", "b8ca8a68b79e30b7938d6c996085f3db14939f29538a59ca5101988bb7f917f6"}, "linkify": {:hex, :linkify, "0.4.0", "7845b6ac33050a41acaf9318923ce6e7f3854418be9a5f22184de103f7a68ff9", [:mix], [], "hexpm", "a0ceb4c78591fecccf1d99fecc10c13dba75a307c663c80e28af9e2cdd9776ee"},
"majic": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/majic.git", "4c692e544b28d1f5e543fb8a44be090f8cd96f80", [branch: "develop"]}, "majic": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/majic.git", "4c692e544b28d1f5e543fb8a44be090f8cd96f80", [ref: "4c692e544b28d1f5e543fb8a44be090f8cd96f80"]},
"makeup": {:hex, :makeup, "1.0.3", "e339e2f766d12e7260e6672dd4047405963c5ec99661abdc432e6ec67d29ef95", [:mix], [{:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "2e9b4996d11832947731f7608fed7ad2f9443011b3b479ae288011265cdd3dad"}, "makeup": {:hex, :makeup, "1.0.3", "e339e2f766d12e7260e6672dd4047405963c5ec99661abdc432e6ec67d29ef95", [:mix], [{:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "2e9b4996d11832947731f7608fed7ad2f9443011b3b479ae288011265cdd3dad"},
"makeup_elixir": {:hex, :makeup_elixir, "0.14.1", "4f0e96847c63c17841d42c08107405a005a2680eb9c7ccadfd757bd31dabccfb", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f2438b1a80eaec9ede832b5c41cd4f373b38fd7aa33e3b22d9db79e640cbde11"}, "makeup_elixir": {:hex, :makeup_elixir, "0.14.1", "4f0e96847c63c17841d42c08107405a005a2680eb9c7ccadfd757bd31dabccfb", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f2438b1a80eaec9ede832b5c41cd4f373b38fd7aa33e3b22d9db79e640cbde11"},
"meck": {:hex, :meck, "0.8.13", "ffedb39f99b0b99703b8601c6f17c7f76313ee12de6b646e671e3188401f7866", [:rebar3], [], "hexpm", "d34f013c156db51ad57cc556891b9720e6a1c1df5fe2e15af999c84d6cebeb1a"}, "meck": {:hex, :meck, "0.8.13", "ffedb39f99b0b99703b8601c6f17c7f76313ee12de6b646e671e3188401f7866", [:rebar3], [], "hexpm", "d34f013c156db51ad57cc556891b9720e6a1c1df5fe2e15af999c84d6cebeb1a"},
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, "metrics": {:git, "https://github.com/benoitc/erlang-metrics", "c6eb4dcf29f9e907539915e2ab996f40c2ec7e8e", [tag: "1.0.1"]},
"mime": {:hex, :mime, "1.4.0", "5066f14944b470286146047d2f73518cf5cca82f8e4815cf35d196b58cf07c47", [:mix], [], "hexpm", "75fa42c4228ea9a23f70f123c74ba7cece6a03b1fd474fe13f6a7a85c6ea4ff6"}, "mime": {:hex, :mime, "1.4.0", "5066f14944b470286146047d2f73518cf5cca82f8e4815cf35d196b58cf07c47", [:mix], [], "hexpm", "75fa42c4228ea9a23f70f123c74ba7cece6a03b1fd474fe13f6a7a85c6ea4ff6"},
"mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, "mimerl": {:git, "https://github.com/benoitc/mimerl", "5a1b22a8fada5b3b40438da00a6923cb87a42bbc", [tag: "1.2.0"]},
"mochiweb": {:hex, :mochiweb, "2.18.0", "eb55f1db3e6e960fac4e6db4e2db9ec3602cc9f30b86cd1481d56545c3145d2e", [:rebar3], [], "hexpm"}, "mochiweb": {:hex, :mochiweb, "2.18.0", "eb55f1db3e6e960fac4e6db4e2db9ec3602cc9f30b86cd1481d56545c3145d2e", [:rebar3], [], "hexpm"},
"mock": {:hex, :mock, "0.3.5", "feb81f52b8dcf0a0d65001d2fec459f6b6a8c22562d94a965862f6cc066b5431", [:mix], [{:meck, "~> 0.8.13", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "6fae404799408300f863550392635d8f7e3da6b71abdd5c393faf41b131c8728"}, "mock": {:hex, :mock, "0.3.5", "feb81f52b8dcf0a0d65001d2fec459f6b6a8c22562d94a965862f6cc066b5431", [:mix], [{:meck, "~> 0.8.13", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "6fae404799408300f863550392635d8f7e3da6b71abdd5c393faf41b131c8728"},
"mogrify": {:hex, :mogrify, "0.7.4", "9b2496dde44b1ce12676f85d7dc531900939e6367bc537c7243a1b089435b32d", [:mix], [], "hexpm", "50d79e337fba6bc95bfbef918058c90f50b17eed9537771e61d4619488f099c3"}, "mogrify": {:hex, :mogrify, "0.7.4", "9b2496dde44b1ce12676f85d7dc531900939e6367bc537c7243a1b089435b32d", [:mix], [], "hexpm", "50d79e337fba6bc95bfbef918058c90f50b17eed9537771e61d4619488f099c3"},
@ -84,7 +84,7 @@
"oban": {:hex, :oban, "2.1.0", "034144686f7e76a102b5d67731f098d98a9e4a52b07c25ad580a01f83a7f1cf5", [:mix], [{:ecto_sql, ">= 3.4.3", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.14", [hex: :postgrex, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c6f067fa3b308ed9e0e6beb2b34277c9c4e48bf95338edabd8f4a757a26e04c2"}, "oban": {:hex, :oban, "2.1.0", "034144686f7e76a102b5d67731f098d98a9e4a52b07c25ad580a01f83a7f1cf5", [:mix], [{:ecto_sql, ">= 3.4.3", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.14", [hex: :postgrex, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c6f067fa3b308ed9e0e6beb2b34277c9c4e48bf95338edabd8f4a757a26e04c2"},
"open_api_spex": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/open_api_spex.git", "f296ac0924ba3cf79c7a588c4c252889df4c2edd", [ref: "f296ac0924ba3cf79c7a588c4c252889df4c2edd"]}, "open_api_spex": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/open_api_spex.git", "f296ac0924ba3cf79c7a588c4c252889df4c2edd", [ref: "f296ac0924ba3cf79c7a588c4c252889df4c2edd"]},
"p1_utils": {:hex, :p1_utils, "1.0.18", "3fe224de5b2e190d730a3c5da9d6e8540c96484cf4b4692921d1e28f0c32b01c", [:rebar3], [], "hexpm", "1fc8773a71a15553b179c986b22fbeead19b28fe486c332d4929700ffeb71f88"}, "p1_utils": {:hex, :p1_utils, "1.0.18", "3fe224de5b2e190d730a3c5da9d6e8540c96484cf4b4692921d1e28f0c32b01c", [:rebar3], [], "hexpm", "1fc8773a71a15553b179c986b22fbeead19b28fe486c332d4929700ffeb71f88"},
"parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm", "17ef63abde837ad30680ea7f857dd9e7ced9476cdd7b0394432af4bfc241b960"}, "parse_trans": {:git, "https://github.com/uwiger/parse_trans.git", "76abb347c3c1d00fb0ccf9e4b43e22b3d2288484", [tag: "3.3.0"]},
"pbkdf2_elixir": {:hex, :pbkdf2_elixir, "1.2.1", "9cbe354b58121075bd20eb83076900a3832324b7dd171a6895fab57b6bb2752c", [:mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}], "hexpm", "d3b40a4a4630f0b442f19eca891fcfeeee4c40871936fed2f68e1c4faa30481f"}, "pbkdf2_elixir": {:hex, :pbkdf2_elixir, "1.2.1", "9cbe354b58121075bd20eb83076900a3832324b7dd171a6895fab57b6bb2752c", [:mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}], "hexpm", "d3b40a4a4630f0b442f19eca891fcfeeee4c40871936fed2f68e1c4faa30481f"},
"phoenix": {:hex, :phoenix, "1.5.6", "8298cdb4e0f943242ba8410780a6a69cbbe972fef199b341a36898dd751bdd66", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 2.13", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.1.2 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "0dc4d39af1306b6aa5122729b0a95ca779e42c708c6fe7abbb3d336d5379e956"}, "phoenix": {:hex, :phoenix, "1.5.6", "8298cdb4e0f943242ba8410780a6a69cbbe972fef199b341a36898dd751bdd66", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 2.13", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.1.2 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "0dc4d39af1306b6aa5122729b0a95ca779e42c708c6fe7abbb3d336d5379e956"},
"phoenix_ecto": {:hex, :phoenix_ecto, "4.2.1", "13f124cf0a3ce0f1948cf24654c7b9f2347169ff75c1123f44674afee6af3b03", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 2.15", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "478a1bae899cac0a6e02be1deec7e2944b7754c04e7d4107fc5a517f877743c0"}, "phoenix_ecto": {:hex, :phoenix_ecto, "4.2.1", "13f124cf0a3ce0f1948cf24654c7b9f2347169ff75c1123f44674afee6af3b03", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 2.15", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "478a1bae899cac0a6e02be1deec7e2944b7754c04e7d4107fc5a517f877743c0"},
@ -110,7 +110,7 @@
"recon": {:hex, :recon, "2.5.1", "430ffa60685ac1efdfb1fe4c97b8767c92d0d92e6e7c3e8621559ba77598678a", [:mix, :rebar3], [], "hexpm", "5721c6b6d50122d8f68cccac712caa1231f97894bab779eff5ff0f886cb44648"}, "recon": {:hex, :recon, "2.5.1", "430ffa60685ac1efdfb1fe4c97b8767c92d0d92e6e7c3e8621559ba77598678a", [:mix, :rebar3], [], "hexpm", "5721c6b6d50122d8f68cccac712caa1231f97894bab779eff5ff0f886cb44648"},
"remote_ip": {:git, "https://git.pleroma.social/pleroma/remote_ip.git", "b647d0deecaa3acb140854fe4bda5b7e1dc6d1c8", [ref: "b647d0deecaa3acb140854fe4bda5b7e1dc6d1c8"]}, "remote_ip": {:git, "https://git.pleroma.social/pleroma/remote_ip.git", "b647d0deecaa3acb140854fe4bda5b7e1dc6d1c8", [ref: "b647d0deecaa3acb140854fe4bda5b7e1dc6d1c8"]},
"sleeplocks": {:hex, :sleeplocks, "1.1.1", "3d462a0639a6ef36cc75d6038b7393ae537ab394641beb59830a1b8271faeed3", [:rebar3], [], "hexpm", "84ee37aeff4d0d92b290fff986d6a95ac5eedf9b383fadfd1d88e9b84a1c02e1"}, "sleeplocks": {:hex, :sleeplocks, "1.1.1", "3d462a0639a6ef36cc75d6038b7393ae537ab394641beb59830a1b8271faeed3", [:rebar3], [], "hexpm", "84ee37aeff4d0d92b290fff986d6a95ac5eedf9b383fadfd1d88e9b84a1c02e1"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.5", "6eaf7ad16cb568bb01753dbbd7a95ff8b91c7979482b95f38443fe2c8852a79b", [:make, :mix, :rebar3], [], "hexpm", "13104d7897e38ed7f044c4de953a6c28597d1c952075eb2e328bc6d6f2bfc496"}, "ssl_verify_fun": {:git, "https://github.com/deadtrickster/ssl_verify_fun.erl", "c5718226b0b9f3d1a38ef6ca3c3b4c75f53dda92", [tag: "1.1.4"]},
"sweet_xml": {:hex, :sweet_xml, "0.6.6", "fc3e91ec5dd7c787b6195757fbcf0abc670cee1e4172687b45183032221b66b8", [:mix], [], "hexpm", "2e1ec458f892ffa81f9f8386e3f35a1af6db7a7a37748a64478f13163a1f3573"}, "sweet_xml": {:hex, :sweet_xml, "0.6.6", "fc3e91ec5dd7c787b6195757fbcf0abc670cee1e4172687b45183032221b66b8", [:mix], [], "hexpm", "2e1ec458f892ffa81f9f8386e3f35a1af6db7a7a37748a64478f13163a1f3573"},
"swoosh": {:hex, :swoosh, "1.0.6", "6765e334c67dacabe721f0d701c7e5a6f06e4595c90df6f91e73ebd54d555833", [:mix], [{:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {: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", "7c50ef78e4acfd1cbd4907dc1fa87b5540675a6be9dc979d04890f49d7ec1830"}, "swoosh": {:hex, :swoosh, "1.0.6", "6765e334c67dacabe721f0d701c7e5a6f06e4595c90df6f91e73ebd54d555833", [:mix], [{:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {: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", "7c50ef78e4acfd1cbd4907dc1fa87b5540675a6be9dc979d04890f49d7ec1830"},
"syslog": {:hex, :syslog, "1.1.0", "6419a232bea84f07b56dc575225007ffe34d9fdc91abe6f1b2f254fd71d8efc2", [:rebar3], [], "hexpm", "4c6a41373c7e20587be33ef841d3de6f3beba08519809329ecc4d27b15b659e1"}, "syslog": {:hex, :syslog, "1.1.0", "6419a232bea84f07b56dc575225007ffe34d9fdc91abe6f1b2f254fd71d8efc2", [:rebar3], [], "hexpm", "4c6a41373c7e20587be33ef841d3de6f3beba08519809329ecc4d27b15b659e1"},
@ -120,7 +120,7 @@
"trailing_format_plug": {:hex, :trailing_format_plug, "0.0.7", "64b877f912cf7273bed03379936df39894149e35137ac9509117e59866e10e45", [:mix], [{:plug, "> 0.12.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "bd4fde4c15f3e993a999e019d64347489b91b7a9096af68b2bdadd192afa693f"}, "trailing_format_plug": {:hex, :trailing_format_plug, "0.0.7", "64b877f912cf7273bed03379936df39894149e35137ac9509117e59866e10e45", [:mix], [{:plug, "> 0.12.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "bd4fde4c15f3e993a999e019d64347489b91b7a9096af68b2bdadd192afa693f"},
"tzdata": {:hex, :tzdata, "1.0.4", "a3baa4709ea8dba552dca165af6ae97c624a2d6ac14bd265165eaa8e8af94af6", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "b02637db3df1fd66dd2d3c4f194a81633d0e4b44308d36c1b2fdfd1e4e6f169b"}, "tzdata": {:hex, :tzdata, "1.0.4", "a3baa4709ea8dba552dca165af6ae97c624a2d6ac14bd265165eaa8e8af94af6", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "b02637db3df1fd66dd2d3c4f194a81633d0e4b44308d36c1b2fdfd1e4e6f169b"},
"ueberauth": {:hex, :ueberauth, "0.6.3", "d42ace28b870e8072cf30e32e385579c57b9cc96ec74fa1f30f30da9c14f3cc0", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "afc293d8a1140d6591b53e3eaf415ca92842cb1d32fad3c450c6f045f7f91b60"}, "ueberauth": {:hex, :ueberauth, "0.6.3", "d42ace28b870e8072cf30e32e385579c57b9cc96ec74fa1f30f30da9c14f3cc0", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "afc293d8a1140d6591b53e3eaf415ca92842cb1d32fad3c450c6f045f7f91b60"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm", "1d1848c40487cdb0b30e8ed975e34e025860c02e419cb615d255849f3427439d"}, "unicode_util_compat": {:git, "https://github.com/benoitc/unicode_util_compat.git", "38d7bc105f51159e8ea3279c40121db9db1e652f", [tag: "0.3.1"]},
"unsafe": {:hex, :unsafe, "1.0.1", "a27e1874f72ee49312e0a9ec2e0b27924214a05e3ddac90e91727bc76f8613d8", [:mix], [], "hexpm", "6c7729a2d214806450d29766abc2afaa7a2cbecf415be64f36a6691afebb50e5"}, "unsafe": {:hex, :unsafe, "1.0.1", "a27e1874f72ee49312e0a9ec2e0b27924214a05e3ddac90e91727bc76f8613d8", [:mix], [], "hexpm", "6c7729a2d214806450d29766abc2afaa7a2cbecf415be64f36a6691afebb50e5"},
"web_push_encryption": {:hex, :web_push_encryption, "0.3.0", "598b5135e696fd1404dc8d0d7c0fa2c027244a4e5d5e5a98ba267f14fdeaabc8", [:mix], [{:httpoison, "~> 1.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jose, "~> 1.8", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "f10bdd1afe527ede694749fb77a2f22f146a51b054c7fa541c9fd920fba7c875"}, "web_push_encryption": {:hex, :web_push_encryption, "0.3.0", "598b5135e696fd1404dc8d0d7c0fa2c027244a4e5d5e5a98ba267f14fdeaabc8", [:mix], [{:httpoison, "~> 1.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jose, "~> 1.8", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "f10bdd1afe527ede694749fb77a2f22f146a51b054c7fa541c9fd920fba7c875"},
"websocket_client": {:git, "https://github.com/jeremyong/websocket_client.git", "9a6f65d05ebf2725d62fb19262b21f1805a59fbf", []}, "websocket_client": {:git, "https://github.com/jeremyong/websocket_client.git", "9a6f65d05ebf2725d62fb19262b21f1805a59fbf", []},

View File

@ -0,0 +1,141 @@
## This file is a PO Template file.
msgid "eperm"
msgstr "Operation not permitted"
msgid "eacces"
msgstr "Permission denied"
msgid "eagain"
msgstr "Resource temporarily unavailable"
msgid "ebadf"
msgstr "Bad file descriptor"
msgid "ebadmsg"
msgstr "Bad message"
msgid "ebusy"
msgstr "Device or resource busy"
msgid "edeadlk"
msgstr "Resource deadlock avoided"
msgid "edeadlock"
msgstr "Resource deadlock avoided"
msgid "edquot"
msgstr "Disk quota exceeded"
msgid "eexist"
msgstr "File exists"
msgid "efault"
msgstr "Bad address"
msgid "efbig"
msgstr "File is too large"
msgid "eftype"
msgstr "Inappropriate file type or format"
msgid "eintr"
msgstr "Interrupted system call"
msgid "einval"
msgstr "Invalid argument"
msgid "eio"
msgstr "Input/output error"
msgid "eisdir"
msgstr "Illegal operation on a directory"
msgid "eloop"
msgstr "Too many levels of symbolic links"
msgid "emfile"
msgstr "Too many open files"
msgid "emlink"
msgstr "Too many links"
msgid "emultihop"
msgstr "Multihop attempted"
msgid "enametoolong"
msgstr "File name is too long"
msgid "enfile"
msgstr "Too many open files in system"
msgid "enobufs"
msgstr "No buffer space available"
msgid "enodev"
msgstr "No such device"
msgid "enolck"
msgstr "No locks available"
msgid "enolink"
msgstr "Link has been severed"
msgid "enoent"
msgstr "No such file or directory"
msgid "enomem"
msgstr "Cannot allocate memory"
msgid "enospc"
msgstr "No space left on device"
msgid "enosr"
msgstr "Out of streams resources"
msgid "enostr"
msgstr "Device is not a stream"
msgid "enosys"
msgstr "Function not implemented"
msgid "enotblk"
msgstr "Block device required"
msgid "enotdir"
msgstr "Not a directory"
msgid "enotsup"
msgstr "Operation not supported"
msgid "enxio"
msgstr "No such device or address"
msgid "eopnotsupp"
msgstr "Operation not supported"
msgid "eoverflow"
msgstr "Value too large for defined data type"
msgid "epipe"
msgstr "Broken pipe"
msgid "erange"
msgstr "Numerical result out of range"
msgid "erofs"
msgstr "Read-only file system"
msgid "espipe"
msgstr "Illegal seek"
msgid "esrch"
msgstr "No such process"
msgid "estale"
msgstr "Stale file handle"
msgid "etxtbsy"
msgstr "Text file busy"
msgid "exdev"
msgstr "Invalid cross-device link"

View File

@ -3,14 +3,17 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-11-10 13:39+0000\n" "POT-Creation-Date: 2020-11-10 13:39+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: 2020-11-21 04:42+0000\n"
"Last-Translator: Automatically generated\n" "Last-Translator: Guy Sheffer <guysoft@gmail.com>\n"
"Language-Team: none\n" "Language-Team: Hebrew <https://translate.pleroma.social/projects/pleroma/"
"pleroma/he/>\n"
"Language: he\n" "Language: he\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"X-Generator: Translate Toolkit 2.5.1\n" "Plural-Forms: nplurals=4; plural=(n == 1) ? 0 : ((n == 2) ? 1 : ((n > 10 && "
"n % 10 == 0) ? 2 : 3));\n"
"X-Generator: Weblate 4.0.4\n"
## This file is a PO Template file. ## This file is a PO Template file.
## ##
@ -23,264 +26,264 @@ msgstr ""
## effect: edit them in PO (`.po`) files instead. ## effect: edit them in PO (`.po`) files instead.
## From Ecto.Changeset.cast/4 ## From Ecto.Changeset.cast/4
msgid "can't be blank" msgid "can't be blank"
msgstr "" msgstr "לא יכול להיות ריק"
## From Ecto.Changeset.unique_constraint/3 ## From Ecto.Changeset.unique_constraint/3
msgid "has already been taken" msgid "has already been taken"
msgstr "" msgstr "כבר נלקח"
## From Ecto.Changeset.put_change/3 ## From Ecto.Changeset.put_change/3
msgid "is invalid" msgid "is invalid"
msgstr "" msgstr "אינו תקני"
## From Ecto.Changeset.validate_format/3 ## From Ecto.Changeset.validate_format/3
msgid "has invalid format" msgid "has invalid format"
msgstr "" msgstr "תבנית אינה תקנית"
## From Ecto.Changeset.validate_subset/3 ## From Ecto.Changeset.validate_subset/3
msgid "has an invalid entry" msgid "has an invalid entry"
msgstr "" msgstr "בעל.ה רשומה לא חוקית"
## From Ecto.Changeset.validate_exclusion/3 ## From Ecto.Changeset.validate_exclusion/3
msgid "is reserved" msgid "is reserved"
msgstr "" msgstr "הינו שמור"
## From Ecto.Changeset.validate_confirmation/3 ## From Ecto.Changeset.validate_confirmation/3
msgid "does not match confirmation" msgid "does not match confirmation"
msgstr "" msgstr "אינו תורם את האימות"
## From Ecto.Changeset.no_assoc_constraint/3 ## From Ecto.Changeset.no_assoc_constraint/3
msgid "is still associated with this entry" msgid "is still associated with this entry"
msgstr "" msgstr "עדיין משויך לרשומה זו"
msgid "are still associated with this entry" msgid "are still associated with this entry"
msgstr "" msgstr "עדיין משויכים לרשומה זו"
## From Ecto.Changeset.validate_length/3 ## From Ecto.Changeset.validate_length/3
msgid "should be %{count} character(s)" msgid "should be %{count} character(s)"
msgid_plural "should be %{count} character(s)" msgid_plural "should be %{count} character(s)"
msgstr[0] "" msgstr[0] "אחד"
msgstr[1] "" msgstr[1] "שני"
msgstr[2] "" msgstr[2] "בודדים"
msgstr[3] "" msgstr[3] "אחר"
msgid "should have %{count} item(s)" msgid "should have %{count} item(s)"
msgid_plural "should have %{count} item(s)" msgid_plural "should have %{count} item(s)"
msgstr[0] "" msgstr[0] "אחד"
msgstr[1] "" msgstr[1] "שני"
msgstr[2] "" msgstr[2] "בודדים"
msgstr[3] "" msgstr[3] "אחר"
msgid "should be at least %{count} character(s)" msgid "should be at least %{count} character(s)"
msgid_plural "should be at least %{count} character(s)" msgid_plural "should be at least %{count} character(s)"
msgstr[0] "" msgstr[0] "אחד"
msgstr[1] "" msgstr[1] "שנים"
msgstr[2] "" msgstr[2] "בודדים"
msgstr[3] "" msgstr[3] "אחר"
msgid "should have at least %{count} item(s)" msgid "should have at least %{count} item(s)"
msgid_plural "should have at least %{count} item(s)" msgid_plural "should have at least %{count} item(s)"
msgstr[0] "" msgstr[0] "אחד"
msgstr[1] "" msgstr[1] "שניים"
msgstr[2] "" msgstr[2] "בודדים"
msgstr[3] "" msgstr[3] "אחר"
msgid "should be at most %{count} character(s)" msgid "should be at most %{count} character(s)"
msgid_plural "should be at most %{count} character(s)" msgid_plural "should be at most %{count} character(s)"
msgstr[0] "" msgstr[0] "אחד"
msgstr[1] "" msgstr[1] "שניים"
msgstr[2] "" msgstr[2] "בודדים"
msgstr[3] "" msgstr[3] "אחר"
msgid "should have at most %{count} item(s)" msgid "should have at most %{count} item(s)"
msgid_plural "should have at most %{count} item(s)" msgid_plural "should have at most %{count} item(s)"
msgstr[0] "" msgstr[0] "אחד"
msgstr[1] "" msgstr[1] "שניים"
msgstr[2] "" msgstr[2] "בודדים"
msgstr[3] "" msgstr[3] "אחר"
## From Ecto.Changeset.validate_number/3 ## From Ecto.Changeset.validate_number/3
msgid "must be less than %{number}" msgid "must be less than %{number}"
msgstr "" msgstr "חייב להיות מתחת ל-%{number}"
msgid "must be greater than %{number}" msgid "must be greater than %{number}"
msgstr "" msgstr "חייב להיות מעל ל-%{number}"
msgid "must be less than or equal to %{number}" msgid "must be less than or equal to %{number}"
msgstr "" msgstr "חייב להיות שווה ל-%{number}"
msgid "must be greater than or equal to %{number}" msgid "must be greater than or equal to %{number}"
msgstr "" msgstr "חייב להיות גדול או שווה ל-%{number}"
msgid "must be equal to %{number}" msgid "must be equal to %{number}"
msgstr "" msgstr "חייב להיות שווה ל-%{number}"
#: lib/pleroma/web/common_api/common_api.ex:505 #: lib/pleroma/web/common_api/common_api.ex:505
#, elixir-format #, elixir-format
msgid "Account not found" msgid "Account not found"
msgstr "" msgstr "חשבון לא נמצא"
#: lib/pleroma/web/common_api/common_api.ex:339 #: lib/pleroma/web/common_api/common_api.ex:339
#, elixir-format #, elixir-format
msgid "Already voted" msgid "Already voted"
msgstr "" msgstr "הצבעה כבר התבצעה"
#: lib/pleroma/web/oauth/oauth_controller.ex:359 #: lib/pleroma/web/oauth/oauth_controller.ex:359
#, elixir-format #, elixir-format
msgid "Bad request" msgid "Bad request"
msgstr "" msgstr "בקשה שגוייה"
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:426 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:426
#, elixir-format #, elixir-format
msgid "Can't delete object" msgid "Can't delete object"
msgstr "" msgstr "לא ניתן למחוק אובייקט"
#: lib/pleroma/web/controller_helper.ex:105 #: lib/pleroma/web/controller_helper.ex:105
#: lib/pleroma/web/controller_helper.ex:111 #: lib/pleroma/web/controller_helper.ex:111
#, elixir-format #, elixir-format
msgid "Can't display this activity" msgid "Can't display this activity"
msgstr "" msgstr "לא ניתן להציג פעילות"
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:285 #: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:285
#, elixir-format #, elixir-format
msgid "Can't find user" msgid "Can't find user"
msgstr "" msgstr "לא ניתן למצוא משתמש"
#: lib/pleroma/web/pleroma_api/controllers/account_controller.ex:61 #: lib/pleroma/web/pleroma_api/controllers/account_controller.ex:61
#, elixir-format #, elixir-format
msgid "Can't get favorites" msgid "Can't get favorites"
msgstr "" msgstr "לא ניתן למצוא מועדפים"
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:438 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:438
#, elixir-format #, elixir-format
msgid "Can't like object" msgid "Can't like object"
msgstr "" msgstr "לא ניתן לעשות לחבב אובייקט"
#: lib/pleroma/web/common_api/utils.ex:563 #: lib/pleroma/web/common_api/utils.ex:563
#, elixir-format #, elixir-format
msgid "Cannot post an empty status without attachments" msgid "Cannot post an empty status without attachments"
msgstr "" msgstr "לא ניתן לשלוח סטטוס ריק ללא קבצים מצורפים"
#: lib/pleroma/web/common_api/utils.ex:511 #: lib/pleroma/web/common_api/utils.ex:511
#, elixir-format #, elixir-format
msgid "Comment must be up to %{max_size} characters" msgid "Comment must be up to %{max_size} characters"
msgstr "" msgstr "תגובה חייבת להיות עד %{max_size} תווים"
#: lib/pleroma/config/config_db.ex:191 #: lib/pleroma/config/config_db.ex:191
#, elixir-format #, elixir-format
msgid "Config with params %{params} not found" msgid "Config with params %{params} not found"
msgstr "" msgstr "הגדרה עם פרמטר %{params} לא נמצאה"
#: lib/pleroma/web/common_api/common_api.ex:181 #: lib/pleroma/web/common_api/common_api.ex:181
#: lib/pleroma/web/common_api/common_api.ex:185 #: lib/pleroma/web/common_api/common_api.ex:185
#, elixir-format #, elixir-format
msgid "Could not delete" msgid "Could not delete"
msgstr "" msgstr "לא ניתן למחוק"
#: lib/pleroma/web/common_api/common_api.ex:231 #: lib/pleroma/web/common_api/common_api.ex:231
#, elixir-format #, elixir-format
msgid "Could not favorite" msgid "Could not favorite"
msgstr "" msgstr "לא ניתן לחבב"
#: lib/pleroma/web/common_api/common_api.ex:453 #: lib/pleroma/web/common_api/common_api.ex:453
#, elixir-format #, elixir-format
msgid "Could not pin" msgid "Could not pin"
msgstr "" msgstr "לא ניתן לנעוץ"
#: lib/pleroma/web/common_api/common_api.ex:278 #: lib/pleroma/web/common_api/common_api.ex:278
#, elixir-format #, elixir-format
msgid "Could not unfavorite" msgid "Could not unfavorite"
msgstr "" msgstr "לא ניתן להסיר חיבוב"
#: lib/pleroma/web/common_api/common_api.ex:463 #: lib/pleroma/web/common_api/common_api.ex:463
#, elixir-format #, elixir-format
msgid "Could not unpin" msgid "Could not unpin"
msgstr "" msgstr "לא ניתן לבטל נעיצה"
#: lib/pleroma/web/common_api/common_api.ex:216 #: lib/pleroma/web/common_api/common_api.ex:216
#, elixir-format #, elixir-format
msgid "Could not unrepeat" msgid "Could not unrepeat"
msgstr "" msgstr "לא ניתן לבטל חזרה"
#: lib/pleroma/web/common_api/common_api.ex:512 #: lib/pleroma/web/common_api/common_api.ex:512
#: lib/pleroma/web/common_api/common_api.ex:521 #: lib/pleroma/web/common_api/common_api.ex:521
#, elixir-format #, elixir-format
msgid "Could not update state" msgid "Could not update state"
msgstr "" msgstr "לא ניתן לעדכן מצב"
#: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:207 #: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:207
#, elixir-format #, elixir-format
msgid "Error." msgid "Error."
msgstr "" msgstr "שגיאה."
#: lib/pleroma/web/twitter_api/twitter_api.ex:106 #: lib/pleroma/web/twitter_api/twitter_api.ex:106
#, elixir-format #, elixir-format
msgid "Invalid CAPTCHA" msgid "Invalid CAPTCHA"
msgstr "" msgstr "CAPTCHA לא תקין"
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:116 #: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:116
#: lib/pleroma/web/oauth/oauth_controller.ex:568 #: lib/pleroma/web/oauth/oauth_controller.ex:568
#, elixir-format #, elixir-format
msgid "Invalid credentials" msgid "Invalid credentials"
msgstr "" msgstr "נתוני אימות לא נכונים"
#: lib/pleroma/plugs/ensure_authenticated_plug.ex:38 #: lib/pleroma/plugs/ensure_authenticated_plug.ex:38
#, elixir-format #, elixir-format
msgid "Invalid credentials." msgid "Invalid credentials."
msgstr "" msgstr "נתוני אימות לא נכונים."
#: lib/pleroma/web/common_api/common_api.ex:355 #: lib/pleroma/web/common_api/common_api.ex:355
#, elixir-format #, elixir-format
msgid "Invalid indices" msgid "Invalid indices"
msgstr "" msgstr "אינדקס לא תקין"
#: lib/pleroma/web/admin_api/controllers/fallback_controller.ex:29 #: lib/pleroma/web/admin_api/controllers/fallback_controller.ex:29
#, elixir-format #, elixir-format
msgid "Invalid parameters" msgid "Invalid parameters"
msgstr "" msgstr "פרמטרים לא תקינים"
#: lib/pleroma/web/common_api/utils.ex:414 #: lib/pleroma/web/common_api/utils.ex:414
#, elixir-format #, elixir-format
msgid "Invalid password." msgid "Invalid password."
msgstr "" msgstr "סיסמה לא תקינה."
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:220 #: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:220
#, elixir-format #, elixir-format
msgid "Invalid request" msgid "Invalid request"
msgstr "" msgstr "בקשה לא תקינה"
#: lib/pleroma/web/twitter_api/twitter_api.ex:109 #: lib/pleroma/web/twitter_api/twitter_api.ex:109
#, elixir-format #, elixir-format
msgid "Kocaptcha service unavailable" msgid "Kocaptcha service unavailable"
msgstr "" msgstr "שירות Kocaptcha לא זמין"
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:112 #: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:112
#, elixir-format #, elixir-format
msgid "Missing parameters" msgid "Missing parameters"
msgstr "" msgstr "פרמטרים חסרים"
#: lib/pleroma/web/common_api/utils.ex:547 #: lib/pleroma/web/common_api/utils.ex:547
#, elixir-format #, elixir-format
msgid "No such conversation" msgid "No such conversation"
msgstr "" msgstr "שיחה לא קיימת"
#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:388 #: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:388
#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:414 lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:456 #: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:414 lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:456
#, elixir-format #, elixir-format
msgid "No such permission_group" msgid "No such permission_group"
msgstr "" msgstr "permission_group לא קיים"
#: lib/pleroma/plugs/uploaded_media.ex:84 #: lib/pleroma/plugs/uploaded_media.ex:84
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:486 lib/pleroma/web/admin_api/controllers/fallback_controller.ex:11 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:486 lib/pleroma/web/admin_api/controllers/fallback_controller.ex:11
#: lib/pleroma/web/feed/user_controller.ex:71 lib/pleroma/web/ostatus/ostatus_controller.ex:143 #: lib/pleroma/web/feed/user_controller.ex:71 lib/pleroma/web/ostatus/ostatus_controller.ex:143
#, elixir-format #, elixir-format
msgid "Not found" msgid "Not found"
msgstr "" msgstr "לא נמצא"
#: lib/pleroma/web/common_api/common_api.ex:331 #: lib/pleroma/web/common_api/common_api.ex:331
#, elixir-format #, elixir-format
msgid "Poll's author can't vote" msgid "Poll's author can't vote"
msgstr "" msgstr "מחבר הסקר לא יכול.ה להצביע"
#: lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:20 #: lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:20
#: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:37 lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:49 #: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:37 lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:49
@ -288,215 +291,215 @@ msgstr ""
#: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:71 #: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:71
#, elixir-format #, elixir-format
msgid "Record not found" msgid "Record not found"
msgstr "" msgstr "רשומה לא נמצאה"
#: lib/pleroma/web/admin_api/controllers/fallback_controller.ex:35 #: lib/pleroma/web/admin_api/controllers/fallback_controller.ex:35
#: lib/pleroma/web/feed/user_controller.ex:77 lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:36 #: lib/pleroma/web/feed/user_controller.ex:77 lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:36
#: lib/pleroma/web/ostatus/ostatus_controller.ex:149 #: lib/pleroma/web/ostatus/ostatus_controller.ex:149
#, elixir-format #, elixir-format
msgid "Something went wrong" msgid "Something went wrong"
msgstr "" msgstr "משהו השתבש"
#: lib/pleroma/web/common_api/activity_draft.ex:107 #: lib/pleroma/web/common_api/activity_draft.ex:107
#, elixir-format #, elixir-format
msgid "The message visibility must be direct" msgid "The message visibility must be direct"
msgstr "" msgstr "הנראות של ההודעה חייבת להיות ישירה"
#: lib/pleroma/web/common_api/utils.ex:573 #: lib/pleroma/web/common_api/utils.ex:573
#, elixir-format #, elixir-format
msgid "The status is over the character limit" msgid "The status is over the character limit"
msgstr "" msgstr "הסטטוס מעל להגבלת התווים"
#: lib/pleroma/plugs/ensure_public_or_authenticated_plug.ex:31 #: lib/pleroma/plugs/ensure_public_or_authenticated_plug.ex:31
#, elixir-format #, elixir-format
msgid "This resource requires authentication." msgid "This resource requires authentication."
msgstr "" msgstr "המשאב הזה דורש הרשאה."
#: lib/pleroma/plugs/rate_limiter/rate_limiter.ex:206 #: lib/pleroma/plugs/rate_limiter/rate_limiter.ex:206
#, elixir-format #, elixir-format
msgid "Throttled" msgid "Throttled"
msgstr "" msgstr "מושנק"
#: lib/pleroma/web/common_api/common_api.ex:356 #: lib/pleroma/web/common_api/common_api.ex:356
#, elixir-format #, elixir-format
msgid "Too many choices" msgid "Too many choices"
msgstr "" msgstr "יותר מדיי אפשרויות"
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:443 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:443
#, elixir-format #, elixir-format
msgid "Unhandled activity type" msgid "Unhandled activity type"
msgstr "" msgstr "אין התמודדות לסוג הפעילות"
#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:485 #: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:485
#, elixir-format #, elixir-format
msgid "You can't revoke your own admin status." msgid "You can't revoke your own admin status."
msgstr "" msgstr "לא ניתן לבטל את הרשאת המנהל של עצמך."
#: lib/pleroma/web/oauth/oauth_controller.ex:221 #: lib/pleroma/web/oauth/oauth_controller.ex:221
#: lib/pleroma/web/oauth/oauth_controller.ex:308 #: lib/pleroma/web/oauth/oauth_controller.ex:308
#, elixir-format #, elixir-format
msgid "Your account is currently disabled" msgid "Your account is currently disabled"
msgstr "" msgstr "החשבון שלך כרגע מבוטל"
#: lib/pleroma/web/oauth/oauth_controller.ex:183 #: lib/pleroma/web/oauth/oauth_controller.ex:183
#: lib/pleroma/web/oauth/oauth_controller.ex:331 #: lib/pleroma/web/oauth/oauth_controller.ex:331
#, elixir-format #, elixir-format
msgid "Your login is missing a confirmed e-mail address" msgid "Your login is missing a confirmed e-mail address"
msgstr "" msgstr "חסר לחשבון שלך כתובת דואר אלקטרוני מאושר"
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:390 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:390
#, elixir-format #, elixir-format
msgid "can't read inbox of %{nickname} as %{as_nickname}" msgid "can't read inbox of %{nickname} as %{as_nickname}"
msgstr "" msgstr "לא ניתן לקרוא את הדואר הנכנס של %{nickname} בתור %{as_nickname}"
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:473 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:473
#, elixir-format #, elixir-format
msgid "can't update outbox of %{nickname} as %{as_nickname}" msgid "can't update outbox of %{nickname} as %{as_nickname}"
msgstr "" msgstr "לא ניתן לעדכן את חשבון הדואר היוצא של %{nickname} בתור %{as_nickname}"
#: lib/pleroma/web/common_api/common_api.ex:471 #: lib/pleroma/web/common_api/common_api.ex:471
#, elixir-format #, elixir-format
msgid "conversation is already muted" msgid "conversation is already muted"
msgstr "" msgstr "שיחה כבר הושתקה"
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:314 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:314
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:492 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:492
#, elixir-format #, elixir-format
msgid "error" msgid "error"
msgstr "" msgstr "שגיאה"
#: lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex:32 #: lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex:32
#, elixir-format #, elixir-format
msgid "mascots can only be images" msgid "mascots can only be images"
msgstr "" msgstr "קמע יכול להיות רק תמונות"
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:62 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:62
#, elixir-format #, elixir-format
msgid "not found" msgid "not found"
msgstr "" msgstr "לא נמצא"
#: lib/pleroma/web/oauth/oauth_controller.ex:394 #: lib/pleroma/web/oauth/oauth_controller.ex:394
#, elixir-format #, elixir-format
msgid "Bad OAuth request." msgid "Bad OAuth request."
msgstr "" msgstr "בקשת OAuth שגוייה."
#: lib/pleroma/web/twitter_api/twitter_api.ex:115 #: lib/pleroma/web/twitter_api/twitter_api.ex:115
#, elixir-format #, elixir-format
msgid "CAPTCHA already used" msgid "CAPTCHA already used"
msgstr "" msgstr "כבר נעשה שימוש ב-CAPTCHA הזה"
#: lib/pleroma/web/twitter_api/twitter_api.ex:112 #: lib/pleroma/web/twitter_api/twitter_api.ex:112
#, elixir-format #, elixir-format
msgid "CAPTCHA expired" msgid "CAPTCHA expired"
msgstr "" msgstr "פג תוקף CAPTCHA"
#: lib/pleroma/plugs/uploaded_media.ex:57 #: lib/pleroma/plugs/uploaded_media.ex:57
#, elixir-format #, elixir-format
msgid "Failed" msgid "Failed"
msgstr "" msgstr "נכשל"
#: lib/pleroma/web/oauth/oauth_controller.ex:410 #: lib/pleroma/web/oauth/oauth_controller.ex:410
#, elixir-format #, elixir-format
msgid "Failed to authenticate: %{message}." msgid "Failed to authenticate: %{message}."
msgstr "" msgstr "נכשל האימות: %{message}."
#: lib/pleroma/web/oauth/oauth_controller.ex:441 #: lib/pleroma/web/oauth/oauth_controller.ex:441
#, elixir-format #, elixir-format
msgid "Failed to set up user account." msgid "Failed to set up user account."
msgstr "" msgstr "הגדרת חשבון משתמש נכשלה."
#: lib/pleroma/plugs/oauth_scopes_plug.ex:38 #: lib/pleroma/plugs/oauth_scopes_plug.ex:38
#, elixir-format #, elixir-format
msgid "Insufficient permissions: %{permissions}." msgid "Insufficient permissions: %{permissions}."
msgstr "" msgstr "אין מספיק הרשאות: %{permissions}."
#: lib/pleroma/plugs/uploaded_media.ex:104 #: lib/pleroma/plugs/uploaded_media.ex:104
#, elixir-format #, elixir-format
msgid "Internal Error" msgid "Internal Error"
msgstr "" msgstr "שגיאה פנימית"
#: lib/pleroma/web/oauth/fallback_controller.ex:22 #: lib/pleroma/web/oauth/fallback_controller.ex:22
#: lib/pleroma/web/oauth/fallback_controller.ex:29 #: lib/pleroma/web/oauth/fallback_controller.ex:29
#, elixir-format #, elixir-format
msgid "Invalid Username/Password" msgid "Invalid Username/Password"
msgstr "" msgstr "שם משתמש/סיסמה שגויים"
#: lib/pleroma/web/twitter_api/twitter_api.ex:118 #: lib/pleroma/web/twitter_api/twitter_api.ex:118
#, elixir-format #, elixir-format
msgid "Invalid answer data" msgid "Invalid answer data"
msgstr "" msgstr "תשובה שגוייה למידע"
#: lib/pleroma/web/nodeinfo/nodeinfo_controller.ex:33 #: lib/pleroma/web/nodeinfo/nodeinfo_controller.ex:33
#, elixir-format #, elixir-format
msgid "Nodeinfo schema version not handled" msgid "Nodeinfo schema version not handled"
msgstr "" msgstr "Nodeinfo של של גרסת הסכמה לא ניתן לטיפול"
#: lib/pleroma/web/oauth/oauth_controller.ex:172 #: lib/pleroma/web/oauth/oauth_controller.ex:172
#, elixir-format #, elixir-format
msgid "This action is outside the authorized scopes" msgid "This action is outside the authorized scopes"
msgstr "" msgstr "הפעולה הזו מחוץ לתחומי ההרשאות"
#: lib/pleroma/web/oauth/fallback_controller.ex:14 #: lib/pleroma/web/oauth/fallback_controller.ex:14
#, elixir-format #, elixir-format
msgid "Unknown error, please check the details and try again." msgid "Unknown error, please check the details and try again."
msgstr "" msgstr "שגיאה לא ידועה, יש לבדוק את פרטים ולנסות שוב."
#: lib/pleroma/web/oauth/oauth_controller.ex:119 #: lib/pleroma/web/oauth/oauth_controller.ex:119
#: lib/pleroma/web/oauth/oauth_controller.ex:158 #: lib/pleroma/web/oauth/oauth_controller.ex:158
#, elixir-format #, elixir-format
msgid "Unlisted redirect_uri." msgid "Unlisted redirect_uri."
msgstr "" msgstr "ניתב redirect_uri לא רשום."
#: lib/pleroma/web/oauth/oauth_controller.ex:390 #: lib/pleroma/web/oauth/oauth_controller.ex:390
#, elixir-format #, elixir-format
msgid "Unsupported OAuth provider: %{provider}." msgid "Unsupported OAuth provider: %{provider}."
msgstr "" msgstr "ספק OAuth לא נתמך: %{provider}."
#: lib/pleroma/uploaders/uploader.ex:72 #: lib/pleroma/uploaders/uploader.ex:72
#, elixir-format #, elixir-format
msgid "Uploader callback timeout" msgid "Uploader callback timeout"
msgstr "" msgstr "קריאה חזרה של מעלה עברה את הזמן הקצוב"
#: lib/pleroma/web/uploader_controller.ex:23 #: lib/pleroma/web/uploader_controller.ex:23
#, elixir-format #, elixir-format
msgid "bad request" msgid "bad request"
msgstr "" msgstr "בקשה שגוייה"
#: lib/pleroma/web/twitter_api/twitter_api.ex:103 #: lib/pleroma/web/twitter_api/twitter_api.ex:103
#, elixir-format #, elixir-format
msgid "CAPTCHA Error" msgid "CAPTCHA Error"
msgstr "" msgstr "שגיאת CAPTCHA"
#: lib/pleroma/web/common_api/common_api.ex:290 #: lib/pleroma/web/common_api/common_api.ex:290
#, elixir-format #, elixir-format
msgid "Could not add reaction emoji" msgid "Could not add reaction emoji"
msgstr "" msgstr "לא ניתן להוסיף סמלון תגובה"
#: lib/pleroma/web/common_api/common_api.ex:301 #: lib/pleroma/web/common_api/common_api.ex:301
#, elixir-format #, elixir-format
msgid "Could not remove reaction emoji" msgid "Could not remove reaction emoji"
msgstr "" msgstr "לא ניתן להסיר סמלון תגובה"
#: lib/pleroma/web/twitter_api/twitter_api.ex:129 #: lib/pleroma/web/twitter_api/twitter_api.ex:129
#, elixir-format #, elixir-format
msgid "Invalid CAPTCHA (Missing parameter: %{name})" msgid "Invalid CAPTCHA (Missing parameter: %{name})"
msgstr "" msgstr "CAPTCHA לא תקני (חסר פרמטר: %{name})"
#: lib/pleroma/web/mastodon_api/controllers/list_controller.ex:92 #: lib/pleroma/web/mastodon_api/controllers/list_controller.ex:92
#, elixir-format #, elixir-format
msgid "List not found" msgid "List not found"
msgstr "" msgstr "רשימה לא נמצאה"
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:123 #: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:123
#, elixir-format #, elixir-format
msgid "Missing parameter: %{name}" msgid "Missing parameter: %{name}"
msgstr "" msgstr "חסר פרמטר: %{name}"
#: lib/pleroma/web/oauth/oauth_controller.ex:210 #: lib/pleroma/web/oauth/oauth_controller.ex:210
#: lib/pleroma/web/oauth/oauth_controller.ex:321 #: lib/pleroma/web/oauth/oauth_controller.ex:321
#, elixir-format #, elixir-format
msgid "Password reset is required" msgid "Password reset is required"
msgstr "" msgstr "נדרש איפוס סיסמה"
#: lib/pleroma/tests/auth_test_controller.ex:9 #: lib/pleroma/tests/auth_test_controller.ex:9
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:6 lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:6 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:6 lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:6
@ -533,64 +536,64 @@ msgstr ""
#: lib/pleroma/web/uploader_controller.ex:6 lib/pleroma/web/web_finger/web_finger_controller.ex:6 #: lib/pleroma/web/uploader_controller.ex:6 lib/pleroma/web/web_finger/web_finger_controller.ex:6
#, elixir-format #, elixir-format
msgid "Security violation: OAuth scopes check was neither handled nor explicitly skipped." msgid "Security violation: OAuth scopes check was neither handled nor explicitly skipped."
msgstr "" msgstr "הפרת אבטחה: OAuth בבדיקת המתחם לא נבדקה או דולגה במכוון."
#: lib/pleroma/plugs/ensure_authenticated_plug.ex:28 #: lib/pleroma/plugs/ensure_authenticated_plug.ex:28
#, elixir-format #, elixir-format
msgid "Two-factor authentication enabled, you must use a access token." msgid "Two-factor authentication enabled, you must use a access token."
msgstr "" msgstr "אימות דו-שלבי הופעל, יש להזין אסימון כניסה."
#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:210 #: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:210
#, elixir-format #, elixir-format
msgid "Unexpected error occurred while adding file to pack." msgid "Unexpected error occurred while adding file to pack."
msgstr "" msgstr "אירעה שגיאה לא צפויה בזמן הוספת הקובץ לחבילה."
#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:138 #: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:138
#, elixir-format #, elixir-format
msgid "Unexpected error occurred while creating pack." msgid "Unexpected error occurred while creating pack."
msgstr "" msgstr "אירעה שגיאה לא צפויה בזמן יצירת חבילה."
#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:278 #: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:278
#, elixir-format #, elixir-format
msgid "Unexpected error occurred while removing file from pack." msgid "Unexpected error occurred while removing file from pack."
msgstr "" msgstr "אירעה שגיאה לא צפויה בזמן הסרת הקובץ מהחבילה."
#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:250 #: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:250
#, elixir-format #, elixir-format
msgid "Unexpected error occurred while updating file in pack." msgid "Unexpected error occurred while updating file in pack."
msgstr "" msgstr "אירעה שגיאה לא צפויה בזמן עדכון הקובץ מהחבילה."
#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:179 #: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:179
#, elixir-format #, elixir-format
msgid "Unexpected error occurred while updating pack metadata." msgid "Unexpected error occurred while updating pack metadata."
msgstr "" msgstr "אירעה שגיאה לא צפויה בזמן עדכון מטא-דאטה של החבילה."
#: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:61 #: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:61
#, elixir-format #, elixir-format
msgid "Web push subscription is disabled on this Pleroma instance" msgid "Web push subscription is disabled on this Pleroma instance"
msgstr "" msgstr "הרשמה לעדכון ווב בדחיפה מבוטלת בשרת פלרומה זה"
#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:451 #: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:451
#, elixir-format #, elixir-format
msgid "You can't revoke your own admin/moderator status." msgid "You can't revoke your own admin/moderator status."
msgstr "" msgstr "לא ניתן לשלול את סטטוס האדמין/מנהל של עצמך."
#: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:126 #: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:126
#, elixir-format #, elixir-format
msgid "authorization required for timeline view" msgid "authorization required for timeline view"
msgstr "" msgstr "הרשאה דרושה על מנת לצפות בציר הזמן"
#: lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:24 #: lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:24
#, elixir-format #, elixir-format
msgid "Access denied" msgid "Access denied"
msgstr "" msgstr "גישה נדחית"
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:282 #: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:282
#, elixir-format #, elixir-format
msgid "This API requires an authenticated user" msgid "This API requires an authenticated user"
msgstr "" msgstr "ה-API דורש הרשאת משתמש"
#: lib/pleroma/plugs/user_is_admin_plug.ex:21 #: lib/pleroma/plugs/user_is_admin_plug.ex:21
#, elixir-format #, elixir-format
msgid "User is not an admin." msgid "User is not an admin."
msgstr "" msgstr "משתמש אינו מנהל."

View File

@ -0,0 +1,149 @@
## This file is a PO Template file.
##
## `msgid`s here are often extracted from source code.
## Add new translations manually only if they're dynamic
## translations that can't be statically extracted.
##
## 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.
msgid "eperm"
msgstr ""
msgid "eacces"
msgstr ""
msgid "eagain"
msgstr ""
msgid "ebadf"
msgstr ""
msgid "ebadmsg"
msgstr ""
msgid "ebusy"
msgstr ""
msgid "edeadlk"
msgstr ""
msgid "edeadlock"
msgstr ""
msgid "edquot"
msgstr ""
msgid "eexist"
msgstr ""
msgid "efault"
msgstr ""
msgid "efbig"
msgstr ""
msgid "eftype"
msgstr ""
msgid "eintr"
msgstr ""
msgid "einval"
msgstr ""
msgid "eio"
msgstr ""
msgid "eisdir"
msgstr ""
msgid "eloop"
msgstr ""
msgid "emfile"
msgstr ""
msgid "emlink"
msgstr ""
msgid "emultihop"
msgstr ""
msgid "enametoolong"
msgstr ""
msgid "enfile"
msgstr ""
msgid "enobufs"
msgstr ""
msgid "enodev"
msgstr ""
msgid "enolck"
msgstr ""
msgid "enolink"
msgstr ""
msgid "enoent"
msgstr ""
msgid "enomem"
msgstr ""
msgid "enospc"
msgstr ""
msgid "enosr"
msgstr ""
msgid "enostr"
msgstr ""
msgid "enosys"
msgstr ""
msgid "enotblk"
msgstr ""
msgid "enotdir"
msgstr ""
msgid "enotsup"
msgstr ""
msgid "enxio"
msgstr ""
msgid "eopnotsupp"
msgstr ""
msgid "eoverflow"
msgstr ""
msgid "epipe"
msgstr ""
msgid "erange"
msgstr ""
msgid "erofs"
msgstr ""
msgid "espipe"
msgstr ""
msgid "esrch"
msgstr ""
msgid "estale"
msgstr ""
msgid "etxtbsy"
msgstr ""
msgid "exdev"
msgstr ""

View File

@ -0,0 +1,599 @@
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-12-10 16:09+0000\n"
"PO-Revision-Date: 2020-12-11 00:56+0000\n"
"Last-Translator: ZEN <xinit.info@gmail.com>\n"
"Language-Team: Ukrainian <https://translate.pleroma.social/projects/pleroma/"
"pleroma/uk/>\n"
"Language: uk\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<="
"4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
"X-Generator: Weblate 4.0.4\n"
## This file is a PO Template file.
##
## `msgid`s here are often extracted from source code.
## Add new translations manually only if they're dynamic
## translations that can't be statically extracted.
##
## 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 "не може бути пустим"
## From Ecto.Changeset.unique_constraint/3
msgid "has already been taken"
msgstr "вже зайнято"
## From Ecto.Changeset.put_change/3
msgid "is invalid"
msgstr "недійсний"
## From Ecto.Changeset.validate_format/3
msgid "has invalid format"
msgstr "має недійсний формат"
## From Ecto.Changeset.validate_subset/3
msgid "has an invalid entry"
msgstr "має недійсний запис"
## From Ecto.Changeset.validate_exclusion/3
msgid "is reserved"
msgstr "зарезервовано"
## From Ecto.Changeset.validate_confirmation/3
msgid "does not match confirmation"
msgstr "не збігається з підтвердженням"
## From Ecto.Changeset.no_assoc_constraint/3
msgid "is still associated with this entry"
msgstr "все ще пов'язаний з цим записом"
msgid "are still associated with this entry"
msgstr "все ще пов'язані з цим записом"
## From Ecto.Changeset.validate_length/3
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} елементів"
## From Ecto.Changeset.validate_number/3
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}"
#: lib/pleroma/web/common_api/common_api.ex:505
#, elixir-format
msgid "Account not found"
msgstr "Обліковий запис не знайдено"
#: lib/pleroma/web/common_api/common_api.ex:339
#, elixir-format
msgid "Already voted"
msgstr "Вже проголосовано"
#: lib/pleroma/web/oauth/oauth_controller.ex:359
#, elixir-format
msgid "Bad request"
msgstr "Невірний запит"
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:426
#, elixir-format
msgid "Can't delete object"
msgstr "Виникла помилка при видаленні об'єкту"
#: lib/pleroma/web/controller_helper.ex:105
#: lib/pleroma/web/controller_helper.ex:111
#, elixir-format
msgid "Can't display this activity"
msgstr "Не вдається відобразити цю активність"
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:285
#, elixir-format
msgid "Can't find user"
msgstr "Користувача не знайдено"
#: lib/pleroma/web/pleroma_api/controllers/account_controller.ex:61
#, elixir-format
msgid "Can't get favorites"
msgstr "Не вдається отримати вподобання"
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:438
#, elixir-format
msgid "Can't like object"
msgstr "Не вдається вподобати об’єкт"
#: lib/pleroma/web/common_api/utils.ex:563
#, elixir-format
msgid "Cannot post an empty status without attachments"
msgstr "Не вдається опублікувати порожнє повідомлення без вкладень"
#: lib/pleroma/web/common_api/utils.ex:511
#, elixir-format
msgid "Comment must be up to %{max_size} characters"
msgstr "Коментар може містити не більше %{max_size} символів"
#: lib/pleroma/config/config_db.ex:191
#, elixir-format
msgid "Config with params %{params} not found"
msgstr "Конфігурація з параметрами %{params} не знайдена"
#: lib/pleroma/web/common_api/common_api.ex:181
#: lib/pleroma/web/common_api/common_api.ex:185
#, elixir-format
msgid "Could not delete"
msgstr "Не можу видалити"
#: lib/pleroma/web/common_api/common_api.ex:231
#, elixir-format
msgid "Could not favorite"
msgstr "Не вдалося додати до вподобаного"
#: lib/pleroma/web/common_api/common_api.ex:453
#, elixir-format
msgid "Could not pin"
msgstr "Не вдалося закріпити"
#: lib/pleroma/web/common_api/common_api.ex:278
#, elixir-format
msgid "Could not unfavorite"
msgstr "Не вдалося видалити з вподобаного"
#: lib/pleroma/web/common_api/common_api.ex:463
#, elixir-format
msgid "Could not unpin"
msgstr "Не вдалося відкріпити"
#: lib/pleroma/web/common_api/common_api.ex:216
#, elixir-format
msgid "Could not unrepeat"
msgstr "Не вдалося скасувати поширення"
#: lib/pleroma/web/common_api/common_api.ex:512
#: lib/pleroma/web/common_api/common_api.ex:521
#, elixir-format
msgid "Could not update state"
msgstr "Не вдалося оновити стан"
#: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:207
#, elixir-format
msgid "Error."
msgstr "Помилка."
#: lib/pleroma/web/twitter_api/twitter_api.ex:106
#, elixir-format
msgid "Invalid CAPTCHA"
msgstr "Невірна CAPTCHA"
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:116
#: lib/pleroma/web/oauth/oauth_controller.ex:568
#, elixir-format
msgid "Invalid credentials"
msgstr "Неправильні дані автентифікації"
#: lib/pleroma/plugs/ensure_authenticated_plug.ex:38
#, elixir-format
msgid "Invalid credentials."
msgstr "Неправильні дані автентифікації."
#: lib/pleroma/web/common_api/common_api.ex:355
#, elixir-format
msgid "Invalid indices"
msgstr "Неправильні індекси"
#: lib/pleroma/web/admin_api/controllers/fallback_controller.ex:29
#, elixir-format
msgid "Invalid parameters"
msgstr "Неправильні параметри"
#: lib/pleroma/web/common_api/utils.ex:414
#, elixir-format
msgid "Invalid password."
msgstr "Неправильний пароль."
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:220
#, elixir-format
msgid "Invalid request"
msgstr "Невірний запит"
#: lib/pleroma/web/twitter_api/twitter_api.ex:109
#, elixir-format
msgid "Kocaptcha service unavailable"
msgstr "Сервіс Kocaptcha недоступний"
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:112
#, elixir-format
msgid "Missing parameters"
msgstr "Відсутні параметри"
#: lib/pleroma/web/common_api/utils.ex:547
#, elixir-format
msgid "No such conversation"
msgstr "Немає такої розмови"
#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:388
#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:414 lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:456
#, elixir-format
msgid "No such permission_group"
msgstr "Не існує такої групи повноважень"
#: lib/pleroma/plugs/uploaded_media.ex:84
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:486 lib/pleroma/web/admin_api/controllers/fallback_controller.ex:11
#: lib/pleroma/web/feed/user_controller.ex:71 lib/pleroma/web/ostatus/ostatus_controller.ex:143
#, elixir-format
msgid "Not found"
msgstr "Не знайдено"
#: lib/pleroma/web/common_api/common_api.ex:331
#, elixir-format
msgid "Poll's author can't vote"
msgstr "Автор опитування не може голосувати"
#: lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:20
#: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:37 lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:49
#: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:50 lib/pleroma/web/mastodon_api/controllers/status_controller.ex:306
#: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:71
#, elixir-format
msgid "Record not found"
msgstr "Запис не знайдено"
#: lib/pleroma/web/admin_api/controllers/fallback_controller.ex:35
#: lib/pleroma/web/feed/user_controller.ex:77 lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:36
#: lib/pleroma/web/ostatus/ostatus_controller.ex:149
#, elixir-format
msgid "Something went wrong"
msgstr "Щось зламалося"
#: lib/pleroma/web/common_api/activity_draft.ex:107
#, elixir-format
msgid "The message visibility must be direct"
msgstr "Видимість у повідомлення повинна бути `Приватний`"
#: lib/pleroma/web/common_api/utils.ex:573
#, elixir-format
msgid "The status is over the character limit"
msgstr "Цей статус перевищує ліміт символів"
#: lib/pleroma/plugs/ensure_public_or_authenticated_plug.ex:31
#, elixir-format
msgid "This resource requires authentication."
msgstr "Цей ресурс вимагає автентифікації."
#: lib/pleroma/plugs/rate_limiter/rate_limiter.ex:206
#, elixir-format
msgid "Throttled"
msgstr "Обмежено. Перевищено ліміт запитів."
#: lib/pleroma/web/common_api/common_api.ex:356
#, elixir-format
msgid "Too many choices"
msgstr "Забагато варіантів вибору"
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:443
#, elixir-format
msgid "Unhandled activity type"
msgstr "Непідтримуваний тип активності"
#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:485
#, elixir-format
msgid "You can't revoke your own admin status."
msgstr "Ви не можете позбавити самого себе статусу адміністратора."
#: lib/pleroma/web/oauth/oauth_controller.ex:221
#: lib/pleroma/web/oauth/oauth_controller.ex:308
#, elixir-format
msgid "Your account is currently disabled"
msgstr "Ваш обліковий запис наразі вимкнено"
#: lib/pleroma/web/oauth/oauth_controller.ex:183
#: lib/pleroma/web/oauth/oauth_controller.ex:331
#, elixir-format
msgid "Your login is missing a confirmed e-mail address"
msgstr "Ваша електрона адреса не підтверджена"
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:390
#, elixir-format
msgid "can't read inbox of %{nickname} as %{as_nickname}"
msgstr ""
"Не вдається прочитати \"Вхідні\" повідомлення %{nickname} як %{as_nickname}"
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:473
#, elixir-format
msgid "can't update outbox of %{nickname} as %{as_nickname}"
msgstr ""
"Не вдається оновити \"Вихідні\" повідомлення %{nickname} як %{as_nickname}"
#: lib/pleroma/web/common_api/common_api.ex:471
#, elixir-format
msgid "conversation is already muted"
msgstr "Розмова вже заглушена"
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:314
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:492
#, elixir-format
msgid "error"
msgstr "помилка"
#: lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex:32
#, elixir-format
msgid "mascots can only be images"
msgstr "талісманами можуть бути лише зображення"
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:62
#, elixir-format
msgid "not found"
msgstr "не знайдено"
#: lib/pleroma/web/oauth/oauth_controller.ex:394
#, elixir-format
msgid "Bad OAuth request."
msgstr "Невірний запит OAuth."
#: lib/pleroma/web/twitter_api/twitter_api.ex:115
#, elixir-format
msgid "CAPTCHA already used"
msgstr "CAPTCHA вже використана"
#: lib/pleroma/web/twitter_api/twitter_api.ex:112
#, elixir-format
msgid "CAPTCHA expired"
msgstr "Термін дії CAPTCHA закінчився"
#: lib/pleroma/plugs/uploaded_media.ex:57
#, elixir-format
msgid "Failed"
msgstr "Не вдалося"
#: lib/pleroma/web/oauth/oauth_controller.ex:410
#, elixir-format
msgid "Failed to authenticate: %{message}."
msgstr "Помилка автентифікації: %{message}."
#: lib/pleroma/web/oauth/oauth_controller.ex:441
#, elixir-format
msgid "Failed to set up user account."
msgstr "Не вдалося створити обліковий запис."
#: lib/pleroma/plugs/oauth_scopes_plug.ex:38
#, elixir-format
msgid "Insufficient permissions: %{permissions}."
msgstr "Недостатньо прав: %{permissions}."
#: lib/pleroma/plugs/uploaded_media.ex:104
#, elixir-format
msgid "Internal Error"
msgstr "Внутрішня помилка"
#: lib/pleroma/web/oauth/fallback_controller.ex:22
#: lib/pleroma/web/oauth/fallback_controller.ex:29
#, elixir-format
msgid "Invalid Username/Password"
msgstr "Неправильне ім'я користувача або пароль"
#: lib/pleroma/web/twitter_api/twitter_api.ex:118
#, elixir-format
msgid "Invalid answer data"
msgstr "Неправильна відповідь"
#: lib/pleroma/web/nodeinfo/nodeinfo_controller.ex:33
#, elixir-format
msgid "Nodeinfo schema version not handled"
msgstr "Версія схеми Nodeinfo не враховується"
#: lib/pleroma/web/oauth/oauth_controller.ex:172
#, elixir-format
msgid "This action is outside the authorized scopes"
msgstr "Ця дія виходить за рамки доступних повноважень"
#: lib/pleroma/web/oauth/fallback_controller.ex:14
#, elixir-format
msgid "Unknown error, please check the details and try again."
msgstr "Невідома помилка. Перевірте деталі та повторіть спробу."
#: lib/pleroma/web/oauth/oauth_controller.ex:119
#: lib/pleroma/web/oauth/oauth_controller.ex:158
#, elixir-format
msgid "Unlisted redirect_uri."
msgstr "Невідомий redirect_uri."
#: lib/pleroma/web/oauth/oauth_controller.ex:390
#, elixir-format
msgid "Unsupported OAuth provider: %{provider}."
msgstr "Непідтримуваний постачальник послуг OAuth: %{provider}."
#: lib/pleroma/uploaders/uploader.ex:72
#, elixir-format
msgid "Uploader callback timeout"
msgstr "Тайм-аут при завантаженні"
#: lib/pleroma/web/uploader_controller.ex:23
#, elixir-format
msgid "bad request"
msgstr "невірний запит"
#: lib/pleroma/web/twitter_api/twitter_api.ex:103
#, elixir-format
msgid "CAPTCHA Error"
msgstr "Помилка CAPTCHA"
#: lib/pleroma/web/common_api/common_api.ex:290
#, elixir-format
msgid "Could not add reaction emoji"
msgstr "Не вдалося додати емодзі для реакції"
#: lib/pleroma/web/common_api/common_api.ex:301
#, elixir-format
msgid "Could not remove reaction emoji"
msgstr "Не вдалося видалити реакцію"
#: lib/pleroma/web/twitter_api/twitter_api.ex:129
#, elixir-format
msgid "Invalid CAPTCHA (Missing parameter: %{name})"
msgstr "Недійсна CAPTCHA (Відсутній параметр: %{name})"
#: lib/pleroma/web/mastodon_api/controllers/list_controller.ex:92
#, elixir-format
msgid "List not found"
msgstr "Список не знайдено"
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:123
#, elixir-format
msgid "Missing parameter: %{name}"
msgstr "Відсутній параметр: %{name}"
#: lib/pleroma/web/oauth/oauth_controller.ex:210
#: lib/pleroma/web/oauth/oauth_controller.ex:321
#, elixir-format
msgid "Password reset is required"
msgstr "Потрібно скинути пароль"
#: lib/pleroma/tests/auth_test_controller.ex:9
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:6 lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:6
#: lib/pleroma/web/admin_api/controllers/config_controller.ex:6 lib/pleroma/web/admin_api/controllers/fallback_controller.ex:6
#: lib/pleroma/web/admin_api/controllers/invite_controller.ex:6 lib/pleroma/web/admin_api/controllers/media_proxy_cache_controller.ex:6
#: lib/pleroma/web/admin_api/controllers/oauth_app_controller.ex:6 lib/pleroma/web/admin_api/controllers/relay_controller.ex:6
#: lib/pleroma/web/admin_api/controllers/report_controller.ex:6 lib/pleroma/web/admin_api/controllers/status_controller.ex:6
#: lib/pleroma/web/controller_helper.ex:6 lib/pleroma/web/embed_controller.ex:6
#: lib/pleroma/web/fallback_redirect_controller.ex:6 lib/pleroma/web/feed/tag_controller.ex:6
#: lib/pleroma/web/feed/user_controller.ex:6 lib/pleroma/web/mailer/subscription_controller.ex:2
#: lib/pleroma/web/masto_fe_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/account_controller.ex:6
#: lib/pleroma/web/mastodon_api/controllers/app_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/auth_controller.ex:6
#: lib/pleroma/web/mastodon_api/controllers/conversation_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/custom_emoji_controller.ex:6
#: lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:6
#: lib/pleroma/web/mastodon_api/controllers/filter_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex:6
#: lib/pleroma/web/mastodon_api/controllers/instance_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/list_controller.ex:6
#: lib/pleroma/web/mastodon_api/controllers/marker_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex:14
#: lib/pleroma/web/mastodon_api/controllers/media_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/notification_controller.ex:6
#: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/report_controller.ex:8
#: lib/pleroma/web/mastodon_api/controllers/scheduled_activity_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/search_controller.ex:6
#: lib/pleroma/web/mastodon_api/controllers/status_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:7
#: lib/pleroma/web/mastodon_api/controllers/suggestion_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:6
#: lib/pleroma/web/media_proxy/media_proxy_controller.ex:6 lib/pleroma/web/mongooseim/mongoose_im_controller.ex:6
#: lib/pleroma/web/nodeinfo/nodeinfo_controller.ex:6 lib/pleroma/web/oauth/fallback_controller.ex:6
#: lib/pleroma/web/oauth/mfa_controller.ex:10 lib/pleroma/web/oauth/oauth_controller.ex:6
#: lib/pleroma/web/ostatus/ostatus_controller.ex:6 lib/pleroma/web/pleroma_api/controllers/account_controller.ex:6
#: lib/pleroma/web/pleroma_api/controllers/chat_controller.ex:5 lib/pleroma/web/pleroma_api/controllers/conversation_controller.ex:6
#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:2 lib/pleroma/web/pleroma_api/controllers/emoji_reaction_controller.ex:6
#: lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex:6 lib/pleroma/web/pleroma_api/controllers/notification_controller.ex:6
#: lib/pleroma/web/pleroma_api/controllers/scrobble_controller.ex:6
#: lib/pleroma/web/pleroma_api/controllers/two_factor_authentication_controller.ex:7 lib/pleroma/web/static_fe/static_fe_controller.ex:6
#: lib/pleroma/web/twitter_api/controllers/password_controller.ex:10 lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex:6
#: lib/pleroma/web/twitter_api/controllers/util_controller.ex:6 lib/pleroma/web/twitter_api/twitter_api_controller.ex:6
#: lib/pleroma/web/uploader_controller.ex:6 lib/pleroma/web/web_finger/web_finger_controller.ex:6
#, elixir-format
msgid "Security violation: OAuth scopes check was neither handled nor explicitly skipped."
msgstr ""
"Порушення безпеки: перевірка обсягу OAuth не була оброблена, ні явно "
"пропущена."
#: lib/pleroma/plugs/ensure_authenticated_plug.ex:28
#, elixir-format
msgid "Two-factor authentication enabled, you must use a access token."
msgstr ""
"Двофакторна автентифікація ввімкнена, ви повинні використовувати ключ "
"доступу."
#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:210
#, elixir-format
msgid "Unexpected error occurred while adding file to pack."
msgstr "Несподівана помилка при додаванні файлу в пакет."
#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:138
#, elixir-format
msgid "Unexpected error occurred while creating pack."
msgstr "Несподівана помилка під час створення пакета."
#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:278
#, elixir-format
msgid "Unexpected error occurred while removing file from pack."
msgstr "Під час видалення файлу з пакета сталася несподівана помилка."
#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:250
#, elixir-format
msgid "Unexpected error occurred while updating file in pack."
msgstr "Під час оновлення файлу в пакеті сталася несподівана помилка."
#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:179
#, elixir-format
msgid "Unexpected error occurred while updating pack metadata."
msgstr "Під час оновлення метаданих пакета сталася несподівана помилка."
#: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:61
#, elixir-format
msgid "Web push subscription is disabled on this Pleroma instance"
msgstr "Web push-сповіщення вимкнені на цьому інстансі Pleroma"
#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:451
#, elixir-format
msgid "You can't revoke your own admin/moderator status."
msgstr "Ви не можете позбавити самого себе статусу адміністратора/модератора."
#: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:126
#, elixir-format
msgid "authorization required for timeline view"
msgstr "необхідно ввійти в систему для перегляду стрічки повідомлень"
#: lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:24
#, elixir-format
msgid "Access denied"
msgstr "Доступ заборонено"
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:282
#, elixir-format
msgid "This API requires an authenticated user"
msgstr "Цей API вимагає автентифікованого користувача"
#: lib/pleroma/plugs/user_is_admin_plug.ex:21
#, elixir-format
msgid "User is not an admin."
msgstr "Користувач не є адміністратором."

View File

@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-09-20 13:18+0000\n" "POT-Creation-Date: 2020-09-20 13:18+0000\n"
"PO-Revision-Date: 2020-10-22 18:25+0000\n" "PO-Revision-Date: 2020-12-14 06:00+0000\n"
"Last-Translator: shironeko <shironeko@tesaguri.club>\n" "Last-Translator: shironeko <shironeko@tesaguri.club>\n"
"Language-Team: Chinese (Simplified) <https://translate.pleroma.social/" "Language-Team: Chinese (Simplified) <https://translate.pleroma.social/"
"projects/pleroma/pleroma/zh_Hans/>\n" "projects/pleroma/pleroma/zh_Hans/>\n"
@ -146,9 +146,9 @@ msgid "Cannot post an empty status without attachments"
msgstr "无法发送空白且不包含附件的状态" msgstr "无法发送空白且不包含附件的状态"
#: lib/pleroma/web/common_api/utils.ex:511 #: lib/pleroma/web/common_api/utils.ex:511
#, elixir-format #, elixir-format, fuzzy
msgid "Comment must be up to %{max_size} characters" msgid "Comment must be up to %{max_size} characters"
msgstr "" msgstr "评论最多可使用 %{max_size} 字符"
#: lib/pleroma/config/config_db.ex:191 #: lib/pleroma/config/config_db.ex:191
#, elixir-format #, elixir-format
@ -250,21 +250,21 @@ msgstr "没有该对话"
#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:388 #: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:388
#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:414 lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:456 #: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:414 lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:456
#, elixir-format #, elixir-format, fuzzy
msgid "No such permission_group" msgid "No such permission_group"
msgstr "" msgstr "没有该权限组"
#: lib/pleroma/plugs/uploaded_media.ex:84 #: lib/pleroma/plugs/uploaded_media.ex:84
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:486 lib/pleroma/web/admin_api/controllers/fallback_controller.ex:11 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:486 lib/pleroma/web/admin_api/controllers/fallback_controller.ex:11
#: lib/pleroma/web/feed/user_controller.ex:71 lib/pleroma/web/ostatus/ostatus_controller.ex:143 #: lib/pleroma/web/feed/user_controller.ex:71 lib/pleroma/web/ostatus/ostatus_controller.ex:143
#, elixir-format #, elixir-format
msgid "Not found" msgid "Not found"
msgstr "" msgstr "未找到"
#: lib/pleroma/web/common_api/common_api.ex:331 #: lib/pleroma/web/common_api/common_api.ex:331
#, elixir-format #, elixir-format
msgid "Poll's author can't vote" msgid "Poll's author can't vote"
msgstr "" msgstr "投票的发起者不能投票"
#: lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:20 #: lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:20
#: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:37 lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:49 #: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:37 lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:49
@ -272,39 +272,39 @@ msgstr ""
#: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:71 #: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:71
#, elixir-format #, elixir-format
msgid "Record not found" msgid "Record not found"
msgstr "" msgstr "未找到该记录"
#: lib/pleroma/web/admin_api/controllers/fallback_controller.ex:35 #: lib/pleroma/web/admin_api/controllers/fallback_controller.ex:35
#: lib/pleroma/web/feed/user_controller.ex:77 lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:36 #: lib/pleroma/web/feed/user_controller.ex:77 lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:36
#: lib/pleroma/web/ostatus/ostatus_controller.ex:149 #: lib/pleroma/web/ostatus/ostatus_controller.ex:149
#, elixir-format #, elixir-format
msgid "Something went wrong" msgid "Something went wrong"
msgstr "" msgstr "发生了一些错误"
#: lib/pleroma/web/common_api/activity_draft.ex:107 #: lib/pleroma/web/common_api/activity_draft.ex:107
#, elixir-format #, elixir-format
msgid "The message visibility must be direct" msgid "The message visibility must be direct"
msgstr "" msgstr "该消息必须为私信"
#: lib/pleroma/web/common_api/utils.ex:573 #: lib/pleroma/web/common_api/utils.ex:573
#, elixir-format #, elixir-format
msgid "The status is over the character limit" msgid "The status is over the character limit"
msgstr "" msgstr "状态超过了字符数限制"
#: lib/pleroma/plugs/ensure_public_or_authenticated_plug.ex:31 #: lib/pleroma/plugs/ensure_public_or_authenticated_plug.ex:31
#, elixir-format #, elixir-format
msgid "This resource requires authentication." msgid "This resource requires authentication."
msgstr "" msgstr "该资源需要认证。"
#: lib/pleroma/plugs/rate_limiter/rate_limiter.ex:206 #: lib/pleroma/plugs/rate_limiter/rate_limiter.ex:206
#, elixir-format #, elixir-format, fuzzy
msgid "Throttled" msgid "Throttled"
msgstr "" msgstr "节流了"
#: lib/pleroma/web/common_api/common_api.ex:356 #: lib/pleroma/web/common_api/common_api.ex:356
#, elixir-format #, elixir-format
msgid "Too many choices" msgid "Too many choices"
msgstr "" msgstr "太多选项"
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:443 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:443
#, elixir-format #, elixir-format
@ -314,101 +314,101 @@ msgstr ""
#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:485 #: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:485
#, elixir-format #, elixir-format
msgid "You can't revoke your own admin status." msgid "You can't revoke your own admin status."
msgstr "" msgstr "您不能撤消自己的管理员权限。"
#: lib/pleroma/web/oauth/oauth_controller.ex:221 #: lib/pleroma/web/oauth/oauth_controller.ex:221
#: lib/pleroma/web/oauth/oauth_controller.ex:308 #: lib/pleroma/web/oauth/oauth_controller.ex:308
#, elixir-format #, elixir-format
msgid "Your account is currently disabled" msgid "Your account is currently disabled"
msgstr "" msgstr "您的账户已被禁用"
#: lib/pleroma/web/oauth/oauth_controller.ex:183 #: lib/pleroma/web/oauth/oauth_controller.ex:183
#: lib/pleroma/web/oauth/oauth_controller.ex:331 #: lib/pleroma/web/oauth/oauth_controller.ex:331
#, elixir-format #, elixir-format
msgid "Your login is missing a confirmed e-mail address" msgid "Your login is missing a confirmed e-mail address"
msgstr "" msgstr "您的账户缺少已认证的 e-mail 地址"
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:390 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:390
#, elixir-format #, elixir-format
msgid "can't read inbox of %{nickname} as %{as_nickname}" msgid "can't read inbox of %{nickname} as %{as_nickname}"
msgstr "" msgstr "无法以 %{as_nickname} 读取 %{nickname} 的收件箱"
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:473 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:473
#, elixir-format #, elixir-format
msgid "can't update outbox of %{nickname} as %{as_nickname}" msgid "can't update outbox of %{nickname} as %{as_nickname}"
msgstr "" msgstr "无法以 %{as_nickname} 更新 %{nickname} 的出件箱"
#: lib/pleroma/web/common_api/common_api.ex:471 #: lib/pleroma/web/common_api/common_api.ex:471
#, elixir-format #, elixir-format
msgid "conversation is already muted" msgid "conversation is already muted"
msgstr "" msgstr "对话已经被静音"
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:314 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:314
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:492 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:492
#, elixir-format #, elixir-format
msgid "error" msgid "error"
msgstr "" msgstr "错误"
#: lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex:32 #: lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex:32
#, elixir-format #, elixir-format
msgid "mascots can only be images" msgid "mascots can only be images"
msgstr "" msgstr "吉祥物只能是图片"
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:62 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:62
#, elixir-format #, elixir-format
msgid "not found" msgid "not found"
msgstr "" msgstr "未找到"
#: lib/pleroma/web/oauth/oauth_controller.ex:394 #: lib/pleroma/web/oauth/oauth_controller.ex:394
#, elixir-format #, elixir-format
msgid "Bad OAuth request." msgid "Bad OAuth request."
msgstr "" msgstr "错误的 OAuth 请求。"
#: lib/pleroma/web/twitter_api/twitter_api.ex:115 #: lib/pleroma/web/twitter_api/twitter_api.ex:115
#, elixir-format #, elixir-format
msgid "CAPTCHA already used" msgid "CAPTCHA already used"
msgstr "" msgstr "验证码已被使用"
#: lib/pleroma/web/twitter_api/twitter_api.ex:112 #: lib/pleroma/web/twitter_api/twitter_api.ex:112
#, elixir-format #, elixir-format
msgid "CAPTCHA expired" msgid "CAPTCHA expired"
msgstr "" msgstr "验证码已过期"
#: lib/pleroma/plugs/uploaded_media.ex:57 #: lib/pleroma/plugs/uploaded_media.ex:57
#, elixir-format #, elixir-format
msgid "Failed" msgid "Failed"
msgstr "" msgstr "失败"
#: lib/pleroma/web/oauth/oauth_controller.ex:410 #: lib/pleroma/web/oauth/oauth_controller.ex:410
#, elixir-format #, elixir-format, fuzzy
msgid "Failed to authenticate: %{message}." msgid "Failed to authenticate: %{message}."
msgstr "" msgstr "认证失败:%{message}。"
#: lib/pleroma/web/oauth/oauth_controller.ex:441 #: lib/pleroma/web/oauth/oauth_controller.ex:441
#, elixir-format #, elixir-format
msgid "Failed to set up user account." msgid "Failed to set up user account."
msgstr "" msgstr "建立用户帐号失败。"
#: lib/pleroma/plugs/oauth_scopes_plug.ex:38 #: lib/pleroma/plugs/oauth_scopes_plug.ex:38
#, elixir-format #, elixir-format
msgid "Insufficient permissions: %{permissions}." msgid "Insufficient permissions: %{permissions}."
msgstr "" msgstr "权限不足:%{permissions}。"
#: lib/pleroma/plugs/uploaded_media.ex:104 #: lib/pleroma/plugs/uploaded_media.ex:104
#, elixir-format #, elixir-format
msgid "Internal Error" msgid "Internal Error"
msgstr "" msgstr "内部错误"
#: lib/pleroma/web/oauth/fallback_controller.ex:22 #: lib/pleroma/web/oauth/fallback_controller.ex:22
#: lib/pleroma/web/oauth/fallback_controller.ex:29 #: lib/pleroma/web/oauth/fallback_controller.ex:29
#, elixir-format #, elixir-format
msgid "Invalid Username/Password" msgid "Invalid Username/Password"
msgstr "" msgstr "无效的用户名/密码"
#: lib/pleroma/web/twitter_api/twitter_api.ex:118 #: lib/pleroma/web/twitter_api/twitter_api.ex:118
#, elixir-format #, elixir-format, fuzzy
msgid "Invalid answer data" msgid "Invalid answer data"
msgstr "" msgstr "无效的回答数据"
#: lib/pleroma/web/nodeinfo/nodeinfo_controller.ex:33 #: lib/pleroma/web/nodeinfo/nodeinfo_controller.ex:33
#, elixir-format #, elixir-format
@ -418,12 +418,12 @@ msgstr ""
#: lib/pleroma/web/oauth/oauth_controller.ex:172 #: lib/pleroma/web/oauth/oauth_controller.ex:172
#, elixir-format #, elixir-format
msgid "This action is outside the authorized scopes" msgid "This action is outside the authorized scopes"
msgstr "" msgstr "此操作在许可范围以外"
#: lib/pleroma/web/oauth/fallback_controller.ex:14 #: lib/pleroma/web/oauth/fallback_controller.ex:14
#, elixir-format #, elixir-format
msgid "Unknown error, please check the details and try again." msgid "Unknown error, please check the details and try again."
msgstr "" msgstr "未知错误,请检查并重试。"
#: lib/pleroma/web/oauth/oauth_controller.ex:119 #: lib/pleroma/web/oauth/oauth_controller.ex:119
#: lib/pleroma/web/oauth/oauth_controller.ex:158 #: lib/pleroma/web/oauth/oauth_controller.ex:158
@ -434,53 +434,53 @@ msgstr ""
#: lib/pleroma/web/oauth/oauth_controller.ex:390 #: lib/pleroma/web/oauth/oauth_controller.ex:390
#, elixir-format #, elixir-format
msgid "Unsupported OAuth provider: %{provider}." msgid "Unsupported OAuth provider: %{provider}."
msgstr "" msgstr "不支持的 OAuth 提供者:%{provider}。"
#: lib/pleroma/uploaders/uploader.ex:72 #: lib/pleroma/uploaders/uploader.ex:72
#, elixir-format #, elixir-format, fuzzy
msgid "Uploader callback timeout" msgid "Uploader callback timeout"
msgstr "" msgstr "上传回复超时"
#: lib/pleroma/web/uploader_controller.ex:23 #: lib/pleroma/web/uploader_controller.ex:23
#, elixir-format #, elixir-format
msgid "bad request" msgid "bad request"
msgstr "" msgstr "错误的请求"
#: lib/pleroma/web/twitter_api/twitter_api.ex:103 #: lib/pleroma/web/twitter_api/twitter_api.ex:103
#, elixir-format #, elixir-format
msgid "CAPTCHA Error" msgid "CAPTCHA Error"
msgstr "" msgstr "验证码错误"
#: lib/pleroma/web/common_api/common_api.ex:290 #: lib/pleroma/web/common_api/common_api.ex:290
#, elixir-format #, elixir-format, fuzzy
msgid "Could not add reaction emoji" msgid "Could not add reaction emoji"
msgstr "" msgstr "无法添加表情反应"
#: lib/pleroma/web/common_api/common_api.ex:301 #: lib/pleroma/web/common_api/common_api.ex:301
#, elixir-format #, elixir-format
msgid "Could not remove reaction emoji" msgid "Could not remove reaction emoji"
msgstr "" msgstr "无法移除表情反应"
#: lib/pleroma/web/twitter_api/twitter_api.ex:129 #: lib/pleroma/web/twitter_api/twitter_api.ex:129
#, elixir-format #, elixir-format
msgid "Invalid CAPTCHA (Missing parameter: %{name})" msgid "Invalid CAPTCHA (Missing parameter: %{name})"
msgstr "" msgstr "无效的验证码(缺少参数:%{name}"
#: lib/pleroma/web/mastodon_api/controllers/list_controller.ex:92 #: lib/pleroma/web/mastodon_api/controllers/list_controller.ex:92
#, elixir-format #, elixir-format
msgid "List not found" msgid "List not found"
msgstr "" msgstr "未找到列表"
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:123 #: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:123
#, elixir-format #, elixir-format
msgid "Missing parameter: %{name}" msgid "Missing parameter: %{name}"
msgstr "" msgstr "缺少参数:%{name}"
#: lib/pleroma/web/oauth/oauth_controller.ex:210 #: lib/pleroma/web/oauth/oauth_controller.ex:210
#: lib/pleroma/web/oauth/oauth_controller.ex:321 #: lib/pleroma/web/oauth/oauth_controller.ex:321
#, elixir-format #, elixir-format
msgid "Password reset is required" msgid "Password reset is required"
msgstr "" msgstr "需要重置密码"
#: lib/pleroma/tests/auth_test_controller.ex:9 #: lib/pleroma/tests/auth_test_controller.ex:9
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:6 lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:6 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:6 lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:6
@ -520,61 +520,61 @@ msgid "Security violation: OAuth scopes check was neither handled nor explicitly
msgstr "" msgstr ""
#: lib/pleroma/plugs/ensure_authenticated_plug.ex:28 #: lib/pleroma/plugs/ensure_authenticated_plug.ex:28
#, elixir-format #, elixir-format, fuzzy
msgid "Two-factor authentication enabled, you must use a access token." msgid "Two-factor authentication enabled, you must use a access token."
msgstr "" msgstr "已启用两因素验证,您需要使用访问令牌。"
#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:210 #: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:210
#, elixir-format #, elixir-format
msgid "Unexpected error occurred while adding file to pack." msgid "Unexpected error occurred while adding file to pack."
msgstr "" msgstr "向表情包添加文件时发生了没有预料到的错误。"
#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:138 #: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:138
#, elixir-format #, elixir-format
msgid "Unexpected error occurred while creating pack." msgid "Unexpected error occurred while creating pack."
msgstr "" msgstr "创建表情包时发生了没有预料到的错误。"
#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:278 #: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:278
#, elixir-format #, elixir-format
msgid "Unexpected error occurred while removing file from pack." msgid "Unexpected error occurred while removing file from pack."
msgstr "" msgstr "从表情包移除文件时发生了没有预料到的错误。"
#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:250 #: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:250
#, elixir-format #, elixir-format
msgid "Unexpected error occurred while updating file in pack." msgid "Unexpected error occurred while updating file in pack."
msgstr "" msgstr "更新表情包内的文件时发生了没有预料到的错误。"
#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:179 #: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:179
#, elixir-format #, elixir-format
msgid "Unexpected error occurred while updating pack metadata." msgid "Unexpected error occurred while updating pack metadata."
msgstr "" msgstr "更新表情包元数据时发生了没有预料到的错误。"
#: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:61 #: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:61
#, elixir-format #, elixir-format, fuzzy
msgid "Web push subscription is disabled on this Pleroma instance" msgid "Web push subscription is disabled on this Pleroma instance"
msgstr "" msgstr "此 Pleroma 实例禁用了网页推送订阅"
#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:451 #: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:451
#, elixir-format #, elixir-format
msgid "You can't revoke your own admin/moderator status." msgid "You can't revoke your own admin/moderator status."
msgstr "" msgstr "您不能撤消自己的管理员权限。"
#: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:126 #: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:126
#, elixir-format #, elixir-format
msgid "authorization required for timeline view" msgid "authorization required for timeline view"
msgstr "" msgstr "浏览时间线需要认证"
#: lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:24 #: lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:24
#, elixir-format #, elixir-format
msgid "Access denied" msgid "Access denied"
msgstr "" msgstr "拒绝访问"
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:282 #: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:282
#, elixir-format #, elixir-format
msgid "This API requires an authenticated user" msgid "This API requires an authenticated user"
msgstr "" msgstr "此 API 需要已认证的用户"
#: lib/pleroma/plugs/user_is_admin_plug.ex:21 #: lib/pleroma/plugs/user_is_admin_plug.ex:21
#, elixir-format #, elixir-format
msgid "User is not an admin." msgid "User is not an admin."
msgstr "" msgstr "该用户不是管理员。"

View File

@ -47,6 +47,11 @@ defmodule Pleroma.HTML.Scrubber.Default do
Meta.allow_tag_with_these_attributes(:strong, []) Meta.allow_tag_with_these_attributes(:strong, [])
Meta.allow_tag_with_these_attributes(:sub, []) Meta.allow_tag_with_these_attributes(:sub, [])
Meta.allow_tag_with_these_attributes(:sup, []) Meta.allow_tag_with_these_attributes(:sup, [])
Meta.allow_tag_with_these_attributes(:ruby, [])
Meta.allow_tag_with_these_attributes(:rb, [])
Meta.allow_tag_with_these_attributes(:rp, [])
Meta.allow_tag_with_these_attributes(:rt, [])
Meta.allow_tag_with_these_attributes(:rtc, [])
Meta.allow_tag_with_these_attributes(:u, []) Meta.allow_tag_with_these_attributes(:u, [])
Meta.allow_tag_with_these_attributes(:ul, []) Meta.allow_tag_with_these_attributes(:ul, [])

BIN
priv/static/images/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

View File

@ -2,12 +2,9 @@
"type": "Delete", "type": "Delete",
"signature": { "signature": {
"type": "RsaSignature2017", "type": "RsaSignature2017",
"signatureValue": "cw0RlfNREf+5VdsOYcCBDrv521eiLsDTAYNHKffjF0bozhCnOh+wHkFik7WamUk$ "signatureValue": "cw0RlfNREf+5VdsOYcCBDrv521eiLsDTAYNHKffjF0bozhCnOh+wHkFik7WamUk$uEiN4L2H6vPlGRprAZGRhEwgy+A7rIFQNmLrpW5qV5UNVI/2F7kngEHqZQgbQYj9hW+5GMYmPkHdv3D72ZefGw$4Xa2NBLGFpAjQllfzt7kzZLKKY2DM99FdUa64I2Wj3iD04Hs23SbrUdAeuGk/c1Cg6bwGNG4vxoiwn1jikgJLA$NAlSGjsRGdR7LfbC7GqWWsW3cSNsLFPoU6FyALjgTrrYoHiXe0QHggw+L3yMLfzB2S/L46/VRbyb+WDKMBIXUL$5owmzHSi6e/ZtCI3w==",
uEiN4L2H6vPlGRprAZGRhEwgy+A7rIFQNmLrpW5qV5UNVI/2F7kngEHqZQgbQYj9hW+5GMYmPkHdv3D72ZefGw$ "creator": "http://mastodon.example.org/users/gargron#main-key",
4Xa2NBLGFpAjQllfzt7kzZLKKY2DM99FdUa64I2Wj3iD04Hs23SbrUdAeuGk/c1Cg6bwGNG4vxoiwn1jikgJLA$ "created": "2018-03-03T16:24:11Z"
NAlSGjsRGdR7LfbC7GqWWsW3cSNsLFPoU6FyALjgTrrYoHiXe0QHggw+L3yMLfzB2S/L46/VRbyb+WDKMBIXUL$
5owmzHSi6e/ZtCI3w==",
"creator": "http://mastodon.example.org/users/gargron#main-key", "created": "2018-03-03T16:24:11Z"
}, },
"object": { "object": {
"type": "Tombstone", "type": "Tombstone",

View File

@ -1,56 +1,52 @@
{ {
"@context":[ "@context": [
"https://www.w3.org/ns/activitystreams", "https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1", "https://w3id.org/security/v1",
"https://apfed.club/apschema/v1.4" "https://apfed.club/apschema/v1.4"
], ],
"id":"https://apfed.club/follow/9", "id": "https://apfed.club/follow/9",
"type":"Follow", "type": "Follow",
"actor":{ "actor": {
"type":"Person", "type": "Person",
"id":"https://apfed.club/channel/indio", "id": "https://apfed.club/channel/indio",
"preferredUsername":"indio", "preferredUsername": "indio",
"name":"Indio", "name": "Indio",
"updated":"2019-08-20T23:52:34Z", "updated": "2019-08-20T23:52:34Z",
"icon":{ "icon": {
"type":"Image", "type": "Image",
"mediaType":"image/jpeg", "mediaType": "image/jpeg",
"updated":"2019-08-20T23:53:37Z", "updated": "2019-08-20T23:53:37Z",
"url":"https://apfed.club/photo/profile/l/2", "url": "https://apfed.club/photo/profile/l/2",
"height":300, "height": 300,
"width":300 "width": 300
}, },
"url":"https://apfed.club/channel/indio", "url": "https://apfed.club/channel/indio",
"inbox":"https://apfed.club/inbox/indio", "inbox": "https://apfed.club/inbox/indio",
"outbox":"https://apfed.club/outbox/indio", "outbox": "https://apfed.club/outbox/indio",
"followers":"https://apfed.club/followers/indio", "followers": "https://apfed.club/followers/indio",
"following":"https://apfed.club/following/indio", "following": "https://apfed.club/following/indio",
"endpoints":{ "endpoints": {
"sharedInbox":"https://apfed.club/inbox" "sharedInbox": "https://apfed.club/inbox"
}, },
"publicKey":{ "publicKey": {
"id":"https://apfed.club/channel/indio", "id": "https://apfed.club/channel/indio",
"owner":"https://apfed.club/channel/indio", "owner": "https://apfed.club/channel/indio",
"publicKeyPem":"-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA77TIR1VuSYFnmDRFGHHb\n4vaGdx9ranzRX4bfOKAqa++Ch5L4EqJpPy08RuM+NrYCYiYl4QQFDSSDXAEgb5g9\nC1TgWTfI7q/E0UBX2Vr0mU6X4i1ztv0tuQvegRjcSJ7l1AvoBs8Ip4MEJ3OPEQhB\ngJqAACB3Gnps4zi2I0yavkxUfGVKr6zKT3BxWh5hTpKC7Do+ChIrVZC2EwxND9K6 "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA77TIR1VuSYFnmDRFGHHb\n4vaGdx9ranzRX4bfOKAqa++Ch5L4EqJpPy08RuM+NrYCYiYl4QQFDSSDXAEgb5g9\nC1TgWTfI7q/E0UBX2Vr0mU6X4i1ztv0tuQvegRjcSJ7l1AvoBs8Ip4MEJ3OPEQhB\ngJqAACB3Gnps4zi2I0yavkxUfGVKr6zKT3BxWh5hTpKC7Do+ChIrVZC2EwxND9K6\nsAnQHThcb5EQuvuzUQZKeS7IEOsd0JpZDmJjbfMGrAWE81pLIfEeeA2joCJiBBTO\nglDsW+juvZ+lWqJpMr2hMWpvfrFjJeUawNJCIzsLdVIZR+aKj5yy6yqoS8hkN9Ha\n1MljZpsXl+EmwcwAIqim1YeLwERCEAQ/JWbSt8pQTQbzZ6ibwQ4mchCxacrRbIVR\nnL59fWMBassJcbY0VwrTugm2SBsYbDjESd55UZV03Rwr8qseGTyi+hH8O7w2SIaY\nzjN6AdZiPmsh00YflzlCk8MSLOHMol1vqIUzXxU8CdXn9+KsuQdZGrTz0YKN/db4\naVwUGJatz2Tsvf7R1tJBjJfeQWOWbbn3pycLVH86LjZ83qngp9ZVnAveUnUqz0yS\nhe+buZ6UMsfGzbIYon2bKNlz6gYTH0YPcr+cLe+29drtt0GZiXha1agbpo4RB8zE\naNL2fucF5YT0yNpbd/5WoV0CAwEAAQ==\n-----END PUBLIC KEY-----\n"
\nsAnQHThcb5EQuvuzUQZKeS7IEOsd0JpZDmJjbfMGrAWE81pLIfEeeA2joCJiBBTO\nglDsW+juvZ+lWqJpMr2hMWpvfrFjJeUawNJCIzsLdVIZR+aKj5yy6yqoS8hkN9Ha\n1MljZpsXl+EmwcwAIqim1YeLwERCEAQ/JWbSt8pQTQbzZ6ibwQ4mchCxacrRbIVR
\nnL59fWMBassJcbY0VwrTugm2SBsYbDjESd55UZV03Rwr8qseGTyi+hH8O7w2SIaY\nzjN6AdZiPmsh00YflzlCk8MSLOHMol1vqIUzXxU8CdXn9+KsuQdZGrTz0YKN/db4\naVwUGJatz2Tsvf7R1tJBjJfeQWOWbbn3pycLVH86LjZ83qngp9ZVnAveUnUqz0yS
\nhe+buZ6UMsfGzbIYon2bKNlz6gYTH0YPcr+cLe+29drtt0GZiXha1agbpo4RB8zE
\naNL2fucF5YT0yNpbd/5WoV0CAwEAAQ==\n-----END PUBLIC KEY-----\n"
} }
}, },
"object":"https://pleroma.site/users/kaniini", "object": "https://pleroma.site/users/kaniini",
"to":[ "to": [
"https://pleroma.site/users/kaniini" "https://pleroma.site/users/kaniini"
], ],
"signature":{ "signature": {
"@context":[ "@context": [
"https://www.w3.org/ns/activitystreams", "https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1" "https://w3id.org/security/v1"
], ],
"type":"RsaSignature2017", "type": "RsaSignature2017",
"nonce":"52c035e0a9e81dce8b486159204e97c22637e91f75cdfad5378de91de68e9117", "nonce": "52c035e0a9e81dce8b486159204e97c22637e91f75cdfad5378de91de68e9117",
"creator":"https://apfed.club/channel/indio/public_key_pem", "creator": "https://apfed.club/channel/indio/public_key_pem",
"created":"2019-08-22T03:38:02Z", "created": "2019-08-22T03:38:02Z",
"signatureValue":"oVliRCIqNIh6yUp851dYrF0y21aHp3Rz6VkIpW1pFMWfXuzExyWSfcELpyLseeRmsw5bUu9zJkH44B4G2LiJQKA9UoEQDjrDMZBmbeUpiQqq3DVUzkrBOI8bHZ7xyJ/CjSZcNHHh0MHhSKxswyxWMGi4zIqzkAZG3vRRgoPVHdjPm00sR3B8jBLw1cjoffv+KKeM/zEUpe13gqX9qHAWHHqZepxgSWmq+EKOkRvHUPBXiEJZfXzc5uW+vZ09F3WBYmaRoy8Y0e1P29fnRLqSy7EEINdrHaGclRqoUZyiawpkgy3lWWlynesV/HiLBR7EXT79eKstxf4wfTDaPKBCfTCsOWuMWHr7Genu37ew2/t7eiBGqCwwW12ylhml/OLHgNK3LOhmRABhtfpaFZSxfDVnlXfaLpY1xekVOj2oC0FpBtnoxVKLpIcyLw6dkfSil5ANd+hl59W/bpPA8KT90ii1fSNCo3+FcwQVx0YsPznJNA60XfFuVsme7zNcOst6393e1WriZxBanFpfB63zVQc9u1fjyfktx/yiUNxIlre+sz9OCc0AACn94iRhBYh4bbzdleUOTnM7lnD4Dj2FP+xeDIP8CA8wXUeq5+9kopSp2kAmlUEyFUdg4no7naIeu1SZnopfUg56PsVCp9JHiUK1SYAyWbdC+FbUECu5CvI=" "signatureValue": "oVliRCIqNIh6yUp851dYrF0y21aHp3Rz6VkIpW1pFMWfXuzExyWSfcELpyLseeRmsw5bUu9zJkH44B4G2LiJQKA9UoEQDjrDMZBmbeUpiQqq3DVUzkrBOI8bHZ7xyJ/CjSZcNHHh0MHhSKxswyxWMGi4zIqzkAZG3vRRgoPVHdjPm00sR3B8jBLw1cjoffv+KKeM/zEUpe13gqX9qHAWHHqZepxgSWmq+EKOkRvHUPBXiEJZfXzc5uW+vZ09F3WBYmaRoy8Y0e1P29fnRLqSy7EEINdrHaGclRqoUZyiawpkgy3lWWlynesV/HiLBR7EXT79eKstxf4wfTDaPKBCfTCsOWuMWHr7Genu37ew2/t7eiBGqCwwW12ylhml/OLHgNK3LOhmRABhtfpaFZSxfDVnlXfaLpY1xekVOj2oC0FpBtnoxVKLpIcyLw6dkfSil5ANd+hl59W/bpPA8KT90ii1fSNCo3+FcwQVx0YsPznJNA60XfFuVsme7zNcOst6393e1WriZxBanFpfB63zVQc9u1fjyfktx/yiUNxIlre+sz9OCc0AACn94iRhBYh4bbzdleUOTnM7lnD4Dj2FP+xeDIP8CA8wXUeq5+9kopSp2kAmlUEyFUdg4no7naIeu1SZnopfUg56PsVCp9JHiUK1SYAyWbdC+FbUECu5CvI="
} }
} }

View File

@ -7,6 +7,7 @@ defmodule Mix.Tasks.Pleroma.ConfigTest do
import Pleroma.Factory import Pleroma.Factory
alias Mix.Tasks.Pleroma.Config, as: MixTask
alias Pleroma.ConfigDB alias Pleroma.ConfigDB
alias Pleroma.Repo alias Pleroma.Repo
@ -22,30 +23,41 @@ defmodule Mix.Tasks.Pleroma.ConfigTest do
:ok :ok
end end
setup_all do: clear_config(:configurable_from_database, true) defp config_records do
ConfigDB
|> Repo.all()
|> Enum.sort()
end
defp insert_config_record(group, key, value) do
insert(:config,
group: group,
key: key,
value: value
)
end
test "error if file with custom settings doesn't exist" do test "error if file with custom settings doesn't exist" do
Mix.Tasks.Pleroma.Config.migrate_to_db("config/not_existance_config_file.exs") MixTask.migrate_to_db("config/non_existent_config_file.exs")
assert_receive {:mix_shell, :info, msg =
[ "To migrate settings, you must define custom settings in config/non_existent_config_file.exs."
"To migrate settings, you must define custom settings in config/not_existance_config_file.exs."
]}, assert_receive {:mix_shell, :info, [^msg]}, 15
15
end end
describe "migrate_to_db/1" do describe "migrate_to_db/1" do
setup do setup do
initial = Application.get_env(:quack, :level) clear_config(:configurable_from_database, true)
on_exit(fn -> Application.put_env(:quack, :level, initial) end) clear_config([:quack, :level])
end end
@tag capture_log: true @tag capture_log: true
test "config migration refused when deprecated settings are found" do test "config migration refused when deprecated settings are found" do
clear_config([:media_proxy, :whitelist], ["domain_without_scheme.com"]) clear_config([:media_proxy, :whitelist], ["domain_without_scheme.com"])
assert Repo.all(ConfigDB) == [] assert config_records() == []
Mix.Tasks.Pleroma.Config.migrate_to_db("test/fixtures/config/temp.secret.exs") MixTask.migrate_to_db("test/fixtures/config/temp.secret.exs")
assert_received {:mix_shell, :error, [message]} assert_received {:mix_shell, :error, [message]}
@ -54,9 +66,9 @@ test "config migration refused when deprecated settings are found" do
end end
test "filtered settings are migrated to db" do test "filtered settings are migrated to db" do
assert Repo.all(ConfigDB) == [] assert config_records() == []
Mix.Tasks.Pleroma.Config.migrate_to_db("test/fixtures/config/temp.secret.exs") MixTask.migrate_to_db("test/fixtures/config/temp.secret.exs")
config1 = ConfigDB.get_by_params(%{group: ":pleroma", key: ":first_setting"}) config1 = ConfigDB.get_by_params(%{group: ":pleroma", key: ":first_setting"})
config2 = ConfigDB.get_by_params(%{group: ":pleroma", key: ":second_setting"}) config2 = ConfigDB.get_by_params(%{group: ":pleroma", key: ":second_setting"})
@ -71,18 +83,19 @@ test "filtered settings are migrated to db" do
end end
test "config table is truncated before migration" do test "config table is truncated before migration" do
insert(:config, key: :first_setting, value: [key: "value", key2: ["Activity"]]) insert_config_record(:pleroma, :first_setting, key: "value", key2: ["Activity"])
assert Repo.aggregate(ConfigDB, :count, :id) == 1 assert length(config_records()) == 1
Mix.Tasks.Pleroma.Config.migrate_to_db("test/fixtures/config/temp.secret.exs") MixTask.migrate_to_db("test/fixtures/config/temp.secret.exs")
config = ConfigDB.get_by_params(%{group: ":pleroma", key: ":first_setting"}) config = ConfigDB.get_by_params(%{group: ":pleroma", key: ":first_setting"})
assert config.value == [key: "value", key2: [Repo]] assert config.value == [key: "value", key2: [Repo]]
end end
end end
describe "with deletion temp file" do describe "with deletion of temp file" do
setup do setup do
clear_config(:configurable_from_database, true)
temp_file = "config/temp.exported_from_db.secret.exs" temp_file = "config/temp.exported_from_db.secret.exs"
on_exit(fn -> on_exit(fn ->
@ -93,13 +106,13 @@ test "config table is truncated before migration" do
end end
test "settings are migrated to file and deleted from db", %{temp_file: temp_file} do test "settings are migrated to file and deleted from db", %{temp_file: temp_file} do
insert(:config, key: :setting_first, value: [key: "value", key2: ["Activity"]]) insert_config_record(:pleroma, :setting_first, key: "value", key2: ["Activity"])
insert(:config, key: :setting_second, value: [key: "value2", key2: [Repo]]) insert_config_record(:pleroma, :setting_second, key: "value2", key2: [Repo])
insert(:config, group: :quack, key: :level, value: :info) insert_config_record(:quack, :level, :info)
Mix.Tasks.Pleroma.Config.run(["migrate_from_db", "--env", "temp", "-d"]) MixTask.run(["migrate_from_db", "--env", "temp", "-d"])
assert Repo.all(ConfigDB) == [] assert config_records() == []
file = File.read!(temp_file) file = File.read!(temp_file)
assert file =~ "config :pleroma, :setting_first," assert file =~ "config :pleroma, :setting_first,"
@ -169,9 +182,9 @@ test "load a settings with large values and pass to file", %{temp_file: temp_fil
] ]
) )
Mix.Tasks.Pleroma.Config.run(["migrate_from_db", "--env", "temp", "-d"]) MixTask.run(["migrate_from_db", "--env", "temp", "-d"])
assert Repo.all(ConfigDB) == [] assert config_records() == []
assert File.exists?(temp_file) assert File.exists?(temp_file)
{:ok, file} = File.read(temp_file) {:ok, file} = File.read(temp_file)
@ -186,4 +199,114 @@ test "load a settings with large values and pass to file", %{temp_file: temp_fil
"#{header}\n\nconfig :pleroma, :instance,\n name: \"Pleroma\",\n email: \"example@example.com\",\n notify_email: \"noreply@example.com\",\n description: \"A Pleroma instance, an alternative fediverse server\",\n limit: 5000,\n chat_limit: 5000,\n remote_limit: 100_000,\n upload_limit: 16_000_000,\n avatar_upload_limit: 2_000_000,\n background_upload_limit: 4_000_000,\n banner_upload_limit: 4_000_000,\n poll_limits: %{\n max_expiration: 31_536_000,\n max_option_chars: 200,\n max_options: 20,\n min_expiration: 0\n },\n registrations_open: true,\n federating: true,\n federation_incoming_replies_max_depth: 100,\n federation_reachability_timeout_days: 7,\n federation_publisher_modules: [Pleroma.Web.ActivityPub.Publisher],\n allow_relay: true,\n public: true,\n quarantined_instances: [],\n managed_config: true,\n static_dir: \"instance/static/\",\n allowed_post_formats: [\"text/plain\", \"text/html\", \"text/markdown\", \"text/bbcode\"],\n autofollowed_nicknames: [],\n max_pinned_statuses: 1,\n attachment_links: false,\n max_report_comment_size: 1000,\n safe_dm_mentions: false,\n healthcheck: false,\n remote_post_retention_days: 90,\n skip_thread_containment: true,\n limit_to_local_content: :unauthenticated,\n user_bio_length: 5000,\n user_name_length: 100,\n max_account_fields: 10,\n max_remote_account_fields: 20,\n account_field_name_length: 512,\n account_field_value_length: 2048,\n external_user_synchronization: true,\n extended_nickname_format: true,\n multi_factor_authentication: [\n totp: [digits: 6, period: 30],\n backup_codes: [number: 2, length: 6]\n ]\n" "#{header}\n\nconfig :pleroma, :instance,\n name: \"Pleroma\",\n email: \"example@example.com\",\n notify_email: \"noreply@example.com\",\n description: \"A Pleroma instance, an alternative fediverse server\",\n limit: 5000,\n chat_limit: 5000,\n remote_limit: 100_000,\n upload_limit: 16_000_000,\n avatar_upload_limit: 2_000_000,\n background_upload_limit: 4_000_000,\n banner_upload_limit: 4_000_000,\n poll_limits: %{\n max_expiration: 31_536_000,\n max_option_chars: 200,\n max_options: 20,\n min_expiration: 0\n },\n registrations_open: true,\n federating: true,\n federation_incoming_replies_max_depth: 100,\n federation_reachability_timeout_days: 7,\n federation_publisher_modules: [Pleroma.Web.ActivityPub.Publisher],\n allow_relay: true,\n public: true,\n quarantined_instances: [],\n managed_config: true,\n static_dir: \"instance/static/\",\n allowed_post_formats: [\"text/plain\", \"text/html\", \"text/markdown\", \"text/bbcode\"],\n autofollowed_nicknames: [],\n max_pinned_statuses: 1,\n attachment_links: false,\n max_report_comment_size: 1000,\n safe_dm_mentions: false,\n healthcheck: false,\n remote_post_retention_days: 90,\n skip_thread_containment: true,\n limit_to_local_content: :unauthenticated,\n user_bio_length: 5000,\n user_name_length: 100,\n max_account_fields: 10,\n max_remote_account_fields: 20,\n account_field_name_length: 512,\n account_field_value_length: 2048,\n external_user_synchronization: true,\n extended_nickname_format: true,\n multi_factor_authentication: [\n totp: [digits: 6, period: 30],\n backup_codes: [number: 2, length: 6]\n ]\n"
end end
end end
describe "operations on database config" do
setup do: clear_config(:configurable_from_database, true)
test "dumping a specific group" do
insert_config_record(:pleroma, :instance, name: "Pleroma Test")
insert_config_record(:web_push_encryption, :vapid_details,
subject: "mailto:administrator@example.com",
public_key:
"BOsPL-_KjNnjj_RMvLeR3dTOrcndi4TbMR0cu56gLGfGaT5m1gXxSfRHOcC4Dd78ycQL1gdhtx13qgKHmTM5xAI",
private_key: "Ism6FNdS31nLCA94EfVbJbDdJXCxAZ8cZiB1JQPN_t4"
)
MixTask.run(["dump", "pleroma"])
assert_receive {:mix_shell, :info,
["config :pleroma, :instance, [name: \"Pleroma Test\"]\r\n\r\n"]}
refute_receive {
:mix_shell,
:info,
[
"config :web_push_encryption, :vapid_details, [subject: \"mailto:administrator@example.com\", public_key: \"BOsPL-_KjNnjj_RMvLeR3dTOrcndi4TbMR0cu56gLGfGaT5m1gXxSfRHOcC4Dd78ycQL1gdhtx13qgKHmTM5xAI\", private_key: \"Ism6FNdS31nLCA94EfVbJbDdJXCxAZ8cZiB1JQPN_t4\"]\r\n\r\n"
]
}
# Ensure operations work when using atom syntax
MixTask.run(["dump", ":pleroma"])
assert_receive {:mix_shell, :info,
["config :pleroma, :instance, [name: \"Pleroma Test\"]\r\n\r\n"]}
end
test "dumping a specific key in a group" do
insert_config_record(:pleroma, :instance, name: "Pleroma Test")
insert_config_record(:pleroma, Pleroma.Captcha, enabled: false)
MixTask.run(["dump", "pleroma", "Pleroma.Captcha"])
refute_receive {:mix_shell, :info,
["config :pleroma, :instance, [name: \"Pleroma Test\"]\r\n\r\n"]}
assert_receive {:mix_shell, :info,
["config :pleroma, Pleroma.Captcha, [enabled: false]\r\n\r\n"]}
end
test "dumps all configuration successfully" do
insert_config_record(:pleroma, :instance, name: "Pleroma Test")
insert_config_record(:pleroma, Pleroma.Captcha, enabled: false)
MixTask.run(["dump"])
assert_receive {:mix_shell, :info,
["config :pleroma, :instance, [name: \"Pleroma Test\"]\r\n\r\n"]}
assert_receive {:mix_shell, :info,
["config :pleroma, Pleroma.Captcha, [enabled: false]\r\n\r\n"]}
end
end
describe "when configdb disabled" do
test "refuses to dump" do
clear_config(:configurable_from_database, false)
insert_config_record(:pleroma, :instance, name: "Pleroma Test")
MixTask.run(["dump"])
msg =
"ConfigDB not enabled. Please check the value of :configurable_from_database in your configuration."
assert_receive {:mix_shell, :error, [^msg]}
end
end
describe "destructive operations" do
setup do: clear_config(:configurable_from_database, true)
setup do
insert_config_record(:pleroma, :instance, name: "Pleroma Test")
insert_config_record(:pleroma, Pleroma.Captcha, enabled: false)
insert_config_record(:pleroma2, :key2, z: 1)
assert length(config_records()) == 3
:ok
end
test "deletes group of settings" do
MixTask.run(["delete", "--force", "pleroma"])
assert [%ConfigDB{group: :pleroma2, key: :key2}] = config_records()
end
test "deletes specified key" do
MixTask.run(["delete", "--force", "pleroma", "Pleroma.Captcha"])
assert [
%ConfigDB{group: :pleroma, key: :instance},
%ConfigDB{group: :pleroma2, key: :key2}
] = config_records()
end
test "resets entire config" do
MixTask.run(["reset", "--force"])
assert config_records() == []
end
end
end end

View File

@ -73,7 +73,7 @@ test "it prunes old objects from the database" do
describe "running update_users_following_followers_counts" do describe "running update_users_following_followers_counts" do
test "following and followers count are updated" do test "following and followers count are updated" do
[user, user2] = insert_pair(:user) [user, user2] = insert_pair(:user)
{:ok, %User{} = user} = User.follow(user, user2) {:ok, %User{} = user, _user2} = User.follow(user, user2)
following = User.following(user) following = User.following(user)
@ -87,7 +87,8 @@ test "following and followers count are updated" do
assert user.follower_count == 3 assert user.follower_count == 3
assert :ok == Mix.Tasks.Pleroma.Database.run(["update_users_following_followers_counts"]) assert {:ok, :ok} ==
Mix.Tasks.Pleroma.Database.run(["update_users_following_followers_counts"])
user = User.get_by_id(user.id) user = User.get_by_id(user.id)

View File

@ -36,7 +36,7 @@ test "user is created" do
unsaved = build(:user) unsaved = build(:user)
# prepare to answer yes # prepare to answer yes
send(self(), {:mix_shell_input, :yes?, true}) send(self(), {:mix_shell_input, :prompt, "Y"})
Mix.Tasks.Pleroma.User.run([ Mix.Tasks.Pleroma.User.run([
"new", "new",
@ -55,7 +55,7 @@ test "user is created" do
assert_received {:mix_shell, :info, [message]} assert_received {:mix_shell, :info, [message]}
assert message =~ "user will be created" assert message =~ "user will be created"
assert_received {:mix_shell, :yes?, [message]} assert_received {:mix_shell, :prompt, [message]}
assert message =~ "Continue" assert message =~ "Continue"
assert_received {:mix_shell, :info, [message]} assert_received {:mix_shell, :info, [message]}
@ -73,14 +73,14 @@ test "user is not created" do
unsaved = build(:user) unsaved = build(:user)
# prepare to answer no # prepare to answer no
send(self(), {:mix_shell_input, :yes?, false}) send(self(), {:mix_shell_input, :prompt, "N"})
Mix.Tasks.Pleroma.User.run(["new", unsaved.nickname, unsaved.email]) Mix.Tasks.Pleroma.User.run(["new", unsaved.nickname, unsaved.email])
assert_received {:mix_shell, :info, [message]} assert_received {:mix_shell, :info, [message]}
assert message =~ "user will be created" assert message =~ "user will be created"
assert_received {:mix_shell, :yes?, [message]} assert_received {:mix_shell, :prompt, [message]}
assert message =~ "Continue" assert message =~ "Continue"
assert_received {:mix_shell, :info, [message]} assert_received {:mix_shell, :info, [message]}
@ -503,7 +503,7 @@ test "it returns users matching" do
moot = insert(:user, nickname: "moot") moot = insert(:user, nickname: "moot")
kawen = insert(:user, nickname: "kawen", name: "fediverse expert moon") kawen = insert(:user, nickname: "kawen", name: "fediverse expert moon")
{:ok, user} = User.follow(user, moon) {:ok, user, moon} = User.follow(user, moon)
assert [moon.id, kawen.id] == User.Search.search("moon") |> Enum.map(& &1.id) assert [moon.id, kawen.id] == User.Search.search("moon") |> Enum.map(& &1.id)

View File

@ -0,0 +1,45 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Activity.SearchTest do
alias Pleroma.Activity.Search
alias Pleroma.Web.CommonAPI
import Pleroma.Factory
use Pleroma.DataCase
test "it finds something" do
user = insert(:user)
{:ok, post} = CommonAPI.post(user, %{status: "it's wednesday my dudes"})
[result] = Search.search(nil, "wednesday")
assert result.id == post.id
end
test "using plainto_tsquery on postgres < 11" do
old_version = :persistent_term.get({Pleroma.Repo, :postgres_version})
:persistent_term.put({Pleroma.Repo, :postgres_version}, 10.0)
on_exit(fn -> :persistent_term.put({Pleroma.Repo, :postgres_version}, old_version) end)
user = insert(:user)
{:ok, post} = CommonAPI.post(user, %{status: "it's wednesday my dudes"})
{:ok, _post2} = CommonAPI.post(user, %{status: "it's wednesday my bros"})
# plainto doesn't understand complex queries
assert [result] = Search.search(nil, "wednesday -dudes")
assert result.id == post.id
end
test "using websearch_to_tsquery" do
user = insert(:user)
{:ok, _post} = CommonAPI.post(user, %{status: "it's wednesday my dudes"})
{:ok, other_post} = CommonAPI.post(user, %{status: "it's wednesday my bros"})
assert [result] = Search.search(nil, "wednesday -dudes")
assert result.id == other_post.id
end
end

View File

@ -197,6 +197,13 @@ test "all_by_ids_with_object/1" do
assert [%{id: ^id1, object: %Object{}}, %{id: ^id2, object: %Object{}}] = activities assert [%{id: ^id1, object: %Object{}}, %{id: ^id2, object: %Object{}}] = activities
end end
test "get_by_id_with_user_actor/1" do
user = insert(:user)
activity = insert(:note_activity, note: insert(:note, user: user))
assert Activity.get_by_id_with_user_actor(activity.id).user_actor == user
end
test "get_by_id_with_object/1" do test "get_by_id_with_object/1" do
%{id: id} = insert(:note_activity) %{id: id} = insert(:note_activity)

View File

@ -12,6 +12,25 @@ defmodule Pleroma.ApplicationRequirementsTest do
alias Pleroma.Config alias Pleroma.Config
alias Pleroma.Repo alias Pleroma.Repo
describe "check_repo_pool_size!/1" do
test "raises if the pool size is unexpected" do
clear_config([Pleroma.Repo, :pool_size], 11)
assert_raise Pleroma.ApplicationRequirements.VerifyError,
"Repo.pool_size different than recommended value.",
fn ->
capture_log(&Pleroma.ApplicationRequirements.verify!/0)
end
end
test "doesn't raise if the pool size is unexpected but the respective flag is set" do
clear_config([Pleroma.Repo, :pool_size], 11)
clear_config([:dangerzone, :override_repo_pool_size], true)
assert Pleroma.ApplicationRequirements.verify!() == :ok
end
end
describe "check_welcome_message_config!/1" do describe "check_welcome_message_config!/1" do
setup do: clear_config([:welcome]) setup do: clear_config([:welcome])
setup do: clear_config([Pleroma.Emails.Mailer]) setup do: clear_config([Pleroma.Emails.Mailer])

View File

@ -19,7 +19,7 @@ test "getting the home timeline" do
user = insert(:user) user = insert(:user)
followed = insert(:user) followed = insert(:user)
{:ok, user} = User.follow(user, followed) {:ok, user, followed} = User.follow(user, followed)
{:ok, _first} = CommonAPI.post(user, %{status: "hey"}) {:ok, _first} = CommonAPI.post(user, %{status: "hey"})
{:ok, _second} = CommonAPI.post(followed, %{status: "hello"}) {:ok, _second} = CommonAPI.post(followed, %{status: "hello"})

View File

@ -9,8 +9,22 @@ defmodule Pleroma.EmojiTest do
describe "is_unicode_emoji?/1" do describe "is_unicode_emoji?/1" do
test "tells if a string is an unicode emoji" do test "tells if a string is an unicode emoji" do
refute Emoji.is_unicode_emoji?("X") refute Emoji.is_unicode_emoji?("X")
assert Emoji.is_unicode_emoji?("") refute Emoji.is_unicode_emoji?("")
# Only accept fully-qualified (RGI) emoji
# See http://www.unicode.org/reports/tr51/
refute Emoji.is_unicode_emoji?("")
refute Emoji.is_unicode_emoji?("")
assert Emoji.is_unicode_emoji?("🥺") assert Emoji.is_unicode_emoji?("🥺")
assert Emoji.is_unicode_emoji?("🤰")
assert Emoji.is_unicode_emoji?("❤️")
assert Emoji.is_unicode_emoji?("🏳️‍⚧️")
# Additionally, we accept regional indicators.
assert Emoji.is_unicode_emoji?("🇵")
assert Emoji.is_unicode_emoji?("🇴")
assert Emoji.is_unicode_emoji?("🇬")
end end
end end

View File

@ -241,16 +241,14 @@ test "it can parse mentions and return the relevant users" do
"@@gsimg According to @archaeme, that is @daggsy. Also hello @archaeme@archae.me and @o and @@@jimm" "@@gsimg According to @archaeme, that is @daggsy. Also hello @archaeme@archae.me and @o and @@@jimm"
o = insert(:user, %{nickname: "o"}) o = insert(:user, %{nickname: "o"})
jimm = insert(:user, %{nickname: "jimm"}) _jimm = insert(:user, %{nickname: "jimm"})
gsimg = insert(:user, %{nickname: "gsimg"}) _gsimg = insert(:user, %{nickname: "gsimg"})
archaeme = insert(:user, %{nickname: "archaeme"}) archaeme = insert(:user, %{nickname: "archaeme"})
archaeme_remote = insert(:user, %{nickname: "archaeme@archae.me"}) archaeme_remote = insert(:user, %{nickname: "archaeme@archae.me"})
expected_mentions = [ expected_mentions = [
{"@archaeme", archaeme}, {"@archaeme", archaeme},
{"@archaeme@archae.me", archaeme_remote}, {"@archaeme@archae.me", archaeme_remote},
{"@gsimg", gsimg},
{"@jimm", jimm},
{"@o", o} {"@o", o}
] ]

View File

@ -3,6 +3,7 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Instances.InstanceTest do defmodule Pleroma.Instances.InstanceTest do
alias Pleroma.Instances
alias Pleroma.Instances.Instance alias Pleroma.Instances.Instance
alias Pleroma.Repo alias Pleroma.Repo
@ -148,5 +149,13 @@ test "Handles not getting a favicon URL properly" do
) )
end) =~ "Instance.scrape_favicon(\"https://no-favicon.example.org/\") error: " end) =~ "Instance.scrape_favicon(\"https://no-favicon.example.org/\") error: "
end end
test "Doesn't scrapes unreachable instances" do
instance = insert(:instance, unreachable_since: Instances.reachability_datetime_threshold())
url = "https://" <> instance.host
assert capture_log(fn -> assert nil == Instance.get_or_update_favicon(URI.parse(url)) end) =~
"Instance.scrape_favicon(\"#{url}\") ignored unreachable host"
end
end end
end end

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