Merge branch 'develop' of git.pleroma.social:pleroma/pleroma into 2307-object-activities-index-issue
This commit is contained in:
commit
002c5b97be
|
@ -1,2 +1,8 @@
|
||||||
*.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
|
||||||
|
# to search/diff those as text files.
|
||||||
|
*.js binary
|
||||||
|
*.js.map binary
|
||||||
|
*.css binary
|
||||||
|
|
|
@ -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,8 +228,8 @@ arm:
|
||||||
artifacts: *release-artifacts
|
artifacts: *release-artifacts
|
||||||
only: *release-only
|
only: *release-only
|
||||||
tags:
|
tags:
|
||||||
- arm32
|
- arm32-specified
|
||||||
image: elixir:1.10.3
|
image: arm32v7/elixir:1.10.3
|
||||||
cache: *release-cache
|
cache: *release-cache
|
||||||
variables: *release-variables
|
variables: *release-variables
|
||||||
before_script: *before-release
|
before_script: *before-release
|
||||||
|
@ -240,8 +240,8 @@ arm-musl:
|
||||||
artifacts: *release-artifacts
|
artifacts: *release-artifacts
|
||||||
only: *release-only
|
only: *release-only
|
||||||
tags:
|
tags:
|
||||||
- arm32
|
- arm32-specified
|
||||||
image: elixir:1.10.3-alpine
|
image: arm32v7/elixir:1.10.3-alpine
|
||||||
cache: *release-cache
|
cache: *release-cache
|
||||||
variables: *release-variables
|
variables: *release-variables
|
||||||
before_script: *before-release-musl
|
before_script: *before-release-musl
|
||||||
|
@ -253,7 +253,7 @@ arm64:
|
||||||
only: *release-only
|
only: *release-only
|
||||||
tags:
|
tags:
|
||||||
- arm
|
- arm
|
||||||
image: elixir:1.10.3
|
image: arm64v8/elixir:1.10.3
|
||||||
cache: *release-cache
|
cache: *release-cache
|
||||||
variables: *release-variables
|
variables: *release-variables
|
||||||
before_script: *before-release
|
before_script: *before-release
|
||||||
|
@ -265,8 +265,7 @@ arm64-musl:
|
||||||
only: *release-only
|
only: *release-only
|
||||||
tags:
|
tags:
|
||||||
- arm
|
- arm
|
||||||
# TODO: Replace with upstream image when 1.9.0 comes out
|
image: arm64v8/elixir:1.10.3-alpine
|
||||||
image: elixir:1.10.3-alpine
|
|
||||||
cache: *release-cache
|
cache: *release-cache
|
||||||
variables: *release-variables
|
variables: *release-variables
|
||||||
before_script: *before-release-musl
|
before_script: *before-release-musl
|
||||||
|
|
122
CHANGELOG.md
122
CHANGELOG.md
|
@ -1,52 +1,92 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Polls now always return a `voters_count`, even if they are single-choice.
|
||||||
|
- Admin Emails: The ap id is used as the user link in emails now.
|
||||||
|
- 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
|
||||||
- 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`)
|
- Reports now generate notifications for admins and mods.
|
||||||
- Mix task option for force-unfollowing relays
|
|
||||||
- Media preview proxy (requires `ffmpeg` and `ImageMagick` to be installed and media proxy to be enabled; see `:media_preview_proxy` config for more details).
|
|
||||||
- Pleroma API: Importing the mutes users from CSV files.
|
|
||||||
- Experimental websocket-based federation between Pleroma instances.
|
- Experimental websocket-based federation between Pleroma instances.
|
||||||
- Support pagination of blocks and mutes
|
- Support for local-only statuses
|
||||||
- App metrics: ability to restrict access to specified IP whitelist.
|
- 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.
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>API Changes</summary>
|
||||||
|
- Admin API: (`GET /api/pleroma/admin/users`) filter users by `unconfirmed` status and `actor_type`.
|
||||||
|
- 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.
|
||||||
|
- 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
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
### 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.).
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>API Changes</summary>
|
||||||
|
- Mastodon API: Current user is now included in conversation if it's the only participant.
|
||||||
|
- Mastodon API: Fixed last_status.account being not filled with account data.
|
||||||
|
</details>
|
||||||
|
|
||||||
|
## Unreleased (Patch)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Config generation: rename `Pleroma.Upload.Filter.ExifTool` to `Pleroma.Upload.Filter.Exiftool`.
|
||||||
|
- Search: RUM index search speed has been fixed.
|
||||||
|
- S3 Uploads with Elixir 1.11.
|
||||||
|
- Emoji Reaction activity filtering from blocked and muted accounts.
|
||||||
|
- 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
|
||||||
|
- Forwarded reports duplication from Pleroma instances.
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>API</summary>
|
||||||
|
- Statuses were not displayed for Mastodon forwarded reports.
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
## [2.2.0] - 2020-11-12
|
||||||
|
|
||||||
|
### Security
|
||||||
|
|
||||||
|
- Fixed the possibility of using file uploads to spoof posts.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- **Breaking** Requires `libmagic` (or `file`) to guess file types.
|
- **Breaking** Requires `libmagic` (or `file`) to guess file types.
|
||||||
|
- **Breaking:** App metrics endpoint (`/api/pleroma/app_metrics`) is disabled by default, check `docs/API/prometheus.md` on enabling and configuring.
|
||||||
- **Breaking:** Pleroma Admin API: emoji packs and files routes changed.
|
- **Breaking:** Pleroma Admin API: emoji packs and files routes changed.
|
||||||
- **Breaking:** Sensitive/NSFW statuses no longer disable link previews.
|
- **Breaking:** Sensitive/NSFW statuses no longer disable link previews.
|
||||||
- **Breaking:** App metrics endpoint (`/api/pleroma/app_metrics`) is disabled by default, check `docs/API/prometheus.md` on enabling and configuring.
|
|
||||||
- Search: Users are now findable by their urls.
|
- Search: Users are now findable by their urls.
|
||||||
- Renamed `:await_up_timeout` in `:connections_pool` namespace to `:connect_timeout`, old name is deprecated.
|
- Renamed `:await_up_timeout` in `:connections_pool` namespace to `:connect_timeout`, old name is deprecated.
|
||||||
- Renamed `:timeout` in `pools` namespace to `:recv_timeout`, old name is deprecated.
|
- Renamed `:timeout` in `pools` namespace to `:recv_timeout`, old name is deprecated.
|
||||||
- The `discoverable` field in the `User` struct will now add a NOINDEX metatag to profile pages when false.
|
- The `discoverable` field in the `User` struct will now add a NOINDEX metatag to profile pages when false.
|
||||||
- Users with the `discoverable` field set to false will not show up in searches.
|
- Users with the `is_discoverable` field set to false will not show up in searches ([bug](https://git.pleroma.social/pleroma/pleroma/-/issues/2301)).
|
||||||
- Minimum lifetime for ephmeral activities changed to 10 minutes and made configurable (`:min_lifetime` option).
|
- Minimum lifetime for ephmeral activities changed to 10 minutes and made configurable (`:min_lifetime` option).
|
||||||
- Introduced optional dependencies on `ffmpeg`, `ImageMagick`, `exiftool` software packages. Please refer to `docs/installation/optional/media_graphics_packages.md`.
|
- Introduced optional dependencies on `ffmpeg`, `ImageMagick`, `exiftool` software packages. Please refer to `docs/installation/optional/media_graphics_packages.md`.
|
||||||
- Polls now always return a `voters_count`, even if they are single-choice
|
- <details>
|
||||||
- Admin Emails: The ap id is used as the user link in emails now.
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>API Changes</summary>
|
<summary>API Changes</summary>
|
||||||
|
- API: Empty parameter values for integer parameters are now ignored in non-strict validaton mode.
|
||||||
- Pleroma API: Importing the mutes users from CSV files.
|
|
||||||
- Admin API: Importing emoji from a zip file
|
|
||||||
- Pleroma API: Pagination for remote/local packs and emoji.
|
|
||||||
- Admin API: (`GET /api/pleroma/admin/users`) added filters user by `unconfirmed` status
|
|
||||||
- Admin API: (`GET /api/pleroma/admin/users`) added filters user by `actor_type`
|
|
||||||
- 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.
|
|
||||||
- Mastodon API: User and conversation mutes can now auto-expire if `expires_in` parameter was given while adding the mute.
|
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
|
@ -57,20 +97,38 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- Removed `:managed_config` option. In practice, it was accidentally removed with 2.0.0 release when frontends were
|
- Removed `:managed_config` option. In practice, it was accidentally removed with 2.0.0 release when frontends were
|
||||||
switched to a new configuration mechanism, however it was not officially removed until now.
|
switched to a new configuration mechanism, however it was not officially removed until now.
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- 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 task for sending confirmation emails to all unconfirmed users (`mix pleroma.email send_confirmation_mails`)
|
||||||
|
- Mix task option for force-unfollowing relays
|
||||||
|
- App metrics: ability to restrict access to specified IP whitelist.
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>API Changes</summary>
|
||||||
|
|
||||||
|
- Admin API: Importing emoji from a zip file
|
||||||
|
- Pleroma API: Importing the mutes users from CSV files.
|
||||||
|
- Pleroma API: Pagination for remote/local packs and emoji.
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Add documented-but-missing chat pagination.
|
- Add documented-but-missing chat pagination.
|
||||||
- Allow sending out emails again.
|
- Allow sending out emails again.
|
||||||
- Allow sending chat messages to yourself.
|
- Allow sending chat messages to yourself
|
||||||
- Fix remote users with a whitespace name.
|
|
||||||
- OStatus / static FE endpoints: fixed inaccessibility for anonymous users on non-federating instances, switched to handling per `:restrict_unauthenticated` setting.
|
- OStatus / static FE endpoints: fixed inaccessibility for anonymous users on non-federating instances, switched to handling per `:restrict_unauthenticated` setting.
|
||||||
- Mastodon API: Current user is now included in conversation if it's the only participant
|
- Fix remote users with a whitespace name.
|
||||||
- Mastodon API: Fixed last_status.account being not filled with account data
|
|
||||||
|
|
||||||
## Unreleased (Patch)
|
### Upgrade notes
|
||||||
|
|
||||||
### Changed
|
1. Install libmagic and development headers (`libmagic-dev` on Ubuntu/Debian, `file-dev` on Alpine Linux)
|
||||||
- API: Empty parameter values for integer parameters are now ignored in non-strict validaton mode.
|
2. Run database migrations (inside Pleroma directory):
|
||||||
|
- OTP: `./bin/pleroma_ctl migrate`
|
||||||
|
- From Source: `mix ecto.migrate`
|
||||||
|
3. Restart Pleroma
|
||||||
|
|
||||||
## [2.1.2] - 2020-09-17
|
## [2.1.2] - 2020-09-17
|
||||||
|
|
||||||
|
|
|
@ -31,9 +31,9 @@ LABEL maintainer="ops@pleroma.social" \
|
||||||
ARG HOME=/opt/pleroma
|
ARG HOME=/opt/pleroma
|
||||||
ARG DATA=/var/lib/pleroma
|
ARG DATA=/var/lib/pleroma
|
||||||
|
|
||||||
RUN echo "https://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 &&\
|
||||||
|
|
|
@ -6,7 +6,7 @@ Currently, Pleroma offers bugfixes and security patches only for the latest mino
|
||||||
|
|
||||||
| Version | Support
|
| Version | Support
|
||||||
|---------| --------
|
|---------| --------
|
||||||
| 2.1 | Bugfixes and security patches
|
| 2.2 | Bugfixes and security patches
|
||||||
|
|
||||||
## Reporting a vulnerability
|
## Reporting a vulnerability
|
||||||
|
|
||||||
|
|
|
@ -129,7 +129,6 @@
|
||||||
dispatch: [
|
dispatch: [
|
||||||
{:_,
|
{:_,
|
||||||
[
|
[
|
||||||
{"/api/fedsocket/v1", Pleroma.Web.FedSockets.IncomingHandler, []},
|
|
||||||
{"/api/v1/streaming", Pleroma.Web.MastodonAPI.WebsocketHandler, []},
|
{"/api/v1/streaming", Pleroma.Web.MastodonAPI.WebsocketHandler, []},
|
||||||
{"/websocket", Phoenix.Endpoint.CowboyWebSocket,
|
{"/websocket", Phoenix.Endpoint.CowboyWebSocket,
|
||||||
{Phoenix.Transports.WebSocket,
|
{Phoenix.Transports.WebSocket,
|
||||||
|
@ -148,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,
|
||||||
|
@ -264,7 +253,8 @@
|
||||||
length: 16
|
length: 16
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
show_reactions: true
|
show_reactions: true,
|
||||||
|
password_reset_token_validity: 60 * 60 * 24
|
||||||
|
|
||||||
config :pleroma, :welcome,
|
config :pleroma, :welcome,
|
||||||
direct_message: [
|
direct_message: [
|
||||||
|
|
|
@ -272,19 +272,6 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
%{
|
|
||||||
group: :pleroma,
|
|
||||||
key: :fed_sockets,
|
|
||||||
type: :group,
|
|
||||||
description: "Websocket based federation",
|
|
||||||
children: [
|
|
||||||
%{
|
|
||||||
key: :enabled,
|
|
||||||
type: :boolean,
|
|
||||||
description: "Enable FedSockets"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
%{
|
%{
|
||||||
group: :pleroma,
|
group: :pleroma,
|
||||||
key: Pleroma.Emails.Mailer,
|
key: Pleroma.Emails.Mailer,
|
||||||
|
|
|
@ -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
|
|
|
@ -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,
|
||||||
|
|
|
@ -554,7 +554,7 @@ Response:
|
||||||
* `show_role`
|
* `show_role`
|
||||||
* `skip_thread_containment`
|
* `skip_thread_containment`
|
||||||
* `fields`
|
* `fields`
|
||||||
* `discoverable`
|
* `is_discoverable`
|
||||||
* `actor_type`
|
* `actor_type`
|
||||||
|
|
||||||
* Responses:
|
* Responses:
|
||||||
|
@ -1499,3 +1499,66 @@ Returns the content of the document
|
||||||
"url": "https://example.com/instance/panel.html"
|
"url": "https://example.com/instance/panel.html"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## `GET /api/pleroma/admin/frontends
|
||||||
|
|
||||||
|
### List available frontends
|
||||||
|
|
||||||
|
- Response:
|
||||||
|
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"build_url": "https://git.pleroma.social/pleroma/fedi-fe/-/jobs/artifacts/${ref}/download?job=build",
|
||||||
|
"git": "https://git.pleroma.social/pleroma/fedi-fe",
|
||||||
|
"installed": true,
|
||||||
|
"name": "fedi-fe",
|
||||||
|
"ref": "master"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"build_url": "https://git.pleroma.social/lambadalambda/kenoma/-/jobs/artifacts/${ref}/download?job=build",
|
||||||
|
"git": "https://git.pleroma.social/lambadalambda/kenoma",
|
||||||
|
"installed": false,
|
||||||
|
"name": "kenoma",
|
||||||
|
"ref": "master"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
## `POST /api/pleroma/admin/frontends/install`
|
||||||
|
|
||||||
|
### Install a frontend
|
||||||
|
|
||||||
|
- Params:
|
||||||
|
- `name`: frontend name, required
|
||||||
|
- `ref`: frontend ref
|
||||||
|
- `file`: path to a frontend zip file
|
||||||
|
- `build_url`: build URL
|
||||||
|
- `build_dir`: build directory
|
||||||
|
|
||||||
|
- Response:
|
||||||
|
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"build_url": "https://git.pleroma.social/pleroma/fedi-fe/-/jobs/artifacts/${ref}/download?job=build",
|
||||||
|
"git": "https://git.pleroma.social/pleroma/fedi-fe",
|
||||||
|
"installed": true,
|
||||||
|
"name": "fedi-fe",
|
||||||
|
"ref": "master"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"build_url": "https://git.pleroma.social/lambadalambda/kenoma/-/jobs/artifacts/${ref}/download?job=build",
|
||||||
|
"git": "https://git.pleroma.social/lambadalambda/kenoma",
|
||||||
|
"installed": false,
|
||||||
|
"name": "kenoma",
|
||||||
|
"ref": "master"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"error": "Could not install frontend"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
|
@ -18,7 +18,7 @@ Adding the parameter `instance=lain.com` to the public timeline will show only s
|
||||||
|
|
||||||
## Statuses
|
## Statuses
|
||||||
|
|
||||||
- `visibility`: has an additional possible value `list`
|
- `visibility`: has additional possible values `list` and `local` (for local-only statuses)
|
||||||
|
|
||||||
Has these additional fields under the `pleroma` object:
|
Has these additional fields under the `pleroma` object:
|
||||||
|
|
||||||
|
@ -84,7 +84,7 @@ Has these additional fields under the `pleroma` object:
|
||||||
|
|
||||||
- `show_role`: boolean, nullable, true when the user wants his role (e.g admin, moderator) to be shown
|
- `show_role`: boolean, nullable, true when the user wants his role (e.g admin, moderator) to be shown
|
||||||
- `no_rich_text` - boolean, nullable, true when html tags are stripped from all statuses requested from the API
|
- `no_rich_text` - boolean, nullable, true when html tags are stripped from all statuses requested from the API
|
||||||
- `discoverable`: boolean, true when the user allows discovery of the account in search results and other services.
|
- `discoverable`: boolean, true when the user allows external services (search bots) etc. to index / list the account (regardless of this setting, user will still appear in regular search results)
|
||||||
- `actor_type`: string, the type of this account.
|
- `actor_type`: string, the type of this account.
|
||||||
|
|
||||||
## Conversations
|
## Conversations
|
||||||
|
@ -129,12 +129,30 @@ The `type` value is `pleroma:emoji_reaction`. Has these fields:
|
||||||
- `account`: The account of the user who reacted
|
- `account`: The account of the user who reacted
|
||||||
- `status`: The status that was reacted on
|
- `status`: The status that was reacted on
|
||||||
|
|
||||||
|
### ChatMention Notification (not default)
|
||||||
|
|
||||||
|
This notification has to be requested explicitly.
|
||||||
|
|
||||||
|
The `type` value is `pleroma:chat_mention`
|
||||||
|
|
||||||
|
- `account`: The account who sent the message
|
||||||
|
- `chat_message`: The chat message
|
||||||
|
|
||||||
|
### Report Notification (not default)
|
||||||
|
|
||||||
|
This notification has to be requested explicitly.
|
||||||
|
|
||||||
|
The `type` value is `pleroma:report`
|
||||||
|
|
||||||
|
- `account`: The account who reported
|
||||||
|
- `report`: The report
|
||||||
|
|
||||||
## GET `/api/v1/notifications`
|
## GET `/api/v1/notifications`
|
||||||
|
|
||||||
Accepts additional parameters:
|
Accepts additional parameters:
|
||||||
|
|
||||||
- `exclude_visibilities`: will exclude the notifications for activities with the given visibilities. The parameter accepts an array of visibility types (`public`, `unlisted`, `private`, `direct`). Usage example: `GET /api/v1/notifications?exclude_visibilities[]=direct&exclude_visibilities[]=private`.
|
- `exclude_visibilities`: will exclude the notifications for activities with the given visibilities. The parameter accepts an array of visibility types (`public`, `unlisted`, `private`, `direct`). Usage example: `GET /api/v1/notifications?exclude_visibilities[]=direct&exclude_visibilities[]=private`.
|
||||||
- `include_types`: will include the notifications for activities with the given types. The parameter accepts an array of types (`mention`, `follow`, `reblog`, `favourite`, `move`, `pleroma:emoji_reaction`). Usage example: `GET /api/v1/notifications?include_types[]=mention&include_types[]=reblog`.
|
- `include_types`: will include the notifications for activities with the given types. The parameter accepts an array of types (`mention`, `follow`, `reblog`, `favourite`, `move`, `pleroma:emoji_reaction`, `pleroma:chat_mention`, `pleroma:report`). Usage example: `GET /api/v1/notifications?include_types[]=mention&include_types[]=reblog`.
|
||||||
|
|
||||||
## DELETE `/api/v1/notifications/destroy_multiple`
|
## DELETE `/api/v1/notifications/destroy_multiple`
|
||||||
|
|
||||||
|
@ -155,7 +173,7 @@ 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 entitiy 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 for post visibility are not affected by this and will still apply.
|
||||||
- `visibility`: string, besides standard MastoAPI values (`direct`, `private`, `unlisted` 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`.
|
||||||
|
|
||||||
|
@ -189,7 +207,7 @@ Additional parameters can be added to the JSON body/Form data:
|
||||||
- `skip_thread_containment` - if true, skip filtering out broken threads
|
- `skip_thread_containment` - if true, skip filtering out broken threads
|
||||||
- `allow_following_move` - if true, allows automatically follow moved following accounts
|
- `allow_following_move` - if true, allows automatically follow moved following accounts
|
||||||
- `pleroma_background_image` - sets the background image of the user. Can be set to "" (an empty string) to reset.
|
- `pleroma_background_image` - sets the background image of the user. Can be set to "" (an empty string) to reset.
|
||||||
- `discoverable` - if true, discovery of this account in search results and other services is allowed.
|
- `discoverable` - if true, external services (search bots) etc. are allowed to index / list the account (regardless of this setting, user will still appear in regular search results).
|
||||||
- `actor_type` - the type of this account.
|
- `actor_type` - the type of this account.
|
||||||
- `accepts_chat_messages` - if false, this account will reject all chat messages.
|
- `accepts_chat_messages` - if false, this account will reject all chat messages.
|
||||||
|
|
||||||
|
@ -215,7 +233,7 @@ Post here request with `grant_type=refresh_token` to obtain new access token. Re
|
||||||
|
|
||||||
`POST /api/v1/accounts`
|
`POST /api/v1/accounts`
|
||||||
|
|
||||||
Has theses additional parameters (which are the same as in Pleroma-API):
|
Has these additional parameters (which are the same as in Pleroma-API):
|
||||||
|
|
||||||
- `fullname`: optional
|
- `fullname`: optional
|
||||||
- `bio`: optional
|
- `bio`: optional
|
||||||
|
@ -243,6 +261,16 @@ Has theses additional parameters (which are the same as in Pleroma-API):
|
||||||
- `pleroma.metadata.post_formats`: A list of the allowed post format types
|
- `pleroma.metadata.post_formats`: A list of the allowed post format types
|
||||||
- `vapid_public_key`: The public key needed for push messages
|
- `vapid_public_key`: The public key needed for push messages
|
||||||
|
|
||||||
|
## Push Subscription
|
||||||
|
|
||||||
|
`POST /api/v1/push/subscription`
|
||||||
|
`PUT /api/v1/push/subscription`
|
||||||
|
|
||||||
|
Permits these additional alert types:
|
||||||
|
|
||||||
|
- pleroma:chat_mention
|
||||||
|
- pleroma:emoji_reaction
|
||||||
|
|
||||||
## Markers
|
## Markers
|
||||||
|
|
||||||
Has these additional fields under the `pleroma` object:
|
Has these additional fields under the `pleroma` object:
|
||||||
|
|
|
@ -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`
|
||||||
|
|
|
@ -63,6 +63,7 @@ To add configuration to your config file, you can copy it from the base config.
|
||||||
* `external_user_synchronization`: Enabling following/followers counters synchronization for external users.
|
* `external_user_synchronization`: Enabling following/followers counters synchronization for external users.
|
||||||
* `cleanup_attachments`: Remove attachments along with statuses. Does not affect duplicate files and attachments without status. Enabling this will increase load to database when deleting statuses on larger instances.
|
* `cleanup_attachments`: Remove attachments along with statuses. Does not affect duplicate files and attachments without status. Enabling this will increase load to database when deleting statuses on larger instances.
|
||||||
* `show_reactions`: Let favourites and emoji reactions be viewed through the API (default: `true`).
|
* `show_reactions`: Let favourites and emoji reactions be viewed through the API (default: `true`).
|
||||||
|
* `password_reset_token_validity`: The time after which reset tokens aren't accepted anymore, in seconds (default: one day).
|
||||||
|
|
||||||
## Welcome
|
## Welcome
|
||||||
* `direct_message`: - welcome message sent as a direct message.
|
* `direct_message`: - welcome message sent as a direct message.
|
||||||
|
@ -220,18 +221,6 @@ config :pleroma, :mrf_user_allowlist, %{
|
||||||
* `total_user_limit`: the number of scheduled activities a user is allowed to create in total (Default: `300`)
|
* `total_user_limit`: the number of scheduled activities a user is allowed to create in total (Default: `300`)
|
||||||
* `enabled`: whether scheduled activities are sent to the job queue to be executed
|
* `enabled`: whether scheduled activities are sent to the job queue to be executed
|
||||||
|
|
||||||
## FedSockets
|
|
||||||
FedSockets is an experimental feature allowing for Pleroma backends to federate using a persistant websocket connection as opposed to making each federation a seperate http connection. This feature is currently off by default. It is configurable throught he following options.
|
|
||||||
|
|
||||||
### :fedsockets
|
|
||||||
* `enabled`: Enables FedSockets for this instance. `false` by default.
|
|
||||||
* `connection_duration`: Time an idle websocket is kept open.
|
|
||||||
* `rejection_duration`: Failures to connect via FedSockets will not be retried for this period of time.
|
|
||||||
* `fed_socket_fetches` and `fed_socket_rejections`: Settings passed to `cachex` for the fetch registry, and rejection stacks. See `Pleroma.Web.FedSockets` for more details.
|
|
||||||
|
|
||||||
|
|
||||||
## Frontends
|
|
||||||
|
|
||||||
### :frontend_configurations
|
### :frontend_configurations
|
||||||
|
|
||||||
This can be used to configure a keyword list that keeps the configuration data for any kind of frontend. By default, settings for `pleroma_fe` and `masto_fe` are configured. You can find the documentation for `pleroma_fe` configuration into [Pleroma-FE configuration and customization for instance administrators](/frontend/CONFIGURATION/#options).
|
This can be used to configure a keyword list that keeps the configuration data for any kind of frontend. By default, settings for `pleroma_fe` and `masto_fe` are configured. You can find the documentation for `pleroma_fe` configuration into [Pleroma-FE configuration and customization for instance administrators](/frontend/CONFIGURATION/#options).
|
||||||
|
|
|
@ -0,0 +1,66 @@
|
||||||
|
# Optimizing the BEAM
|
||||||
|
|
||||||
|
Pleroma is built upon the Erlang/OTP VM known as BEAM. The BEAM VM is highly optimized for latency, but this has drawbacks in environments without dedicated hardware. One of the tricks used by the BEAM VM is [busy waiting](https://en.wikipedia.org/wiki/Busy_waiting). This allows the application to pretend to be busy working so the OS kernel does not pause the application process and switch to another process waiting for the CPU to execute its workload. It does this by spinning for a period of time which inflates the apparent CPU usage of the application so it is immediately ready to execute another task. This can be observed with utilities like **top(1)** which will show consistently high CPU usage for the process. Switching between procesess is a rather expensive operation and also clears CPU caches further affecting latency and performance. The goal of busy waiting is to avoid this penalty.
|
||||||
|
|
||||||
|
This strategy is very successful in making a performant and responsive application, but is not desirable on Virtual Machines or hardware with few CPU cores. Pleroma instances are often deployed on the same server as the required PostgreSQL database which can lead to situations where the Pleroma application is holding the CPU in a busy-wait loop and as a result the database cannot process requests in a timely manner. The fewer CPUs available, the more this problem is exacerbated. The latency is further amplified by the OS being installed on a Virtual Machine as the Hypervisor uses CPU time-slicing to pause the entire OS and switch between other tasks.
|
||||||
|
|
||||||
|
More adventurous admins can be creative with CPU affinity (e.g., *taskset* for Linux and *cpuset* on FreeBSD) to pin processes to specific CPUs and eliminate much of this contention. The most important advice is to run as few processes as possible on your server to achieve the best performance. Even idle background processes can occasionally create [software interrupts](https://en.wikipedia.org/wiki/Interrupt) and take attention away from the executing process creating latency spikes and invalidation of the CPU caches as they must be cleared when switching between processes for security.
|
||||||
|
|
||||||
|
Please only change these settings if you are experiencing issues or really know what you are doing. In general, there's no need to change these settings.
|
||||||
|
|
||||||
|
## VPS Provider Recommendations
|
||||||
|
|
||||||
|
### Good
|
||||||
|
|
||||||
|
* Hetzner Cloud
|
||||||
|
|
||||||
|
### Bad
|
||||||
|
|
||||||
|
* AWS (known to use burst scheduling)
|
||||||
|
|
||||||
|
|
||||||
|
## Example configurations
|
||||||
|
|
||||||
|
Tuning the BEAM requires you provide a config file normally called [vm.args](http://erlang.org/doc/man/erl.html#emulator-flags). If you are using systemd to manage the service you can modify the unit file as such:
|
||||||
|
|
||||||
|
`ExecStart=/usr/bin/elixir --erl '-args_file /opt/pleroma/config/vm.args' -S /usr/bin/mix phx.server`
|
||||||
|
|
||||||
|
Check your OS documentation to adopt a similar strategy on other platforms.
|
||||||
|
|
||||||
|
### Virtual Machine and/or few CPU cores
|
||||||
|
|
||||||
|
Disable the busy-waiting. This should generally only be done if you're on a platform that does burst scheduling, like AWS.
|
||||||
|
|
||||||
|
**vm.args:**
|
||||||
|
|
||||||
|
```
|
||||||
|
+sbwt none
|
||||||
|
+sbwtdcpu none
|
||||||
|
+sbwtdio none
|
||||||
|
```
|
||||||
|
|
||||||
|
### Dedicated Hardware
|
||||||
|
|
||||||
|
Enable more busy waiting, increase the internal maximum limit of BEAM processes and ports. You can use this if you run on dedicated hardware, but it is not necessary.
|
||||||
|
|
||||||
|
**vm.args:**
|
||||||
|
|
||||||
|
```
|
||||||
|
+P 16777216
|
||||||
|
+Q 16777216
|
||||||
|
+K true
|
||||||
|
+A 128
|
||||||
|
+sbt db
|
||||||
|
+sbwt very_long
|
||||||
|
+swt very_low
|
||||||
|
+sub true
|
||||||
|
+Mulmbcs 32767
|
||||||
|
+Mumbcgs 1
|
||||||
|
+Musmbcs 2047
|
||||||
|
```
|
||||||
|
|
||||||
|
## Additional Reading
|
||||||
|
|
||||||
|
* [WhatsApp: Scaling to Millions of Simultaneous Connections](https://www.erlang-factory.com/upload/presentations/558/efsf2012-whatsapp-scaling.pdf)
|
||||||
|
* [Preemptive Scheduling and Spinlocks](https://www.uio.no/studier/emner/matnat/ifi/nedlagte-emner/INF3150/h03/annet/slides/preemptive.pdf)
|
||||||
|
* [The Curious Case of BEAM CPU Usage](https://stressgrid.com/blog/beam_cpu_usage/)
|
|
@ -35,7 +35,7 @@ sudo apt full-upgrade
|
||||||
* Install some of the above mentioned programs:
|
* Install some of the above mentioned programs:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
sudo apt install git build-essential postgresql postgresql-contrib cmake libmagic-devel
|
sudo apt install git build-essential postgresql postgresql-contrib cmake libmagic-dev
|
||||||
```
|
```
|
||||||
|
|
||||||
### Install Elixir and Erlang
|
### Install Elixir and Erlang
|
||||||
|
|
|
@ -93,9 +93,4 @@ server {
|
||||||
chunked_transfer_encoding on;
|
chunked_transfer_encoding on;
|
||||||
proxy_pass http://phoenix;
|
proxy_pass http://phoenix;
|
||||||
}
|
}
|
||||||
|
|
||||||
location /api/fedsocket/v1 {
|
|
||||||
proxy_request_buffering off;
|
|
||||||
proxy_pass http://phoenix/api/fedsocket/v1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,15 +14,16 @@ defmodule Mix.Pleroma do
|
||||||
:swoosh,
|
:swoosh,
|
||||||
:timex
|
:timex
|
||||||
]
|
]
|
||||||
@cachex_children ["object", "user", "scrubber"]
|
@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"
|
||||||
def start_pleroma do
|
def start_pleroma do
|
||||||
Pleroma.Config.Holder.save_default()
|
Pleroma.Config.Holder.save_default()
|
||||||
Pleroma.Config.Oban.warn()
|
Pleroma.Config.Oban.warn()
|
||||||
|
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)
|
||||||
|
@ -97,12 +98,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),
|
||||||
|
|
|
@ -17,8 +17,6 @@ def run(["install", "none" | _args]) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def run(["install", frontend | args]) do
|
def run(["install", frontend | args]) do
|
||||||
log_level = Logger.level()
|
|
||||||
Logger.configure(level: :warn)
|
|
||||||
start_pleroma()
|
start_pleroma()
|
||||||
|
|
||||||
{options, [], []} =
|
{options, [], []} =
|
||||||
|
@ -33,109 +31,6 @@ def run(["install", frontend | args]) do
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
instance_static_dir =
|
Pleroma.Frontend.install(frontend, options)
|
||||||
with nil <- options[:static_dir] do
|
|
||||||
Pleroma.Config.get!([:instance, :static_dir])
|
|
||||||
end
|
|
||||||
|
|
||||||
cmd_frontend_info = %{
|
|
||||||
"name" => frontend,
|
|
||||||
"ref" => options[:ref],
|
|
||||||
"build_url" => options[:build_url],
|
|
||||||
"build_dir" => options[:build_dir]
|
|
||||||
}
|
|
||||||
|
|
||||||
config_frontend_info = Pleroma.Config.get([:frontends, :available, frontend], %{})
|
|
||||||
|
|
||||||
frontend_info =
|
|
||||||
Map.merge(config_frontend_info, cmd_frontend_info, fn _key, config, cmd ->
|
|
||||||
# This only overrides things that are actually set
|
|
||||||
cmd || config
|
|
||||||
end)
|
|
||||||
|
|
||||||
ref = frontend_info["ref"]
|
|
||||||
|
|
||||||
unless ref do
|
|
||||||
raise "No ref given or configured"
|
|
||||||
end
|
|
||||||
|
|
||||||
dest =
|
|
||||||
Path.join([
|
|
||||||
instance_static_dir,
|
|
||||||
"frontends",
|
|
||||||
frontend,
|
|
||||||
ref
|
|
||||||
])
|
|
||||||
|
|
||||||
fe_label = "#{frontend} (#{ref})"
|
|
||||||
|
|
||||||
tmp_dir = Path.join([instance_static_dir, "frontends", "tmp"])
|
|
||||||
|
|
||||||
with {_, :ok} <-
|
|
||||||
{:download_or_unzip, download_or_unzip(frontend_info, tmp_dir, options[:file])},
|
|
||||||
shell_info("Installing #{fe_label} to #{dest}"),
|
|
||||||
:ok <- install_frontend(frontend_info, tmp_dir, dest) do
|
|
||||||
File.rm_rf!(tmp_dir)
|
|
||||||
shell_info("Frontend #{fe_label} installed to #{dest}")
|
|
||||||
|
|
||||||
Logger.configure(level: log_level)
|
|
||||||
else
|
|
||||||
{:download_or_unzip, _} ->
|
|
||||||
shell_info("Could not download or unzip the frontend")
|
|
||||||
|
|
||||||
_e ->
|
|
||||||
shell_info("Could not install the frontend")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp download_or_unzip(frontend_info, temp_dir, file) do
|
|
||||||
if file do
|
|
||||||
with {:ok, zip} <- File.read(Path.expand(file)) do
|
|
||||||
unzip(zip, temp_dir)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
download_build(frontend_info, temp_dir)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def unzip(zip, dest) do
|
|
||||||
with {:ok, unzipped} <- :zip.unzip(zip, [:memory]) do
|
|
||||||
File.rm_rf!(dest)
|
|
||||||
File.mkdir_p!(dest)
|
|
||||||
|
|
||||||
Enum.each(unzipped, fn {filename, data} ->
|
|
||||||
path = filename
|
|
||||||
|
|
||||||
new_file_path = Path.join(dest, path)
|
|
||||||
|
|
||||||
new_file_path
|
|
||||||
|> Path.dirname()
|
|
||||||
|> File.mkdir_p!()
|
|
||||||
|
|
||||||
File.write!(new_file_path, data)
|
|
||||||
end)
|
|
||||||
|
|
||||||
:ok
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp download_build(frontend_info, dest) do
|
|
||||||
shell_info("Downloading pre-built bundle for #{frontend_info["name"]}")
|
|
||||||
url = String.replace(frontend_info["build_url"], "${ref}", frontend_info["ref"])
|
|
||||||
|
|
||||||
with {:ok, %{status: 200, body: zip_body}} <-
|
|
||||||
Pleroma.HTTP.get(url, [], pool: :media, recv_timeout: 120_000) do
|
|
||||||
unzip(zip_body, dest)
|
|
||||||
else
|
|
||||||
e -> {:error, e}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp install_frontend(frontend_info, source, dest) do
|
|
||||||
from = frontend_info["build_dir"] || "dist"
|
|
||||||
File.rm_rf!(dest)
|
|
||||||
File.mkdir_p!(dest)
|
|
||||||
File.cp_r!(Path.join([source, from]), dest)
|
|
||||||
:ok
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -284,7 +284,7 @@ defp write_robots_txt(static_dir, indexable, template_dir) do
|
||||||
defp upload_filters(filters) when is_map(filters) do
|
defp upload_filters(filters) when is_map(filters) do
|
||||||
enabled_filters =
|
enabled_filters =
|
||||||
if filters.strip do
|
if filters.strip do
|
||||||
[Pleroma.Upload.Filter.ExifTool]
|
[Pleroma.Upload.Filter.Exiftool]
|
||||||
else
|
else
|
||||||
[]
|
[]
|
||||||
end
|
end
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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)
|
||||||
|
@ -356,4 +369,15 @@ def pinned_by_actor?(%Activity{} = activity) do
|
||||||
actor = user_actor(activity)
|
actor = user_actor(activity)
|
||||||
activity.id in actor.pinned_activities
|
activity.id in actor.pinned_activities
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec get_by_object_ap_id_with_object(String.t()) :: t() | nil
|
||||||
|
def get_by_object_ap_id_with_object(ap_id) when is_binary(ap_id) do
|
||||||
|
ap_id
|
||||||
|
|> Queries.by_object_id()
|
||||||
|
|> with_preloaded_object()
|
||||||
|
|> first()
|
||||||
|
|> Repo.one()
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_by_object_ap_id_with_object(_), do: nil
|
||||||
end
|
end
|
||||||
|
|
|
@ -19,15 +19,25 @@ 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)
|
||||||
|> Pagination.fetch_paginated(%{"offset" => offset, "limit" => limit}, :offset)
|
|> Pagination.fetch_paginated(
|
||||||
|
%{"offset" => offset, "limit" => limit, "skip_order" => index_type == :rum},
|
||||||
|
:offset
|
||||||
|
)
|
||||||
|> maybe_fetch(user, search_query)
|
|> maybe_fetch(user, search_query)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -50,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(
|
||||||
|
@ -61,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(
|
||||||
|
@ -73,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)
|
||||||
|
|
||||||
|
|
|
@ -57,6 +57,7 @@ def start(_type, _args) do
|
||||||
setup_instrumenters()
|
setup_instrumenters()
|
||||||
load_custom_modules()
|
load_custom_modules()
|
||||||
Pleroma.Docs.JSON.compile()
|
Pleroma.Docs.JSON.compile()
|
||||||
|
limiters_setup()
|
||||||
|
|
||||||
adapter = Application.get_env(:tesla, :adapter)
|
adapter = Application.get_env(:tesla, :adapter)
|
||||||
|
|
||||||
|
@ -109,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
|
||||||
|
@ -207,8 +229,7 @@ defp dont_run_in_test(_) do
|
||||||
name: Pleroma.Web.Streamer.registry(),
|
name: Pleroma.Web.Streamer.registry(),
|
||||||
keys: :duplicate,
|
keys: :duplicate,
|
||||||
partitions: System.schedulers_online()
|
partitions: System.schedulers_online()
|
||||||
]},
|
]}
|
||||||
Pleroma.Web.FedSockets.Supervisor
|
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -273,4 +294,10 @@ defp http_children(Tesla.Adapter.Gun, _) do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp http_children(_, _), do: []
|
defp http_children(_, _), do: []
|
||||||
|
|
||||||
|
@spec limiters_setup() :: :ok
|
||||||
|
def limiters_setup do
|
||||||
|
[Pleroma.Web.RichMedia.Helpers, Pleroma.Web.MediaProxy]
|
||||||
|
|> Enum.each(&ConcurrentLimiter.new(&1, 1, 0))
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
|
@ -26,4 +26,6 @@ defmodule Pleroma.Constants do
|
||||||
do:
|
do:
|
||||||
~w(index.html robots.txt static static-fe finmoji emoji packs sounds images instance sw.js sw-pleroma.js favicon.png schemas doc embed.js embed.css)
|
~w(index.html robots.txt static static-fe finmoji emoji packs sounds images instance sw.js sw-pleroma.js favicon.png schemas doc embed.js embed.css)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def as_local_public, do: Pleroma.Web.base_url() <> "/#Public"
|
||||||
end
|
end
|
||||||
|
|
|
@ -48,6 +48,9 @@ def report(to, reporter, account, statuses, comment) do
|
||||||
status_url = Helpers.o_status_url(Pleroma.Web.Endpoint, :notice, id)
|
status_url = Helpers.o_status_url(Pleroma.Web.Endpoint, :notice, id)
|
||||||
"<li><a href=\"#{status_url}\">#{status_url}</li>"
|
"<li><a href=\"#{status_url}\">#{status_url}</li>"
|
||||||
|
|
||||||
|
%{"id" => id} when is_binary(id) ->
|
||||||
|
"<li><a href=\"#{id}\">#{id}</li>"
|
||||||
|
|
||||||
id when is_binary(id) ->
|
id when is_binary(id) ->
|
||||||
"<li><a href=\"#{id}\">#{id}</li>"
|
"<li><a href=\"#{id}\">#{id}</li>"
|
||||||
end)
|
end)
|
||||||
|
|
|
@ -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 worker’s 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 o’clock..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 o’clock..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 o’clock..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
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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
|
||||||
|
|
|
@ -0,0 +1,110 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Frontend do
|
||||||
|
alias Pleroma.Config
|
||||||
|
|
||||||
|
require Logger
|
||||||
|
|
||||||
|
def install(name, opts \\ []) do
|
||||||
|
frontend_info = %{
|
||||||
|
"ref" => opts[:ref],
|
||||||
|
"build_url" => opts[:build_url],
|
||||||
|
"build_dir" => opts[:build_dir]
|
||||||
|
}
|
||||||
|
|
||||||
|
frontend_info =
|
||||||
|
[:frontends, :available, name]
|
||||||
|
|> Config.get(%{})
|
||||||
|
|> Map.merge(frontend_info, fn _key, config, cmd ->
|
||||||
|
# This only overrides things that are actually set
|
||||||
|
cmd || config
|
||||||
|
end)
|
||||||
|
|
||||||
|
ref = frontend_info["ref"]
|
||||||
|
|
||||||
|
unless ref do
|
||||||
|
raise "No ref given or configured"
|
||||||
|
end
|
||||||
|
|
||||||
|
dest = Path.join([dir(), name, ref])
|
||||||
|
|
||||||
|
label = "#{name} (#{ref})"
|
||||||
|
tmp_dir = Path.join(dir(), "tmp")
|
||||||
|
|
||||||
|
with {_, :ok} <-
|
||||||
|
{:download_or_unzip, download_or_unzip(frontend_info, tmp_dir, opts[:file])},
|
||||||
|
Logger.info("Installing #{label} to #{dest}"),
|
||||||
|
:ok <- install_frontend(frontend_info, tmp_dir, dest) do
|
||||||
|
File.rm_rf!(tmp_dir)
|
||||||
|
Logger.info("Frontend #{label} installed to #{dest}")
|
||||||
|
else
|
||||||
|
{:download_or_unzip, _} ->
|
||||||
|
Logger.info("Could not download or unzip the frontend")
|
||||||
|
{:error, "Could not download or unzip the frontend"}
|
||||||
|
|
||||||
|
_e ->
|
||||||
|
Logger.info("Could not install the frontend")
|
||||||
|
{:error, "Could not install the frontend"}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def dir(opts \\ []) do
|
||||||
|
if is_nil(opts[:static_dir]) do
|
||||||
|
Pleroma.Config.get!([:instance, :static_dir])
|
||||||
|
else
|
||||||
|
opts[:static_dir]
|
||||||
|
end
|
||||||
|
|> Path.join("frontends")
|
||||||
|
end
|
||||||
|
|
||||||
|
defp download_or_unzip(frontend_info, temp_dir, nil),
|
||||||
|
do: download_build(frontend_info, temp_dir)
|
||||||
|
|
||||||
|
defp download_or_unzip(_frontend_info, temp_dir, file) do
|
||||||
|
with {:ok, zip} <- File.read(Path.expand(file)) do
|
||||||
|
unzip(zip, temp_dir)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def unzip(zip, dest) do
|
||||||
|
with {:ok, unzipped} <- :zip.unzip(zip, [:memory]) do
|
||||||
|
File.rm_rf!(dest)
|
||||||
|
File.mkdir_p!(dest)
|
||||||
|
|
||||||
|
Enum.each(unzipped, fn {filename, data} ->
|
||||||
|
path = filename
|
||||||
|
|
||||||
|
new_file_path = Path.join(dest, path)
|
||||||
|
|
||||||
|
new_file_path
|
||||||
|
|> Path.dirname()
|
||||||
|
|> File.mkdir_p!()
|
||||||
|
|
||||||
|
File.write!(new_file_path, data)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp download_build(frontend_info, dest) do
|
||||||
|
Logger.info("Downloading pre-built bundle for #{frontend_info["name"]}")
|
||||||
|
url = String.replace(frontend_info["build_url"], "${ref}", frontend_info["ref"])
|
||||||
|
|
||||||
|
with {:ok, %{status: 200, body: zip_body}} <-
|
||||||
|
Pleroma.HTTP.get(url, [], pool: :media, recv_timeout: 120_000) do
|
||||||
|
unzip(zip_body, dest)
|
||||||
|
else
|
||||||
|
{:error, e} -> {:error, e}
|
||||||
|
e -> {:error, e}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp install_frontend(frontend_info, source, dest) do
|
||||||
|
from = frontend_info["build_dir"] || "dist"
|
||||||
|
File.rm_rf!(dest)
|
||||||
|
File.mkdir_p!(dest)
|
||||||
|
File.cp_r!(Path.join([source, from]), dest)
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
end
|
|
@ -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
|
||||||
|
|
|
@ -70,6 +70,7 @@ def unread_notifications_count(%User{id: user_id}) do
|
||||||
move
|
move
|
||||||
pleroma:chat_mention
|
pleroma:chat_mention
|
||||||
pleroma:emoji_reaction
|
pleroma:emoji_reaction
|
||||||
|
pleroma:report
|
||||||
reblog
|
reblog
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -367,7 +368,7 @@ def create_notifications(%Activity{data: %{"to" => _, "type" => "Create"}} = act
|
||||||
end
|
end
|
||||||
|
|
||||||
def create_notifications(%Activity{data: %{"type" => type}} = activity, options)
|
def create_notifications(%Activity{data: %{"type" => type}} = activity, options)
|
||||||
when type in ["Follow", "Like", "Announce", "Move", "EmojiReact"] do
|
when type in ["Follow", "Like", "Announce", "Move", "EmojiReact", "Flag"] do
|
||||||
do_create_notifications(activity, options)
|
do_create_notifications(activity, options)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -410,6 +411,9 @@ defp type_from_activity(%{data: %{"type" => type}} = activity) do
|
||||||
"EmojiReact" ->
|
"EmojiReact" ->
|
||||||
"pleroma:emoji_reaction"
|
"pleroma:emoji_reaction"
|
||||||
|
|
||||||
|
"Flag" ->
|
||||||
|
"pleroma:report"
|
||||||
|
|
||||||
# Compatibility with old reactions
|
# Compatibility with old reactions
|
||||||
"EmojiReaction" ->
|
"EmojiReaction" ->
|
||||||
"pleroma:emoji_reaction"
|
"pleroma:emoji_reaction"
|
||||||
|
@ -467,7 +471,7 @@ def create_notification(%Activity{} = activity, %User{} = user, do_send \\ true)
|
||||||
def get_notified_from_activity(activity, local_only \\ true)
|
def get_notified_from_activity(activity, local_only \\ true)
|
||||||
|
|
||||||
def get_notified_from_activity(%Activity{data: %{"type" => type}} = activity, local_only)
|
def get_notified_from_activity(%Activity{data: %{"type" => type}} = activity, local_only)
|
||||||
when type in ["Create", "Like", "Announce", "Follow", "Move", "EmojiReact"] do
|
when type in ["Create", "Like", "Announce", "Follow", "Move", "EmojiReact", "Flag"] do
|
||||||
potential_receiver_ap_ids = get_potential_receiver_ap_ids(activity)
|
potential_receiver_ap_ids = get_potential_receiver_ap_ids(activity)
|
||||||
|
|
||||||
potential_receivers =
|
potential_receivers =
|
||||||
|
@ -503,6 +507,10 @@ def get_potential_receiver_ap_ids(%{data: %{"type" => "Follow", "object" => obje
|
||||||
[object_id]
|
[object_id]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def get_potential_receiver_ap_ids(%{data: %{"type" => "Flag"}}) do
|
||||||
|
User.all_superusers() |> Enum.map(fn user -> user.ap_id end)
|
||||||
|
end
|
||||||
|
|
||||||
def get_potential_receiver_ap_ids(activity) do
|
def get_potential_receiver_ap_ids(activity) do
|
||||||
[]
|
[]
|
||||||
|> Utils.maybe_notify_to_recipients(activity)
|
|> Utils.maybe_notify_to_recipients(activity)
|
||||||
|
|
|
@ -12,7 +12,6 @@ defmodule Pleroma.Object.Fetcher do
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidator
|
alias Pleroma.Web.ActivityPub.ObjectValidator
|
||||||
alias Pleroma.Web.ActivityPub.Transmogrifier
|
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||||
alias Pleroma.Web.Federator
|
alias Pleroma.Web.Federator
|
||||||
alias Pleroma.Web.FedSockets
|
|
||||||
|
|
||||||
require Logger
|
require Logger
|
||||||
require Pleroma.Constants
|
require Pleroma.Constants
|
||||||
|
@ -183,16 +182,16 @@ defp maybe_date_fetch(headers, date) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def fetch_and_contain_remote_object_from_id(prm, opts \\ [])
|
def fetch_and_contain_remote_object_from_id(id)
|
||||||
|
|
||||||
def fetch_and_contain_remote_object_from_id(%{"id" => id}, opts),
|
def fetch_and_contain_remote_object_from_id(%{"id" => id}),
|
||||||
do: fetch_and_contain_remote_object_from_id(id, opts)
|
do: fetch_and_contain_remote_object_from_id(id)
|
||||||
|
|
||||||
def fetch_and_contain_remote_object_from_id(id, opts) when is_binary(id) do
|
def fetch_and_contain_remote_object_from_id(id) when is_binary(id) do
|
||||||
Logger.debug("Fetching object #{id} via AP")
|
Logger.debug("Fetching object #{id} via AP")
|
||||||
|
|
||||||
with {:scheme, true} <- {:scheme, String.starts_with?(id, "http")},
|
with {:scheme, true} <- {:scheme, String.starts_with?(id, "http")},
|
||||||
{:ok, body} <- get_object(id, opts),
|
{:ok, body} <- get_object(id),
|
||||||
{:ok, data} <- safe_json_decode(body),
|
{:ok, data} <- safe_json_decode(body),
|
||||||
:ok <- Containment.contain_origin_from_id(id, data) do
|
:ok <- Containment.contain_origin_from_id(id, data) do
|
||||||
{:ok, data}
|
{:ok, data}
|
||||||
|
@ -208,22 +207,10 @@ def fetch_and_contain_remote_object_from_id(id, opts) when is_binary(id) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def fetch_and_contain_remote_object_from_id(_id, _opts),
|
def fetch_and_contain_remote_object_from_id(_id),
|
||||||
do: {:error, "id must be a string"}
|
do: {:error, "id must be a string"}
|
||||||
|
|
||||||
defp get_object(id, opts) do
|
defp get_object(id) do
|
||||||
with false <- Keyword.get(opts, :force_http, false),
|
|
||||||
{:ok, fedsocket} <- FedSockets.get_or_create_fed_socket(id) do
|
|
||||||
Logger.debug("fetching via fedsocket - #{inspect(id)}")
|
|
||||||
FedSockets.fetch(fedsocket, id)
|
|
||||||
else
|
|
||||||
_other ->
|
|
||||||
Logger.debug("fetching via http - #{inspect(id)}")
|
|
||||||
get_object_http(id)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp get_object_http(id) do
|
|
||||||
date = Pleroma.Signature.signed_date()
|
date = Pleroma.Signature.signed_date()
|
||||||
|
|
||||||
headers =
|
headers =
|
||||||
|
|
|
@ -40,6 +40,7 @@ def used_changeset(struct) do
|
||||||
@spec reset_password(binary(), map()) :: {:ok, User.t()} | {:error, binary()}
|
@spec reset_password(binary(), map()) :: {:ok, User.t()} | {:error, binary()}
|
||||||
def reset_password(token, data) do
|
def reset_password(token, data) do
|
||||||
with %{used: false} = token <- Repo.get_by(PasswordResetToken, %{token: token}),
|
with %{used: false} = token <- Repo.get_by(PasswordResetToken, %{token: token}),
|
||||||
|
false <- expired?(token),
|
||||||
%User{} = user <- User.get_cached_by_id(token.user_id),
|
%User{} = user <- User.get_cached_by_id(token.user_id),
|
||||||
{:ok, _user} <- User.reset_password(user, data),
|
{:ok, _user} <- User.reset_password(user, data),
|
||||||
{:ok, token} <- Repo.update(used_changeset(token)) do
|
{:ok, token} <- Repo.update(used_changeset(token)) do
|
||||||
|
@ -48,4 +49,14 @@ def reset_password(token, data) do
|
||||||
_e -> {:error, token}
|
_e -> {:error, token}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def expired?(%__MODULE__{inserted_at: inserted_at}) do
|
||||||
|
validity = Pleroma.Config.get([:instance, :password_reset_token_validity], 0)
|
||||||
|
|
||||||
|
now = NaiveDateTime.utc_now()
|
||||||
|
|
||||||
|
difference = NaiveDateTime.diff(now, inserted_at)
|
||||||
|
|
||||||
|
difference > validity
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -39,7 +39,7 @@ def key_id_to_actor_id(key_id) do
|
||||||
def fetch_public_key(conn) do
|
def fetch_public_key(conn) do
|
||||||
with %{"keyId" => kid} <- HTTPSignatures.signature_for_conn(conn),
|
with %{"keyId" => kid} <- HTTPSignatures.signature_for_conn(conn),
|
||||||
{:ok, actor_id} <- key_id_to_actor_id(kid),
|
{:ok, actor_id} <- key_id_to_actor_id(kid),
|
||||||
{:ok, public_key} <- User.get_public_key_for_ap_id(actor_id, force_http: true) do
|
{:ok, public_key} <- User.get_public_key_for_ap_id(actor_id) do
|
||||||
{:ok, public_key}
|
{:ok, public_key}
|
||||||
else
|
else
|
||||||
e ->
|
e ->
|
||||||
|
@ -50,8 +50,8 @@ def fetch_public_key(conn) do
|
||||||
def refetch_public_key(conn) do
|
def refetch_public_key(conn) do
|
||||||
with %{"keyId" => kid} <- HTTPSignatures.signature_for_conn(conn),
|
with %{"keyId" => kid} <- HTTPSignatures.signature_for_conn(conn),
|
||||||
{:ok, actor_id} <- key_id_to_actor_id(kid),
|
{:ok, actor_id} <- key_id_to_actor_id(kid),
|
||||||
{:ok, _user} <- ActivityPub.make_user_from_ap_id(actor_id, force_http: true),
|
{:ok, _user} <- ActivityPub.make_user_from_ap_id(actor_id),
|
||||||
{:ok, public_key} <- User.get_public_key_for_ap_id(actor_id, force_http: true) do
|
{:ok, public_key} <- User.get_public_key_for_ap_id(actor_id) do
|
||||||
{:ok, public_key}
|
{:ok, public_key}
|
||||||
else
|
else
|
||||||
e ->
|
e ->
|
||||||
|
|
|
@ -245,6 +245,18 @@ def unquote(:"#{outgoing_relation_target}_ap_ids")(user, restrict_deactivated? \
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def cached_blocked_users_ap_ids(user) do
|
||||||
|
Cachex.fetch!(:user_cache, "blocked_users_ap_ids:#{user.ap_id}", fn _ ->
|
||||||
|
blocked_users_ap_ids(user)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
def cached_muted_users_ap_ids(user) do
|
||||||
|
Cachex.fetch!(:user_cache, "muted_users_ap_ids:#{user.ap_id}", fn _ ->
|
||||||
|
muted_users_ap_ids(user)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
defdelegate following_count(user), to: FollowingRelationship
|
defdelegate following_count(user), to: FollowingRelationship
|
||||||
defdelegate following(user), to: FollowingRelationship
|
defdelegate following(user), to: FollowingRelationship
|
||||||
defdelegate following?(follower, followed), to: FollowingRelationship
|
defdelegate following?(follower, followed), to: FollowingRelationship
|
||||||
|
@ -461,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
|
||||||
|
@ -1036,6 +1060,8 @@ def invalidate_cache(user) do
|
||||||
Cachex.del(:user_cache, "ap_id:#{user.ap_id}")
|
Cachex.del(:user_cache, "ap_id:#{user.ap_id}")
|
||||||
Cachex.del(:user_cache, "nickname:#{user.nickname}")
|
Cachex.del(:user_cache, "nickname:#{user.nickname}")
|
||||||
Cachex.del(:user_cache, "friends_ap_ids:#{user.ap_id}")
|
Cachex.del(:user_cache, "friends_ap_ids:#{user.ap_id}")
|
||||||
|
Cachex.del(:user_cache, "blocked_users_ap_ids:#{user.ap_id}")
|
||||||
|
Cachex.del(:user_cache, "muted_users_ap_ids:#{user.ap_id}")
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec get_cached_by_ap_id(String.t()) :: User.t() | nil
|
@spec get_cached_by_ap_id(String.t()) :: User.t() | nil
|
||||||
|
@ -1342,6 +1368,8 @@ def mute(%User{} = muter, %User{} = mutee, params \\ %{}) do
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
Cachex.del(:user_cache, "muted_users_ap_ids:#{muter.ap_id}")
|
||||||
|
|
||||||
{:ok, Enum.filter([user_mute, user_notification_mute], & &1)}
|
{:ok, Enum.filter([user_mute, user_notification_mute], & &1)}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1350,6 +1378,7 @@ def unmute(%User{} = muter, %User{} = mutee) do
|
||||||
with {:ok, user_mute} <- UserRelationship.delete_mute(muter, mutee),
|
with {:ok, user_mute} <- UserRelationship.delete_mute(muter, mutee),
|
||||||
{:ok, user_notification_mute} <-
|
{:ok, user_notification_mute} <-
|
||||||
UserRelationship.delete_notification_mute(muter, mutee) do
|
UserRelationship.delete_notification_mute(muter, mutee) do
|
||||||
|
Cachex.del(:user_cache, "muted_users_ap_ids:#{muter.ap_id}")
|
||||||
{:ok, [user_mute, user_notification_mute]}
|
{:ok, [user_mute, user_notification_mute]}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1772,12 +1801,12 @@ def html_filter_policy(%User{no_rich_text: true}) do
|
||||||
|
|
||||||
def html_filter_policy(_), do: Config.get([:markup, :scrub_policy])
|
def html_filter_policy(_), do: Config.get([:markup, :scrub_policy])
|
||||||
|
|
||||||
def fetch_by_ap_id(ap_id, opts \\ []), do: ActivityPub.make_user_from_ap_id(ap_id, opts)
|
def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id)
|
||||||
|
|
||||||
def get_or_fetch_by_ap_id(ap_id, opts \\ []) do
|
def get_or_fetch_by_ap_id(ap_id) do
|
||||||
cached_user = get_cached_by_ap_id(ap_id)
|
cached_user = get_cached_by_ap_id(ap_id)
|
||||||
|
|
||||||
maybe_fetched_user = needs_update?(cached_user) && fetch_by_ap_id(ap_id, opts)
|
maybe_fetched_user = needs_update?(cached_user) && fetch_by_ap_id(ap_id)
|
||||||
|
|
||||||
case {cached_user, maybe_fetched_user} do
|
case {cached_user, maybe_fetched_user} do
|
||||||
{_, {:ok, %User{} = user}} ->
|
{_, {:ok, %User{} = user}} ->
|
||||||
|
@ -1850,8 +1879,8 @@ def public_key(%{public_key: public_key_pem}) when is_binary(public_key_pem) do
|
||||||
|
|
||||||
def public_key(_), do: {:error, "key not found"}
|
def public_key(_), do: {:error, "key not found"}
|
||||||
|
|
||||||
def get_public_key_for_ap_id(ap_id, opts \\ []) do
|
def get_public_key_for_ap_id(ap_id) do
|
||||||
with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id, opts),
|
with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
|
||||||
{:ok, public_key} <- public_key(user) do
|
{:ok, public_key} <- public_key(user) do
|
||||||
{:ok, public_key}
|
{:ok, public_key}
|
||||||
else
|
else
|
||||||
|
@ -2345,13 +2374,19 @@ def unblock_domain(user, domain_blocked) do
|
||||||
@spec add_to_block(User.t(), User.t()) ::
|
@spec add_to_block(User.t(), User.t()) ::
|
||||||
{:ok, UserRelationship.t()} | {:error, Ecto.Changeset.t()}
|
{:ok, UserRelationship.t()} | {:error, Ecto.Changeset.t()}
|
||||||
defp add_to_block(%User{} = user, %User{} = blocked) do
|
defp add_to_block(%User{} = user, %User{} = blocked) do
|
||||||
UserRelationship.create_block(user, blocked)
|
with {:ok, relationship} <- UserRelationship.create_block(user, blocked) do
|
||||||
|
Cachex.del(:user_cache, "blocked_users_ap_ids:#{user.ap_id}")
|
||||||
|
{:ok, relationship}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec add_to_block(User.t(), User.t()) ::
|
@spec add_to_block(User.t(), User.t()) ::
|
||||||
{:ok, UserRelationship.t()} | {:ok, nil} | {:error, Ecto.Changeset.t()}
|
{:ok, UserRelationship.t()} | {:ok, nil} | {:error, Ecto.Changeset.t()}
|
||||||
defp remove_from_block(%User{} = user, %User{} = blocked) do
|
defp remove_from_block(%User{} = user, %User{} = blocked) do
|
||||||
UserRelationship.delete_block(user, blocked)
|
with {:ok, relationship} <- UserRelationship.delete_block(user, blocked) do
|
||||||
|
Cachex.del(:user_cache, "blocked_users_ap_ids:#{user.ap_id}")
|
||||||
|
{:ok, relationship}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_invisible(user, invisible) do
|
def set_invisible(user, invisible) do
|
||||||
|
|
|
@ -85,7 +85,6 @@ defp search_query(query_string, for_user, following, top_user_ids) do
|
||||||
|> base_query(following)
|
|> base_query(following)
|
||||||
|> filter_blocked_user(for_user)
|
|> filter_blocked_user(for_user)
|
||||||
|> filter_invisible_users()
|
|> filter_invisible_users()
|
||||||
|> filter_discoverable_users()
|
|
||||||
|> filter_internal_users()
|
|> filter_internal_users()
|
||||||
|> filter_blocked_domains(for_user)
|
|> filter_blocked_domains(for_user)
|
||||||
|> fts_search(query_string)
|
|> fts_search(query_string)
|
||||||
|
@ -163,10 +162,6 @@ defp filter_invisible_users(query) do
|
||||||
from(q in query, where: q.invisible == false)
|
from(q in query, where: q.invisible == false)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp filter_discoverable_users(query) do
|
|
||||||
from(q in query, where: q.is_discoverable == true)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp filter_internal_users(query) do
|
defp filter_internal_users(query) do
|
||||||
from(q in query, where: q.actor_type != "Application")
|
from(q in query, where: q.actor_type != "Application")
|
||||||
end
|
end
|
||||||
|
|
|
@ -123,7 +123,9 @@ def insert(map, local \\ true, fake \\ false, bypass_actor_check \\ false) when
|
||||||
# Splice in the child object if we have one.
|
# Splice in the child object if we have one.
|
||||||
activity = Maps.put_if_present(activity, :object, object)
|
activity = Maps.put_if_present(activity, :object, object)
|
||||||
|
|
||||||
BackgroundWorker.enqueue("fetch_data_for_activity", %{"activity_id" => activity.id})
|
ConcurrentLimiter.limit(Pleroma.Web.RichMedia.Helpers, fn ->
|
||||||
|
Task.start(fn -> Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity) end)
|
||||||
|
end)
|
||||||
|
|
||||||
{:ok, activity}
|
{:ok, activity}
|
||||||
else
|
else
|
||||||
|
@ -332,15 +334,21 @@ defp do_unfollow(follower, followed, activity_id, local) do
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec flag(map()) :: {:ok, Activity.t()} | {:error, any()}
|
@spec flag(map()) :: {:ok, Activity.t()} | {:error, any()}
|
||||||
def flag(
|
def flag(params) do
|
||||||
%{
|
with {:ok, result} <- Repo.transaction(fn -> do_flag(params) end) do
|
||||||
actor: actor,
|
result
|
||||||
context: _context,
|
end
|
||||||
account: account,
|
end
|
||||||
statuses: statuses,
|
|
||||||
content: content
|
defp do_flag(
|
||||||
} = params
|
%{
|
||||||
) do
|
actor: actor,
|
||||||
|
context: _context,
|
||||||
|
account: account,
|
||||||
|
statuses: statuses,
|
||||||
|
content: content
|
||||||
|
} = params
|
||||||
|
) do
|
||||||
# only accept false as false value
|
# only accept false as false value
|
||||||
local = !(params[:local] == false)
|
local = !(params[:local] == false)
|
||||||
forward = !(params[:forward] == false)
|
forward = !(params[:forward] == false)
|
||||||
|
@ -358,7 +366,8 @@ def flag(
|
||||||
{:ok, activity} <- insert(flag_data, local),
|
{:ok, activity} <- insert(flag_data, local),
|
||||||
{:ok, stripped_activity} <- strip_report_status_data(activity),
|
{:ok, stripped_activity} <- strip_report_status_data(activity),
|
||||||
_ <- notify_and_stream(activity),
|
_ <- notify_and_stream(activity),
|
||||||
:ok <- maybe_federate(stripped_activity) do
|
:ok <-
|
||||||
|
maybe_federate(stripped_activity) do
|
||||||
User.all_superusers()
|
User.all_superusers()
|
||||||
|> Enum.filter(fn user -> not is_nil(user.email) end)
|
|> Enum.filter(fn user -> not is_nil(user.email) end)
|
||||||
|> Enum.each(fn superuser ->
|
|> Enum.each(fn superuser ->
|
||||||
|
@ -368,6 +377,8 @@ def flag(
|
||||||
end)
|
end)
|
||||||
|
|
||||||
{:ok, activity}
|
{:ok, activity}
|
||||||
|
else
|
||||||
|
{:error, error} -> Repo.rollback(error)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -791,10 +802,10 @@ defp restrict_replies(query, %{
|
||||||
where:
|
where:
|
||||||
fragment(
|
fragment(
|
||||||
"""
|
"""
|
||||||
?->>'type' != 'Create' -- This isn't a Create
|
?->>'type' != 'Create' -- This isn't a Create
|
||||||
OR ?->>'inReplyTo' is null -- this isn't a reply
|
OR ?->>'inReplyTo' is null -- this isn't a reply
|
||||||
OR ? && array_remove(?, ?) -- The recipient is us or one of our friends,
|
OR ? && array_remove(?, ?) -- The recipient is us or one of our friends,
|
||||||
-- unless they are the author (because authors
|
-- unless they are the author (because authors
|
||||||
-- are also part of the recipients). This leads
|
-- are also part of the recipients). This leads
|
||||||
-- to a bug that self-replies by friends won't
|
-- to a bug that self-replies by friends won't
|
||||||
-- show up.
|
-- show up.
|
||||||
|
@ -1289,12 +1300,10 @@ defp object_to_user_data(data) do
|
||||||
|
|
||||||
def fetch_follow_information_for_user(user) do
|
def fetch_follow_information_for_user(user) do
|
||||||
with {:ok, following_data} <-
|
with {:ok, following_data} <-
|
||||||
Fetcher.fetch_and_contain_remote_object_from_id(user.following_address,
|
Fetcher.fetch_and_contain_remote_object_from_id(user.following_address),
|
||||||
force_http: true
|
|
||||||
),
|
|
||||||
{:ok, hide_follows} <- collection_private(following_data),
|
{:ok, hide_follows} <- collection_private(following_data),
|
||||||
{:ok, followers_data} <-
|
{:ok, followers_data} <-
|
||||||
Fetcher.fetch_and_contain_remote_object_from_id(user.follower_address, force_http: true),
|
Fetcher.fetch_and_contain_remote_object_from_id(user.follower_address),
|
||||||
{:ok, hide_followers} <- collection_private(followers_data) do
|
{:ok, hide_followers} <- collection_private(followers_data) do
|
||||||
{:ok,
|
{:ok,
|
||||||
%{
|
%{
|
||||||
|
@ -1368,8 +1377,8 @@ def user_data_from_user_object(data) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def fetch_and_prepare_user_from_ap_id(ap_id, opts \\ []) do
|
def fetch_and_prepare_user_from_ap_id(ap_id) do
|
||||||
with {:ok, data} <- Fetcher.fetch_and_contain_remote_object_from_id(ap_id, opts),
|
with {:ok, data} <- Fetcher.fetch_and_contain_remote_object_from_id(ap_id),
|
||||||
{:ok, data} <- user_data_from_user_object(data) do
|
{:ok, data} <- user_data_from_user_object(data) do
|
||||||
{:ok, maybe_update_follow_information(data)}
|
{:ok, maybe_update_follow_information(data)}
|
||||||
else
|
else
|
||||||
|
@ -1412,13 +1421,13 @@ def maybe_handle_clashing_nickname(data) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def make_user_from_ap_id(ap_id, opts \\ []) do
|
def make_user_from_ap_id(ap_id) do
|
||||||
user = User.get_cached_by_ap_id(ap_id)
|
user = User.get_cached_by_ap_id(ap_id)
|
||||||
|
|
||||||
if user && !User.ap_enabled?(user) do
|
if user && !User.ap_enabled?(user) do
|
||||||
Transmogrifier.upgrade_user_from_ap_id(ap_id)
|
Transmogrifier.upgrade_user_from_ap_id(ap_id)
|
||||||
else
|
else
|
||||||
with {:ok, data} <- fetch_and_prepare_user_from_ap_id(ap_id, opts) do
|
with {:ok, data} <- fetch_and_prepare_user_from_ap_id(ap_id) do
|
||||||
if user do
|
if user do
|
||||||
user
|
user
|
||||||
|> User.remote_user_changeset(data)
|
|> User.remote_user_changeset(data)
|
||||||
|
|
|
@ -82,7 +82,8 @@ def user(conn, %{"nickname" => nickname}) do
|
||||||
def object(conn, _) do
|
def object(conn, _) do
|
||||||
with ap_id <- Endpoint.url() <> conn.request_path,
|
with ap_id <- Endpoint.url() <> conn.request_path,
|
||||||
%Object{} = object <- Object.get_cached_by_ap_id(ap_id),
|
%Object{} = object <- Object.get_cached_by_ap_id(ap_id),
|
||||||
{_, true} <- {:public?, Visibility.is_public?(object)} do
|
{_, true} <- {:public?, Visibility.is_public?(object)},
|
||||||
|
{_, false} <- {:local?, Visibility.is_local_public?(object)} do
|
||||||
conn
|
conn
|
||||||
|> assign(:tracking_fun_data, object.id)
|
|> assign(:tracking_fun_data, object.id)
|
||||||
|> set_cache_ttl_for(object)
|
|> set_cache_ttl_for(object)
|
||||||
|
@ -92,6 +93,9 @@ def object(conn, _) do
|
||||||
else
|
else
|
||||||
{:public?, false} ->
|
{:public?, false} ->
|
||||||
{:error, :not_found}
|
{:error, :not_found}
|
||||||
|
|
||||||
|
{:local?, true} ->
|
||||||
|
{:error, :not_found}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -108,7 +112,8 @@ def track_object_fetch(conn, object_id) do
|
||||||
def activity(conn, _params) do
|
def activity(conn, _params) do
|
||||||
with ap_id <- Endpoint.url() <> conn.request_path,
|
with ap_id <- Endpoint.url() <> conn.request_path,
|
||||||
%Activity{} = activity <- Activity.normalize(ap_id),
|
%Activity{} = activity <- Activity.normalize(ap_id),
|
||||||
{_, true} <- {:public?, Visibility.is_public?(activity)} do
|
{_, true} <- {:public?, Visibility.is_public?(activity)},
|
||||||
|
{_, false} <- {:local?, Visibility.is_local_public?(activity)} do
|
||||||
conn
|
conn
|
||||||
|> maybe_set_tracking_data(activity)
|
|> maybe_set_tracking_data(activity)
|
||||||
|> set_cache_ttl_for(activity)
|
|> set_cache_ttl_for(activity)
|
||||||
|
@ -117,6 +122,7 @@ def activity(conn, _params) do
|
||||||
|> render("object.json", object: activity)
|
|> render("object.json", object: activity)
|
||||||
else
|
else
|
||||||
{:public?, false} -> {:error, :not_found}
|
{:public?, false} -> {:error, :not_found}
|
||||||
|
{:local?, true} -> {:error, :not_found}
|
||||||
nil -> {:error, :not_found}
|
nil -> {:error, :not_found}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -222,6 +222,9 @@ def announce(actor, object, options \\ []) do
|
||||||
actor.ap_id == Relay.ap_id() ->
|
actor.ap_id == Relay.ap_id() ->
|
||||||
[actor.follower_address]
|
[actor.follower_address]
|
||||||
|
|
||||||
|
public? and Visibility.is_local_public?(object) ->
|
||||||
|
[actor.follower_address, object.data["actor"], Pleroma.Constants.as_local_public()]
|
||||||
|
|
||||||
public? ->
|
public? ->
|
||||||
[actor.follower_address, object.data["actor"], Pleroma.Constants.as_public()]
|
[actor.follower_address, object.data["actor"], Pleroma.Constants.as_public()]
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,6 @@ defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy do
|
||||||
|
|
||||||
alias Pleroma.HTTP
|
alias Pleroma.HTTP
|
||||||
alias Pleroma.Web.MediaProxy
|
alias Pleroma.Web.MediaProxy
|
||||||
alias Pleroma.Workers.BackgroundWorker
|
|
||||||
|
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
|
@ -17,7 +16,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy do
|
||||||
recv_timeout: 10_000
|
recv_timeout: 10_000
|
||||||
]
|
]
|
||||||
|
|
||||||
def perform(:prefetch, url) do
|
defp prefetch(url) do
|
||||||
# Fetching only proxiable resources
|
# Fetching only proxiable resources
|
||||||
if MediaProxy.enabled?() and MediaProxy.url_proxiable?(url) do
|
if MediaProxy.enabled?() and MediaProxy.url_proxiable?(url) do
|
||||||
# If preview proxy is enabled, it'll also hit media proxy (so we're caching both requests)
|
# If preview proxy is enabled, it'll also hit media proxy (so we're caching both requests)
|
||||||
|
@ -25,17 +24,25 @@ def perform(:prefetch, url) do
|
||||||
|
|
||||||
Logger.debug("Prefetching #{inspect(url)} as #{inspect(prefetch_url)}")
|
Logger.debug("Prefetching #{inspect(url)} as #{inspect(prefetch_url)}")
|
||||||
|
|
||||||
HTTP.get(prefetch_url, [], @adapter_options)
|
if Pleroma.Config.get(:env) == :test do
|
||||||
|
fetch(prefetch_url)
|
||||||
|
else
|
||||||
|
ConcurrentLimiter.limit(MediaProxy, fn ->
|
||||||
|
Task.start(fn -> fetch(prefetch_url) end)
|
||||||
|
end)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def perform(:preload, %{"object" => %{"attachment" => attachments}} = _message) do
|
defp fetch(url), do: HTTP.get(url, [], @adapter_options)
|
||||||
|
|
||||||
|
defp preload(%{"object" => %{"attachment" => attachments}} = _message) do
|
||||||
Enum.each(attachments, fn
|
Enum.each(attachments, fn
|
||||||
%{"url" => url} when is_list(url) ->
|
%{"url" => url} when is_list(url) ->
|
||||||
url
|
url
|
||||||
|> Enum.each(fn
|
|> Enum.each(fn
|
||||||
%{"href" => href} ->
|
%{"href" => href} ->
|
||||||
BackgroundWorker.enqueue("media_proxy_prefetch", %{"url" => href})
|
prefetch(href)
|
||||||
|
|
||||||
x ->
|
x ->
|
||||||
Logger.debug("Unhandled attachment URL object #{inspect(x)}")
|
Logger.debug("Unhandled attachment URL object #{inspect(x)}")
|
||||||
|
@ -51,7 +58,7 @@ def filter(
|
||||||
%{"type" => "Create", "object" => %{"attachment" => attachments} = _object} = message
|
%{"type" => "Create", "object" => %{"attachment" => attachments} = _object} = message
|
||||||
)
|
)
|
||||||
when is_list(attachments) and length(attachments) > 0 do
|
when is_list(attachments) and length(attachments) > 0 do
|
||||||
BackgroundWorker.enqueue("media_proxy_preload", %{"message" => message})
|
preload(message)
|
||||||
|
|
||||||
{:ok, message}
|
{:ok, message}
|
||||||
end
|
end
|
||||||
|
|
|
@ -67,7 +67,12 @@ def validate_announcable(cng) do
|
||||||
%Object{} = object <- Object.get_cached_by_ap_id(object),
|
%Object{} = object <- Object.get_cached_by_ap_id(object),
|
||||||
false <- Visibility.is_public?(object) do
|
false <- Visibility.is_public?(object) do
|
||||||
same_actor = object.data["actor"] == actor.ap_id
|
same_actor = object.data["actor"] == actor.ap_id
|
||||||
is_public = Pleroma.Constants.as_public() in (get_field(cng, :to) ++ get_field(cng, :cc))
|
recipients = get_field(cng, :to) ++ get_field(cng, :cc)
|
||||||
|
local_public = Pleroma.Constants.as_local_public()
|
||||||
|
|
||||||
|
is_public =
|
||||||
|
Enum.member?(recipients, Pleroma.Constants.as_public()) or
|
||||||
|
Enum.member?(recipients, local_public)
|
||||||
|
|
||||||
cond do
|
cond do
|
||||||
same_actor && is_public ->
|
same_actor && is_public ->
|
||||||
|
|
|
@ -11,6 +11,7 @@ defmodule Pleroma.Web.ActivityPub.Pipeline do
|
||||||
alias Pleroma.Web.ActivityPub.MRF
|
alias Pleroma.Web.ActivityPub.MRF
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidator
|
alias Pleroma.Web.ActivityPub.ObjectValidator
|
||||||
alias Pleroma.Web.ActivityPub.SideEffects
|
alias Pleroma.Web.ActivityPub.SideEffects
|
||||||
|
alias Pleroma.Web.ActivityPub.Visibility
|
||||||
alias Pleroma.Web.Federator
|
alias Pleroma.Web.Federator
|
||||||
|
|
||||||
@spec common_pipeline(map(), keyword()) ::
|
@spec common_pipeline(map(), keyword()) ::
|
||||||
|
@ -55,7 +56,7 @@ defp maybe_federate(%Activity{} = activity, meta) do
|
||||||
with {:ok, local} <- Keyword.fetch(meta, :local) do
|
with {:ok, local} <- Keyword.fetch(meta, :local) do
|
||||||
do_not_federate = meta[:do_not_federate] || !Config.get([:instance, :federating])
|
do_not_federate = meta[:do_not_federate] || !Config.get([:instance, :federating])
|
||||||
|
|
||||||
if !do_not_federate && local do
|
if !do_not_federate and local and not Visibility.is_local_public?(activity) do
|
||||||
activity =
|
activity =
|
||||||
if object = Keyword.get(meta, :object_data) do
|
if object = Keyword.get(meta, :object_data) do
|
||||||
%{activity | data: Map.put(activity.data, "object", object)}
|
%{activity | data: Map.put(activity.data, "object", object)}
|
||||||
|
|
|
@ -13,7 +13,6 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Web.ActivityPub.Relay
|
alias Pleroma.Web.ActivityPub.Relay
|
||||||
alias Pleroma.Web.ActivityPub.Transmogrifier
|
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||||
alias Pleroma.Web.FedSockets
|
|
||||||
|
|
||||||
require Pleroma.Constants
|
require Pleroma.Constants
|
||||||
|
|
||||||
|
@ -50,28 +49,6 @@ def is_representable?(%Activity{} = activity) do
|
||||||
"""
|
"""
|
||||||
def publish_one(%{inbox: inbox, json: json, actor: %User{} = actor, id: id} = params) do
|
def publish_one(%{inbox: inbox, json: json, actor: %User{} = actor, id: id} = params) do
|
||||||
Logger.debug("Federating #{id} to #{inbox}")
|
Logger.debug("Federating #{id} to #{inbox}")
|
||||||
|
|
||||||
case FedSockets.get_or_create_fed_socket(inbox) do
|
|
||||||
{:ok, fedsocket} ->
|
|
||||||
Logger.debug("publishing via fedsockets - #{inspect(inbox)}")
|
|
||||||
FedSockets.publish(fedsocket, json)
|
|
||||||
|
|
||||||
_ ->
|
|
||||||
Logger.debug("publishing via http - #{inspect(inbox)}")
|
|
||||||
http_publish(inbox, actor, json, params)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def publish_one(%{actor_id: actor_id} = params) do
|
|
||||||
actor = User.get_cached_by_id(actor_id)
|
|
||||||
|
|
||||||
params
|
|
||||||
|> Map.delete(:actor_id)
|
|
||||||
|> Map.put(:actor, actor)
|
|
||||||
|> publish_one()
|
|
||||||
end
|
|
||||||
|
|
||||||
defp http_publish(inbox, actor, json, params) do
|
|
||||||
uri = %{path: path} = URI.parse(inbox)
|
uri = %{path: path} = URI.parse(inbox)
|
||||||
digest = "SHA-256=" <> (:crypto.hash(:sha256, json) |> Base.encode64())
|
digest = "SHA-256=" <> (:crypto.hash(:sha256, json) |> Base.encode64())
|
||||||
|
|
||||||
|
@ -110,6 +87,15 @@ defp http_publish(inbox, actor, json, params) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def publish_one(%{actor_id: actor_id} = params) do
|
||||||
|
actor = User.get_cached_by_id(actor_id)
|
||||||
|
|
||||||
|
params
|
||||||
|
|> Map.delete(:actor_id)
|
||||||
|
|> Map.put(:actor, actor)
|
||||||
|
|> publish_one()
|
||||||
|
end
|
||||||
|
|
||||||
defp signature_host(%URI{port: port, scheme: scheme, host: host}) do
|
defp signature_host(%URI{port: port, scheme: scheme, host: host}) do
|
||||||
if port == URI.default_port(scheme) do
|
if port == URI.default_port(scheme) do
|
||||||
host
|
host
|
||||||
|
|
|
@ -24,7 +24,6 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
|
||||||
alias Pleroma.Web.ActivityPub.Utils
|
alias Pleroma.Web.ActivityPub.Utils
|
||||||
alias Pleroma.Web.Push
|
alias Pleroma.Web.Push
|
||||||
alias Pleroma.Web.Streamer
|
alias Pleroma.Web.Streamer
|
||||||
alias Pleroma.Workers.BackgroundWorker
|
|
||||||
|
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
|
@ -191,7 +190,9 @@ def handle(%{data: %{"type" => "Create"}} = activity, meta) do
|
||||||
Object.increase_replies_count(in_reply_to)
|
Object.increase_replies_count(in_reply_to)
|
||||||
end
|
end
|
||||||
|
|
||||||
BackgroundWorker.enqueue("fetch_data_for_activity", %{"activity_id" => activity.id})
|
ConcurrentLimiter.limit(Pleroma.Web.RichMedia.Helpers, fn ->
|
||||||
|
Task.start(fn -> Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity) end)
|
||||||
|
end)
|
||||||
|
|
||||||
meta =
|
meta =
|
||||||
meta
|
meta
|
||||||
|
|
|
@ -1008,7 +1008,7 @@ def perform(:user_upgrade, user) do
|
||||||
|
|
||||||
def upgrade_user_from_ap_id(ap_id) do
|
def upgrade_user_from_ap_id(ap_id) do
|
||||||
with %User{local: false} = user <- User.get_cached_by_ap_id(ap_id),
|
with %User{local: false} = user <- User.get_cached_by_ap_id(ap_id),
|
||||||
{:ok, data} <- ActivityPub.fetch_and_prepare_user_from_ap_id(ap_id, force_http: true),
|
{:ok, data} <- ActivityPub.fetch_and_prepare_user_from_ap_id(ap_id),
|
||||||
{:ok, user} <- update_user(user, data) do
|
{:ok, user} <- update_user(user, data) do
|
||||||
TransmogrifierWorker.enqueue("user_upgrade", %{"user_id" => user.id})
|
TransmogrifierWorker.enqueue("user_upgrade", %{"user_id" => user.id})
|
||||||
{:ok, user}
|
{:ok, user}
|
||||||
|
|
|
@ -175,7 +175,8 @@ def maybe_federate(%Activity{local: true, data: %{"type" => type}} = activity) d
|
||||||
outgoing_blocks = Config.get([:activitypub, :outgoing_blocks])
|
outgoing_blocks = Config.get([:activitypub, :outgoing_blocks])
|
||||||
|
|
||||||
with true <- Config.get!([:instance, :federating]),
|
with true <- Config.get!([:instance, :federating]),
|
||||||
true <- type != "Block" || outgoing_blocks do
|
true <- type != "Block" || outgoing_blocks,
|
||||||
|
false <- Visibility.is_local_public?(activity) do
|
||||||
Pleroma.Web.Federator.publish(activity)
|
Pleroma.Web.Federator.publish(activity)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -701,14 +702,30 @@ def make_flag_data(%{actor: actor, context: context, content: content} = params,
|
||||||
|
|
||||||
def make_flag_data(_, _), do: %{}
|
def make_flag_data(_, _), do: %{}
|
||||||
|
|
||||||
defp build_flag_object(%{account: account, statuses: statuses} = _) do
|
defp build_flag_object(%{account: account, statuses: statuses}) do
|
||||||
[account.ap_id] ++ build_flag_object(%{statuses: statuses})
|
[account.ap_id | build_flag_object(%{statuses: statuses})]
|
||||||
end
|
end
|
||||||
|
|
||||||
defp build_flag_object(%{statuses: statuses}) do
|
defp build_flag_object(%{statuses: statuses}) do
|
||||||
Enum.map(statuses || [], &build_flag_object/1)
|
Enum.map(statuses || [], &build_flag_object/1)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp build_flag_object(%Activity{data: %{"id" => id}, object: %{data: data}}) do
|
||||||
|
activity_actor = User.get_by_ap_id(data["actor"])
|
||||||
|
|
||||||
|
%{
|
||||||
|
"type" => "Note",
|
||||||
|
"id" => id,
|
||||||
|
"content" => data["content"],
|
||||||
|
"published" => data["published"],
|
||||||
|
"actor" =>
|
||||||
|
AccountView.render(
|
||||||
|
"show.json",
|
||||||
|
%{user: activity_actor, skip_visibility_check: true}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
defp build_flag_object(act) when is_map(act) or is_binary(act) do
|
defp build_flag_object(act) when is_map(act) or is_binary(act) do
|
||||||
id =
|
id =
|
||||||
case act do
|
case act do
|
||||||
|
@ -719,22 +736,14 @@ defp build_flag_object(act) when is_map(act) or is_binary(act) do
|
||||||
|
|
||||||
case Activity.get_by_ap_id_with_object(id) do
|
case Activity.get_by_ap_id_with_object(id) do
|
||||||
%Activity{} = activity ->
|
%Activity{} = activity ->
|
||||||
activity_actor = User.get_by_ap_id(activity.object.data["actor"])
|
build_flag_object(activity)
|
||||||
|
|
||||||
%{
|
nil ->
|
||||||
"type" => "Note",
|
if activity = Activity.get_by_object_ap_id_with_object(id) do
|
||||||
"id" => activity.data["id"],
|
build_flag_object(activity)
|
||||||
"content" => activity.object.data["content"],
|
else
|
||||||
"published" => activity.object.data["published"],
|
%{"id" => id, "deleted" => true}
|
||||||
"actor" =>
|
end
|
||||||
AccountView.render(
|
|
||||||
"show.json",
|
|
||||||
%{user: activity_actor, skip_visibility_check: true}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
_ ->
|
|
||||||
%{"id" => id, "deleted" => true}
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -110,6 +110,7 @@ def render("user.json", %{user: user}) do
|
||||||
"endpoints" => endpoints,
|
"endpoints" => endpoints,
|
||||||
"attachment" => fields,
|
"attachment" => fields,
|
||||||
"tag" => emoji_tags,
|
"tag" => emoji_tags,
|
||||||
|
# Note: key name is indeed "discoverable" (not an error)
|
||||||
"discoverable" => user.is_discoverable,
|
"discoverable" => user.is_discoverable,
|
||||||
"capabilities" => capabilities
|
"capabilities" => capabilities
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,19 @@ def is_public?(%Object{data: data}), do: is_public?(data)
|
||||||
def is_public?(%Activity{data: %{"type" => "Move"}}), do: true
|
def is_public?(%Activity{data: %{"type" => "Move"}}), do: true
|
||||||
def is_public?(%Activity{data: data}), do: is_public?(data)
|
def is_public?(%Activity{data: data}), do: is_public?(data)
|
||||||
def is_public?(%{"directMessage" => true}), do: false
|
def is_public?(%{"directMessage" => true}), do: false
|
||||||
def is_public?(data), do: Utils.label_in_message?(Pleroma.Constants.as_public(), data)
|
|
||||||
|
def is_public?(data) do
|
||||||
|
Utils.label_in_message?(Pleroma.Constants.as_public(), data) or
|
||||||
|
Utils.label_in_message?(Pleroma.Constants.as_local_public(), data)
|
||||||
|
end
|
||||||
|
|
||||||
|
def is_local_public?(%Object{data: data}), do: is_local_public?(data)
|
||||||
|
def is_local_public?(%Activity{data: data}), do: is_local_public?(data)
|
||||||
|
|
||||||
|
def is_local_public?(data) do
|
||||||
|
Utils.label_in_message?(Pleroma.Constants.as_local_public(), data) and
|
||||||
|
not Utils.label_in_message?(Pleroma.Constants.as_public(), data)
|
||||||
|
end
|
||||||
|
|
||||||
def is_private?(activity) do
|
def is_private?(activity) do
|
||||||
with false <- is_public?(activity),
|
with false <- is_public?(activity),
|
||||||
|
@ -114,6 +126,9 @@ def get_visibility(object) do
|
||||||
Pleroma.Constants.as_public() in cc ->
|
Pleroma.Constants.as_public() in cc ->
|
||||||
"unlisted"
|
"unlisted"
|
||||||
|
|
||||||
|
Pleroma.Constants.as_local_public() in to ->
|
||||||
|
"local"
|
||||||
|
|
||||||
# this should use the sql for the object's activity
|
# this should use the sql for the object's activity
|
||||||
Enum.any?(to, &String.contains?(&1, "/followers")) ->
|
Enum.any?(to, &String.contains?(&1, "/followers")) ->
|
||||||
"private"
|
"private"
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.AdminAPI.FrontendController do
|
||||||
|
use Pleroma.Web, :controller
|
||||||
|
|
||||||
|
alias Pleroma.Config
|
||||||
|
alias Pleroma.Web.Plugs.OAuthScopesPlug
|
||||||
|
|
||||||
|
plug(Pleroma.Web.ApiSpec.CastAndValidate)
|
||||||
|
plug(OAuthScopesPlug, %{scopes: ["write"], admin: true} when action == :install)
|
||||||
|
plug(OAuthScopesPlug, %{scopes: ["read"], admin: true} when action == :index)
|
||||||
|
action_fallback(Pleroma.Web.AdminAPI.FallbackController)
|
||||||
|
|
||||||
|
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.Admin.FrontendOperation
|
||||||
|
|
||||||
|
def index(conn, _params) do
|
||||||
|
installed = installed()
|
||||||
|
|
||||||
|
frontends =
|
||||||
|
[:frontends, :available]
|
||||||
|
|> Config.get([])
|
||||||
|
|> Enum.map(fn {name, desc} ->
|
||||||
|
Map.put(desc, "installed", name in installed)
|
||||||
|
end)
|
||||||
|
|
||||||
|
render(conn, "index.json", frontends: frontends)
|
||||||
|
end
|
||||||
|
|
||||||
|
def install(%{body_params: params} = conn, _params) do
|
||||||
|
with :ok <- Pleroma.Frontend.install(params.name, Map.delete(params, :name)) do
|
||||||
|
index(conn, %{})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp installed do
|
||||||
|
File.ls!(Pleroma.Frontend.dir())
|
||||||
|
end
|
||||||
|
end
|
|
@ -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
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.AdminAPI.FrontendView do
|
||||||
|
use Pleroma.Web, :view
|
||||||
|
|
||||||
|
def render("index.json", %{frontends: frontends}) do
|
||||||
|
render_many(frontends, __MODULE__, "show.json")
|
||||||
|
end
|
||||||
|
|
||||||
|
def render("show.json", %{frontend: frontend}) do
|
||||||
|
%{
|
||||||
|
name: frontend["name"],
|
||||||
|
git: frontend["git"],
|
||||||
|
build_url: frontend["build_url"],
|
||||||
|
ref: frontend["ref"],
|
||||||
|
installed: frontend["installed"]
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
|
@ -139,6 +139,12 @@ def statuses_operation do
|
||||||
:query,
|
:query,
|
||||||
%Schema{type: :array, items: VisibilityScope},
|
%Schema{type: :array, items: VisibilityScope},
|
||||||
"Exclude visibilities"
|
"Exclude visibilities"
|
||||||
|
),
|
||||||
|
Operation.parameter(
|
||||||
|
:with_muted,
|
||||||
|
:query,
|
||||||
|
BooleanLike,
|
||||||
|
"Include reactions from muted acccounts."
|
||||||
)
|
)
|
||||||
] ++ pagination_params(),
|
] ++ pagination_params(),
|
||||||
responses: %{
|
responses: %{
|
||||||
|
@ -618,7 +624,7 @@ defp update_credentials_request do
|
||||||
allOf: [BooleanLike],
|
allOf: [BooleanLike],
|
||||||
nullable: true,
|
nullable: true,
|
||||||
description:
|
description:
|
||||||
"Discovery of this account in search results and other services is allowed."
|
"Discovery (listing, indexing) of this account by external services (search bots etc.) is allowed."
|
||||||
},
|
},
|
||||||
actor_type: ActorType
|
actor_type: ActorType
|
||||||
},
|
},
|
||||||
|
|
|
@ -0,0 +1,85 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.ApiSpec.Admin.FrontendOperation do
|
||||||
|
alias OpenApiSpex.Operation
|
||||||
|
alias OpenApiSpex.Schema
|
||||||
|
alias Pleroma.Web.ApiSpec.Schemas.ApiError
|
||||||
|
|
||||||
|
import Pleroma.Web.ApiSpec.Helpers
|
||||||
|
|
||||||
|
def open_api_operation(action) do
|
||||||
|
operation = String.to_existing_atom("#{action}_operation")
|
||||||
|
apply(__MODULE__, operation, [])
|
||||||
|
end
|
||||||
|
|
||||||
|
def index_operation do
|
||||||
|
%Operation{
|
||||||
|
tags: ["Admin", "Reports"],
|
||||||
|
summary: "Get a list of available frontends",
|
||||||
|
operationId: "AdminAPI.FrontendController.index",
|
||||||
|
security: [%{"oAuth" => ["read"]}],
|
||||||
|
responses: %{
|
||||||
|
200 => Operation.response("Response", "application/json", list_of_frontends()),
|
||||||
|
403 => Operation.response("Forbidden", "application/json", ApiError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def install_operation do
|
||||||
|
%Operation{
|
||||||
|
tags: ["Admin", "Reports"],
|
||||||
|
summary: "Install a frontend",
|
||||||
|
operationId: "AdminAPI.FrontendController.install",
|
||||||
|
security: [%{"oAuth" => ["read"]}],
|
||||||
|
requestBody: request_body("Parameters", install_request(), required: true),
|
||||||
|
responses: %{
|
||||||
|
200 => Operation.response("Response", "application/json", list_of_frontends()),
|
||||||
|
403 => Operation.response("Forbidden", "application/json", ApiError),
|
||||||
|
400 => Operation.response("Error", "application/json", ApiError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp list_of_frontends do
|
||||||
|
%Schema{
|
||||||
|
type: :array,
|
||||||
|
items: %Schema{
|
||||||
|
type: :object,
|
||||||
|
properties: %{
|
||||||
|
name: %Schema{type: :string},
|
||||||
|
git: %Schema{type: :string, format: :uri, nullable: true},
|
||||||
|
build_url: %Schema{type: :string, format: :uri, nullable: true},
|
||||||
|
ref: %Schema{type: :string},
|
||||||
|
installed: %Schema{type: :boolean}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp install_request do
|
||||||
|
%Schema{
|
||||||
|
title: "FrontendInstallRequest",
|
||||||
|
type: :object,
|
||||||
|
required: [:name],
|
||||||
|
properties: %{
|
||||||
|
name: %Schema{
|
||||||
|
type: :string
|
||||||
|
},
|
||||||
|
ref: %Schema{
|
||||||
|
type: :string
|
||||||
|
},
|
||||||
|
file: %Schema{
|
||||||
|
type: :string
|
||||||
|
},
|
||||||
|
build_url: %Schema{
|
||||||
|
type: :string
|
||||||
|
},
|
||||||
|
build_dir: %Schema{
|
||||||
|
type: :string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
|
@ -24,6 +24,12 @@ def index_operation do
|
||||||
Operation.parameter(:id, :path, FlakeID, "Status ID", required: true),
|
Operation.parameter(:id, :path, FlakeID, "Status ID", required: true),
|
||||||
Operation.parameter(:emoji, :path, :string, "Filter by a single unicode emoji",
|
Operation.parameter(:emoji, :path, :string, "Filter by a single unicode emoji",
|
||||||
required: nil
|
required: nil
|
||||||
|
),
|
||||||
|
Operation.parameter(
|
||||||
|
:with_muted,
|
||||||
|
:query,
|
||||||
|
:boolean,
|
||||||
|
"Include reactions from muted acccounts."
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
security: [%{"oAuth" => ["read:statuses"]}],
|
security: [%{"oAuth" => ["read:statuses"]}],
|
||||||
|
|
|
@ -193,6 +193,7 @@ defp notification_type do
|
||||||
"mention",
|
"mention",
|
||||||
"pleroma:emoji_reaction",
|
"pleroma:emoji_reaction",
|
||||||
"pleroma:chat_mention",
|
"pleroma:chat_mention",
|
||||||
|
"pleroma:report",
|
||||||
"move",
|
"move",
|
||||||
"follow_request"
|
"follow_request"
|
||||||
],
|
],
|
||||||
|
@ -206,6 +207,8 @@ defp notification_type do
|
||||||
- `poll` - A poll you have voted in or created has ended
|
- `poll` - A poll you have voted in or created has ended
|
||||||
- `move` - Someone moved their account
|
- `move` - Someone moved their account
|
||||||
- `pleroma:emoji_reaction` - Someone reacted with emoji to your status
|
- `pleroma:emoji_reaction` - Someone reacted with emoji to your status
|
||||||
|
- `pleroma:chat_mention` - Someone mentioned you in a chat message
|
||||||
|
- `pleroma:report` - Someone was reported
|
||||||
"""
|
"""
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
|
@ -31,6 +31,12 @@ def index_operation do
|
||||||
:query,
|
:query,
|
||||||
%Schema{type: :array, items: FlakeID},
|
%Schema{type: :array, items: FlakeID},
|
||||||
"Array of status IDs"
|
"Array of status IDs"
|
||||||
|
),
|
||||||
|
Operation.parameter(
|
||||||
|
:with_muted,
|
||||||
|
:query,
|
||||||
|
BooleanLike,
|
||||||
|
"Include reactions from muted acccounts."
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
operationId: "StatusController.index",
|
operationId: "StatusController.index",
|
||||||
|
@ -67,7 +73,15 @@ def show_operation do
|
||||||
description: "View information about a status",
|
description: "View information about a status",
|
||||||
operationId: "StatusController.show",
|
operationId: "StatusController.show",
|
||||||
security: [%{"oAuth" => ["read:statuses"]}],
|
security: [%{"oAuth" => ["read:statuses"]}],
|
||||||
parameters: [id_param()],
|
parameters: [
|
||||||
|
id_param(),
|
||||||
|
Operation.parameter(
|
||||||
|
:with_muted,
|
||||||
|
:query,
|
||||||
|
BooleanLike,
|
||||||
|
"Include reactions from muted acccounts."
|
||||||
|
)
|
||||||
|
],
|
||||||
responses: %{
|
responses: %{
|
||||||
200 => status_response(),
|
200 => status_response(),
|
||||||
404 => Operation.response("Not Found", "application/json", ApiError)
|
404 => Operation.response("Not Found", "application/json", ApiError)
|
||||||
|
|
|
@ -146,6 +146,11 @@ defp create_request do
|
||||||
allOf: [BooleanLike],
|
allOf: [BooleanLike],
|
||||||
nullable: true,
|
nullable: true,
|
||||||
description: "Receive chat notifications?"
|
description: "Receive chat notifications?"
|
||||||
|
},
|
||||||
|
"pleroma:emoji_reaction": %Schema{
|
||||||
|
allOf: [BooleanLike],
|
||||||
|
nullable: true,
|
||||||
|
description: "Receive emoji reaction notifications?"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -210,6 +215,16 @@ defp update_request do
|
||||||
allOf: [BooleanLike],
|
allOf: [BooleanLike],
|
||||||
nullable: true,
|
nullable: true,
|
||||||
description: "Receive poll notifications?"
|
description: "Receive poll notifications?"
|
||||||
|
},
|
||||||
|
"pleroma:chat_mention": %Schema{
|
||||||
|
allOf: [BooleanLike],
|
||||||
|
nullable: true,
|
||||||
|
description: "Receive chat notifications?"
|
||||||
|
},
|
||||||
|
"pleroma:emoji_reaction": %Schema{
|
||||||
|
allOf: [BooleanLike],
|
||||||
|
nullable: true,
|
||||||
|
description: "Receive emoji reaction notifications?"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -127,7 +127,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
|
||||||
discoverable: %Schema{
|
discoverable: %Schema{
|
||||||
type: :boolean,
|
type: :boolean,
|
||||||
description:
|
description:
|
||||||
"whether the user allows discovery of the account in search results and other services."
|
"whether the user allows indexing / listing of the account by external services (search engines etc.)."
|
||||||
},
|
},
|
||||||
no_rich_text: %Schema{
|
no_rich_text: %Schema{
|
||||||
type: :boolean,
|
type: :boolean,
|
||||||
|
|
|
@ -9,6 +9,6 @@ defmodule Pleroma.Web.ApiSpec.Schemas.VisibilityScope do
|
||||||
title: "VisibilityScope",
|
title: "VisibilityScope",
|
||||||
description: "Status visibility",
|
description: "Status visibility",
|
||||||
type: :string,
|
type: :string,
|
||||||
enum: ["public", "unlisted", "private", "direct", "list"]
|
enum: ["public", "unlisted", "local", "private", "direct", "list"]
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
|
@ -15,6 +15,7 @@ defmodule Pleroma.Web.CommonAPI do
|
||||||
alias Pleroma.Web.ActivityPub.Pipeline
|
alias Pleroma.Web.ActivityPub.Pipeline
|
||||||
alias Pleroma.Web.ActivityPub.Utils
|
alias Pleroma.Web.ActivityPub.Utils
|
||||||
alias Pleroma.Web.ActivityPub.Visibility
|
alias Pleroma.Web.ActivityPub.Visibility
|
||||||
|
alias Pleroma.Web.CommonAPI.ActivityDraft
|
||||||
|
|
||||||
import Pleroma.Web.Gettext
|
import Pleroma.Web.Gettext
|
||||||
import Pleroma.Web.CommonAPI.Utils
|
import Pleroma.Web.CommonAPI.Utils
|
||||||
|
@ -358,7 +359,7 @@ def public_announce?(object, _) do
|
||||||
def get_visibility(_, _, %Participation{}), do: {"direct", "direct"}
|
def get_visibility(_, _, %Participation{}), do: {"direct", "direct"}
|
||||||
|
|
||||||
def get_visibility(%{visibility: visibility}, in_reply_to, _)
|
def get_visibility(%{visibility: visibility}, in_reply_to, _)
|
||||||
when visibility in ~w{public unlisted private direct},
|
when visibility in ~w{public local unlisted private direct},
|
||||||
do: {visibility, get_replied_to_visibility(in_reply_to)}
|
do: {visibility, get_replied_to_visibility(in_reply_to)}
|
||||||
|
|
||||||
def get_visibility(%{visibility: "list:" <> list_id}, in_reply_to, _) do
|
def get_visibility(%{visibility: "list:" <> list_id}, in_reply_to, _) do
|
||||||
|
@ -399,31 +400,13 @@ def check_expiry_date(expiry_str) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def listen(user, data) do
|
def listen(user, data) do
|
||||||
visibility = Map.get(data, :visibility, "public")
|
with {:ok, draft} <- ActivityDraft.listen(user, data) do
|
||||||
|
ActivityPub.listen(draft.changes)
|
||||||
with {to, cc} <- get_to_and_cc(user, [], nil, visibility, nil),
|
|
||||||
listen_data <-
|
|
||||||
data
|
|
||||||
|> Map.take([:album, :artist, :title, :length])
|
|
||||||
|> Map.new(fn {key, value} -> {to_string(key), value} end)
|
|
||||||
|> Map.put("type", "Audio")
|
|
||||||
|> Map.put("to", to)
|
|
||||||
|> Map.put("cc", cc)
|
|
||||||
|> Map.put("actor", user.ap_id),
|
|
||||||
{:ok, activity} <-
|
|
||||||
ActivityPub.listen(%{
|
|
||||||
actor: user,
|
|
||||||
to: to,
|
|
||||||
object: listen_data,
|
|
||||||
context: Utils.generate_context_id(),
|
|
||||||
additional: %{"cc" => cc}
|
|
||||||
}) do
|
|
||||||
{:ok, activity}
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def post(user, %{status: _} = data) do
|
def post(user, %{status: _} = data) do
|
||||||
with {:ok, draft} <- Pleroma.Web.CommonAPI.ActivityDraft.create(user, data) do
|
with {:ok, draft} <- ActivityDraft.create(user, data) do
|
||||||
ActivityPub.create(draft.changes, draft.preview?)
|
ActivityPub.create(draft.changes, draft.preview?)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -22,7 +22,7 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do
|
||||||
in_reply_to_conversation: nil,
|
in_reply_to_conversation: nil,
|
||||||
visibility: nil,
|
visibility: nil,
|
||||||
expires_at: nil,
|
expires_at: nil,
|
||||||
poll: nil,
|
extra: nil,
|
||||||
emoji: %{},
|
emoji: %{},
|
||||||
content_html: nil,
|
content_html: nil,
|
||||||
mentions: [],
|
mentions: [],
|
||||||
|
@ -35,9 +35,14 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do
|
||||||
preview?: false,
|
preview?: false,
|
||||||
changes: %{}
|
changes: %{}
|
||||||
|
|
||||||
def create(user, params) do
|
def new(user, params) do
|
||||||
%__MODULE__{user: user}
|
%__MODULE__{user: user}
|
||||||
|> put_params(params)
|
|> put_params(params)
|
||||||
|
end
|
||||||
|
|
||||||
|
def create(user, params) do
|
||||||
|
user
|
||||||
|
|> new(params)
|
||||||
|> status()
|
|> status()
|
||||||
|> summary()
|
|> summary()
|
||||||
|> with_valid(&attachments/1)
|
|> with_valid(&attachments/1)
|
||||||
|
@ -57,6 +62,30 @@ def create(user, params) do
|
||||||
|> validate()
|
|> validate()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def listen(user, params) do
|
||||||
|
user
|
||||||
|
|> new(params)
|
||||||
|
|> visibility()
|
||||||
|
|> to_and_cc()
|
||||||
|
|> context()
|
||||||
|
|> listen_object()
|
||||||
|
|> with_valid(&changes/1)
|
||||||
|
|> validate()
|
||||||
|
end
|
||||||
|
|
||||||
|
defp listen_object(draft) do
|
||||||
|
object =
|
||||||
|
draft.params
|
||||||
|
|> Map.take([:album, :artist, :title, :length])
|
||||||
|
|> Map.new(fn {key, value} -> {to_string(key), value} end)
|
||||||
|
|> Map.put("type", "Audio")
|
||||||
|
|> Map.put("to", draft.to)
|
||||||
|
|> Map.put("cc", draft.cc)
|
||||||
|
|> Map.put("actor", draft.user.ap_id)
|
||||||
|
|
||||||
|
%__MODULE__{draft | object: object}
|
||||||
|
end
|
||||||
|
|
||||||
defp put_params(draft, params) do
|
defp put_params(draft, params) do
|
||||||
params = Map.put_new(params, :in_reply_to_status_id, params[:in_reply_to_id])
|
params = Map.put_new(params, :in_reply_to_status_id, params[:in_reply_to_id])
|
||||||
%__MODULE__{draft | params: params}
|
%__MODULE__{draft | params: params}
|
||||||
|
@ -121,7 +150,7 @@ defp expires_at(draft) do
|
||||||
defp poll(draft) do
|
defp poll(draft) do
|
||||||
case Utils.make_poll_data(draft.params) do
|
case Utils.make_poll_data(draft.params) do
|
||||||
{:ok, {poll, poll_emoji}} ->
|
{:ok, {poll, poll_emoji}} ->
|
||||||
%__MODULE__{draft | poll: poll, emoji: Map.merge(draft.emoji, poll_emoji)}
|
%__MODULE__{draft | extra: poll, emoji: Map.merge(draft.emoji, poll_emoji)}
|
||||||
|
|
||||||
{:error, message} ->
|
{:error, message} ->
|
||||||
add_error(draft, message)
|
add_error(draft, message)
|
||||||
|
@ -129,32 +158,18 @@ defp poll(draft) do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp content(draft) do
|
defp content(draft) do
|
||||||
{content_html, mentions, tags} =
|
{content_html, mentioned_users, tags} = Utils.make_content_html(draft)
|
||||||
Utils.make_content_html(
|
|
||||||
draft.status,
|
mentions =
|
||||||
draft.attachments,
|
mentioned_users
|
||||||
draft.params,
|
|> Enum.map(fn {_, mentioned_user} -> mentioned_user.ap_id end)
|
||||||
draft.visibility
|
|> Utils.get_addressed_users(draft.params[:to])
|
||||||
)
|
|
||||||
|
|
||||||
%__MODULE__{draft | content_html: content_html, mentions: mentions, tags: tags}
|
%__MODULE__{draft | content_html: content_html, mentions: mentions, tags: tags}
|
||||||
end
|
end
|
||||||
|
|
||||||
defp to_and_cc(draft) do
|
defp to_and_cc(draft) do
|
||||||
addressed_users =
|
{to, cc} = Utils.get_to_and_cc(draft)
|
||||||
draft.mentions
|
|
||||||
|> Enum.map(fn {_, mentioned_user} -> mentioned_user.ap_id end)
|
|
||||||
|> Utils.get_addressed_users(draft.params[:to])
|
|
||||||
|
|
||||||
{to, cc} =
|
|
||||||
Utils.get_to_and_cc(
|
|
||||||
draft.user,
|
|
||||||
addressed_users,
|
|
||||||
draft.in_reply_to,
|
|
||||||
draft.visibility,
|
|
||||||
draft.in_reply_to_conversation
|
|
||||||
)
|
|
||||||
|
|
||||||
%__MODULE__{draft | to: to, cc: cc}
|
%__MODULE__{draft | to: to, cc: cc}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -172,19 +187,7 @@ defp object(draft) do
|
||||||
emoji = Map.merge(Pleroma.Emoji.Formatter.get_emoji_map(draft.full_payload), draft.emoji)
|
emoji = Map.merge(Pleroma.Emoji.Formatter.get_emoji_map(draft.full_payload), draft.emoji)
|
||||||
|
|
||||||
object =
|
object =
|
||||||
Utils.make_note_data(
|
Utils.make_note_data(draft)
|
||||||
draft.user.ap_id,
|
|
||||||
draft.to,
|
|
||||||
draft.context,
|
|
||||||
draft.content_html,
|
|
||||||
draft.attachments,
|
|
||||||
draft.in_reply_to,
|
|
||||||
draft.tags,
|
|
||||||
draft.summary,
|
|
||||||
draft.cc,
|
|
||||||
draft.sensitive,
|
|
||||||
draft.poll
|
|
||||||
)
|
|
||||||
|> Map.put("emoji", emoji)
|
|> Map.put("emoji", emoji)
|
||||||
|> Map.put("source", draft.status)
|
|> Map.put("source", draft.status)
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Web.ActivityPub.Utils
|
alias Pleroma.Web.ActivityPub.Utils
|
||||||
alias Pleroma.Web.ActivityPub.Visibility
|
alias Pleroma.Web.ActivityPub.Visibility
|
||||||
|
alias Pleroma.Web.CommonAPI.ActivityDraft
|
||||||
alias Pleroma.Web.MediaProxy
|
alias Pleroma.Web.MediaProxy
|
||||||
alias Pleroma.Web.Plugs.AuthenticationPlug
|
alias Pleroma.Web.Plugs.AuthenticationPlug
|
||||||
|
|
||||||
|
@ -50,67 +51,62 @@ def attachments_from_ids_descs(ids, descs_str) do
|
||||||
{_, descs} = Jason.decode(descs_str)
|
{_, descs} = Jason.decode(descs_str)
|
||||||
|
|
||||||
Enum.map(ids, fn media_id ->
|
Enum.map(ids, fn media_id ->
|
||||||
case Repo.get(Object, media_id) do
|
with %Object{data: data} <- Repo.get(Object, media_id) do
|
||||||
%Object{data: data} ->
|
Map.put(data, "name", descs[media_id])
|
||||||
Map.put(data, "name", descs[media_id])
|
|
||||||
|
|
||||||
_ ->
|
|
||||||
nil
|
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|> Enum.reject(&is_nil/1)
|
|> Enum.reject(&is_nil/1)
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec get_to_and_cc(
|
@spec get_to_and_cc(ActivityDraft.t()) :: {list(String.t()), list(String.t())}
|
||||||
User.t(),
|
|
||||||
list(String.t()),
|
|
||||||
Activity.t() | nil,
|
|
||||||
String.t(),
|
|
||||||
Participation.t() | nil
|
|
||||||
) :: {list(String.t()), list(String.t())}
|
|
||||||
|
|
||||||
def get_to_and_cc(_, _, _, _, %Participation{} = participation) do
|
def get_to_and_cc(%{in_reply_to_conversation: %Participation{} = participation}) do
|
||||||
participation = Repo.preload(participation, :recipients)
|
participation = Repo.preload(participation, :recipients)
|
||||||
{Enum.map(participation.recipients, & &1.ap_id), []}
|
{Enum.map(participation.recipients, & &1.ap_id), []}
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_to_and_cc(user, mentioned_users, inReplyTo, "public", _) do
|
def get_to_and_cc(%{visibility: visibility} = draft) when visibility in ["public", "local"] do
|
||||||
to = [Pleroma.Constants.as_public() | mentioned_users]
|
to =
|
||||||
cc = [user.follower_address]
|
case visibility do
|
||||||
|
"public" -> [Pleroma.Constants.as_public() | draft.mentions]
|
||||||
|
"local" -> [Pleroma.Constants.as_local_public() | draft.mentions]
|
||||||
|
end
|
||||||
|
|
||||||
if inReplyTo do
|
cc = [draft.user.follower_address]
|
||||||
{Enum.uniq([inReplyTo.data["actor"] | to]), cc}
|
|
||||||
|
if draft.in_reply_to do
|
||||||
|
{Enum.uniq([draft.in_reply_to.data["actor"] | to]), cc}
|
||||||
else
|
else
|
||||||
{to, cc}
|
{to, cc}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_to_and_cc(user, mentioned_users, inReplyTo, "unlisted", _) do
|
def get_to_and_cc(%{visibility: "unlisted"} = draft) do
|
||||||
to = [user.follower_address | mentioned_users]
|
to = [draft.user.follower_address | draft.mentions]
|
||||||
cc = [Pleroma.Constants.as_public()]
|
cc = [Pleroma.Constants.as_public()]
|
||||||
|
|
||||||
if inReplyTo do
|
if draft.in_reply_to do
|
||||||
{Enum.uniq([inReplyTo.data["actor"] | to]), cc}
|
{Enum.uniq([draft.in_reply_to.data["actor"] | to]), cc}
|
||||||
else
|
else
|
||||||
{to, cc}
|
{to, cc}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_to_and_cc(user, mentioned_users, inReplyTo, "private", _) do
|
def get_to_and_cc(%{visibility: "private"} = draft) do
|
||||||
{to, cc} = get_to_and_cc(user, mentioned_users, inReplyTo, "direct", nil)
|
{to, cc} = get_to_and_cc(struct(draft, visibility: "direct"))
|
||||||
{[user.follower_address | to], cc}
|
{[draft.user.follower_address | to], cc}
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_to_and_cc(_user, mentioned_users, inReplyTo, "direct", _) do
|
def get_to_and_cc(%{visibility: "direct"} = draft) do
|
||||||
# If the OP is a DM already, add the implicit actor.
|
# If the OP is a DM already, add the implicit actor.
|
||||||
if inReplyTo && Visibility.is_direct?(inReplyTo) do
|
if draft.in_reply_to && Visibility.is_direct?(draft.in_reply_to) do
|
||||||
{Enum.uniq([inReplyTo.data["actor"] | mentioned_users]), []}
|
{Enum.uniq([draft.in_reply_to.data["actor"] | draft.mentions]), []}
|
||||||
else
|
else
|
||||||
{mentioned_users, []}
|
{draft.mentions, []}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_to_and_cc(_user, mentions, _inReplyTo, {:list, _}, _), do: {mentions, []}
|
def get_to_and_cc(%{visibility: {:list, _}, mentions: mentions}), do: {mentions, []}
|
||||||
|
|
||||||
def get_addressed_users(_, to) when is_list(to) do
|
def get_addressed_users(_, to) when is_list(to) do
|
||||||
User.get_ap_ids_by_nicknames(to)
|
User.get_ap_ids_by_nicknames(to)
|
||||||
|
@ -203,30 +199,25 @@ defp validate_poll_expiration(expires_in, %{min_expiration: min, max_expiration:
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def make_content_html(
|
def make_content_html(%ActivityDraft{} = draft) do
|
||||||
status,
|
|
||||||
attachments,
|
|
||||||
data,
|
|
||||||
visibility
|
|
||||||
) do
|
|
||||||
attachment_links =
|
attachment_links =
|
||||||
data
|
draft.params
|
||||||
|> Map.get("attachment_links", Config.get([:instance, :attachment_links]))
|
|> Map.get("attachment_links", Config.get([:instance, :attachment_links]))
|
||||||
|> truthy_param?()
|
|> truthy_param?()
|
||||||
|
|
||||||
content_type = get_content_type(data[:content_type])
|
content_type = get_content_type(draft.params[:content_type])
|
||||||
|
|
||||||
options =
|
options =
|
||||||
if visibility == "direct" && Config.get([:instance, :safe_dm_mentions]) do
|
if draft.visibility == "direct" && Config.get([:instance, :safe_dm_mentions]) do
|
||||||
[safe_mention: true]
|
[safe_mention: true]
|
||||||
else
|
else
|
||||||
[]
|
[]
|
||||||
end
|
end
|
||||||
|
|
||||||
status
|
draft.status
|
||||||
|> format_input(content_type, options)
|
|> format_input(content_type, options)
|
||||||
|> maybe_add_attachments(attachments, attachment_links)
|
|> maybe_add_attachments(draft.attachments, attachment_links)
|
||||||
|> maybe_add_nsfw_tag(data)
|
|> maybe_add_nsfw_tag(draft.params)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp get_content_type(content_type) do
|
defp get_content_type(content_type) do
|
||||||
|
@ -308,33 +299,21 @@ def format_input(text, "text/markdown", options) do
|
||||||
|> Formatter.html_escape("text/html")
|
|> Formatter.html_escape("text/html")
|
||||||
end
|
end
|
||||||
|
|
||||||
def make_note_data(
|
def make_note_data(%ActivityDraft{} = draft) do
|
||||||
actor,
|
|
||||||
to,
|
|
||||||
context,
|
|
||||||
content_html,
|
|
||||||
attachments,
|
|
||||||
in_reply_to,
|
|
||||||
tags,
|
|
||||||
summary \\ nil,
|
|
||||||
cc \\ [],
|
|
||||||
sensitive \\ false,
|
|
||||||
extra_params \\ %{}
|
|
||||||
) do
|
|
||||||
%{
|
%{
|
||||||
"type" => "Note",
|
"type" => "Note",
|
||||||
"to" => to,
|
"to" => draft.to,
|
||||||
"cc" => cc,
|
"cc" => draft.cc,
|
||||||
"content" => content_html,
|
"content" => draft.content_html,
|
||||||
"summary" => summary,
|
"summary" => draft.summary,
|
||||||
"sensitive" => truthy_param?(sensitive),
|
"sensitive" => draft.sensitive,
|
||||||
"context" => context,
|
"context" => draft.context,
|
||||||
"attachment" => attachments,
|
"attachment" => draft.attachments,
|
||||||
"actor" => actor,
|
"actor" => draft.user.ap_id,
|
||||||
"tag" => Keyword.values(tags) |> Enum.uniq()
|
"tag" => Keyword.values(draft.tags) |> Enum.uniq()
|
||||||
}
|
}
|
||||||
|> add_in_reply_to(in_reply_to)
|
|> add_in_reply_to(draft.in_reply_to)
|
||||||
|> Map.merge(extra_params)
|
|> Map.merge(draft.extra)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp add_in_reply_to(object, nil), do: object
|
defp add_in_reply_to(object, nil), do: object
|
||||||
|
|
|
@ -1,185 +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.FedSockets do
|
|
||||||
@moduledoc """
|
|
||||||
This documents the FedSockets framework. A framework for federating
|
|
||||||
ActivityPub objects between servers via persistant WebSocket connections.
|
|
||||||
|
|
||||||
FedSockets allow servers to authenticate on first contact and maintain that
|
|
||||||
connection, eliminating the need to authenticate every time data needs to be shared.
|
|
||||||
|
|
||||||
## Protocol
|
|
||||||
FedSockets currently support 2 types of data transfer:
|
|
||||||
* `publish` method which doesn't require a response
|
|
||||||
* `fetch` method requires a response be sent
|
|
||||||
|
|
||||||
### Publish
|
|
||||||
The publish operation sends a json encoded map of the shape:
|
|
||||||
%{action: :publish, data: json}
|
|
||||||
and accepts (but does not require) a reply of form:
|
|
||||||
%{"action" => "publish_reply"}
|
|
||||||
|
|
||||||
The outgoing params represent
|
|
||||||
* data: ActivityPub object encoded into json
|
|
||||||
|
|
||||||
|
|
||||||
### Fetch
|
|
||||||
The fetch operation sends a json encoded map of the shape:
|
|
||||||
%{action: :fetch, data: id, uuid: fetch_uuid}
|
|
||||||
and requires a reply of form:
|
|
||||||
%{"action" => "fetch_reply", "uuid" => uuid, "data" => data}
|
|
||||||
|
|
||||||
The outgoing params represent
|
|
||||||
* id: an ActivityPub object URI
|
|
||||||
* uuid: a unique uuid generated by the sender
|
|
||||||
|
|
||||||
The reply params represent
|
|
||||||
* data: an ActivityPub object encoded into json
|
|
||||||
* uuid: the uuid sent along with the fetch request
|
|
||||||
|
|
||||||
## Examples
|
|
||||||
Clients of FedSocket transfers shouldn't need to use any of the functions outside of this module.
|
|
||||||
|
|
||||||
A typical publish operation can be performed through the following code, and a fetch operation in a similar manner.
|
|
||||||
|
|
||||||
case FedSockets.get_or_create_fed_socket(inbox) do
|
|
||||||
{:ok, fedsocket} ->
|
|
||||||
FedSockets.publish(fedsocket, json)
|
|
||||||
|
|
||||||
_ ->
|
|
||||||
alternative_publish(inbox, actor, json, params)
|
|
||||||
end
|
|
||||||
|
|
||||||
## Configuration
|
|
||||||
FedSockets have the following config settings
|
|
||||||
|
|
||||||
config :pleroma, :fed_sockets,
|
|
||||||
enabled: true,
|
|
||||||
ping_interval: :timer.seconds(15),
|
|
||||||
connection_duration: :timer.hours(1),
|
|
||||||
rejection_duration: :timer.hours(1),
|
|
||||||
fed_socket_fetches: [
|
|
||||||
default: 12_000,
|
|
||||||
interval: 3_000,
|
|
||||||
lazy: false
|
|
||||||
]
|
|
||||||
* enabled - turn FedSockets on or off with this flag. Can be toggled at runtime.
|
|
||||||
* connection_duration - How long a FedSocket can sit idle before it's culled.
|
|
||||||
* rejection_duration - After failing to make a FedSocket connection a host will be excluded
|
|
||||||
from further connections for this amount of time
|
|
||||||
* fed_socket_fetches - Use these parameters to pass options to the Cachex queue backing the FetchRegistry
|
|
||||||
* fed_socket_rejections - Use these parameters to pass options to the Cachex queue backing the FedRegistry
|
|
||||||
|
|
||||||
Cachex options are
|
|
||||||
* default: the minimum amount of time a fetch can wait before it times out.
|
|
||||||
* interval: the interval between checks for timed out entries. This plus the default represent the maximum time allowed
|
|
||||||
* lazy: leave at false for consistant and fast lookups, set to true for stricter timeout enforcement
|
|
||||||
|
|
||||||
"""
|
|
||||||
require Logger
|
|
||||||
|
|
||||||
alias Pleroma.Web.FedSockets.FedRegistry
|
|
||||||
alias Pleroma.Web.FedSockets.FedSocket
|
|
||||||
alias Pleroma.Web.FedSockets.SocketInfo
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
returns a FedSocket for the given origin. Will reuse an existing one or create a new one.
|
|
||||||
|
|
||||||
address is expected to be a fully formed URL such as:
|
|
||||||
"http://www.example.com" or "http://www.example.com:8080"
|
|
||||||
|
|
||||||
It can and usually does include additional path parameters,
|
|
||||||
but these are ignored as the FedSockets are organized by host and port info alone.
|
|
||||||
"""
|
|
||||||
def get_or_create_fed_socket(address) do
|
|
||||||
with {:cache, {:error, :missing}} <- {:cache, get_fed_socket(address)},
|
|
||||||
{:connect, {:ok, _pid}} <- {:connect, FedSocket.connect_to_host(address)},
|
|
||||||
{:cache, {:ok, fed_socket}} <- {:cache, get_fed_socket(address)} do
|
|
||||||
Logger.debug("fedsocket created for - #{inspect(address)}")
|
|
||||||
{:ok, fed_socket}
|
|
||||||
else
|
|
||||||
{:cache, {:ok, socket}} ->
|
|
||||||
Logger.debug("fedsocket found in cache - #{inspect(address)}")
|
|
||||||
{:ok, socket}
|
|
||||||
|
|
||||||
{:cache, {:error, :rejected} = e} ->
|
|
||||||
e
|
|
||||||
|
|
||||||
{:connect, {:error, _host}} ->
|
|
||||||
Logger.debug("set host rejected for - #{inspect(address)}")
|
|
||||||
FedRegistry.set_host_rejected(address)
|
|
||||||
{:error, :rejected}
|
|
||||||
|
|
||||||
{_, {:error, :disabled}} ->
|
|
||||||
{:error, :disabled}
|
|
||||||
|
|
||||||
{_, {:error, reason}} ->
|
|
||||||
Logger.warn("get_or_create_fed_socket error - #{inspect(reason)}")
|
|
||||||
{:error, reason}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
returns a FedSocket for the given origin. Will not create a new FedSocket if one does not exist.
|
|
||||||
|
|
||||||
address is expected to be a fully formed URL such as:
|
|
||||||
"http://www.example.com" or "http://www.example.com:8080"
|
|
||||||
"""
|
|
||||||
def get_fed_socket(address) do
|
|
||||||
origin = SocketInfo.origin(address)
|
|
||||||
|
|
||||||
with {:config, true} <- {:config, Pleroma.Config.get([:fed_sockets, :enabled], false)},
|
|
||||||
{:ok, socket} <- FedRegistry.get_fed_socket(origin) do
|
|
||||||
{:ok, socket}
|
|
||||||
else
|
|
||||||
{:config, _} ->
|
|
||||||
{:error, :disabled}
|
|
||||||
|
|
||||||
{:error, :rejected} ->
|
|
||||||
Logger.debug("FedSocket previously rejected - #{inspect(origin)}")
|
|
||||||
{:error, :rejected}
|
|
||||||
|
|
||||||
{:error, reason} ->
|
|
||||||
{:error, reason}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
Sends the supplied data via the publish protocol.
|
|
||||||
It will not block waiting for a reply.
|
|
||||||
Returns :ok but this is not an indication of a successful transfer.
|
|
||||||
|
|
||||||
the data is expected to be JSON encoded binary data.
|
|
||||||
"""
|
|
||||||
def publish(%SocketInfo{} = fed_socket, json) do
|
|
||||||
FedSocket.publish(fed_socket, json)
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
Sends the supplied data via the fetch protocol.
|
|
||||||
It will block waiting for a reply or timeout.
|
|
||||||
|
|
||||||
Returns {:ok, object} where object is the requested object (or nil)
|
|
||||||
{:error, :timeout} in the event the message was not responded to
|
|
||||||
|
|
||||||
the id is expected to be the URI of an ActivityPub object.
|
|
||||||
"""
|
|
||||||
def fetch(%SocketInfo{} = fed_socket, id) do
|
|
||||||
FedSocket.fetch(fed_socket, id)
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
Disconnect all and restart FedSockets.
|
|
||||||
This is mainly used in development and testing but could be useful in production.
|
|
||||||
"""
|
|
||||||
def reset do
|
|
||||||
FedRegistry
|
|
||||||
|> Process.whereis()
|
|
||||||
|> Process.exit(:testing)
|
|
||||||
end
|
|
||||||
|
|
||||||
def uri_for_origin(origin),
|
|
||||||
do: "ws://#{origin}/api/fedsocket/v1"
|
|
||||||
end
|
|
|
@ -1,185 +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.FedSockets.FedRegistry do
|
|
||||||
@moduledoc """
|
|
||||||
The FedRegistry stores the active FedSockets for quick retrieval.
|
|
||||||
|
|
||||||
The storage and retrieval portion of the FedRegistry is done in process through
|
|
||||||
elixir's `Registry` module for speed and its ability to monitor for terminated processes.
|
|
||||||
|
|
||||||
Dropped connections will be caught by `Registry` and deleted. Since the next
|
|
||||||
message will initiate a new connection there is no reason to try and reconnect at that point.
|
|
||||||
|
|
||||||
Normally outside modules should have no need to call or use the FedRegistry themselves.
|
|
||||||
"""
|
|
||||||
|
|
||||||
alias Pleroma.Web.FedSockets.FedSocket
|
|
||||||
alias Pleroma.Web.FedSockets.SocketInfo
|
|
||||||
|
|
||||||
require Logger
|
|
||||||
|
|
||||||
@default_rejection_duration 15 * 60 * 1000
|
|
||||||
@rejections :fed_socket_rejections
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
Retrieves a FedSocket from the Registry given it's origin.
|
|
||||||
|
|
||||||
The origin is expected to be a string identifying the endpoint "example.com" or "example2.com:8080"
|
|
||||||
|
|
||||||
Will return:
|
|
||||||
* {:ok, fed_socket} for working FedSockets
|
|
||||||
* {:error, :rejected} for origins that have been tried and refused within the rejection duration interval
|
|
||||||
* {:error, some_reason} usually :missing for unknown origins
|
|
||||||
"""
|
|
||||||
def get_fed_socket(origin) do
|
|
||||||
case get_registry_data(origin) do
|
|
||||||
{:error, reason} ->
|
|
||||||
{:error, reason}
|
|
||||||
|
|
||||||
{:ok, %{state: :connected} = socket_info} ->
|
|
||||||
{:ok, socket_info}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
Adds a connected FedSocket to the Registry.
|
|
||||||
|
|
||||||
Always returns {:ok, fed_socket}
|
|
||||||
"""
|
|
||||||
def add_fed_socket(origin, pid \\ nil) do
|
|
||||||
origin
|
|
||||||
|> SocketInfo.build(pid)
|
|
||||||
|> SocketInfo.connect()
|
|
||||||
|> add_socket_info
|
|
||||||
end
|
|
||||||
|
|
||||||
defp add_socket_info(%{origin: origin, state: :connected} = socket_info) do
|
|
||||||
case Registry.register(FedSockets.Registry, origin, socket_info) do
|
|
||||||
{:ok, _owner} ->
|
|
||||||
clear_prior_rejection(origin)
|
|
||||||
Logger.debug("fedsocket added: #{inspect(origin)}")
|
|
||||||
|
|
||||||
{:ok, socket_info}
|
|
||||||
|
|
||||||
{:error, {:already_registered, _pid}} ->
|
|
||||||
FedSocket.close(socket_info)
|
|
||||||
existing_socket_info = Registry.lookup(FedSockets.Registry, origin)
|
|
||||||
|
|
||||||
{:ok, existing_socket_info}
|
|
||||||
|
|
||||||
_ ->
|
|
||||||
{:error, :error_adding_socket}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
Mark this origin as having rejected a connection attempt.
|
|
||||||
This will keep it from getting additional connection attempts
|
|
||||||
for a period of time specified in the config.
|
|
||||||
|
|
||||||
Always returns {:ok, new_reg_data}
|
|
||||||
"""
|
|
||||||
def set_host_rejected(uri) do
|
|
||||||
new_reg_data =
|
|
||||||
uri
|
|
||||||
|> SocketInfo.origin()
|
|
||||||
|> get_or_create_registry_data()
|
|
||||||
|> set_to_rejected()
|
|
||||||
|> save_registry_data()
|
|
||||||
|
|
||||||
{:ok, new_reg_data}
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
Retrieves the FedRegistryData from the Registry given it's origin.
|
|
||||||
|
|
||||||
The origin is expected to be a string identifying the endpoint "example.com" or "example2.com:8080"
|
|
||||||
|
|
||||||
Will return:
|
|
||||||
* {:ok, fed_registry_data} for known origins
|
|
||||||
* {:error, :missing} for uniknown origins
|
|
||||||
* {:error, :cache_error} indicating some low level runtime issues
|
|
||||||
"""
|
|
||||||
def get_registry_data(origin) do
|
|
||||||
case Registry.lookup(FedSockets.Registry, origin) do
|
|
||||||
[] ->
|
|
||||||
if is_rejected?(origin) do
|
|
||||||
Logger.debug("previously rejected fedsocket requested")
|
|
||||||
{:error, :rejected}
|
|
||||||
else
|
|
||||||
{:error, :missing}
|
|
||||||
end
|
|
||||||
|
|
||||||
[{_pid, %{state: :connected} = socket_info}] ->
|
|
||||||
{:ok, socket_info}
|
|
||||||
|
|
||||||
_ ->
|
|
||||||
{:error, :cache_error}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
Retrieves a map of all sockets from the Registry. The keys are the origins and the values are the corresponding SocketInfo
|
|
||||||
"""
|
|
||||||
def list_all do
|
|
||||||
(list_all_connected() ++ list_all_rejected())
|
|
||||||
|> Enum.into(%{})
|
|
||||||
end
|
|
||||||
|
|
||||||
defp list_all_connected do
|
|
||||||
FedSockets.Registry
|
|
||||||
|> Registry.select([{{:"$1", :_, :"$3"}, [], [{{:"$1", :"$3"}}]}])
|
|
||||||
end
|
|
||||||
|
|
||||||
defp list_all_rejected do
|
|
||||||
{:ok, keys} = Cachex.keys(@rejections)
|
|
||||||
|
|
||||||
{:ok, registry_data} =
|
|
||||||
Cachex.execute(@rejections, fn worker ->
|
|
||||||
Enum.map(keys, fn k -> {k, Cachex.get!(worker, k)} end)
|
|
||||||
end)
|
|
||||||
|
|
||||||
registry_data
|
|
||||||
end
|
|
||||||
|
|
||||||
defp clear_prior_rejection(origin),
|
|
||||||
do: Cachex.del(@rejections, origin)
|
|
||||||
|
|
||||||
defp is_rejected?(origin) do
|
|
||||||
case Cachex.get(@rejections, origin) do
|
|
||||||
{:ok, nil} ->
|
|
||||||
false
|
|
||||||
|
|
||||||
{:ok, _} ->
|
|
||||||
true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp get_or_create_registry_data(origin) do
|
|
||||||
case get_registry_data(origin) do
|
|
||||||
{:error, :missing} ->
|
|
||||||
%SocketInfo{origin: origin}
|
|
||||||
|
|
||||||
{:ok, socket_info} ->
|
|
||||||
socket_info
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp save_registry_data(%SocketInfo{origin: origin, state: :connected} = socket_info) do
|
|
||||||
{:ok, true} = Registry.update_value(FedSockets.Registry, origin, fn _ -> socket_info end)
|
|
||||||
socket_info
|
|
||||||
end
|
|
||||||
|
|
||||||
defp save_registry_data(%SocketInfo{origin: origin, state: :rejected} = socket_info) do
|
|
||||||
rejection_expiration =
|
|
||||||
Pleroma.Config.get([:fed_sockets, :rejection_duration], @default_rejection_duration)
|
|
||||||
|
|
||||||
{:ok, true} = Cachex.put(@rejections, origin, socket_info, ttl: rejection_expiration)
|
|
||||||
socket_info
|
|
||||||
end
|
|
||||||
|
|
||||||
defp set_to_rejected(%SocketInfo{} = socket_info),
|
|
||||||
do: %SocketInfo{socket_info | state: :rejected}
|
|
||||||
end
|
|
|
@ -1,137 +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.FedSockets.FedSocket do
|
|
||||||
@moduledoc """
|
|
||||||
The FedSocket module abstracts the actions to be taken taken on connections regardless of
|
|
||||||
whether the connection started as inbound or outbound.
|
|
||||||
|
|
||||||
|
|
||||||
Normally outside modules will have no need to call the FedSocket module directly.
|
|
||||||
"""
|
|
||||||
|
|
||||||
alias Pleroma.Object
|
|
||||||
alias Pleroma.Object.Containment
|
|
||||||
alias Pleroma.User
|
|
||||||
alias Pleroma.Web.ActivityPub.ObjectView
|
|
||||||
alias Pleroma.Web.ActivityPub.UserView
|
|
||||||
alias Pleroma.Web.ActivityPub.Visibility
|
|
||||||
alias Pleroma.Web.FedSockets.FetchRegistry
|
|
||||||
alias Pleroma.Web.FedSockets.IngesterWorker
|
|
||||||
alias Pleroma.Web.FedSockets.OutgoingHandler
|
|
||||||
alias Pleroma.Web.FedSockets.SocketInfo
|
|
||||||
|
|
||||||
require Logger
|
|
||||||
|
|
||||||
@shake "61dd18f7-f1e6-49a4-939a-a749fcdc1103"
|
|
||||||
|
|
||||||
def connect_to_host(uri) do
|
|
||||||
case OutgoingHandler.start_link(uri) do
|
|
||||||
{:ok, pid} ->
|
|
||||||
{:ok, pid}
|
|
||||||
|
|
||||||
error ->
|
|
||||||
{:error, error}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def close(%SocketInfo{pid: socket_pid}),
|
|
||||||
do: Process.send(socket_pid, :close, [])
|
|
||||||
|
|
||||||
def publish(%SocketInfo{pid: socket_pid}, json) do
|
|
||||||
%{action: :publish, data: json}
|
|
||||||
|> Jason.encode!()
|
|
||||||
|> send_packet(socket_pid)
|
|
||||||
end
|
|
||||||
|
|
||||||
def fetch(%SocketInfo{pid: socket_pid}, id) do
|
|
||||||
fetch_uuid = FetchRegistry.register_fetch(id)
|
|
||||||
|
|
||||||
%{action: :fetch, data: id, uuid: fetch_uuid}
|
|
||||||
|> Jason.encode!()
|
|
||||||
|> send_packet(socket_pid)
|
|
||||||
|
|
||||||
wait_for_fetch_to_return(fetch_uuid, 0)
|
|
||||||
end
|
|
||||||
|
|
||||||
def receive_package(%SocketInfo{} = fed_socket, json) do
|
|
||||||
json
|
|
||||||
|> Jason.decode!()
|
|
||||||
|> process_package(fed_socket)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp wait_for_fetch_to_return(uuid, cntr) do
|
|
||||||
case FetchRegistry.check_fetch(uuid) do
|
|
||||||
{:error, :waiting} ->
|
|
||||||
Process.sleep(:math.pow(cntr, 3) |> Kernel.trunc())
|
|
||||||
wait_for_fetch_to_return(uuid, cntr + 1)
|
|
||||||
|
|
||||||
{:error, :missing} ->
|
|
||||||
Logger.error("FedSocket fetch timed out - #{inspect(uuid)}")
|
|
||||||
{:error, :timeout}
|
|
||||||
|
|
||||||
{:ok, _fr} ->
|
|
||||||
FetchRegistry.pop_fetch(uuid)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp process_package(%{"action" => "publish", "data" => data}, %{origin: origin} = _fed_socket) do
|
|
||||||
if Containment.contain_origin(origin, data) do
|
|
||||||
IngesterWorker.enqueue("ingest", %{"object" => data})
|
|
||||||
end
|
|
||||||
|
|
||||||
{:reply, %{"action" => "publish_reply", "status" => "processed"}}
|
|
||||||
end
|
|
||||||
|
|
||||||
defp process_package(%{"action" => "fetch_reply", "uuid" => uuid, "data" => data}, _fed_socket) do
|
|
||||||
FetchRegistry.register_fetch_received(uuid, data)
|
|
||||||
{:noreply, nil}
|
|
||||||
end
|
|
||||||
|
|
||||||
defp process_package(%{"action" => "fetch", "uuid" => uuid, "data" => ap_id}, _fed_socket) do
|
|
||||||
{:ok, data} = render_fetched_data(ap_id, uuid)
|
|
||||||
{:reply, data}
|
|
||||||
end
|
|
||||||
|
|
||||||
defp process_package(%{"action" => "publish_reply"}, _fed_socket) do
|
|
||||||
{:noreply, nil}
|
|
||||||
end
|
|
||||||
|
|
||||||
defp process_package(other, _fed_socket) do
|
|
||||||
Logger.warn("unknown json packages received #{inspect(other)}")
|
|
||||||
{:noreply, nil}
|
|
||||||
end
|
|
||||||
|
|
||||||
defp render_fetched_data(ap_id, uuid) do
|
|
||||||
{:ok,
|
|
||||||
%{
|
|
||||||
"action" => "fetch_reply",
|
|
||||||
"status" => "processed",
|
|
||||||
"uuid" => uuid,
|
|
||||||
"data" => represent_item(ap_id)
|
|
||||||
}}
|
|
||||||
end
|
|
||||||
|
|
||||||
defp represent_item(ap_id) do
|
|
||||||
case User.get_by_ap_id(ap_id) do
|
|
||||||
nil ->
|
|
||||||
object = Object.get_cached_by_ap_id(ap_id)
|
|
||||||
|
|
||||||
if Visibility.is_public?(object) do
|
|
||||||
Phoenix.View.render_to_string(ObjectView, "object.json", object: object)
|
|
||||||
else
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
|
|
||||||
user ->
|
|
||||||
Phoenix.View.render_to_string(UserView, "user.json", user: user)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp send_packet(data, socket_pid) do
|
|
||||||
Process.send(socket_pid, {:send, data}, [])
|
|
||||||
end
|
|
||||||
|
|
||||||
def shake, do: @shake
|
|
||||||
end
|
|
|
@ -1,151 +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.FedSockets.FetchRegistry do
|
|
||||||
@moduledoc """
|
|
||||||
The FetchRegistry acts as a broker for fetch requests and return values.
|
|
||||||
This allows calling processes to block while waiting for a reply.
|
|
||||||
It doesn't impose it's own process instead using `Cachex` to handle fetches in process, allowing
|
|
||||||
multi threaded processes to avoid bottlenecking.
|
|
||||||
|
|
||||||
Normally outside modules will have no need to call or use the FetchRegistry themselves.
|
|
||||||
|
|
||||||
The `Cachex` parameters can be controlled from the config. Since exact timeout intervals
|
|
||||||
aren't necessary the following settings are used by default:
|
|
||||||
|
|
||||||
config :pleroma, :fed_sockets,
|
|
||||||
fed_socket_fetches: [
|
|
||||||
default: 12_000,
|
|
||||||
interval: 3_000,
|
|
||||||
lazy: false
|
|
||||||
]
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
defmodule FetchRegistryData do
|
|
||||||
defstruct uuid: nil,
|
|
||||||
sent_json: nil,
|
|
||||||
received_json: nil,
|
|
||||||
sent_at: nil,
|
|
||||||
received_at: nil
|
|
||||||
end
|
|
||||||
|
|
||||||
alias Ecto.UUID
|
|
||||||
|
|
||||||
require Logger
|
|
||||||
|
|
||||||
@fetches :fed_socket_fetches
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
Registers a json request wth the FetchRegistry and returns the identifying UUID.
|
|
||||||
"""
|
|
||||||
def register_fetch(json) do
|
|
||||||
%FetchRegistryData{uuid: uuid} =
|
|
||||||
json
|
|
||||||
|> new_registry_data
|
|
||||||
|> save_registry_data
|
|
||||||
|
|
||||||
uuid
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
Reports on the status of a Fetch given the identifying UUID.
|
|
||||||
|
|
||||||
Will return
|
|
||||||
* {:ok, fetched_object} if a fetch has completed
|
|
||||||
* {:error, :waiting} if a fetch is still pending
|
|
||||||
* {:error, other_error} usually :missing to indicate a fetch that has timed out
|
|
||||||
"""
|
|
||||||
def check_fetch(uuid) do
|
|
||||||
case get_registry_data(uuid) do
|
|
||||||
{:ok, %FetchRegistryData{received_at: nil}} ->
|
|
||||||
{:error, :waiting}
|
|
||||||
|
|
||||||
{:ok, %FetchRegistryData{} = reg_data} ->
|
|
||||||
{:ok, reg_data}
|
|
||||||
|
|
||||||
e ->
|
|
||||||
e
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
Retrieves the response to a fetch given the identifying UUID.
|
|
||||||
The completed fetch will be deleted from the FetchRegistry
|
|
||||||
|
|
||||||
Will return
|
|
||||||
* {:ok, fetched_object} if a fetch has completed
|
|
||||||
* {:error, :waiting} if a fetch is still pending
|
|
||||||
* {:error, other_error} usually :missing to indicate a fetch that has timed out
|
|
||||||
"""
|
|
||||||
def pop_fetch(uuid) do
|
|
||||||
case check_fetch(uuid) do
|
|
||||||
{:ok, %FetchRegistryData{received_json: received_json}} ->
|
|
||||||
delete_registry_data(uuid)
|
|
||||||
{:ok, received_json}
|
|
||||||
|
|
||||||
e ->
|
|
||||||
e
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
This is called to register a fetch has returned.
|
|
||||||
It expects the result data along with the UUID that was sent in the request
|
|
||||||
|
|
||||||
Will return the fetched object or :error
|
|
||||||
"""
|
|
||||||
def register_fetch_received(uuid, data) do
|
|
||||||
case get_registry_data(uuid) do
|
|
||||||
{:ok, %FetchRegistryData{received_at: nil} = reg_data} ->
|
|
||||||
reg_data
|
|
||||||
|> set_fetch_received(data)
|
|
||||||
|> save_registry_data()
|
|
||||||
|
|
||||||
{:ok, %FetchRegistryData{} = reg_data} ->
|
|
||||||
Logger.warn("tried to add fetched data twice - #{uuid}")
|
|
||||||
reg_data
|
|
||||||
|
|
||||||
{:error, _} ->
|
|
||||||
Logger.warn("Error adding fetch to registry - #{uuid}")
|
|
||||||
:error
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp new_registry_data(json) do
|
|
||||||
%FetchRegistryData{
|
|
||||||
uuid: UUID.generate(),
|
|
||||||
sent_json: json,
|
|
||||||
sent_at: :erlang.monotonic_time(:millisecond)
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
defp get_registry_data(origin) do
|
|
||||||
case Cachex.get(@fetches, origin) do
|
|
||||||
{:ok, nil} ->
|
|
||||||
{:error, :missing}
|
|
||||||
|
|
||||||
{:ok, reg_data} ->
|
|
||||||
{:ok, reg_data}
|
|
||||||
|
|
||||||
_ ->
|
|
||||||
{:error, :cache_error}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp set_fetch_received(%FetchRegistryData{} = reg_data, data),
|
|
||||||
do: %FetchRegistryData{
|
|
||||||
reg_data
|
|
||||||
| received_at: :erlang.monotonic_time(:millisecond),
|
|
||||||
received_json: data
|
|
||||||
}
|
|
||||||
|
|
||||||
defp save_registry_data(%FetchRegistryData{uuid: uuid} = reg_data) do
|
|
||||||
{:ok, true} = Cachex.put(@fetches, uuid, reg_data)
|
|
||||||
reg_data
|
|
||||||
end
|
|
||||||
|
|
||||||
defp delete_registry_data(origin),
|
|
||||||
do: {:ok, true} = Cachex.del(@fetches, origin)
|
|
||||||
end
|
|
|
@ -1,88 +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.FedSockets.IncomingHandler do
|
|
||||||
require Logger
|
|
||||||
|
|
||||||
alias Pleroma.Web.FedSockets.FedRegistry
|
|
||||||
alias Pleroma.Web.FedSockets.FedSocket
|
|
||||||
alias Pleroma.Web.FedSockets.SocketInfo
|
|
||||||
|
|
||||||
import HTTPSignatures, only: [validate_conn: 1, split_signature: 1]
|
|
||||||
|
|
||||||
@behaviour :cowboy_websocket
|
|
||||||
|
|
||||||
def init(req, state) do
|
|
||||||
shake = FedSocket.shake()
|
|
||||||
|
|
||||||
with true <- Pleroma.Config.get([:fed_sockets, :enabled]),
|
|
||||||
sec_protocol <- :cowboy_req.header("sec-websocket-protocol", req, nil),
|
|
||||||
headers = %{"(request-target)" => ^shake} <- :cowboy_req.headers(req),
|
|
||||||
true <- validate_conn(%{req_headers: headers}),
|
|
||||||
%{"keyId" => origin} <- split_signature(headers["signature"]) do
|
|
||||||
req =
|
|
||||||
if is_nil(sec_protocol) do
|
|
||||||
req
|
|
||||||
else
|
|
||||||
:cowboy_req.set_resp_header("sec-websocket-protocol", sec_protocol, req)
|
|
||||||
end
|
|
||||||
|
|
||||||
{:cowboy_websocket, req, %{origin: origin}, %{}}
|
|
||||||
else
|
|
||||||
_ ->
|
|
||||||
{:ok, req, state}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def websocket_init(%{origin: origin}) do
|
|
||||||
case FedRegistry.add_fed_socket(origin) do
|
|
||||||
{:ok, socket_info} ->
|
|
||||||
{:ok, socket_info}
|
|
||||||
|
|
||||||
e ->
|
|
||||||
Logger.error("FedSocket websocket_init failed - #{inspect(e)}")
|
|
||||||
{:error, inspect(e)}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Use the ping to check if the connection should be expired
|
|
||||||
def websocket_handle(:ping, socket_info) do
|
|
||||||
if SocketInfo.expired?(socket_info) do
|
|
||||||
{:stop, socket_info}
|
|
||||||
else
|
|
||||||
{:ok, socket_info, :hibernate}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def websocket_handle({:text, data}, socket_info) do
|
|
||||||
socket_info = SocketInfo.touch(socket_info)
|
|
||||||
|
|
||||||
case FedSocket.receive_package(socket_info, data) do
|
|
||||||
{:noreply, _} ->
|
|
||||||
{:ok, socket_info}
|
|
||||||
|
|
||||||
{:reply, reply} ->
|
|
||||||
{:reply, {:text, Jason.encode!(reply)}, socket_info}
|
|
||||||
|
|
||||||
{:error, reason} ->
|
|
||||||
Logger.error("incoming error - receive_package: #{inspect(reason)}")
|
|
||||||
{:ok, socket_info}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def websocket_info({:send, message}, socket_info) do
|
|
||||||
socket_info = SocketInfo.touch(socket_info)
|
|
||||||
|
|
||||||
{:reply, {:text, message}, socket_info}
|
|
||||||
end
|
|
||||||
|
|
||||||
def websocket_info(:close, state) do
|
|
||||||
{:stop, state}
|
|
||||||
end
|
|
||||||
|
|
||||||
def websocket_info(message, state) do
|
|
||||||
Logger.debug("#{__MODULE__} unknown message #{inspect(message)}")
|
|
||||||
{:ok, state}
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,33 +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.FedSockets.IngesterWorker do
|
|
||||||
use Pleroma.Workers.WorkerHelper, queue: "ingestion_queue"
|
|
||||||
require Logger
|
|
||||||
|
|
||||||
alias Pleroma.Web.Federator
|
|
||||||
|
|
||||||
@impl Oban.Worker
|
|
||||||
def perform(%Job{args: %{"op" => "ingest", "object" => ingestee}}) do
|
|
||||||
try do
|
|
||||||
ingestee
|
|
||||||
|> Jason.decode!()
|
|
||||||
|> do_ingestion()
|
|
||||||
rescue
|
|
||||||
e ->
|
|
||||||
Logger.error("IngesterWorker error - #{inspect(e)}")
|
|
||||||
e
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp do_ingestion(params) do
|
|
||||||
case Federator.incoming_ap_doc(params) do
|
|
||||||
{:error, reason} ->
|
|
||||||
{:error, reason}
|
|
||||||
|
|
||||||
{:ok, object} ->
|
|
||||||
{:ok, object}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,151 +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.FedSockets.OutgoingHandler do
|
|
||||||
use GenServer
|
|
||||||
|
|
||||||
require Logger
|
|
||||||
|
|
||||||
alias Pleroma.Application
|
|
||||||
alias Pleroma.Web.ActivityPub.InternalFetchActor
|
|
||||||
alias Pleroma.Web.FedSockets
|
|
||||||
alias Pleroma.Web.FedSockets.FedRegistry
|
|
||||||
alias Pleroma.Web.FedSockets.FedSocket
|
|
||||||
alias Pleroma.Web.FedSockets.SocketInfo
|
|
||||||
|
|
||||||
def start_link(uri) do
|
|
||||||
GenServer.start_link(__MODULE__, %{uri: uri})
|
|
||||||
end
|
|
||||||
|
|
||||||
def init(%{uri: uri}) do
|
|
||||||
case initiate_connection(uri) do
|
|
||||||
{:ok, ws_origin, conn_pid} ->
|
|
||||||
FedRegistry.add_fed_socket(ws_origin, conn_pid)
|
|
||||||
|
|
||||||
{:error, reason} ->
|
|
||||||
Logger.debug("Outgoing connection failed - #{inspect(reason)}")
|
|
||||||
:ignore
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_info({:gun_ws, conn_pid, _ref, {:text, data}}, socket_info) do
|
|
||||||
socket_info = SocketInfo.touch(socket_info)
|
|
||||||
|
|
||||||
case FedSocket.receive_package(socket_info, data) do
|
|
||||||
{:noreply, _} ->
|
|
||||||
{:noreply, socket_info}
|
|
||||||
|
|
||||||
{:reply, reply} ->
|
|
||||||
:gun.ws_send(conn_pid, {:text, Jason.encode!(reply)})
|
|
||||||
{:noreply, socket_info}
|
|
||||||
|
|
||||||
{:error, reason} ->
|
|
||||||
Logger.error("incoming error - receive_package: #{inspect(reason)}")
|
|
||||||
{:noreply, socket_info}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_info(:close, state) do
|
|
||||||
Logger.debug("Sending close frame !!!!!!!")
|
|
||||||
{:close, state}
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_info({:gun_down, _pid, _prot, :closed, _}, state) do
|
|
||||||
{:stop, :normal, state}
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_info({:send, data}, %{conn_pid: conn_pid} = socket_info) do
|
|
||||||
socket_info = SocketInfo.touch(socket_info)
|
|
||||||
:gun.ws_send(conn_pid, {:text, data})
|
|
||||||
{:noreply, socket_info}
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_info({:gun_ws, _, _, :pong}, state) do
|
|
||||||
{:noreply, state, :hibernate}
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_info(msg, state) do
|
|
||||||
Logger.debug("#{__MODULE__} unhandled event #{inspect(msg)}")
|
|
||||||
{:noreply, state}
|
|
||||||
end
|
|
||||||
|
|
||||||
def terminate(reason, state) do
|
|
||||||
Logger.debug(
|
|
||||||
"#{__MODULE__} terminating outgoing connection for #{inspect(state)} for #{inspect(reason)}"
|
|
||||||
)
|
|
||||||
|
|
||||||
{:ok, state}
|
|
||||||
end
|
|
||||||
|
|
||||||
def initiate_connection(uri) do
|
|
||||||
ws_uri =
|
|
||||||
uri
|
|
||||||
|> SocketInfo.origin()
|
|
||||||
|> FedSockets.uri_for_origin()
|
|
||||||
|
|
||||||
%{host: host, port: port, path: path} = URI.parse(ws_uri)
|
|
||||||
|
|
||||||
with {:ok, conn_pid} <- :gun.open(to_charlist(host), port, %{protocols: [:http]}),
|
|
||||||
{:ok, _} <- :gun.await_up(conn_pid),
|
|
||||||
reference <-
|
|
||||||
:gun.get(conn_pid, to_charlist(path), [
|
|
||||||
{'user-agent', to_charlist(Application.user_agent())}
|
|
||||||
]),
|
|
||||||
{:response, :fin, 204, _} <- :gun.await(conn_pid, reference),
|
|
||||||
headers <- build_headers(uri),
|
|
||||||
ref <- :gun.ws_upgrade(conn_pid, to_charlist(path), headers, %{silence_pings: false}) do
|
|
||||||
receive do
|
|
||||||
{:gun_upgrade, ^conn_pid, ^ref, [<<"websocket">>], _} ->
|
|
||||||
{:ok, ws_uri, conn_pid}
|
|
||||||
after
|
|
||||||
15_000 ->
|
|
||||||
Logger.debug("Fedsocket timeout connecting to #{inspect(uri)}")
|
|
||||||
{:error, :timeout}
|
|
||||||
end
|
|
||||||
else
|
|
||||||
{:response, :nofin, 404, _} ->
|
|
||||||
{:error, :fedsockets_not_supported}
|
|
||||||
|
|
||||||
e ->
|
|
||||||
Logger.debug("Fedsocket error connecting to #{inspect(uri)}")
|
|
||||||
{:error, e}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp build_headers(uri) do
|
|
||||||
host_for_sig = uri |> URI.parse() |> host_signature()
|
|
||||||
|
|
||||||
shake = FedSocket.shake()
|
|
||||||
digest = "SHA-256=" <> (:crypto.hash(:sha256, shake) |> Base.encode64())
|
|
||||||
date = Pleroma.Signature.signed_date()
|
|
||||||
shake_size = byte_size(shake)
|
|
||||||
|
|
||||||
signature_opts = %{
|
|
||||||
"(request-target)": shake,
|
|
||||||
"content-length": to_charlist("#{shake_size}"),
|
|
||||||
date: date,
|
|
||||||
digest: digest,
|
|
||||||
host: host_for_sig
|
|
||||||
}
|
|
||||||
|
|
||||||
signature = Pleroma.Signature.sign(InternalFetchActor.get_actor(), signature_opts)
|
|
||||||
|
|
||||||
[
|
|
||||||
{'signature', to_charlist(signature)},
|
|
||||||
{'date', date},
|
|
||||||
{'digest', to_charlist(digest)},
|
|
||||||
{'content-length', to_charlist("#{shake_size}")},
|
|
||||||
{to_charlist("(request-target)"), to_charlist(shake)},
|
|
||||||
{'user-agent', to_charlist(Application.user_agent())}
|
|
||||||
]
|
|
||||||
end
|
|
||||||
|
|
||||||
defp host_signature(%{host: host, scheme: scheme, port: port}) do
|
|
||||||
if port == URI.default_port(scheme) do
|
|
||||||
host
|
|
||||||
else
|
|
||||||
"#{host}:#{port}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,52 +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.FedSockets.SocketInfo do
|
|
||||||
defstruct origin: nil,
|
|
||||||
pid: nil,
|
|
||||||
conn_pid: nil,
|
|
||||||
state: :default,
|
|
||||||
connected_until: nil
|
|
||||||
|
|
||||||
alias Pleroma.Web.FedSockets.SocketInfo
|
|
||||||
@default_connection_duration 15 * 60 * 1000
|
|
||||||
|
|
||||||
def build(uri, conn_pid \\ nil) do
|
|
||||||
uri
|
|
||||||
|> build_origin()
|
|
||||||
|> build_pids(conn_pid)
|
|
||||||
|> touch()
|
|
||||||
end
|
|
||||||
|
|
||||||
def touch(%SocketInfo{} = socket_info),
|
|
||||||
do: %{socket_info | connected_until: new_ttl()}
|
|
||||||
|
|
||||||
def connect(%SocketInfo{} = socket_info),
|
|
||||||
do: %{socket_info | state: :connected}
|
|
||||||
|
|
||||||
def expired?(%{connected_until: connected_until}),
|
|
||||||
do: connected_until < :erlang.monotonic_time(:millisecond)
|
|
||||||
|
|
||||||
def origin(uri),
|
|
||||||
do: build_origin(uri).origin
|
|
||||||
|
|
||||||
defp build_pids(socket_info, conn_pid),
|
|
||||||
do: struct(socket_info, pid: self(), conn_pid: conn_pid)
|
|
||||||
|
|
||||||
defp build_origin(uri) when is_binary(uri),
|
|
||||||
do: uri |> URI.parse() |> build_origin
|
|
||||||
|
|
||||||
defp build_origin(%{host: host, port: nil, scheme: scheme}),
|
|
||||||
do: build_origin(%{host: host, port: URI.default_port(scheme)})
|
|
||||||
|
|
||||||
defp build_origin(%{host: host, port: port}),
|
|
||||||
do: %SocketInfo{origin: "#{host}:#{port}"}
|
|
||||||
|
|
||||||
defp new_ttl do
|
|
||||||
connection_duration =
|
|
||||||
Pleroma.Config.get([:fed_sockets, :connection_duration], @default_connection_duration)
|
|
||||||
|
|
||||||
:erlang.monotonic_time(:millisecond) + connection_duration
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,59 +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.FedSockets.Supervisor do
|
|
||||||
use Supervisor
|
|
||||||
import Cachex.Spec
|
|
||||||
|
|
||||||
def start_link(opts) do
|
|
||||||
Supervisor.start_link(__MODULE__, opts, name: __MODULE__)
|
|
||||||
end
|
|
||||||
|
|
||||||
def init(args) do
|
|
||||||
children = [
|
|
||||||
build_cache(:fed_socket_fetches, args),
|
|
||||||
build_cache(:fed_socket_rejections, args),
|
|
||||||
{Registry, keys: :unique, name: FedSockets.Registry, meta: [rejected: %{}]}
|
|
||||||
]
|
|
||||||
|
|
||||||
opts = [strategy: :one_for_all, name: Pleroma.Web.Streamer.Supervisor]
|
|
||||||
Supervisor.init(children, opts)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp build_cache(name, args) do
|
|
||||||
opts = get_opts(name, args)
|
|
||||||
|
|
||||||
%{
|
|
||||||
id: String.to_atom("#{name}_cache"),
|
|
||||||
start: {Cachex, :start_link, [name, opts]},
|
|
||||||
type: :worker
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
defp get_opts(cache_name, args)
|
|
||||||
when cache_name in [:fed_socket_fetches, :fed_socket_rejections] do
|
|
||||||
default = get_opts_or_config(args, cache_name, :default, 15_000)
|
|
||||||
interval = get_opts_or_config(args, cache_name, :interval, 3_000)
|
|
||||||
lazy = get_opts_or_config(args, cache_name, :lazy, false)
|
|
||||||
|
|
||||||
[expiration: expiration(default: default, interval: interval, lazy: lazy)]
|
|
||||||
end
|
|
||||||
|
|
||||||
defp get_opts(name, args) do
|
|
||||||
Keyword.get(args, name, [])
|
|
||||||
end
|
|
||||||
|
|
||||||
defp get_opts_or_config(args, name, key, default) do
|
|
||||||
args
|
|
||||||
|> Keyword.get(name, [])
|
|
||||||
|> Keyword.get(key)
|
|
||||||
|> case do
|
|
||||||
nil ->
|
|
||||||
Pleroma.Config.get([:fed_sockets, name, key], default)
|
|
||||||
|
|
||||||
value ->
|
|
||||||
value
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -208,7 +208,9 @@ def update_credentials(%{assigns: %{user: user}, body_params: params} = conn, _p
|
||||||
if bot, do: {:ok, "Service"}, else: {:ok, "Person"}
|
if bot, do: {:ok, "Service"}, else: {:ok, "Person"}
|
||||||
end)
|
end)
|
||||||
|> Maps.put_if_present(:actor_type, params[:actor_type])
|
|> Maps.put_if_present(:actor_type, params[:actor_type])
|
||||||
|
# Note: param name is indeed :locked (not an error)
|
||||||
|> Maps.put_if_present(:is_locked, params[:locked])
|
|> Maps.put_if_present(:is_locked, params[:locked])
|
||||||
|
# Note: param name is indeed :discoverable (not an error)
|
||||||
|> Maps.put_if_present(:is_discoverable, params[:discoverable])
|
|> Maps.put_if_present(:is_discoverable, params[:discoverable])
|
||||||
|
|
||||||
# What happens here:
|
# What happens here:
|
||||||
|
@ -292,7 +294,8 @@ def statuses(%{assigns: %{user: reading_user}} = conn, params) do
|
||||||
|> render("index.json",
|
|> render("index.json",
|
||||||
activities: activities,
|
activities: activities,
|
||||||
for: reading_user,
|
for: reading_user,
|
||||||
as: :activity
|
as: :activity,
|
||||||
|
with_muted: Map.get(params, :with_muted, false)
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
error -> user_visibility_error(conn, error)
|
error -> user_visibility_error(conn, error)
|
||||||
|
|
|
@ -109,7 +109,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
|
||||||
|
|
||||||
`ids` query param is required
|
`ids` query param is required
|
||||||
"""
|
"""
|
||||||
def index(%{assigns: %{user: user}} = conn, %{ids: ids} = _params) do
|
def index(%{assigns: %{user: user}} = conn, %{ids: ids} = params) do
|
||||||
limit = 100
|
limit = 100
|
||||||
|
|
||||||
activities =
|
activities =
|
||||||
|
@ -121,7 +121,8 @@ def index(%{assigns: %{user: user}} = conn, %{ids: ids} = _params) do
|
||||||
render(conn, "index.json",
|
render(conn, "index.json",
|
||||||
activities: activities,
|
activities: activities,
|
||||||
for: user,
|
for: user,
|
||||||
as: :activity
|
as: :activity,
|
||||||
|
with_muted: Map.get(params, :with_muted, false)
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -189,13 +190,14 @@ def create(%{assigns: %{user: _user}, body_params: %{media_ids: _} = params} = c
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc "GET /api/v1/statuses/:id"
|
@doc "GET /api/v1/statuses/:id"
|
||||||
def show(%{assigns: %{user: user}} = conn, %{id: id}) do
|
def show(%{assigns: %{user: user}} = conn, %{id: id} = params) do
|
||||||
with %Activity{} = activity <- Activity.get_by_id_with_object(id),
|
with %Activity{} = activity <- Activity.get_by_id_with_object(id),
|
||||||
true <- Visibility.visible_for_user?(activity, user) do
|
true <- Visibility.visible_for_user?(activity, user) do
|
||||||
try_render(conn, "show.json",
|
try_render(conn, "show.json",
|
||||||
activity: activity,
|
activity: activity,
|
||||||
for: user,
|
for: user,
|
||||||
with_direct_conversation_id: true
|
with_direct_conversation_id: true,
|
||||||
|
with_muted: Map.get(params, :with_muted, false)
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
_ -> {:error, :not_found}
|
_ -> {:error, :not_found}
|
||||||
|
|
|
@ -62,7 +62,8 @@ def home(%{assigns: %{user: user}} = conn, params) do
|
||||||
|> render("index.json",
|
|> render("index.json",
|
||||||
activities: activities,
|
activities: activities,
|
||||||
for: user,
|
for: user,
|
||||||
as: :activity
|
as: :activity,
|
||||||
|
with_muted: Map.get(params, :with_muted, false)
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -119,7 +120,8 @@ def public(%{assigns: %{user: user}} = conn, params) do
|
||||||
|> render("index.json",
|
|> render("index.json",
|
||||||
activities: activities,
|
activities: activities,
|
||||||
for: user,
|
for: user,
|
||||||
as: :activity
|
as: :activity,
|
||||||
|
with_muted: Map.get(params, :with_muted, false)
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -173,7 +175,8 @@ def hashtag(%{assigns: %{user: user}} = conn, params) do
|
||||||
|> render("index.json",
|
|> render("index.json",
|
||||||
activities: activities,
|
activities: activities,
|
||||||
for: user,
|
for: user,
|
||||||
as: :activity
|
as: :activity,
|
||||||
|
with_muted: Map.get(params, :with_muted, false)
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -202,7 +205,8 @@ def list(%{assigns: %{user: user}} = conn, %{list_id: id} = params) do
|
||||||
render(conn, "index.json",
|
render(conn, "index.json",
|
||||||
activities: activities,
|
activities: activities,
|
||||||
for: user,
|
for: user,
|
||||||
as: :activity
|
as: :activity,
|
||||||
|
with_muted: Map.get(params, :with_muted, false)
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
_e -> render_error(conn, :forbidden, "Error.")
|
_e -> render_error(conn, :forbidden, "Error.")
|
||||||
|
|
|
@ -23,7 +23,7 @@ def render("show.json", _) do
|
||||||
streaming_api: Pleroma.Web.Endpoint.websocket_url()
|
streaming_api: Pleroma.Web.Endpoint.websocket_url()
|
||||||
},
|
},
|
||||||
stats: Pleroma.Stats.get_stats(),
|
stats: Pleroma.Stats.get_stats(),
|
||||||
thumbnail: Keyword.get(instance, :instance_thumbnail),
|
thumbnail: Pleroma.Web.base_url() <> Keyword.get(instance, :instance_thumbnail),
|
||||||
languages: ["en"],
|
languages: ["en"],
|
||||||
registrations: Keyword.get(instance, :registrations_open),
|
registrations: Keyword.get(instance, :registrations_open),
|
||||||
approval_required: Keyword.get(instance, :account_approval_required),
|
approval_required: Keyword.get(instance, :account_approval_required),
|
||||||
|
@ -34,7 +34,7 @@ def render("show.json", _) do
|
||||||
avatar_upload_limit: Keyword.get(instance, :avatar_upload_limit),
|
avatar_upload_limit: Keyword.get(instance, :avatar_upload_limit),
|
||||||
background_upload_limit: Keyword.get(instance, :background_upload_limit),
|
background_upload_limit: Keyword.get(instance, :background_upload_limit),
|
||||||
banner_upload_limit: Keyword.get(instance, :banner_upload_limit),
|
banner_upload_limit: Keyword.get(instance, :banner_upload_limit),
|
||||||
background_image: Keyword.get(instance, :background_image),
|
background_image: Pleroma.Web.base_url() <> Keyword.get(instance, :background_image),
|
||||||
chat_limit: Keyword.get(instance, :chat_limit),
|
chat_limit: Keyword.get(instance, :chat_limit),
|
||||||
description_limit: Keyword.get(instance, :description_limit),
|
description_limit: Keyword.get(instance, :description_limit),
|
||||||
pleroma: %{
|
pleroma: %{
|
||||||
|
|
|
@ -11,6 +11,8 @@ defmodule Pleroma.Web.MastodonAPI.NotificationView do
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.UserRelationship
|
alias Pleroma.UserRelationship
|
||||||
|
alias Pleroma.Web.AdminAPI.Report
|
||||||
|
alias Pleroma.Web.AdminAPI.ReportView
|
||||||
alias Pleroma.Web.CommonAPI
|
alias Pleroma.Web.CommonAPI
|
||||||
alias Pleroma.Web.MastodonAPI.AccountView
|
alias Pleroma.Web.MastodonAPI.AccountView
|
||||||
alias Pleroma.Web.MastodonAPI.NotificationView
|
alias Pleroma.Web.MastodonAPI.NotificationView
|
||||||
|
@ -118,11 +120,20 @@ def render(
|
||||||
"pleroma:chat_mention" ->
|
"pleroma:chat_mention" ->
|
||||||
put_chat_message(response, activity, reading_user, status_render_opts)
|
put_chat_message(response, activity, reading_user, status_render_opts)
|
||||||
|
|
||||||
|
"pleroma:report" ->
|
||||||
|
put_report(response, activity)
|
||||||
|
|
||||||
type when type in ["follow", "follow_request"] ->
|
type when type in ["follow", "follow_request"] ->
|
||||||
response
|
response
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp put_report(response, activity) do
|
||||||
|
report_render = ReportView.render("show.json", Report.extract_report_info(activity))
|
||||||
|
|
||||||
|
Map.put(response, :report, report_render)
|
||||||
|
end
|
||||||
|
|
||||||
defp put_emoji(response, activity) do
|
defp put_emoji(response, activity) do
|
||||||
Map.put(response, :emoji, activity.data["content"])
|
Map.put(response, :emoji, activity.data["content"])
|
||||||
end
|
end
|
||||||
|
|
|
@ -19,6 +19,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
|
||||||
alias Pleroma.Web.MastodonAPI.PollView
|
alias Pleroma.Web.MastodonAPI.PollView
|
||||||
alias Pleroma.Web.MastodonAPI.StatusView
|
alias Pleroma.Web.MastodonAPI.StatusView
|
||||||
alias Pleroma.Web.MediaProxy
|
alias Pleroma.Web.MediaProxy
|
||||||
|
alias Pleroma.Web.PleromaAPI.EmojiReactionController
|
||||||
|
|
||||||
import Pleroma.Web.ActivityPub.Visibility, only: [get_visibility: 1, visible_for_user?: 2]
|
import Pleroma.Web.ActivityPub.Visibility, only: [get_visibility: 1, visible_for_user?: 2]
|
||||||
|
|
||||||
|
@ -294,21 +295,16 @@ def render("show.json", %{activity: %{data: %{"object" => _object}} = activity}
|
||||||
end
|
end
|
||||||
|
|
||||||
emoji_reactions =
|
emoji_reactions =
|
||||||
with %{data: %{"reactions" => emoji_reactions}} <- object do
|
object.data
|
||||||
Enum.map(emoji_reactions, fn
|
|> Map.get("reactions", [])
|
||||||
[emoji, users] when is_list(users) ->
|
|> EmojiReactionController.filter_allowed_users(
|
||||||
build_emoji_map(emoji, users, opts[:for])
|
opts[:for],
|
||||||
|
Map.get(opts, :with_muted, false)
|
||||||
{emoji, users} when is_list(users) ->
|
)
|
||||||
build_emoji_map(emoji, users, opts[:for])
|
|> Stream.map(fn {emoji, users} ->
|
||||||
|
build_emoji_map(emoji, users, opts[:for])
|
||||||
_ ->
|
end)
|
||||||
nil
|
|> Enum.to_list()
|
||||||
end)
|
|
||||||
|> Enum.reject(&is_nil/1)
|
|
||||||
else
|
|
||||||
_ -> []
|
|
||||||
end
|
|
||||||
|
|
||||||
# Status muted state (would do 1 request per status unless user mutes are preloaded)
|
# Status muted state (would do 1 request per status unless user mutes are preloaded)
|
||||||
muted =
|
muted =
|
||||||
|
|
|
@ -6,7 +6,7 @@ defmodule Pleroma.Web.Metadata.Providers.RestrictIndexing do
|
||||||
@behaviour Pleroma.Web.Metadata.Providers.Provider
|
@behaviour Pleroma.Web.Metadata.Providers.Provider
|
||||||
|
|
||||||
@moduledoc """
|
@moduledoc """
|
||||||
Restricts indexing of remote users.
|
Restricts indexing of remote and/or non-discoverable users.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
|
|
|
@ -140,8 +140,8 @@ def messages(%{assigns: %{user: user}} = conn, %{id: id} = params) do
|
||||||
|
|
||||||
def index(%{assigns: %{user: %{id: user_id} = user}} = conn, params) do
|
def index(%{assigns: %{user: %{id: user_id} = user}} = conn, params) do
|
||||||
exclude_users =
|
exclude_users =
|
||||||
User.blocked_users_ap_ids(user) ++
|
User.cached_blocked_users_ap_ids(user) ++
|
||||||
if params[:with_muted], do: [], else: User.muted_users_ap_ids(user)
|
if params[:with_muted], do: [], else: User.cached_muted_users_ap_ids(user)
|
||||||
|
|
||||||
chats =
|
chats =
|
||||||
user_id
|
user_id
|
||||||
|
|
|
@ -7,6 +7,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiReactionController do
|
||||||
|
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
|
alias Pleroma.User
|
||||||
alias Pleroma.Web.CommonAPI
|
alias Pleroma.Web.CommonAPI
|
||||||
alias Pleroma.Web.MastodonAPI.StatusView
|
alias Pleroma.Web.MastodonAPI.StatusView
|
||||||
alias Pleroma.Web.Plugs.OAuthScopesPlug
|
alias Pleroma.Web.Plugs.OAuthScopesPlug
|
||||||
|
@ -29,13 +30,42 @@ def index(%{assigns: %{user: user}} = conn, %{id: activity_id} = params) do
|
||||||
%Activity{} = activity <- Activity.get_by_id_with_object(activity_id),
|
%Activity{} = activity <- Activity.get_by_id_with_object(activity_id),
|
||||||
%Object{data: %{"reactions" => reactions}} when is_list(reactions) <-
|
%Object{data: %{"reactions" => reactions}} when is_list(reactions) <-
|
||||||
Object.normalize(activity) do
|
Object.normalize(activity) do
|
||||||
reactions = filter(reactions, params)
|
reactions =
|
||||||
|
reactions
|
||||||
|
|> filter(params)
|
||||||
|
|> filter_allowed_users(user, Map.get(params, :with_muted, false))
|
||||||
|
|
||||||
render(conn, "index.json", emoji_reactions: reactions, user: user)
|
render(conn, "index.json", emoji_reactions: reactions, user: user)
|
||||||
else
|
else
|
||||||
_e -> json(conn, [])
|
_e -> json(conn, [])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def filter_allowed_users(reactions, user, with_muted) do
|
||||||
|
exclude_ap_ids =
|
||||||
|
if is_nil(user) do
|
||||||
|
[]
|
||||||
|
else
|
||||||
|
User.cached_blocked_users_ap_ids(user) ++
|
||||||
|
if not with_muted, do: User.cached_muted_users_ap_ids(user), else: []
|
||||||
|
end
|
||||||
|
|
||||||
|
filter_emoji = fn emoji, users ->
|
||||||
|
case Enum.reject(users, &(&1 in exclude_ap_ids)) do
|
||||||
|
[] -> nil
|
||||||
|
users -> {emoji, users}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
reactions
|
||||||
|
|> Stream.map(fn
|
||||||
|
[emoji, users] when is_list(users) -> filter_emoji.(emoji, users)
|
||||||
|
{emoji, users} when is_list(users) -> filter_emoji.(emoji, users)
|
||||||
|
_ -> nil
|
||||||
|
end)
|
||||||
|
|> Stream.reject(&is_nil/1)
|
||||||
|
end
|
||||||
|
|
||||||
defp filter(reactions, %{emoji: emoji}) when is_binary(emoji) do
|
defp filter(reactions, %{emoji: emoji}) when is_binary(emoji) do
|
||||||
Enum.filter(reactions, fn [e, _] -> e == emoji end)
|
Enum.filter(reactions, fn [e, _] -> e == emoji end)
|
||||||
end
|
end
|
||||||
|
|
|
@ -11,7 +11,7 @@ def render("index.json", %{emoji_reactions: emoji_reactions} = opts) do
|
||||||
render_many(emoji_reactions, __MODULE__, "show.json", opts)
|
render_many(emoji_reactions, __MODULE__, "show.json", opts)
|
||||||
end
|
end
|
||||||
|
|
||||||
def render("show.json", %{emoji_reaction: [emoji, user_ap_ids], user: user}) do
|
def render("show.json", %{emoji_reaction: {emoji, user_ap_ids}, user: user}) do
|
||||||
users = fetch_users(user_ap_ids)
|
users = fetch_users(user_ap_ids)
|
||||||
|
|
||||||
%{
|
%{
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -16,7 +16,7 @@ defmodule Pleroma.Web.Push.Impl do
|
||||||
require Logger
|
require Logger
|
||||||
import Ecto.Query
|
import Ecto.Query
|
||||||
|
|
||||||
@types ["Create", "Follow", "Announce", "Like", "Move"]
|
@types ["Create", "Follow", "Announce", "Like", "Move", "EmojiReact"]
|
||||||
|
|
||||||
@doc "Performs sending notifications for user subscriptions"
|
@doc "Performs sending notifications for user subscriptions"
|
||||||
@spec perform(Notification.t()) :: list(any) | :error | {:error, :unknown_type}
|
@spec perform(Notification.t()) :: list(any) | :error | {:error, :unknown_type}
|
||||||
|
@ -149,6 +149,15 @@ def format_body(
|
||||||
"@#{actor.nickname} repeated: #{Utils.scrub_html_and_truncate(content, 80)}"
|
"@#{actor.nickname} repeated: #{Utils.scrub_html_and_truncate(content, 80)}"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def format_body(
|
||||||
|
%{activity: %{data: %{"type" => "EmojiReact", "content" => content}}},
|
||||||
|
actor,
|
||||||
|
_object,
|
||||||
|
_mastodon_type
|
||||||
|
) do
|
||||||
|
"@#{actor.nickname} reacted with #{content}"
|
||||||
|
end
|
||||||
|
|
||||||
def format_body(
|
def format_body(
|
||||||
%{activity: %{data: %{"type" => type}}} = notification,
|
%{activity: %{data: %{"type" => type}}} = notification,
|
||||||
actor,
|
actor,
|
||||||
|
@ -179,6 +188,7 @@ def format_title(%{type: type}, mastodon_type) do
|
||||||
"reblog" -> "New Repeat"
|
"reblog" -> "New Repeat"
|
||||||
"favourite" -> "New Favorite"
|
"favourite" -> "New Favorite"
|
||||||
"pleroma:chat_mention" -> "New Chat Message"
|
"pleroma:chat_mention" -> "New Chat Message"
|
||||||
|
"pleroma:emoji_reaction" -> "New Reaction"
|
||||||
type -> "New #{String.capitalize(type || "event")}"
|
type -> "New #{String.capitalize(type || "event")}"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -25,7 +25,8 @@ defmodule Pleroma.Web.Push.Subscription do
|
||||||
timestamps()
|
timestamps()
|
||||||
end
|
end
|
||||||
|
|
||||||
@supported_alert_types ~w[follow favourite mention reblog pleroma:chat_mention]a
|
# credo:disable-for-next-line Credo.Check.Readability.MaxLineLength
|
||||||
|
@supported_alert_types ~w[follow favourite mention reblog pleroma:chat_mention pleroma:emoji_reaction]a
|
||||||
|
|
||||||
defp alerts(%{data: %{alerts: alerts}}) do
|
defp alerts(%{data: %{alerts: alerts}}) do
|
||||||
alerts = Map.take(alerts, @supported_alert_types)
|
alerts = Map.take(alerts, @supported_alert_types)
|
||||||
|
|
|
@ -78,11 +78,6 @@ def fetch_data_for_activity(%Activity{data: %{"type" => "Create"}} = activity) d
|
||||||
|
|
||||||
def fetch_data_for_activity(_), do: %{}
|
def fetch_data_for_activity(_), do: %{}
|
||||||
|
|
||||||
def perform(:fetch, %Activity{} = activity) do
|
|
||||||
fetch_data_for_activity(activity)
|
|
||||||
:ok
|
|
||||||
end
|
|
||||||
|
|
||||||
def rich_media_get(url) do
|
def rich_media_get(url) do
|
||||||
headers = [{"user-agent", Pleroma.Application.user_agent() <> "; Bot"}]
|
headers = [{"user-agent", Pleroma.Application.user_agent() <> "; Bot"}]
|
||||||
|
|
||||||
|
|
|
@ -244,6 +244,9 @@ defmodule Pleroma.Web.Router do
|
||||||
get("/chats/:id/messages", ChatController, :messages)
|
get("/chats/:id/messages", ChatController, :messages)
|
||||||
delete("/chats/:id/messages/:message_id", ChatController, :delete_message)
|
delete("/chats/:id/messages/:message_id", ChatController, :delete_message)
|
||||||
|
|
||||||
|
get("/frontends", FrontendController, :index)
|
||||||
|
post("/frontends/install", FrontendController, :install)
|
||||||
|
|
||||||
post("/backups", AdminAPIController, :create_backup)
|
post("/backups", AdminAPIController, :create_backup)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@ defmodule Pleroma.Web.TwitterAPI.PasswordController do
|
||||||
|
|
||||||
def reset(conn, %{"token" => token}) do
|
def reset(conn, %{"token" => token}) do
|
||||||
with %{used: false} = token <- Repo.get_by(PasswordResetToken, %{token: token}),
|
with %{used: false} = token <- Repo.get_by(PasswordResetToken, %{token: token}),
|
||||||
|
false <- PasswordResetToken.expired?(token),
|
||||||
%User{} = user <- User.get_cached_by_id(token.user_id) do
|
%User{} = user <- User.get_cached_by_id(token.user_id) do
|
||||||
render(conn, "reset.html", %{
|
render(conn, "reset.html", %{
|
||||||
token: token,
|
token: token,
|
||||||
|
|
|
@ -3,9 +3,7 @@
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Pleroma.Workers.BackgroundWorker do
|
defmodule Pleroma.Workers.BackgroundWorker do
|
||||||
alias Pleroma.Activity
|
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy
|
|
||||||
|
|
||||||
use Pleroma.Workers.WorkerHelper, queue: "background"
|
use Pleroma.Workers.WorkerHelper, queue: "background"
|
||||||
|
|
||||||
|
@ -32,19 +30,6 @@ def perform(%Job{args: %{"op" => op, "user_id" => user_id, "identifiers" => iden
|
||||||
{:ok, User.Import.perform(String.to_atom(op), user, identifiers)}
|
{:ok, User.Import.perform(String.to_atom(op), user, identifiers)}
|
||||||
end
|
end
|
||||||
|
|
||||||
def perform(%Job{args: %{"op" => "media_proxy_preload", "message" => message}}) do
|
|
||||||
MediaProxyWarmingPolicy.perform(:preload, message)
|
|
||||||
end
|
|
||||||
|
|
||||||
def perform(%Job{args: %{"op" => "media_proxy_prefetch", "url" => url}}) do
|
|
||||||
MediaProxyWarmingPolicy.perform(:prefetch, url)
|
|
||||||
end
|
|
||||||
|
|
||||||
def perform(%Job{args: %{"op" => "fetch_data_for_activity", "activity_id" => activity_id}}) do
|
|
||||||
activity = Activity.get_by_id(activity_id)
|
|
||||||
Pleroma.Web.RichMedia.Helpers.perform(:fetch, activity)
|
|
||||||
end
|
|
||||||
|
|
||||||
def perform(%Job{
|
def perform(%Job{
|
||||||
args: %{"op" => "move_following", "origin_id" => origin_id, "target_id" => target_id}
|
args: %{"op" => "move_following", "origin_id" => origin_id, "target_id" => target_id}
|
||||||
}) do
|
}) do
|
||||||
|
|
14
mix.exs
14
mix.exs
|
@ -4,7 +4,7 @@ defmodule Pleroma.Mixfile do
|
||||||
def project do
|
def project do
|
||||||
[
|
[
|
||||||
app: :pleroma,
|
app: :pleroma,
|
||||||
version: version("2.1.50"),
|
version: version("2.2.50"),
|
||||||
elixir: "~> 1.9",
|
elixir: "~> 1.9",
|
||||||
elixirc_paths: elixirc_paths(Mix.env()),
|
elixirc_paths: elixirc_paths(Mix.env()),
|
||||||
compilers: [:phoenix, :gettext] ++ Mix.compilers(),
|
compilers: [:phoenix, :gettext] ++ Mix.compilers(),
|
||||||
|
@ -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, ©_files/1, ©_nginx_config/1]
|
steps: [:assemble, &put_otp_version/1, ©_files/1, ©_nginx_config/1],
|
||||||
|
config_providers: [{Pleroma.Config.ReleaseRuntimeProvider, nil}]
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
@ -133,17 +134,14 @@ defp deps do
|
||||||
{:calendar, "~> 1.0"},
|
{:calendar, "~> 1.0"},
|
||||||
{:cachex, "~> 3.2"},
|
{:cachex, "~> 3.2"},
|
||||||
{:poison, "~> 3.0", override: true},
|
{:poison, "~> 3.0", override: true},
|
||||||
{:tesla,
|
{:tesla, "~> 1.4.0", override: true},
|
||||||
git: "https://github.com/teamon/tesla.git",
|
|
||||||
ref: "9f7261ca49f9f901ceb73b60219ad6f8a9f6aa30",
|
|
||||||
override: true},
|
|
||||||
{:castore, "~> 0.1"},
|
{:castore, "~> 0.1"},
|
||||||
{:cowlib, "~> 2.9", override: true},
|
{:cowlib, "~> 2.9", override: true},
|
||||||
{:gun,
|
{:gun,
|
||||||
github: "ninenines/gun", ref: "921c47146b2d9567eac7e9a4d2ccc60fffd4f327", override: true},
|
github: "ninenines/gun", ref: "921c47146b2d9567eac7e9a4d2ccc60fffd4f327", override: true},
|
||||||
{:jason, "~> 1.2"},
|
{:jason, "~> 1.2"},
|
||||||
{:mogrify, "~> 0.7.4"},
|
{:mogrify, "~> 0.7.4"},
|
||||||
{:ex_aws, "~> 2.1"},
|
{:ex_aws, "~> 2.1.6"},
|
||||||
{:ex_aws_s3, "~> 2.0"},
|
{:ex_aws_s3, "~> 2.0"},
|
||||||
{:sweet_xml, "~> 0.6.6"},
|
{:sweet_xml, "~> 0.6.6"},
|
||||||
{:earmark, "1.4.3"},
|
{:earmark, "1.4.3"},
|
||||||
|
@ -160,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"},
|
||||||
|
|
6
mix.lock
6
mix.lock
|
@ -37,7 +37,7 @@
|
||||||
"esshd": {:hex, :esshd, "0.1.1", "d4dd4c46698093a40a56afecce8a46e246eb35463c457c246dacba2e056f31b5", [:mix], [], "hexpm", "d73e341e3009d390aa36387dc8862860bf9f874c94d9fd92ade2926376f49981"},
|
"esshd": {:hex, :esshd, "0.1.1", "d4dd4c46698093a40a56afecce8a46e246eb35463c457c246dacba2e056f31b5", [:mix], [], "hexpm", "d73e341e3009d390aa36387dc8862860bf9f874c94d9fd92ade2926376f49981"},
|
||||||
"eternal": {:hex, :eternal, "1.2.1", "d5b6b2499ba876c57be2581b5b999ee9bdf861c647401066d3eeed111d096bc4", [:mix], [], "hexpm", "b14f1dc204321429479c569cfbe8fb287541184ed040956c8862cb7a677b8406"},
|
"eternal": {:hex, :eternal, "1.2.1", "d5b6b2499ba876c57be2581b5b999ee9bdf861c647401066d3eeed111d096bc4", [:mix], [], "hexpm", "b14f1dc204321429479c569cfbe8fb287541184ed040956c8862cb7a677b8406"},
|
||||||
"ex2ms": {:hex, :ex2ms, "1.5.0", "19e27f9212be9a96093fed8cdfbef0a2b56c21237196d26760f11dfcfae58e97", [:mix], [], "hexpm"},
|
"ex2ms": {:hex, :ex2ms, "1.5.0", "19e27f9212be9a96093fed8cdfbef0a2b56c21237196d26760f11dfcfae58e97", [:mix], [], "hexpm"},
|
||||||
"ex_aws": {:hex, :ex_aws, "2.1.3", "26b6f036f0127548706aade4a509978fc7c26bd5334b004fba9bfe2687a525df", [:mix], [{:configparser_ex, "~> 4.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8", [hex: :jsx, repo: "hexpm", optional: true]}, {:sweet_xml, "~> 0.6", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "0bdbe2aed9f326922fc5a6a80417e32f0c895f4b3b2b0b9676ebf23dd16c5da4"},
|
"ex_aws": {:hex, :ex_aws, "2.1.6", "41ab8b4caa48035c96d07faa035d2d9de6df480e7e084c054e662ac888dcd4d4", [:mix], [{:configparser_ex, "~> 4.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8", [hex: :jsx, repo: "hexpm", optional: true]}, {:sweet_xml, "~> 0.6", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "a541bd042c1ee26412bb1e749ddf2a1c327e4fb7e382b1cd227e1b00eed3d469"},
|
||||||
"ex_aws_s3": {:hex, :ex_aws_s3, "2.0.2", "c0258bbdfea55de4f98f0b2f0ca61fe402cc696f573815134beb1866e778f47b", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:sweet_xml, ">= 0.0.0", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "0569f5b211b1a3b12b705fe2a9d0e237eb1360b9d76298028df2346cad13097a"},
|
"ex_aws_s3": {:hex, :ex_aws_s3, "2.0.2", "c0258bbdfea55de4f98f0b2f0ca61fe402cc696f573815134beb1866e778f47b", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:sweet_xml, ">= 0.0.0", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "0569f5b211b1a3b12b705fe2a9d0e237eb1360b9d76298028df2346cad13097a"},
|
||||||
"ex_const": {:hex, :ex_const, "0.2.4", "d06e540c9d834865b012a17407761455efa71d0ce91e5831e86881b9c9d82448", [:mix], [], "hexpm", "96fd346610cc992b8f896ed26a98be82ac4efb065a0578f334a32d60a3ba9767"},
|
"ex_const": {:hex, :ex_const, "0.2.4", "d06e540c9d834865b012a17407761455efa71d0ce91e5831e86881b9c9d82448", [:mix], [], "hexpm", "96fd346610cc992b8f896ed26a98be82ac4efb065a0578f334a32d60a3ba9767"},
|
||||||
"ex_doc": {:hex, :ex_doc, "0.22.2", "03a2a58bdd2ba0d83d004507c4ee113b9c521956938298eba16e55cc4aba4a6c", [:mix], [{:earmark_parser, "~> 1.4.0", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm", "cf60e1b3e2efe317095b6bb79651f83a2c1b3edcb4d319c421d7fcda8b3aff26"},
|
"ex_doc": {:hex, :ex_doc, "0.22.2", "03a2a58bdd2ba0d83d004507c4ee113b9c521956938298eba16e55cc4aba4a6c", [:mix], [{:earmark_parser, "~> 1.4.0", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm", "cf60e1b3e2efe317095b6bb79651f83a2c1b3edcb4d319c421d7fcda8b3aff26"},
|
||||||
|
@ -65,7 +65,7 @@
|
||||||
"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", [branch: "develop"]},
|
||||||
"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"},
|
||||||
|
@ -115,7 +115,7 @@
|
||||||
"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"},
|
||||||
"telemetry": {:hex, :telemetry, "0.4.2", "2808c992455e08d6177322f14d3bdb6b625fbcfd233a73505870d8738a2f4599", [:rebar3], [], "hexpm", "2d1419bd9dda6a206d7b5852179511722e2b18812310d304620c7bd92a13fcef"},
|
"telemetry": {:hex, :telemetry, "0.4.2", "2808c992455e08d6177322f14d3bdb6b625fbcfd233a73505870d8738a2f4599", [:rebar3], [], "hexpm", "2d1419bd9dda6a206d7b5852179511722e2b18812310d304620c7bd92a13fcef"},
|
||||||
"tesla": {:git, "https://github.com/teamon/tesla.git", "9f7261ca49f9f901ceb73b60219ad6f8a9f6aa30", [ref: "9f7261ca49f9f901ceb73b60219ad6f8a9f6aa30"]},
|
"tesla": {:hex, :tesla, "1.4.0", "1081bef0124b8bdec1c3d330bbe91956648fb008cf0d3950a369cda466a31a87", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.3", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, "~> 1.3", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "~> 4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "bf1374a5569f5fca8e641363b63f7347d680d91388880979a33bc12a6eb3e0aa"},
|
||||||
"timex": {:hex, :timex, "3.6.2", "845cdeb6119e2fef10751c0b247b6c59d86d78554c83f78db612e3290f819bc2", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5 or ~> 1.0.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "26030b46199d02a590be61c2394b37ea25a3664c02fafbeca0b24c972025d47a"},
|
"timex": {:hex, :timex, "3.6.2", "845cdeb6119e2fef10751c0b247b6c59d86d78554c83f78db612e3290f819bc2", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5 or ~> 1.0.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "26030b46199d02a590be61c2394b37ea25a3664c02fafbeca0b24c972025d47a"},
|
||||||
"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"},
|
||||||
|
|
|
@ -0,0 +1,599 @@
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2020-11-10 13:39+0000\n"
|
||||||
|
"PO-Revision-Date: 2020-11-21 04:42+0000\n"
|
||||||
|
"Last-Translator: Guy Sheffer <guysoft@gmail.com>\n"
|
||||||
|
"Language-Team: Hebrew <https://translate.pleroma.social/projects/pleroma/"
|
||||||
|
"pleroma/he/>\n"
|
||||||
|
"Language: he\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\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.
|
||||||
|
##
|
||||||
|
## `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] "אחד"
|
||||||
|
msgstr[1] "שני"
|
||||||
|
msgstr[2] "בודדים"
|
||||||
|
msgstr[3] "אחר"
|
||||||
|
|
||||||
|
msgid "should have %{count} item(s)"
|
||||||
|
msgid_plural "should have %{count} item(s)"
|
||||||
|
msgstr[0] "אחד"
|
||||||
|
msgstr[1] "שני"
|
||||||
|
msgstr[2] "בודדים"
|
||||||
|
msgstr[3] "אחר"
|
||||||
|
|
||||||
|
msgid "should be at least %{count} character(s)"
|
||||||
|
msgid_plural "should be at least %{count} character(s)"
|
||||||
|
msgstr[0] "אחד"
|
||||||
|
msgstr[1] "שנים"
|
||||||
|
msgstr[2] "בודדים"
|
||||||
|
msgstr[3] "אחר"
|
||||||
|
|
||||||
|
msgid "should have at least %{count} item(s)"
|
||||||
|
msgid_plural "should have at least %{count} item(s)"
|
||||||
|
msgstr[0] "אחד"
|
||||||
|
msgstr[1] "שניים"
|
||||||
|
msgstr[2] "בודדים"
|
||||||
|
msgstr[3] "אחר"
|
||||||
|
|
||||||
|
msgid "should be at most %{count} character(s)"
|
||||||
|
msgid_plural "should be at most %{count} character(s)"
|
||||||
|
msgstr[0] "אחד"
|
||||||
|
msgstr[1] "שניים"
|
||||||
|
msgstr[2] "בודדים"
|
||||||
|
msgstr[3] "אחר"
|
||||||
|
|
||||||
|
msgid "should have at most %{count} item(s)"
|
||||||
|
msgid_plural "should have at most %{count} item(s)"
|
||||||
|
msgstr[0] "אחד"
|
||||||
|
msgstr[1] "שניים"
|
||||||
|
msgstr[2] "בודדים"
|
||||||
|
msgstr[3] "אחר"
|
||||||
|
|
||||||
|
## 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 "permission_group לא קיים"
|
||||||
|
|
||||||
|
#: 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 "הרשמה לעדכון ווב בדחיפה מבוטלת בשרת פלרומה זה"
|
||||||
|
|
||||||
|
#: 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 "משתמש אינו מנהל."
|
|
@ -0,0 +1,48 @@
|
||||||
|
defmodule Pleroma.Repo.Migrations.AddPleromaReportTypeToEnumForNotifications do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
@disable_ddl_transaction true
|
||||||
|
|
||||||
|
def up do
|
||||||
|
"""
|
||||||
|
alter type notification_type add value 'pleroma:report'
|
||||||
|
"""
|
||||||
|
|> execute()
|
||||||
|
end
|
||||||
|
|
||||||
|
def down do
|
||||||
|
alter table(:notifications) do
|
||||||
|
modify(:type, :string)
|
||||||
|
end
|
||||||
|
|
||||||
|
"""
|
||||||
|
delete from notifications where type = 'pleroma:report'
|
||||||
|
"""
|
||||||
|
|> execute()
|
||||||
|
|
||||||
|
"""
|
||||||
|
drop type if exists notification_type
|
||||||
|
"""
|
||||||
|
|> execute()
|
||||||
|
|
||||||
|
"""
|
||||||
|
create type notification_type as enum (
|
||||||
|
'follow',
|
||||||
|
'follow_request',
|
||||||
|
'mention',
|
||||||
|
'move',
|
||||||
|
'pleroma:emoji_reaction',
|
||||||
|
'pleroma:chat_mention',
|
||||||
|
'reblog',
|
||||||
|
'favourite'
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
|> execute()
|
||||||
|
|
||||||
|
"""
|
||||||
|
alter table notifications
|
||||||
|
alter column type type notification_type using (type::notification_type)
|
||||||
|
"""
|
||||||
|
|> execute()
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,22 @@
|
||||||
|
defmodule Pleroma.Repo.Migrations.RemoveBackgroundJobs do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
import Ecto.Query, only: [from: 2]
|
||||||
|
|
||||||
|
def up do
|
||||||
|
from(j in "oban_jobs",
|
||||||
|
where:
|
||||||
|
j.queue == ^"background" and
|
||||||
|
fragment("?->>'op'", j.args) in ^[
|
||||||
|
"fetch_data_for_activity",
|
||||||
|
"media_proxy_prefetch",
|
||||||
|
"media_proxy_preload"
|
||||||
|
] and
|
||||||
|
j.worker == ^"Pleroma.Workers.BackgroundWorker",
|
||||||
|
select: [:id]
|
||||||
|
)
|
||||||
|
|> Pleroma.Repo.delete_all()
|
||||||
|
end
|
||||||
|
|
||||||
|
def down, do: :ok
|
||||||
|
end
|
|
@ -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, [])
|
||||||
|
|
||||||
|
|
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue