Fix merge conflicts with upstream
This commit is contained in:
commit
dc4814f0cd
|
@ -28,6 +28,7 @@ erl_crash.dump
|
||||||
# variables.
|
# variables.
|
||||||
/config/*.secret.exs
|
/config/*.secret.exs
|
||||||
/config/generated_config.exs
|
/config/generated_config.exs
|
||||||
|
/config/runtime.exs
|
||||||
/config/*.env
|
/config/*.env
|
||||||
|
|
||||||
|
|
||||||
|
@ -56,4 +57,4 @@ pleroma.iml
|
||||||
|
|
||||||
# Editor temp files
|
# Editor temp files
|
||||||
/*~
|
/*~
|
||||||
/*#
|
/*#
|
||||||
|
|
|
@ -8,7 +8,9 @@ variables: &global_variables
|
||||||
MIX_ENV: test
|
MIX_ENV: test
|
||||||
|
|
||||||
cache: &global_cache_policy
|
cache: &global_cache_policy
|
||||||
key: ${CI_COMMIT_REF_SLUG}
|
key:
|
||||||
|
files:
|
||||||
|
- mix.lock
|
||||||
paths:
|
paths:
|
||||||
- deps
|
- deps
|
||||||
- _build
|
- _build
|
||||||
|
@ -22,6 +24,7 @@ stages:
|
||||||
- docker
|
- docker
|
||||||
|
|
||||||
before_script:
|
before_script:
|
||||||
|
- rm -rf _build/*/lib/pleroma
|
||||||
- apt-get update && apt-get install -y cmake
|
- apt-get update && apt-get install -y cmake
|
||||||
- mix local.hex --force
|
- mix local.hex --force
|
||||||
- mix local.rebar --force
|
- mix local.rebar --force
|
||||||
|
@ -29,13 +32,25 @@ before_script:
|
||||||
- apt-get -qq update
|
- apt-get -qq update
|
||||||
- apt-get install -y libmagic-dev
|
- apt-get install -y libmagic-dev
|
||||||
|
|
||||||
|
after_script:
|
||||||
|
- rm -rf _build/*/lib/pleroma
|
||||||
|
|
||||||
build:
|
build:
|
||||||
stage: build
|
stage: build
|
||||||
|
only:
|
||||||
|
changes:
|
||||||
|
- "**/*.ex"
|
||||||
|
- "**/*.exs"
|
||||||
|
- "mix.lock"
|
||||||
script:
|
script:
|
||||||
- mix compile --force
|
- mix compile --force
|
||||||
|
|
||||||
spec-build:
|
spec-build:
|
||||||
stage: test
|
stage: test
|
||||||
|
only:
|
||||||
|
changes:
|
||||||
|
- "lib/pleroma/web/api_spec/**/*.ex"
|
||||||
|
- "lib/pleroma/web/api_spec.ex"
|
||||||
artifacts:
|
artifacts:
|
||||||
paths:
|
paths:
|
||||||
- spec.json
|
- spec.json
|
||||||
|
@ -58,6 +73,11 @@ benchmark:
|
||||||
|
|
||||||
unit-testing:
|
unit-testing:
|
||||||
stage: test
|
stage: test
|
||||||
|
only:
|
||||||
|
changes:
|
||||||
|
- "**/*.ex"
|
||||||
|
- "**/*.exs"
|
||||||
|
- "mix.lock"
|
||||||
retry: 2
|
retry: 2
|
||||||
cache: &testing_cache_policy
|
cache: &testing_cache_policy
|
||||||
<<: *global_cache_policy
|
<<: *global_cache_policy
|
||||||
|
@ -91,6 +111,11 @@ unit-testing:
|
||||||
|
|
||||||
unit-testing-rum:
|
unit-testing-rum:
|
||||||
stage: test
|
stage: test
|
||||||
|
only:
|
||||||
|
changes:
|
||||||
|
- "**/*.ex"
|
||||||
|
- "**/*.exs"
|
||||||
|
- "mix.lock"
|
||||||
retry: 2
|
retry: 2
|
||||||
cache: *testing_cache_policy
|
cache: *testing_cache_policy
|
||||||
services:
|
services:
|
||||||
|
@ -109,12 +134,22 @@ unit-testing-rum:
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
stage: test
|
stage: test
|
||||||
|
only:
|
||||||
|
changes:
|
||||||
|
- "**/*.ex"
|
||||||
|
- "**/*.exs"
|
||||||
|
- "mix.lock"
|
||||||
cache: *testing_cache_policy
|
cache: *testing_cache_policy
|
||||||
script:
|
script:
|
||||||
- mix format --check-formatted
|
- mix format --check-formatted
|
||||||
|
|
||||||
analysis:
|
analysis:
|
||||||
stage: test
|
stage: test
|
||||||
|
only:
|
||||||
|
changes:
|
||||||
|
- "**/*.ex"
|
||||||
|
- "**/*.exs"
|
||||||
|
- "mix.lock"
|
||||||
cache: *testing_cache_policy
|
cache: *testing_cache_policy
|
||||||
script:
|
script:
|
||||||
- mix credo --strict --only=warnings,todo,fixme,consistency,readability
|
- mix credo --strict --only=warnings,todo,fixme,consistency,readability
|
||||||
|
@ -171,8 +206,8 @@ spec-deploy:
|
||||||
- apk add curl
|
- apk add curl
|
||||||
script:
|
script:
|
||||||
- curl -X POST -F"token=$API_DOCS_PIPELINE_TRIGGER" -F'ref=master' -F"variables[BRANCH]=$CI_COMMIT_REF_NAME" -F"variables[JOB_REF]=$CI_JOB_ID" https://git.pleroma.social/api/v4/projects/1130/trigger/pipeline
|
- curl -X POST -F"token=$API_DOCS_PIPELINE_TRIGGER" -F'ref=master' -F"variables[BRANCH]=$CI_COMMIT_REF_NAME" -F"variables[JOB_REF]=$CI_JOB_ID" https://git.pleroma.social/api/v4/projects/1130/trigger/pipeline
|
||||||
|
|
||||||
|
|
||||||
stop_review_app:
|
stop_review_app:
|
||||||
image: alpine:3.9
|
image: alpine:3.9
|
||||||
stage: deploy
|
stage: deploy
|
||||||
|
@ -231,7 +266,7 @@ amd64-musl:
|
||||||
stage: release
|
stage: release
|
||||||
artifacts: *release-artifacts
|
artifacts: *release-artifacts
|
||||||
only: *release-only
|
only: *release-only
|
||||||
image: 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
|
||||||
|
|
17
CHANGELOG.md
17
CHANGELOG.md
|
@ -12,11 +12,25 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
- **Breaking:** Configuration: `:chat, enabled` moved to `:shout, enabled` and `:instance, chat_limit` moved to `:shout, limit`
|
||||||
- The `application` metadata returned with statuses is no longer hardcoded. Apps that want to display these details will now have valid data for new posts after this change.
|
- The `application` metadata returned with statuses is no longer hardcoded. Apps that want to display these details will now have valid data for new posts after this change.
|
||||||
|
- HTTPSecurityPlug now sends a response header to opt out of Google's FLoC (Federated Learning of Cohorts) targeted advertising.
|
||||||
|
- Email address is now returned if requesting user is the owner of the user account so it can be exposed in client and FE user settings UIs.
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- MRF (`FollowBotPolicy`): New MRF Policy which makes a designated local Bot account attempt to follow all users in public Notes received by your instance. Users who require approving follower requests or have #nobot in their profile are excluded.
|
- MRF (`FollowBotPolicy`): New MRF Policy which makes a designated local Bot account attempt to follow all users in public Notes received by your instance. Users who require approving follower requests or have #nobot in their profile are excluded.
|
||||||
|
- Return OAuth token `id` (primary key) in POST `/oauth/token`.
|
||||||
|
- `AnalyzeMetadata` upload filter for extracting attachment dimensions and generating blurhashes.
|
||||||
|
- Attachment dimensions and blurhashes are federated when available.
|
||||||
|
- Pinned posts federation
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Don't crash so hard when email settings are invalid.
|
||||||
|
- Checking activated Upload Filters for required commands.
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
- **Breaking**: Remove deprecated `/api/qvitter/statuses/notifications/read` (replaced by `/api/v1/pleroma/notifications/read`)
|
||||||
|
|
||||||
## Unreleased (Patch)
|
## Unreleased (Patch)
|
||||||
|
|
||||||
|
@ -26,6 +40,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- Uploading custom instance thumbnail via AdminAPI/AdminFE generated invalid URL to the image
|
- Uploading custom instance thumbnail via AdminAPI/AdminFE generated invalid URL to the image
|
||||||
- Applying ConcurrentLimiter settings via AdminAPI
|
- Applying ConcurrentLimiter settings via AdminAPI
|
||||||
- User login failures if their `notification_settings` were in a NULL state.
|
- User login failures if their `notification_settings` were in a NULL state.
|
||||||
|
- Mix task `pleroma.user delete_activities` query transaction timeout is now :infinity
|
||||||
|
- MRF (`SimplePolicy`): Embedded objects are now checked. If any embedded object would be rejected, its parent is rejected. This fixes Announces leaking posts from blocked domains.
|
||||||
|
- Fixed some Markdown issues, including trailing slash in links.
|
||||||
|
|
||||||
## [2.3.0] - 2020-03-01
|
## [2.3.0] - 2020-03-01
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,7 @@ ARG DATA=/var/lib/pleroma
|
||||||
|
|
||||||
RUN echo "http://nl.alpinelinux.org/alpine/latest-stable/community" >> /etc/apk/repositories &&\
|
RUN echo "http://nl.alpinelinux.org/alpine/latest-stable/community" >> /etc/apk/repositories &&\
|
||||||
apk update &&\
|
apk update &&\
|
||||||
apk add exiftool imagemagick libmagic ncurses postgresql-client &&\
|
apk add exiftool ffmpeg 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 &&\
|
||||||
|
|
|
@ -50,5 +50,5 @@ If you are not developing Pleroma, it is better to use the OTP release, which co
|
||||||
- Latest Git revision: <https://docs-develop.pleroma.social>
|
- Latest Git revision: <https://docs-develop.pleroma.social>
|
||||||
|
|
||||||
## Community Channels
|
## Community Channels
|
||||||
* IRC: **#pleroma** and **#pleroma-dev** on freenode, webchat is available at <https://irc.pleroma.social>
|
* IRC: **#pleroma** and **#pleroma-dev** on libera.chat, webchat is available at <https://irc.pleroma.social>
|
||||||
* Matrix: <https://matrix.to/#/#freenode_#pleroma:matrix.org> and <https://matrix.to/#/#freenode_#pleroma-dev:matrix.org>
|
* Matrix: [#pleroma:libera.chat](https://matrix.to/#/#pleroma:libera.chat) and [#pleroma-dev:libera.chat](https://matrix.to/#/#pleroma-dev:libera.chat)
|
||||||
|
|
|
@ -299,7 +299,7 @@ defp insert_activity(:attachment, visibility, group, users, _opts) do
|
||||||
"url" => [
|
"url" => [
|
||||||
%{
|
%{
|
||||||
"href" =>
|
"href" =>
|
||||||
"#{Pleroma.Web.base_url()}/media/b1b873552422a07bf53af01f3c231c841db4dfc42c35efde681abaf0f2a4eab7.jpg",
|
"#{Pleroma.Web.Endpoint.url()}/media/b1b873552422a07bf53af01f3c231c841db4dfc42c35efde681abaf0f2a4eab7.jpg",
|
||||||
"mediaType" => "image/jpeg",
|
"mediaType" => "image/jpeg",
|
||||||
"type" => "Link"
|
"type" => "Link"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use Mix.Config
|
import Config
|
||||||
|
|
||||||
# We don't run a server during test. If one is required,
|
# We don't run a server during test. If one is required,
|
||||||
# you can enable the server option below.
|
# you can enable the server option below.
|
||||||
|
|
|
@ -41,7 +41,7 @@
|
||||||
#
|
#
|
||||||
# This configuration file is loaded before any dependency and
|
# This configuration file is loaded before any dependency and
|
||||||
# is restricted to this project.
|
# is restricted to this project.
|
||||||
use Mix.Config
|
import Config
|
||||||
|
|
||||||
# General application configuration
|
# General application configuration
|
||||||
config :pleroma, ecto_repos: [Pleroma.Repo]
|
config :pleroma, ecto_repos: [Pleroma.Repo]
|
||||||
|
@ -190,7 +190,6 @@
|
||||||
instance_thumbnail: "/instance/thumbnail.jpeg",
|
instance_thumbnail: "/instance/thumbnail.jpeg",
|
||||||
limit: 5_000,
|
limit: 5_000,
|
||||||
description_limit: 5_000,
|
description_limit: 5_000,
|
||||||
chat_limit: 5_000,
|
|
||||||
remote_limit: 100_000,
|
remote_limit: 100_000,
|
||||||
upload_limit: 16_000_000,
|
upload_limit: 16_000_000,
|
||||||
avatar_upload_limit: 2_000_000,
|
avatar_upload_limit: 2_000_000,
|
||||||
|
@ -454,7 +453,9 @@
|
||||||
image_quality: 85,
|
image_quality: 85,
|
||||||
min_content_length: 100 * 1024
|
min_content_length: 100 * 1024
|
||||||
|
|
||||||
config :pleroma, :chat, enabled: true
|
config :pleroma, :shout,
|
||||||
|
enabled: true,
|
||||||
|
limit: 5_000
|
||||||
|
|
||||||
config :phoenix, :format_encoders, json: Jason
|
config :phoenix, :format_encoders, json: Jason
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use Mix.Config
|
import Config
|
||||||
|
|
||||||
websocket_config = [
|
websocket_config = [
|
||||||
path: "/websocket",
|
path: "/websocket",
|
||||||
|
@ -544,14 +544,6 @@
|
||||||
5_000
|
5_000
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
%{
|
|
||||||
key: :chat_limit,
|
|
||||||
type: :integer,
|
|
||||||
description: "Character limit of the instance chat messages",
|
|
||||||
suggestions: [
|
|
||||||
5_000
|
|
||||||
]
|
|
||||||
},
|
|
||||||
%{
|
%{
|
||||||
key: :remote_limit,
|
key: :remote_limit,
|
||||||
type: :integer,
|
type: :integer,
|
||||||
|
@ -682,7 +674,8 @@
|
||||||
%{
|
%{
|
||||||
key: :allow_relay,
|
key: :allow_relay,
|
||||||
type: :boolean,
|
type: :boolean,
|
||||||
description: "Enable Pleroma's Relay, which makes it possible to follow a whole instance"
|
description:
|
||||||
|
"Permits remote instances to subscribe to all public posts of your instance. (Important!) This may increase the visibility of your instance."
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
key: :public,
|
key: :public,
|
||||||
|
@ -1182,7 +1175,6 @@
|
||||||
alwaysShowSubjectInput: true,
|
alwaysShowSubjectInput: true,
|
||||||
background: "/static/aurora_borealis.jpg",
|
background: "/static/aurora_borealis.jpg",
|
||||||
collapseMessageWithSubject: false,
|
collapseMessageWithSubject: false,
|
||||||
disableChat: false,
|
|
||||||
greentext: false,
|
greentext: false,
|
||||||
hideFilteredStatuses: false,
|
hideFilteredStatuses: false,
|
||||||
hideMutedPosts: false,
|
hideMutedPosts: false,
|
||||||
|
@ -1229,12 +1221,6 @@
|
||||||
description:
|
description:
|
||||||
"When a message has a subject (aka Content Warning), collapse it by default"
|
"When a message has a subject (aka Content Warning), collapse it by default"
|
||||||
},
|
},
|
||||||
%{
|
|
||||||
key: :disableChat,
|
|
||||||
label: "PleromaFE Chat",
|
|
||||||
type: :boolean,
|
|
||||||
description: "Disables PleromaFE Chat component"
|
|
||||||
},
|
|
||||||
%{
|
%{
|
||||||
key: :greentext,
|
key: :greentext,
|
||||||
label: "Greentext",
|
label: "Greentext",
|
||||||
|
@ -2633,13 +2619,22 @@
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
group: :pleroma,
|
group: :pleroma,
|
||||||
key: :chat,
|
key: :shout,
|
||||||
type: :group,
|
type: :group,
|
||||||
description: "Pleroma chat settings",
|
description: "Pleroma shout settings",
|
||||||
children: [
|
children: [
|
||||||
%{
|
%{
|
||||||
key: :enabled,
|
key: :enabled,
|
||||||
type: :boolean
|
type: :boolean,
|
||||||
|
description: "Enables the backend Shoutbox chat feature."
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
key: :limit,
|
||||||
|
type: :integer,
|
||||||
|
description: "Shout message character limit.",
|
||||||
|
suggestions: [
|
||||||
|
5_000
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use Mix.Config
|
import Config
|
||||||
|
|
||||||
# For development, we disable any cache and enable
|
# For development, we disable any cache and enable
|
||||||
# debugging and code reloading.
|
# debugging and code reloading.
|
||||||
|
@ -54,6 +54,10 @@
|
||||||
|
|
||||||
config :pleroma, Pleroma.Web.ApiSpec.CastAndValidate, strict: true
|
config :pleroma, Pleroma.Web.ApiSpec.CastAndValidate, strict: true
|
||||||
|
|
||||||
|
# Reduce recompilation time
|
||||||
|
# https://dashbit.co/blog/speeding-up-re-compilation-of-elixir-projects
|
||||||
|
config :phoenix, :plug_init_mode, :runtime
|
||||||
|
|
||||||
if File.exists?("./config/dev.secret.exs") do
|
if File.exists?("./config/dev.secret.exs") do
|
||||||
import_config "dev.secret.exs"
|
import_config "dev.secret.exs"
|
||||||
else
|
else
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use Mix.Config
|
import Config
|
||||||
|
|
||||||
config :pleroma, Pleroma.Web.Endpoint,
|
config :pleroma, Pleroma.Web.Endpoint,
|
||||||
http: [
|
http: [
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use Mix.Config
|
import Config
|
||||||
|
|
||||||
# For production, we often load configuration from external
|
# For production, we often load configuration from external
|
||||||
# sources, such as your system environment. For this reason,
|
# sources, such as your system environment. For this reason,
|
||||||
|
@ -63,7 +63,12 @@
|
||||||
|
|
||||||
# Finally import the config/prod.secret.exs
|
# Finally import the config/prod.secret.exs
|
||||||
# which should be versioned separately.
|
# which should be versioned separately.
|
||||||
import_config "prod.secret.exs"
|
if File.exists?("./config/prod.secret.exs") do
|
||||||
|
import_config "prod.secret.exs"
|
||||||
|
else
|
||||||
|
"`config/prod.secret.exs` not found. You may want to create one by running `mix pleroma.instance gen`"
|
||||||
|
|> IO.warn([])
|
||||||
|
end
|
||||||
|
|
||||||
if File.exists?("./config/prod.exported_from_db.secret.exs"),
|
if File.exists?("./config/prod.exported_from_db.secret.exs"),
|
||||||
do: import_config("prod.exported_from_db.secret.exs")
|
do: import_config("prod.exported_from_db.secret.exs")
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use Mix.Config
|
import Config
|
||||||
|
|
||||||
# We don't run a server during test. If one is required,
|
# We don't run a server during test. If one is required,
|
||||||
# you can enable the server option below.
|
# you can enable the server option below.
|
||||||
|
@ -133,6 +133,10 @@
|
||||||
ap_streamer: Pleroma.Web.ActivityPub.ActivityPubMock,
|
ap_streamer: Pleroma.Web.ActivityPub.ActivityPubMock,
|
||||||
logger: Pleroma.LoggerMock
|
logger: Pleroma.LoggerMock
|
||||||
|
|
||||||
|
# Reduce recompilation time
|
||||||
|
# https://dashbit.co/blog/speeding-up-re-compilation-of-elixir-projects
|
||||||
|
config :phoenix, :plug_init_mode, :runtime
|
||||||
|
|
||||||
if File.exists?("./config/test.secret.exs") do
|
if File.exists?("./config/test.secret.exs") do
|
||||||
import_config "test.secret.exs"
|
import_config "test.secret.exs"
|
||||||
else
|
else
|
||||||
|
|
|
@ -8,9 +8,10 @@ For from source installations Pleroma configuration works by first importing the
|
||||||
|
|
||||||
To add configuration to your config file, you can copy it from the base config. The latest version of it can be viewed [here](https://git.pleroma.social/pleroma/pleroma/blob/develop/config/config.exs). You can also use this file if you don't know how an option is supposed to be formatted.
|
To add configuration to your config file, you can copy it from the base config. The latest version of it can be viewed [here](https://git.pleroma.social/pleroma/pleroma/blob/develop/config/config.exs). You can also use this file if you don't know how an option is supposed to be formatted.
|
||||||
|
|
||||||
## :chat
|
## :shout
|
||||||
|
|
||||||
* `enabled` - Enables the backend chat. Defaults to `true`.
|
* `enabled` - Enables the backend Shoutbox chat feature. Defaults to `true`.
|
||||||
|
* `limit` - Shout character limit. Defaults to `5_000`
|
||||||
|
|
||||||
## :instance
|
## :instance
|
||||||
* `name`: The instance’s name.
|
* `name`: The instance’s name.
|
||||||
|
@ -19,7 +20,6 @@ To add configuration to your config file, you can copy it from the base config.
|
||||||
* `description`: The instance’s description, can be seen in nodeinfo and ``/api/v1/instance``.
|
* `description`: The instance’s description, can be seen in nodeinfo and ``/api/v1/instance``.
|
||||||
* `limit`: Posts character limit (CW/Subject included in the counter).
|
* `limit`: Posts character limit (CW/Subject included in the counter).
|
||||||
* `description_limit`: The character limit for image descriptions.
|
* `description_limit`: The character limit for image descriptions.
|
||||||
* `chat_limit`: Character limit of the instance chat messages.
|
|
||||||
* `remote_limit`: Hard character limit beyond which remote posts will be dropped.
|
* `remote_limit`: Hard character limit beyond which remote posts will be dropped.
|
||||||
* `upload_limit`: File size limit of uploads (except for avatar, background, banner).
|
* `upload_limit`: File size limit of uploads (except for avatar, background, banner).
|
||||||
* `avatar_upload_limit`: File size limit of user’s profile avatars.
|
* `avatar_upload_limit`: File size limit of user’s profile avatars.
|
||||||
|
@ -37,7 +37,7 @@ To add configuration to your config file, you can copy it from the base config.
|
||||||
* `federating`: Enable federation with other instances.
|
* `federating`: Enable federation with other instances.
|
||||||
* `federation_incoming_replies_max_depth`: Max. depth of reply-to activities fetching on incoming federation, to prevent out-of-memory situations while fetching very long threads. If set to `nil`, threads of any depth will be fetched. Lower this value if you experience out-of-memory crashes.
|
* `federation_incoming_replies_max_depth`: Max. depth of reply-to activities fetching on incoming federation, to prevent out-of-memory situations while fetching very long threads. If set to `nil`, threads of any depth will be fetched. Lower this value if you experience out-of-memory crashes.
|
||||||
* `federation_reachability_timeout_days`: Timeout (in days) of each external federation target being unreachable prior to pausing federating to it.
|
* `federation_reachability_timeout_days`: Timeout (in days) of each external federation target being unreachable prior to pausing federating to it.
|
||||||
* `allow_relay`: Enable Pleroma’s Relay, which makes it possible to follow a whole instance.
|
* `allow_relay`: Permits remote instances to subscribe to all public posts of your instance. This may increase the visibility of your instance.
|
||||||
* `public`: Makes the client API in authenticated mode-only except for user-profiles. Useful for disabling the Local Timeline and The Whole Known Network. Note that there is a dependent setting restricting or allowing unauthenticated access to specific resources, see `restrict_unauthenticated` for more details.
|
* `public`: Makes the client API in authenticated mode-only except for user-profiles. Useful for disabling the Local Timeline and The Whole Known Network. Note that there is a dependent setting restricting or allowing unauthenticated access to specific resources, see `restrict_unauthenticated` for more details.
|
||||||
* `quarantined_instances`: List of ActivityPub instances where private (DMs, followers-only) activities will not be send.
|
* `quarantined_instances`: List of ActivityPub instances where private (DMs, followers-only) activities will not be send.
|
||||||
* `allowed_post_formats`: MIME-type list of formats allowed to be posted (transformed into HTML).
|
* `allowed_post_formats`: MIME-type list of formats allowed to be posted (transformed into HTML).
|
||||||
|
|
|
@ -38,6 +38,7 @@ Has these additional fields under the `pleroma` object:
|
||||||
- `thread_muted`: true if the thread the post belongs to is muted
|
- `thread_muted`: true if the thread the post belongs to is muted
|
||||||
- `emoji_reactions`: A list with emoji / reaction maps. The format is `{name: "☕", count: 1, me: true}`. Contains no information about the reacting users, for that use the `/statuses/:id/reactions` endpoint.
|
- `emoji_reactions`: A list with emoji / reaction maps. The format is `{name: "☕", count: 1, me: true}`. Contains no information about the reacting users, for that use the `/statuses/:id/reactions` endpoint.
|
||||||
- `parent_visible`: If the parent of this post is visible to the user or not.
|
- `parent_visible`: If the parent of this post is visible to the user or not.
|
||||||
|
- `pinned_at`: a datetime (iso8601) when status was pinned, `null` otherwise.
|
||||||
|
|
||||||
## Scheduled statuses
|
## Scheduled statuses
|
||||||
|
|
||||||
|
@ -255,9 +256,29 @@ This information is returned in the `/api/v1/accounts/verify_credentials` endpoi
|
||||||
|
|
||||||
*Pleroma supports refreshing tokens.*
|
*Pleroma supports refreshing tokens.*
|
||||||
|
|
||||||
`POST /oauth/token`
|
### POST `/oauth/token`
|
||||||
|
|
||||||
Post here request with `grant_type=refresh_token` to obtain new access token. Returns an access token.
|
You can obtain access tokens for a user in a few additional ways.
|
||||||
|
|
||||||
|
#### Refreshing a token
|
||||||
|
|
||||||
|
To obtain a new access token from a refresh token, pass `grant_type=refresh_token` with the following extra parameters:
|
||||||
|
|
||||||
|
- `refresh_token`: The refresh token.
|
||||||
|
|
||||||
|
#### Getting a token with a password
|
||||||
|
|
||||||
|
To obtain a token from a user's password, pass `grant_type=password` with the following extra parameters:
|
||||||
|
|
||||||
|
- `username`: Username to authenticate.
|
||||||
|
- `password`: The user's password.
|
||||||
|
|
||||||
|
#### Response body
|
||||||
|
|
||||||
|
Additional fields are returned in the response:
|
||||||
|
|
||||||
|
- `id`: The primary key of this token in Pleroma's database.
|
||||||
|
- `me` (user tokens only): The ActivityPub ID of the user who owns the token.
|
||||||
|
|
||||||
## Account Registration
|
## Account Registration
|
||||||
|
|
||||||
|
|
|
@ -117,7 +117,7 @@ cd /opt/pleroma
|
||||||
sudo -Hu pleroma mix deps.get
|
sudo -Hu pleroma mix deps.get
|
||||||
```
|
```
|
||||||
|
|
||||||
* Generate the configuration: `sudo -Hu pleroma mix pleroma.instance gen`
|
* Generate the configuration: `sudo -Hu pleroma MIX_ENV=prod mix pleroma.instance gen`
|
||||||
* Answer with `yes` if it asks you to install `rebar3`.
|
* Answer with `yes` if it asks you to install `rebar3`.
|
||||||
* This may take some time, because parts of pleroma get compiled first.
|
* This may take some time, because parts of pleroma get compiled first.
|
||||||
* After that it will ask you a few questions about your instance and generates a configuration file in `config/generated_config.exs`.
|
* After that it will ask you a few questions about your instance and generates a configuration file in `config/generated_config.exs`.
|
||||||
|
@ -240,4 +240,4 @@ sudo -Hu pleroma MIX_ENV=prod mix pleroma.user new <username> <your@emailaddress
|
||||||
|
|
||||||
## Questions
|
## Questions
|
||||||
|
|
||||||
Questions about the installation or didn’t it work as it should be, ask in [#pleroma:matrix.org](https://matrix.heldscal.la/#/room/#freenode_#pleroma:matrix.org) or IRC Channel **#pleroma** on **Freenode**.
|
Questions about the installation or didn’t it work as it should be, ask in [#pleroma:libera.chat](https://matrix.to/#/#pleroma:libera.chat) via Matrix or **#pleroma** on **libera.chat** via IRC.
|
||||||
|
|
|
@ -92,7 +92,7 @@ cd /opt/pleroma
|
||||||
sudo -Hu pleroma mix deps.get
|
sudo -Hu pleroma mix deps.get
|
||||||
```
|
```
|
||||||
|
|
||||||
* Generate the configuration: `sudo -Hu pleroma mix pleroma.instance gen`
|
* Generate the configuration: `sudo -Hu pleroma MIX_ENV=prod mix pleroma.instance gen`
|
||||||
* Answer with `yes` if it asks you to install `rebar3`.
|
* Answer with `yes` if it asks you to install `rebar3`.
|
||||||
* This may take some time, because parts of pleroma get compiled first.
|
* This may take some time, because parts of pleroma get compiled first.
|
||||||
* After that it will ask you a few questions about your instance and generates a configuration file in `config/generated_config.exs`.
|
* After that it will ask you a few questions about your instance and generates a configuration file in `config/generated_config.exs`.
|
||||||
|
@ -215,4 +215,4 @@ sudo -Hu pleroma MIX_ENV=prod mix pleroma.user new <username> <your@emailaddress
|
||||||
|
|
||||||
## Questions
|
## Questions
|
||||||
|
|
||||||
Questions about the installation or didn’t it work as it should be, ask in [#pleroma:matrix.org](https://matrix.heldscal.la/#/room/#freenode_#pleroma:matrix.org) or IRC Channel **#pleroma** on **Freenode**.
|
Questions about the installation or didn’t it work as it should be, ask in [#pleroma:libera.chat](https://matrix.to/#/#pleroma:libera.chat) via Matrix or **#pleroma** on **libera.chat** via IRC.
|
||||||
|
|
|
@ -90,7 +90,7 @@ cd /opt/pleroma
|
||||||
sudo -Hu pleroma mix deps.get
|
sudo -Hu pleroma mix deps.get
|
||||||
```
|
```
|
||||||
|
|
||||||
* Generate the configuration: `sudo -Hu pleroma mix pleroma.instance gen`
|
* Generate the configuration: `sudo -Hu pleroma MIX_ENV=prod mix pleroma.instance gen`
|
||||||
* Answer with `yes` if it asks you to install `rebar3`.
|
* Answer with `yes` if it asks you to install `rebar3`.
|
||||||
* This may take some time, because parts of pleroma get compiled first.
|
* This may take some time, because parts of pleroma get compiled first.
|
||||||
* After that it will ask you a few questions about your instance and generates a configuration file in `config/generated_config.exs`.
|
* After that it will ask you a few questions about your instance and generates a configuration file in `config/generated_config.exs`.
|
||||||
|
@ -202,4 +202,4 @@ sudo -Hu pleroma MIX_ENV=prod mix pleroma.user new <username> <your@emailaddress
|
||||||
|
|
||||||
## Questions
|
## Questions
|
||||||
|
|
||||||
Questions about the installation or didn’t it work as it should be, ask in [#pleroma:matrix.org](https://matrix.heldscal.la/#/room/#freenode_#pleroma:matrix.org) or IRC Channel **#pleroma** on **Freenode**.
|
Questions about the installation or didn’t it work as it should be, ask in [#pleroma:libera.chat](https://matrix.to/#/#pleroma:libera.chat) via Matrix or **#pleroma** on **libera.chat** via IRC.
|
||||||
|
|
|
@ -89,7 +89,7 @@ sudo -Hu pleroma mix deps.get
|
||||||
|
|
||||||
* コンフィギュレーションを生成します。
|
* コンフィギュレーションを生成します。
|
||||||
```
|
```
|
||||||
sudo -Hu pleroma mix pleroma.instance gen
|
sudo -Hu pleroma MIX_ENV=prod mix pleroma.instance gen
|
||||||
```
|
```
|
||||||
* rebar3をインストールしてもよいか聞かれたら、yesを入力してください。
|
* rebar3をインストールしてもよいか聞かれたら、yesを入力してください。
|
||||||
* このときにpleromaの一部がコンパイルされるため、この処理には時間がかかります。
|
* このときにpleromaの一部がコンパイルされるため、この処理には時間がかかります。
|
||||||
|
@ -103,7 +103,7 @@ sudo -Hu pleroma mv config/{generated_config.exs,prod.secret.exs}
|
||||||
|
|
||||||
* 先程のコマンドで、すでに `config/setup_db.psql` というファイルが作られています。このファイルをもとに、データベースを作成します。
|
* 先程のコマンドで、すでに `config/setup_db.psql` というファイルが作られています。このファイルをもとに、データベースを作成します。
|
||||||
```
|
```
|
||||||
sudo -Hu pleroma mix pleroma.instance gen
|
sudo -Hu pleroma MIX_ENV=prod mix pleroma.instance gen
|
||||||
```
|
```
|
||||||
|
|
||||||
* そして、データベースのマイグレーションを実行します。
|
* そして、データベースのマイグレーションを実行します。
|
||||||
|
@ -191,5 +191,5 @@ sudo -Hu pleroma MIX_ENV=prod mix pleroma.user new <username> <your@emailaddress
|
||||||
|
|
||||||
インストールについて質問がある、もしくは、うまくいかないときは、以下のところで質問できます。
|
インストールについて質問がある、もしくは、うまくいかないときは、以下のところで質問できます。
|
||||||
|
|
||||||
* [#pleroma:matrix.org](https://matrix.heldscal.la/#/room/#freenode_#pleroma:matrix.org)
|
* [#pleroma:libera.chat](https://matrix.to/#/#pleroma:libera.chat)
|
||||||
* **Freenode** の **#pleroma** IRCチャンネル
|
* **libera.chat** の **#pleroma** IRCチャンネル
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
# Installing on FreeBSD
|
# Installing on FreeBSD
|
||||||
|
|
||||||
This document was written for FreeBSD 12.1, but should be work on future releases.
|
This document was written for FreeBSD 12.1, but should be work on future releases.
|
||||||
|
|
||||||
## Required software
|
## Required software
|
||||||
|
|
||||||
This assumes the target system has `pkg(8)`.
|
This assumes the target system has `pkg(8)`.
|
||||||
|
|
||||||
|
@ -54,7 +54,7 @@ Configure Pleroma. Note that you need a domain name at this point:
|
||||||
```
|
```
|
||||||
$ cd /home/pleroma/pleroma
|
$ cd /home/pleroma/pleroma
|
||||||
$ mix deps.get # Enter "y" when asked to install Hex
|
$ mix deps.get # Enter "y" when asked to install Hex
|
||||||
$ mix pleroma.instance gen # You will be asked a few questions here.
|
$ MIX_ENV=prod mix pleroma.instance gen # You will be asked a few questions here.
|
||||||
$ cp config/generated_config.exs config/prod.secret.exs
|
$ cp config/generated_config.exs config/prod.secret.exs
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -213,4 +213,4 @@ incorrect timestamps. You should have ntpd running.
|
||||||
|
|
||||||
## Questions
|
## Questions
|
||||||
|
|
||||||
Questions about the installation or didn’t it work as it should be, ask in [#pleroma:matrix.org](https://matrix.heldscal.la/#/room/#freenode_#pleroma:matrix.org) or IRC Channel **#pleroma** on **Freenode**.
|
Questions about the installation or didn’t it work as it should be, ask in [#pleroma:libera.chat](https://matrix.to/#/#pleroma:libera.chat) via Matrix or **#pleroma** on **libera.chat** via IRC.
|
||||||
|
|
|
@ -54,7 +54,7 @@ Gentoo quite pointedly does not come with a cron daemon installed, and as such i
|
||||||
# emerge --ask dev-db/postgresql dev-lang/elixir dev-vcs/git www-servers/nginx app-crypt/certbot app-crypt/certbot-nginx dev-util/cmake sys-apps/file
|
# emerge --ask dev-db/postgresql dev-lang/elixir dev-vcs/git www-servers/nginx app-crypt/certbot app-crypt/certbot-nginx dev-util/cmake sys-apps/file
|
||||||
```
|
```
|
||||||
|
|
||||||
If you would not like to install the optional packages, remove them from this line.
|
If you would not like to install the optional packages, remove them from this line.
|
||||||
|
|
||||||
If you're running this from a low-powered virtual machine, it should work though it will take some time. There were no issues on a VPS with a single core and 1GB of RAM; if you are using an even more limited device and run into issues, you can try creating a swapfile or use a more powerful machine running Gentoo to [cross build](https://wiki.gentoo.org/wiki/Cross_build_environment). If you have a wait ahead of you, now would be a good time to take a break, strech a bit, refresh your beverage of choice and/or get a snack, and reply to Arch users' posts with "I use Gentoo btw" as we do.
|
If you're running this from a low-powered virtual machine, it should work though it will take some time. There were no issues on a VPS with a single core and 1GB of RAM; if you are using an even more limited device and run into issues, you can try creating a swapfile or use a more powerful machine running Gentoo to [cross build](https://wiki.gentoo.org/wiki/Cross_build_environment). If you have a wait ahead of you, now would be a good time to take a break, strech a bit, refresh your beverage of choice and/or get a snack, and reply to Arch users' posts with "I use Gentoo btw" as we do.
|
||||||
|
|
||||||
|
@ -79,12 +79,12 @@ The output from emerging postgresql should give you a command for initializing t
|
||||||
```
|
```
|
||||||
|
|
||||||
* Start postgres and enable the system service
|
* Start postgres and enable the system service
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
# /etc/init.d/postgresql-11 start
|
# /etc/init.d/postgresql-11 start
|
||||||
# rc-update add postgresql-11 default
|
# rc-update add postgresql-11 default
|
||||||
```
|
```
|
||||||
|
|
||||||
### A note on licenses, the AGPL, and deployment procedures
|
### A note on licenses, the AGPL, and deployment procedures
|
||||||
|
|
||||||
If you do not plan to make any modifications to your Pleroma instance, cloning directly from the main repo will get you what you need. However, if you plan on doing any contributions to upstream development, making changes or modifications to your instance, making custom themes, or want to play around--and let's be honest here, if you're using Gentoo that is most likely you--you will save yourself a lot of headache later if you take the time right now to fork the Pleroma repo and use that in the following section.
|
If you do not plan to make any modifications to your Pleroma instance, cloning directly from the main repo will get you what you need. However, if you plan on doing any contributions to upstream development, making changes or modifications to your instance, making custom themes, or want to play around--and let's be honest here, if you're using Gentoo that is most likely you--you will save yourself a lot of headache later if you take the time right now to fork the Pleroma repo and use that in the following section.
|
||||||
|
@ -135,7 +135,7 @@ pleroma$ mix deps.get
|
||||||
* Generate the configuration:
|
* Generate the configuration:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
pleroma$ mix pleroma.instance gen
|
pleroma$ MIX_ENV=prod mix pleroma.instance gen
|
||||||
```
|
```
|
||||||
|
|
||||||
* Answer with `yes` if it asks you to install `rebar3`.
|
* Answer with `yes` if it asks you to install `rebar3`.
|
||||||
|
@ -241,7 +241,7 @@ First, ensure that the command you will be installing into your crontab works.
|
||||||
# /usr/bin/certbot renew --nginx
|
# /usr/bin/certbot renew --nginx
|
||||||
```
|
```
|
||||||
|
|
||||||
Assuming not much time has passed since you got certbot working a few steps ago, you should get a message for all domains you installed certificates for saying `Cert not yet due for renewal`.
|
Assuming not much time has passed since you got certbot working a few steps ago, you should get a message for all domains you installed certificates for saying `Cert not yet due for renewal`.
|
||||||
|
|
||||||
Now, run crontab as a superuser with `crontab -e` or `sudo crontab -e` as appropriate, and add the following line to your cron:
|
Now, run crontab as a superuser with `crontab -e` or `sudo crontab -e` as appropriate, and add the following line to your cron:
|
||||||
|
|
||||||
|
@ -298,4 +298,4 @@ If you opted to allow sudo for the `pleroma` user but would like to remove the a
|
||||||
|
|
||||||
## Questions
|
## Questions
|
||||||
|
|
||||||
Questions about the installation or didn’t it work as it should be, ask in [#pleroma:matrix.org](https://matrix.heldscal.la/#/room/#freenode_#pleroma:matrix.org) or IRC Channel **#pleroma** on **Freenode**.
|
Questions about the installation or didn’t it work as it should be, ask in [#pleroma:libera.chat](https://matrix.to/#/#pleroma:libera.chat) via Matrix or **#pleroma** on **libera.chat** via IRC.
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# Installing on NetBSD
|
# Installing on NetBSD
|
||||||
|
|
||||||
## Required software
|
## Required software
|
||||||
|
|
||||||
pkgin should have been installed by the NetBSD installer if you selected
|
pkgin should have been installed by the NetBSD installer if you selected
|
||||||
the right options. If it isn't installed, install it using pkg_add.
|
the right options. If it isn't installed, install it using pkg_add.
|
||||||
|
@ -71,7 +71,7 @@ Configure Pleroma. Note that you need a domain name at this point:
|
||||||
```
|
```
|
||||||
$ cd /home/pleroma/pleroma
|
$ cd /home/pleroma/pleroma
|
||||||
$ mix deps.get
|
$ mix deps.get
|
||||||
$ mix pleroma.instance gen # You will be asked a few questions here.
|
$ MIX_ENV=prod mix pleroma.instance gen # You will be asked a few questions here.
|
||||||
```
|
```
|
||||||
|
|
||||||
Since Postgres is configured, we can now initialize the database. There should
|
Since Postgres is configured, we can now initialize the database. There should
|
||||||
|
@ -193,8 +193,6 @@ Run `# /etc/rc.d/pleroma start` to start Pleroma.
|
||||||
|
|
||||||
Restart nginx with `# /etc/rc.d/nginx restart` and you should be up and running.
|
Restart nginx with `# /etc/rc.d/nginx restart` and you should be up and running.
|
||||||
|
|
||||||
If you need further help, contact niaa on freenode.
|
|
||||||
|
|
||||||
Make sure your time is in sync, or other instances will receive your posts with
|
Make sure your time is in sync, or other instances will receive your posts with
|
||||||
incorrect timestamps. You should have ntpd running.
|
incorrect timestamps. You should have ntpd running.
|
||||||
|
|
||||||
|
@ -208,4 +206,4 @@ incorrect timestamps. You should have ntpd running.
|
||||||
|
|
||||||
## Questions
|
## Questions
|
||||||
|
|
||||||
Questions about the installation or didn’t it work as it should be, ask in [#pleroma:matrix.org](https://matrix.heldscal.la/#/room/#freenode_#pleroma:matrix.org) or IRC Channel **#pleroma** on **Freenode**.
|
Questions about the installation or didn’t it work as it should be, ask in [#pleroma:libera.chat](https://matrix.to/#/#pleroma:libera.chat) via Matrix or **#pleroma** on **libera.chat** via IRC.
|
||||||
|
|
|
@ -239,7 +239,7 @@ Enter a shell as \_pleroma (as root `su _pleroma -`) and enter pleroma's install
|
||||||
Then follow the main installation guide:
|
Then follow the main installation guide:
|
||||||
|
|
||||||
* run `mix deps.get`
|
* run `mix deps.get`
|
||||||
* run `mix pleroma.instance gen` and enter your instance's information when asked
|
* run `MIX_ENV=prod mix pleroma.instance gen` and enter your instance's information when asked
|
||||||
* copy config/generated\_config.exs to config/prod.secret.exs. The default values should be sufficient but you should edit it and check that everything seems OK.
|
* copy config/generated\_config.exs to config/prod.secret.exs. The default values should be sufficient but you should edit it and check that everything seems OK.
|
||||||
* exit your current shell back to a root one and run `psql -U postgres -f /home/_pleroma/pleroma/config/setup_db.psql` to setup the database.
|
* exit your current shell back to a root one and run `psql -U postgres -f /home/_pleroma/pleroma/config/setup_db.psql` to setup the database.
|
||||||
* return to a \_pleroma shell into pleroma's installation directory (`su _pleroma -;cd ~/pleroma`) and run `MIX_ENV=prod mix ecto.migrate`
|
* return to a \_pleroma shell into pleroma's installation directory (`su _pleroma -;cd ~/pleroma`) and run `MIX_ENV=prod mix ecto.migrate`
|
||||||
|
@ -264,4 +264,4 @@ LC_ALL=en_US.UTF-8 MIX_ENV=prod mix pleroma.user new <username> <your@emailaddre
|
||||||
|
|
||||||
## Questions
|
## Questions
|
||||||
|
|
||||||
Questions about the installation or didn’t it work as it should be, ask in [#pleroma:matrix.org](https://matrix.heldscal.la/#/room/#freenode_#pleroma:matrix.org) or IRC Channel **#pleroma** on **Freenode**.
|
Questions about the installation or didn’t it work as it should be, ask in [#pleroma:libera.chat](https://matrix.to/#/#pleroma:libera.chat) via Matrix or **#pleroma** on **libera.chat** via IRC.
|
||||||
|
|
|
@ -10,8 +10,8 @@ suositeltavaa tehdä komennon `doas` avulla, katso `doas (1)` ja `doas.conf (5)`
|
||||||
Tästä eteenpäin oletuksena on, että domain "esimerkki.com" osoittaa
|
Tästä eteenpäin oletuksena on, että domain "esimerkki.com" osoittaa
|
||||||
serverin IP-osoitteeseen.
|
serverin IP-osoitteeseen.
|
||||||
|
|
||||||
Jos asennuksen kanssa on ongelmia, IRC-kanava #pleroma Freenodessa tai
|
Jos asennuksen kanssa on ongelmia, IRC-kanava #pleroma Libera.chat tai
|
||||||
Matrix-kanava #freenode_#pleroma:matrix.org ovat hyviä paikkoja löytää apua
|
Matrix-kanava #pleroma:libera.chat ovat hyviä paikkoja löytää apua
|
||||||
(englanniksi), `/msg eal kukkuu` jos haluat välttämättä puhua härmää.
|
(englanniksi), `/msg eal kukkuu` jos haluat välttämättä puhua härmää.
|
||||||
|
|
||||||
Asenna tarvittava ohjelmisto:
|
Asenna tarvittava ohjelmisto:
|
||||||
|
|
|
@ -232,7 +232,7 @@ At this point if you open your (sub)domain in a browser you should see a 502 err
|
||||||
|
|
||||||
If everything worked, you should see Pleroma-FE when visiting your domain. If that didn't happen, try reviewing the installation steps, starting Pleroma in the foreground and seeing if there are any errrors.
|
If everything worked, you should see Pleroma-FE when visiting your domain. If that didn't happen, try reviewing the installation steps, starting Pleroma in the foreground and seeing if there are any errrors.
|
||||||
|
|
||||||
Still doesn't work? Feel free to contact us on [#pleroma on freenode](https://irc.pleroma.social) or via matrix at <https://matrix.heldscal.la/#/room/#freenode_#pleroma:matrix.org>, you can also [file an issue on our Gitlab](https://git.pleroma.social/pleroma/pleroma-support/issues/new)
|
Questions about the installation or didn’t it work as it should be, ask in [#pleroma:libera.chat](https://matrix.to/#/#pleroma:libera.chat) via Matrix or **#pleroma** on **libera.chat** via IRC, you can also [file an issue on our Gitlab](https://git.pleroma.social/pleroma/pleroma-support/issues/new).
|
||||||
|
|
||||||
## Post installation
|
## Post installation
|
||||||
|
|
||||||
|
@ -290,7 +290,7 @@ nginx -t
|
||||||
|
|
||||||
## Create your first user and set as admin
|
## Create your first user and set as admin
|
||||||
```sh
|
```sh
|
||||||
cd /opt/pleroma/bin
|
cd /opt/pleroma
|
||||||
su pleroma -s $SHELL -lc "./bin/pleroma_ctl user new joeuser joeuser@sld.tld --admin"
|
su pleroma -s $SHELL -lc "./bin/pleroma_ctl user new joeuser joeuser@sld.tld --admin"
|
||||||
```
|
```
|
||||||
This will create an account withe the username of 'joeuser' with the email address of joeuser@sld.tld, and set that user's account as an admin. This will result in a link that you can paste into the browser, which logs you in and enables you to set the password.
|
This will create an account withe the username of 'joeuser' with the email address of joeuser@sld.tld, and set that user's account as an admin. This will result in a link that you can paste into the browser, which logs you in and enables you to set the password.
|
||||||
|
@ -301,4 +301,4 @@ This will create an account withe the username of 'joeuser' with the email addre
|
||||||
|
|
||||||
## Questions
|
## Questions
|
||||||
|
|
||||||
Questions about the installation or didn’t it work as it should be, ask in [#pleroma:matrix.org](https://matrix.heldscal.la/#/room/#freenode_#pleroma:matrix.org) or IRC Channel **#pleroma** on **Freenode**.
|
Questions about the installation or didn’t it work as it should be, ask in [#pleroma:libera.chat](https://matrix.to/#/#pleroma:libera.chat) via Matrix or **#pleroma** on **libera.chat** via IRC, you can also [file an issue on our Gitlab](https://git.pleroma.social/pleroma/pleroma-support/issues/new).
|
||||||
|
|
|
@ -184,40 +184,48 @@ def get_by_ap_id_with_object(ap_id) do
|
||||||
|> Repo.one()
|
|> Repo.one()
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec get_by_id(String.t()) :: Activity.t() | nil
|
@doc """
|
||||||
def get_by_id(id) do
|
Gets activity by ID, doesn't load activities from deactivated actors by default.
|
||||||
case FlakeId.flake_id?(id) do
|
"""
|
||||||
true ->
|
@spec get_by_id(String.t(), keyword()) :: t() | nil
|
||||||
Activity
|
def get_by_id(id, opts \\ [filter: [:restrict_deactivated]]), do: get_by_id_with_opts(id, opts)
|
||||||
|> where([a], a.id == ^id)
|
|
||||||
|> restrict_deactivated_users()
|
|
||||||
|> Repo.one()
|
|
||||||
|
|
||||||
_ ->
|
@spec get_by_id_with_user_actor(String.t()) :: t() | nil
|
||||||
nil
|
def get_by_id_with_user_actor(id), do: get_by_id_with_opts(id, preload: [:user_actor])
|
||||||
|
|
||||||
|
@spec get_by_id_with_object(String.t()) :: t() | nil
|
||||||
|
def get_by_id_with_object(id), do: get_by_id_with_opts(id, preload: [:object])
|
||||||
|
|
||||||
|
defp get_by_id_with_opts(id, opts) do
|
||||||
|
if FlakeId.flake_id?(id) do
|
||||||
|
query = Queries.by_id(id)
|
||||||
|
|
||||||
|
with_filters_query =
|
||||||
|
if is_list(opts[:filter]) do
|
||||||
|
Enum.reduce(opts[:filter], query, fn
|
||||||
|
{:type, type}, acc -> Queries.by_type(acc, type)
|
||||||
|
:restrict_deactivated, acc -> restrict_deactivated_users(acc)
|
||||||
|
_, acc -> acc
|
||||||
|
end)
|
||||||
|
else
|
||||||
|
query
|
||||||
|
end
|
||||||
|
|
||||||
|
with_preloads_query =
|
||||||
|
if is_list(opts[:preload]) do
|
||||||
|
Enum.reduce(opts[:preload], with_filters_query, fn
|
||||||
|
:user_actor, acc -> with_preloaded_user_actor(acc)
|
||||||
|
:object, acc -> with_preloaded_object(acc)
|
||||||
|
_, acc -> acc
|
||||||
|
end)
|
||||||
|
else
|
||||||
|
with_filters_query
|
||||||
|
end
|
||||||
|
|
||||||
|
Repo.one(with_preloads_query)
|
||||||
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
|
|
||||||
Activity
|
|
||||||
|> where(id: ^id)
|
|
||||||
|> with_preloaded_object()
|
|
||||||
|> Repo.one()
|
|
||||||
end
|
|
||||||
|
|
||||||
def all_by_ids_with_object(ids) do
|
def all_by_ids_with_object(ids) do
|
||||||
Activity
|
Activity
|
||||||
|> where([a], a.id in ^ids)
|
|> where([a], a.id in ^ids)
|
||||||
|
@ -269,6 +277,11 @@ def get_create_by_object_ap_id_with_object(ap_id) when is_binary(ap_id) do
|
||||||
|
|
||||||
def get_create_by_object_ap_id_with_object(_), do: nil
|
def get_create_by_object_ap_id_with_object(_), do: nil
|
||||||
|
|
||||||
|
@spec create_by_id_with_object(String.t()) :: t() | nil
|
||||||
|
def create_by_id_with_object(id) do
|
||||||
|
get_by_id_with_opts(id, preload: [:object], filter: [type: "Create"])
|
||||||
|
end
|
||||||
|
|
||||||
defp get_in_reply_to_activity_from_object(%Object{data: %{"inReplyTo" => ap_id}}) do
|
defp get_in_reply_to_activity_from_object(%Object{data: %{"inReplyTo" => ap_id}}) do
|
||||||
get_create_by_object_ap_id_with_object(ap_id)
|
get_create_by_object_ap_id_with_object(ap_id)
|
||||||
end
|
end
|
||||||
|
@ -368,12 +381,6 @@ def direct_conversation_id(activity, for_user) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec pinned_by_actor?(Activity.t()) :: boolean()
|
|
||||||
def pinned_by_actor?(%Activity{} = activity) do
|
|
||||||
actor = user_actor(activity)
|
|
||||||
activity.id in actor.pinned_activities
|
|
||||||
end
|
|
||||||
|
|
||||||
@spec get_by_object_ap_id_with_object(String.t()) :: t() | nil
|
@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
|
def get_by_object_ap_id_with_object(ap_id) when is_binary(ap_id) do
|
||||||
ap_id
|
ap_id
|
||||||
|
@ -384,4 +391,13 @@ def get_by_object_ap_id_with_object(ap_id) when is_binary(ap_id) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_by_object_ap_id_with_object(_), do: nil
|
def get_by_object_ap_id_with_object(_), do: nil
|
||||||
|
|
||||||
|
@spec add_by_params_query(String.t(), String.t(), String.t()) :: Ecto.Query.t()
|
||||||
|
def add_by_params_query(object_id, actor, target) do
|
||||||
|
object_id
|
||||||
|
|> Queries.by_object_id()
|
||||||
|
|> Queries.by_type("Add")
|
||||||
|
|> Queries.by_actor(actor)
|
||||||
|
|> where([a], fragment("?->>'target' = ?", a.data, ^target))
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Activity.HTML do
|
||||||
|
alias Pleroma.HTML
|
||||||
|
alias Pleroma.Object
|
||||||
|
|
||||||
|
@cachex Pleroma.Config.get([:cachex, :provider], Cachex)
|
||||||
|
|
||||||
|
def get_cached_scrubbed_html_for_activity(
|
||||||
|
content,
|
||||||
|
scrubbers,
|
||||||
|
activity,
|
||||||
|
key \\ "",
|
||||||
|
callback \\ fn x -> x end
|
||||||
|
) do
|
||||||
|
key = "#{key}#{generate_scrubber_signature(scrubbers)}|#{activity.id}"
|
||||||
|
|
||||||
|
@cachex.fetch!(:scrubber_cache, key, fn _key ->
|
||||||
|
object = Object.normalize(activity, fetch: false)
|
||||||
|
HTML.ensure_scrubbed_html(content, scrubbers, object.data["fake"] || false, callback)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_cached_stripped_html_for_activity(content, activity, key) do
|
||||||
|
get_cached_scrubbed_html_for_activity(
|
||||||
|
content,
|
||||||
|
FastSanitize.Sanitizer.StripTags,
|
||||||
|
activity,
|
||||||
|
key,
|
||||||
|
&HtmlEntities.decode/1
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp generate_scrubber_signature(scrubber) when is_atom(scrubber) do
|
||||||
|
generate_scrubber_signature([scrubber])
|
||||||
|
end
|
||||||
|
|
||||||
|
defp generate_scrubber_signature(scrubbers) do
|
||||||
|
Enum.reduce(scrubbers, "", fn scrubber, signature ->
|
||||||
|
"#{signature}#{to_string(scrubber)}"
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end
|
|
@ -14,6 +14,11 @@ defmodule Pleroma.Activity.Queries do
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
|
||||||
|
@spec by_id(query(), String.t()) :: query()
|
||||||
|
def by_id(query \\ Activity, id) do
|
||||||
|
from(a in query, where: a.id == ^id)
|
||||||
|
end
|
||||||
|
|
||||||
@spec by_ap_id(query, String.t()) :: query
|
@spec by_ap_id(query, String.t()) :: query
|
||||||
def by_ap_id(query \\ Activity, ap_id) do
|
def by_ap_id(query \\ Activity, ap_id) do
|
||||||
from(
|
from(
|
||||||
|
|
|
@ -25,7 +25,7 @@ def user_agent do
|
||||||
if Process.whereis(Pleroma.Web.Endpoint) do
|
if Process.whereis(Pleroma.Web.Endpoint) do
|
||||||
case Config.get([:http, :user_agent], :default) do
|
case Config.get([:http, :user_agent], :default) do
|
||||||
:default ->
|
:default ->
|
||||||
info = "#{Pleroma.Web.base_url()} <#{Config.get([:instance, :email], "")}>"
|
info = "#{Pleroma.Web.Endpoint.url()} <#{Config.get([:instance, :email], "")}>"
|
||||||
named_version() <> "; " <> info
|
named_version() <> "; " <> info
|
||||||
|
|
||||||
custom ->
|
custom ->
|
||||||
|
@ -102,7 +102,7 @@ def start(_type, _args) do
|
||||||
] ++
|
] ++
|
||||||
task_children(@mix_env) ++
|
task_children(@mix_env) ++
|
||||||
dont_run_in_test(@mix_env) ++
|
dont_run_in_test(@mix_env) ++
|
||||||
chat_child(chat_enabled?()) ++
|
shout_child(shout_enabled?()) ++
|
||||||
[Pleroma.Gopher.Server]
|
[Pleroma.Gopher.Server]
|
||||||
|
|
||||||
# See http://elixir-lang.org/docs/stable/elixir/Supervisor.html
|
# See http://elixir-lang.org/docs/stable/elixir/Supervisor.html
|
||||||
|
@ -216,7 +216,7 @@ def build_cachex(type, opts),
|
||||||
type: :worker
|
type: :worker
|
||||||
}
|
}
|
||||||
|
|
||||||
defp chat_enabled?, do: Config.get([:chat, :enabled])
|
defp shout_enabled?, do: Config.get([:shout, :enabled])
|
||||||
|
|
||||||
defp dont_run_in_test(env) when env in [:test, :benchmark], do: []
|
defp dont_run_in_test(env) when env in [:test, :benchmark], do: []
|
||||||
|
|
||||||
|
@ -237,14 +237,14 @@ defp background_migrators do
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
defp chat_child(true) do
|
defp shout_child(true) do
|
||||||
[
|
[
|
||||||
Pleroma.Web.ChatChannel.ChatChannelState,
|
Pleroma.Web.ShoutChannel.ShoutChannelState,
|
||||||
{Phoenix.PubSub, [name: Pleroma.PubSub, adapter: Phoenix.PubSub.PG2]}
|
{Phoenix.PubSub, [name: Pleroma.PubSub, adapter: Phoenix.PubSub.PG2]}
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
defp chat_child(_), do: []
|
defp shout_child(_), do: []
|
||||||
|
|
||||||
defp task_children(:test) do
|
defp task_children(:test) do
|
||||||
[
|
[
|
||||||
|
|
|
@ -34,15 +34,16 @@ defp handle_result({:error, message}), do: raise(VerifyError, message: message)
|
||||||
defp check_welcome_message_config!(:ok) do
|
defp check_welcome_message_config!(:ok) do
|
||||||
if Pleroma.Config.get([:welcome, :email, :enabled], false) and
|
if Pleroma.Config.get([:welcome, :email, :enabled], false) and
|
||||||
not Pleroma.Emails.Mailer.enabled?() do
|
not Pleroma.Emails.Mailer.enabled?() do
|
||||||
Logger.error("""
|
Logger.warn("""
|
||||||
To send welcome email do you need to enable mail.
|
To send welcome emails, you need to enable the mailer.
|
||||||
\nconfig :pleroma, Pleroma.Emails.Mailer, enabled: true
|
Welcome emails will NOT be sent with the current config.
|
||||||
""")
|
|
||||||
|
|
||||||
{:error, "The mail disabled."}
|
Enable the mailer:
|
||||||
else
|
config :pleroma, Pleroma.Emails.Mailer, enabled: true
|
||||||
:ok
|
""")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
:ok
|
||||||
end
|
end
|
||||||
|
|
||||||
defp check_welcome_message_config!(result), do: result
|
defp check_welcome_message_config!(result), do: result
|
||||||
|
@ -51,18 +52,21 @@ defp check_welcome_message_config!(result), do: result
|
||||||
#
|
#
|
||||||
def check_confirmation_accounts!(:ok) do
|
def check_confirmation_accounts!(:ok) do
|
||||||
if Pleroma.Config.get([:instance, :account_activation_required]) &&
|
if Pleroma.Config.get([:instance, :account_activation_required]) &&
|
||||||
not Pleroma.Config.get([Pleroma.Emails.Mailer, :enabled]) do
|
not Pleroma.Emails.Mailer.enabled?() do
|
||||||
Logger.error(
|
Logger.warn("""
|
||||||
"Account activation enabled, but no Mailer settings enabled.\n" <>
|
Account activation is required, but the mailer is disabled.
|
||||||
"Please set config :pleroma, :instance, account_activation_required: false\n" <>
|
Users will NOT be able to confirm their accounts with this config.
|
||||||
"Otherwise setup and enable Mailer."
|
Either disable account activation or enable the mailer.
|
||||||
)
|
|
||||||
|
|
||||||
{:error,
|
Disable account activation:
|
||||||
"Account activation enabled, but Mailer is disabled. Cannot send confirmation emails."}
|
config :pleroma, :instance, account_activation_required: false
|
||||||
else
|
|
||||||
:ok
|
Enable the mailer:
|
||||||
|
config :pleroma, Pleroma.Emails.Mailer, enabled: true
|
||||||
|
""")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
:ok
|
||||||
end
|
end
|
||||||
|
|
||||||
def check_confirmation_accounts!(result), do: result
|
def check_confirmation_accounts!(result), do: result
|
||||||
|
@ -160,9 +164,11 @@ defp do_check_rum!(setting, migrate) do
|
||||||
|
|
||||||
defp check_system_commands!(:ok) do
|
defp check_system_commands!(:ok) do
|
||||||
filter_commands_statuses = [
|
filter_commands_statuses = [
|
||||||
check_filter(Pleroma.Upload.Filters.Exiftool, "exiftool"),
|
check_filter(Pleroma.Upload.Filter.Exiftool, "exiftool"),
|
||||||
check_filter(Pleroma.Upload.Filters.Mogrify, "mogrify"),
|
check_filter(Pleroma.Upload.Filter.Mogrify, "mogrify"),
|
||||||
check_filter(Pleroma.Upload.Filters.Mogrifun, "mogrify")
|
check_filter(Pleroma.Upload.Filter.Mogrifun, "mogrify"),
|
||||||
|
check_filter(Pleroma.Upload.Filter.AnalyzeMetadata, "mogrify"),
|
||||||
|
check_filter(Pleroma.Upload.Filter.AnalyzeMetadata, "convert")
|
||||||
]
|
]
|
||||||
|
|
||||||
preview_proxy_commands_status =
|
preview_proxy_commands_status =
|
||||||
|
|
|
@ -41,7 +41,8 @@ def warn do
|
||||||
:ok <- check_gun_pool_options(),
|
:ok <- check_gun_pool_options(),
|
||||||
:ok <- check_activity_expiration_config(),
|
:ok <- check_activity_expiration_config(),
|
||||||
:ok <- check_remote_ip_plug_name(),
|
:ok <- check_remote_ip_plug_name(),
|
||||||
:ok <- check_uploders_s3_public_endpoint() do
|
:ok <- check_uploders_s3_public_endpoint(),
|
||||||
|
:ok <- check_old_chat_shoutbox() do
|
||||||
:ok
|
:ok
|
||||||
else
|
else
|
||||||
_ ->
|
_ ->
|
||||||
|
@ -215,4 +216,27 @@ def check_uploders_s3_public_endpoint do
|
||||||
:ok
|
:ok
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec check_old_chat_shoutbox() :: :ok | nil
|
||||||
|
def check_old_chat_shoutbox do
|
||||||
|
instance_config = Pleroma.Config.get([:instance])
|
||||||
|
chat_config = Pleroma.Config.get([:chat]) || []
|
||||||
|
|
||||||
|
use_old_config =
|
||||||
|
Keyword.has_key?(instance_config, :chat_limit) or
|
||||||
|
Keyword.has_key?(chat_config, :enabled)
|
||||||
|
|
||||||
|
if use_old_config do
|
||||||
|
Logger.error("""
|
||||||
|
!!!DEPRECATION WARNING!!!
|
||||||
|
Your config is using the old namespace for the Shoutbox configuration. You need to convert to the new namespace. e.g.,
|
||||||
|
\n* `config :pleroma, :chat, enabled` and `config :pleroma, :instance, chat_limit` are now equal to:
|
||||||
|
\n* `config :pleroma, :shout, enabled` and `config :pleroma, :shout, limit`
|
||||||
|
""")
|
||||||
|
|
||||||
|
:error
|
||||||
|
else
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,19 +3,21 @@
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Pleroma.Config.Loader do
|
defmodule Pleroma.Config.Loader do
|
||||||
@reject_keys [
|
defp reject_keys,
|
||||||
Pleroma.Repo,
|
do: [
|
||||||
Pleroma.Web.Endpoint,
|
Pleroma.Repo,
|
||||||
:env,
|
Pleroma.Web.Endpoint,
|
||||||
:configurable_from_database,
|
:env,
|
||||||
:database,
|
:configurable_from_database,
|
||||||
:swarm
|
:database,
|
||||||
]
|
:swarm
|
||||||
|
]
|
||||||
|
|
||||||
@reject_groups [
|
defp reject_groups,
|
||||||
:postgrex,
|
do: [
|
||||||
:tesla
|
:postgrex,
|
||||||
]
|
:tesla
|
||||||
|
]
|
||||||
|
|
||||||
if Code.ensure_loaded?(Config.Reader) do
|
if Code.ensure_loaded?(Config.Reader) do
|
||||||
@reader Config.Reader
|
@reader Config.Reader
|
||||||
|
@ -52,7 +54,7 @@ defp filter(configs) do
|
||||||
@spec filter_group(atom(), keyword()) :: keyword()
|
@spec filter_group(atom(), keyword()) :: keyword()
|
||||||
def filter_group(group, configs) do
|
def filter_group(group, configs) do
|
||||||
Enum.reject(configs[group], fn {key, _v} ->
|
Enum.reject(configs[group], fn {key, _v} ->
|
||||||
key in @reject_keys or group in @reject_groups or
|
key in reject_keys() or group in reject_groups() or
|
||||||
(group == :phoenix and key == :serve_endpoints)
|
(group == :phoenix and key == :serve_endpoints)
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
defmodule Pleroma.Config.ReleaseRuntimeProvider do
|
defmodule Pleroma.Config.ReleaseRuntimeProvider do
|
||||||
@moduledoc """
|
@moduledoc """
|
||||||
Imports `runtime.exs` and `{env}.exported_from_db.secret.exs` for elixir releases.
|
Imports runtime config and `{env}.exported_from_db.secret.exs` for releases.
|
||||||
"""
|
"""
|
||||||
@behaviour Config.Provider
|
@behaviour Config.Provider
|
||||||
|
|
||||||
|
@ -8,10 +8,11 @@ defmodule Pleroma.Config.ReleaseRuntimeProvider do
|
||||||
def init(opts), do: opts
|
def init(opts), do: opts
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def load(config, _opts) do
|
def load(config, opts) do
|
||||||
with_defaults = Config.Reader.merge(config, Pleroma.Config.Holder.release_defaults())
|
with_defaults = Config.Reader.merge(config, Pleroma.Config.Holder.release_defaults())
|
||||||
|
|
||||||
config_path = System.get_env("PLEROMA_CONFIG_PATH") || "/etc/pleroma/config.exs"
|
config_path =
|
||||||
|
opts[:config_path] || System.get_env("PLEROMA_CONFIG_PATH") || "/etc/pleroma/config.exs"
|
||||||
|
|
||||||
with_runtime_config =
|
with_runtime_config =
|
||||||
if File.exists?(config_path) do
|
if File.exists?(config_path) do
|
||||||
|
@ -24,7 +25,7 @@ def load(config, _opts) do
|
||||||
warning = [
|
warning = [
|
||||||
IO.ANSI.red(),
|
IO.ANSI.red(),
|
||||||
IO.ANSI.bright(),
|
IO.ANSI.bright(),
|
||||||
"!!! #{config_path} not found! Please ensure it exists and that PLEROMA_CONFIG_PATH is unset or points to an existing file",
|
"!!! Config path is not declared! Please ensure it exists and that PLEROMA_CONFIG_PATH is unset or points to an existing file",
|
||||||
IO.ANSI.reset()
|
IO.ANSI.reset()
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -33,13 +34,14 @@ def load(config, _opts) do
|
||||||
end
|
end
|
||||||
|
|
||||||
exported_config_path =
|
exported_config_path =
|
||||||
config_path
|
opts[:exported_config_path] ||
|
||||||
|> Path.dirname()
|
config_path
|
||||||
|> Path.join("prod.exported_from_db.secret.exs")
|
|> Path.dirname()
|
||||||
|
|> Path.join("#{Pleroma.Config.get(:env)}.exported_from_db.secret.exs")
|
||||||
|
|
||||||
with_exported =
|
with_exported =
|
||||||
if File.exists?(exported_config_path) do
|
if File.exists?(exported_config_path) do
|
||||||
exported_config = Config.Reader.read!(with_runtime_config)
|
exported_config = Config.Reader.read!(exported_config_path)
|
||||||
Config.Reader.merge(with_runtime_config, exported_config)
|
Config.Reader.merge(with_runtime_config, exported_config)
|
||||||
else
|
else
|
||||||
with_runtime_config
|
with_runtime_config
|
||||||
|
|
|
@ -13,23 +13,25 @@ defmodule Pleroma.Config.TransferTask do
|
||||||
|
|
||||||
@type env() :: :test | :benchmark | :dev | :prod
|
@type env() :: :test | :benchmark | :dev | :prod
|
||||||
|
|
||||||
@reboot_time_keys [
|
defp reboot_time_keys,
|
||||||
{:pleroma, :hackney_pools},
|
do: [
|
||||||
{:pleroma, :chat},
|
{:pleroma, :hackney_pools},
|
||||||
{:pleroma, Oban},
|
{:pleroma, :shout},
|
||||||
{:pleroma, :rate_limit},
|
{:pleroma, Oban},
|
||||||
{:pleroma, :markup},
|
{:pleroma, :rate_limit},
|
||||||
{:pleroma, :streamer},
|
{:pleroma, :markup},
|
||||||
{:pleroma, :pools},
|
{:pleroma, :streamer},
|
||||||
{:pleroma, :connections_pool}
|
{:pleroma, :pools},
|
||||||
]
|
{:pleroma, :connections_pool}
|
||||||
|
]
|
||||||
|
|
||||||
@reboot_time_subkeys [
|
defp reboot_time_subkeys,
|
||||||
{:pleroma, Pleroma.Captcha, [:seconds_valid]},
|
do: [
|
||||||
{:pleroma, Pleroma.Upload, [:proxy_remote]},
|
{:pleroma, Pleroma.Captcha, [:seconds_valid]},
|
||||||
{:pleroma, :instance, [:upload_limit]},
|
{:pleroma, Pleroma.Upload, [:proxy_remote]},
|
||||||
{:pleroma, :gopher, [:enabled]}
|
{:pleroma, :instance, [:upload_limit]},
|
||||||
]
|
{:pleroma, :gopher, [:enabled]}
|
||||||
|
]
|
||||||
|
|
||||||
def start_link(restart_pleroma? \\ true) do
|
def start_link(restart_pleroma? \\ true) do
|
||||||
load_and_update_env([], restart_pleroma?)
|
load_and_update_env([], restart_pleroma?)
|
||||||
|
@ -165,12 +167,12 @@ def pleroma_need_restart?(group, key, value) do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp group_and_key_need_reboot?(group, key) do
|
defp group_and_key_need_reboot?(group, key) do
|
||||||
Enum.any?(@reboot_time_keys, fn {g, k} -> g == group and k == key end)
|
Enum.any?(reboot_time_keys(), fn {g, k} -> g == group and k == key end)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp group_and_subkey_need_reboot?(group, key, value) do
|
defp group_and_subkey_need_reboot?(group, key, value) do
|
||||||
Keyword.keyword?(value) and
|
Keyword.keyword?(value) and
|
||||||
Enum.any?(@reboot_time_subkeys, fn {g, k, subkeys} ->
|
Enum.any?(reboot_time_subkeys(), fn {g, k, subkeys} ->
|
||||||
g == group and k == key and
|
g == group and k == key and
|
||||||
Enum.any?(Keyword.keys(value), &(&1 in subkeys))
|
Enum.any?(Keyword.keys(value), &(&1 in subkeys))
|
||||||
end)
|
end)
|
||||||
|
|
|
@ -27,6 +27,4 @@ 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
|
||||||
|
|
|
@ -1,256 +0,0 @@
|
||||||
# Pleroma: A lightweight social networking server
|
|
||||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
#
|
|
||||||
# This file is derived from Earmark, under the following copyright:
|
|
||||||
# Copyright © 2014 Dave Thomas, The Pragmatic Programmers
|
|
||||||
# SPDX-License-Identifier: Apache-2.0
|
|
||||||
# Upstream: https://github.com/pragdave/earmark/blob/master/lib/earmark/html_renderer.ex
|
|
||||||
defmodule Pleroma.EarmarkRenderer do
|
|
||||||
@moduledoc false
|
|
||||||
|
|
||||||
alias Earmark.Block
|
|
||||||
alias Earmark.Context
|
|
||||||
alias Earmark.HtmlRenderer
|
|
||||||
alias Earmark.Options
|
|
||||||
|
|
||||||
import Earmark.Inline, only: [convert: 3]
|
|
||||||
import Earmark.Helpers.HtmlHelpers
|
|
||||||
import Earmark.Message, only: [add_messages_from: 2, get_messages: 1, set_messages: 2]
|
|
||||||
import Earmark.Context, only: [append: 2, set_value: 2]
|
|
||||||
import Earmark.Options, only: [get_mapper: 1]
|
|
||||||
|
|
||||||
@doc false
|
|
||||||
def render(blocks, %Context{options: %Options{}} = context) do
|
|
||||||
messages = get_messages(context)
|
|
||||||
|
|
||||||
{contexts, html} =
|
|
||||||
get_mapper(context.options).(
|
|
||||||
blocks,
|
|
||||||
&render_block(&1, put_in(context.options.messages, []))
|
|
||||||
)
|
|
||||||
|> Enum.unzip()
|
|
||||||
|
|
||||||
all_messages =
|
|
||||||
contexts
|
|
||||||
|> Enum.reduce(messages, fn ctx, messages1 -> messages1 ++ get_messages(ctx) end)
|
|
||||||
|
|
||||||
{put_in(context.options.messages, all_messages), html |> IO.iodata_to_binary()}
|
|
||||||
end
|
|
||||||
|
|
||||||
#############
|
|
||||||
# Paragraph #
|
|
||||||
#############
|
|
||||||
defp render_block(%Block.Para{lnb: lnb, lines: lines, attrs: attrs}, context) do
|
|
||||||
lines = convert(lines, lnb, context)
|
|
||||||
add_attrs(lines, "<p>#{lines.value}</p>", attrs, [], lnb)
|
|
||||||
end
|
|
||||||
|
|
||||||
########
|
|
||||||
# Html #
|
|
||||||
########
|
|
||||||
defp render_block(%Block.Html{html: html}, context) do
|
|
||||||
{context, html}
|
|
||||||
end
|
|
||||||
|
|
||||||
defp render_block(%Block.HtmlComment{lines: lines}, context) do
|
|
||||||
{context, lines}
|
|
||||||
end
|
|
||||||
|
|
||||||
defp render_block(%Block.HtmlOneline{html: html}, context) do
|
|
||||||
{context, html}
|
|
||||||
end
|
|
||||||
|
|
||||||
#########
|
|
||||||
# Ruler #
|
|
||||||
#########
|
|
||||||
defp render_block(%Block.Ruler{lnb: lnb, attrs: attrs}, context) do
|
|
||||||
add_attrs(context, "<hr />", attrs, [], lnb)
|
|
||||||
end
|
|
||||||
|
|
||||||
###########
|
|
||||||
# Heading #
|
|
||||||
###########
|
|
||||||
defp render_block(
|
|
||||||
%Block.Heading{lnb: lnb, level: level, content: content, attrs: attrs},
|
|
||||||
context
|
|
||||||
) do
|
|
||||||
converted = convert(content, lnb, context)
|
|
||||||
html = "<h#{level}>#{converted.value}</h#{level}>"
|
|
||||||
add_attrs(converted, html, attrs, [], lnb)
|
|
||||||
end
|
|
||||||
|
|
||||||
##############
|
|
||||||
# Blockquote #
|
|
||||||
##############
|
|
||||||
|
|
||||||
defp render_block(%Block.BlockQuote{lnb: lnb, blocks: blocks, attrs: attrs}, context) do
|
|
||||||
{context1, body} = render(blocks, context)
|
|
||||||
html = "<blockquote>#{body}</blockquote>"
|
|
||||||
add_attrs(context1, html, attrs, [], lnb)
|
|
||||||
end
|
|
||||||
|
|
||||||
#########
|
|
||||||
# Table #
|
|
||||||
#########
|
|
||||||
|
|
||||||
defp render_block(
|
|
||||||
%Block.Table{lnb: lnb, header: header, rows: rows, alignments: aligns, attrs: attrs},
|
|
||||||
context
|
|
||||||
) do
|
|
||||||
{context1, html} = add_attrs(context, "<table>", attrs, [], lnb)
|
|
||||||
context2 = set_value(context1, html)
|
|
||||||
|
|
||||||
context3 =
|
|
||||||
if header do
|
|
||||||
append(add_trs(append(context2, "<thead>"), [header], "th", aligns, lnb), "</thead>")
|
|
||||||
else
|
|
||||||
# Maybe an error, needed append(context, html)
|
|
||||||
context2
|
|
||||||
end
|
|
||||||
|
|
||||||
context4 = append(add_trs(append(context3, "<tbody>"), rows, "td", aligns, lnb), "</tbody>")
|
|
||||||
|
|
||||||
{context4, [context4.value, "</table>"]}
|
|
||||||
end
|
|
||||||
|
|
||||||
########
|
|
||||||
# Code #
|
|
||||||
########
|
|
||||||
|
|
||||||
defp render_block(
|
|
||||||
%Block.Code{lnb: lnb, language: language, attrs: attrs} = block,
|
|
||||||
%Context{options: options} = context
|
|
||||||
) do
|
|
||||||
class =
|
|
||||||
if language, do: ~s{ class="#{code_classes(language, options.code_class_prefix)}"}, else: ""
|
|
||||||
|
|
||||||
tag = ~s[<pre><code#{class}>]
|
|
||||||
lines = options.render_code.(block)
|
|
||||||
html = ~s[#{tag}#{lines}</code></pre>]
|
|
||||||
add_attrs(context, html, attrs, [], lnb)
|
|
||||||
end
|
|
||||||
|
|
||||||
#########
|
|
||||||
# Lists #
|
|
||||||
#########
|
|
||||||
|
|
||||||
defp render_block(
|
|
||||||
%Block.List{lnb: lnb, type: type, blocks: items, attrs: attrs, start: start},
|
|
||||||
context
|
|
||||||
) do
|
|
||||||
{context1, content} = render(items, context)
|
|
||||||
html = "<#{type}#{start}>#{content}</#{type}>"
|
|
||||||
add_attrs(context1, html, attrs, [], lnb)
|
|
||||||
end
|
|
||||||
|
|
||||||
# format a single paragraph list item, and remove the para tags
|
|
||||||
defp render_block(
|
|
||||||
%Block.ListItem{lnb: lnb, blocks: blocks, spaced: false, attrs: attrs},
|
|
||||||
context
|
|
||||||
)
|
|
||||||
when length(blocks) == 1 do
|
|
||||||
{context1, content} = render(blocks, context)
|
|
||||||
content = Regex.replace(~r{</?p>}, content, "")
|
|
||||||
html = "<li>#{content}</li>"
|
|
||||||
add_attrs(context1, html, attrs, [], lnb)
|
|
||||||
end
|
|
||||||
|
|
||||||
# format a spaced list item
|
|
||||||
defp render_block(%Block.ListItem{lnb: lnb, blocks: blocks, attrs: attrs}, context) do
|
|
||||||
{context1, content} = render(blocks, context)
|
|
||||||
html = "<li>#{content}</li>"
|
|
||||||
add_attrs(context1, html, attrs, [], lnb)
|
|
||||||
end
|
|
||||||
|
|
||||||
##################
|
|
||||||
# Footnote Block #
|
|
||||||
##################
|
|
||||||
|
|
||||||
defp render_block(%Block.FnList{blocks: footnotes}, context) do
|
|
||||||
items =
|
|
||||||
Enum.map(footnotes, fn note ->
|
|
||||||
blocks = append_footnote_link(note)
|
|
||||||
%Block.ListItem{attrs: "#fn:#{note.number}", type: :ol, blocks: blocks}
|
|
||||||
end)
|
|
||||||
|
|
||||||
{context1, html} = render_block(%Block.List{type: :ol, blocks: items}, context)
|
|
||||||
{context1, Enum.join([~s[<div class="footnotes">], "<hr />", html, "</div>"])}
|
|
||||||
end
|
|
||||||
|
|
||||||
#######################################
|
|
||||||
# Isolated IALs are rendered as paras #
|
|
||||||
#######################################
|
|
||||||
|
|
||||||
defp render_block(%Block.Ial{verbatim: verbatim}, context) do
|
|
||||||
{context, "<p>{:#{verbatim}}</p>"}
|
|
||||||
end
|
|
||||||
|
|
||||||
####################
|
|
||||||
# IDDef is ignored #
|
|
||||||
####################
|
|
||||||
|
|
||||||
defp render_block(%Block.IdDef{}, context), do: {context, ""}
|
|
||||||
|
|
||||||
#####################################
|
|
||||||
# And here are the inline renderers #
|
|
||||||
#####################################
|
|
||||||
|
|
||||||
defdelegate br, to: HtmlRenderer
|
|
||||||
defdelegate codespan(text), to: HtmlRenderer
|
|
||||||
defdelegate em(text), to: HtmlRenderer
|
|
||||||
defdelegate strong(text), to: HtmlRenderer
|
|
||||||
defdelegate strikethrough(text), to: HtmlRenderer
|
|
||||||
|
|
||||||
defdelegate link(url, text), to: HtmlRenderer
|
|
||||||
defdelegate link(url, text, title), to: HtmlRenderer
|
|
||||||
|
|
||||||
defdelegate image(path, alt, title), to: HtmlRenderer
|
|
||||||
|
|
||||||
defdelegate footnote_link(ref, backref, number), to: HtmlRenderer
|
|
||||||
|
|
||||||
# Table rows
|
|
||||||
defp add_trs(context, rows, tag, aligns, lnb) do
|
|
||||||
numbered_rows =
|
|
||||||
rows
|
|
||||||
|> Enum.zip(Stream.iterate(lnb, &(&1 + 1)))
|
|
||||||
|
|
||||||
numbered_rows
|
|
||||||
|> Enum.reduce(context, fn {row, lnb}, ctx ->
|
|
||||||
append(add_tds(append(ctx, "<tr>"), row, tag, aligns, lnb), "</tr>")
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp add_tds(context, row, tag, aligns, lnb) do
|
|
||||||
Enum.reduce(1..length(row), context, add_td_fn(row, tag, aligns, lnb))
|
|
||||||
end
|
|
||||||
|
|
||||||
defp add_td_fn(row, tag, aligns, lnb) do
|
|
||||||
fn n, ctx ->
|
|
||||||
style =
|
|
||||||
case Enum.at(aligns, n - 1, :default) do
|
|
||||||
:default -> ""
|
|
||||||
align -> " style=\"text-align: #{align}\""
|
|
||||||
end
|
|
||||||
|
|
||||||
col = Enum.at(row, n - 1)
|
|
||||||
converted = convert(col, lnb, set_messages(ctx, []))
|
|
||||||
append(add_messages_from(ctx, converted), "<#{tag}#{style}>#{converted.value}</#{tag}>")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
###############################
|
|
||||||
# Append Footnote Return Link #
|
|
||||||
###############################
|
|
||||||
|
|
||||||
defdelegate append_footnote_link(note), to: HtmlRenderer
|
|
||||||
defdelegate append_footnote_link(note, fnlink), to: HtmlRenderer
|
|
||||||
|
|
||||||
defdelegate render_code(lines), to: HtmlRenderer
|
|
||||||
|
|
||||||
defp code_classes(language, prefix) do
|
|
||||||
["" | String.split(prefix || "")]
|
|
||||||
|> Enum.map(fn pfx -> "#{pfx}#{language}" end)
|
|
||||||
|> Enum.join(" ")
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -13,21 +13,33 @@ def cast(object) when is_binary(object) do
|
||||||
cast([object])
|
cast([object])
|
||||||
end
|
end
|
||||||
|
|
||||||
def cast(data) when is_list(data) do
|
def cast(object) when is_map(object) do
|
||||||
data
|
case ObjectID.cast(object) do
|
||||||
|> Enum.reduce_while({:ok, []}, fn element, {:ok, list} ->
|
{:ok, data} -> {:ok, [data]}
|
||||||
case ObjectID.cast(element) do
|
_ -> :error
|
||||||
{:ok, id} ->
|
end
|
||||||
{:cont, {:ok, [id | list]}}
|
|
||||||
|
|
||||||
_ ->
|
|
||||||
{:halt, :error}
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def cast(_) do
|
def cast(data) when is_list(data) do
|
||||||
:error
|
data =
|
||||||
|
data
|
||||||
|
|> Enum.reduce_while([], fn element, list ->
|
||||||
|
case ObjectID.cast(element) do
|
||||||
|
{:ok, id} ->
|
||||||
|
{:cont, [id | list]}
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
{:cont, list}
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|> Enum.sort()
|
||||||
|
|> Enum.uniq()
|
||||||
|
|
||||||
|
{:ok, data}
|
||||||
|
end
|
||||||
|
|
||||||
|
def cast(data) do
|
||||||
|
{:error, data}
|
||||||
end
|
end
|
||||||
|
|
||||||
def dump(data) do
|
def dump(data) do
|
||||||
|
|
|
@ -73,7 +73,7 @@ def report(to, reporter, account, statuses, comment) do
|
||||||
#{comment_html}
|
#{comment_html}
|
||||||
#{statuses_html}
|
#{statuses_html}
|
||||||
<p>
|
<p>
|
||||||
<a href="#{Pleroma.Web.base_url()}/pleroma/admin/#/reports/index">View Reports in AdminFE</a>
|
<a href="#{Pleroma.Web.Endpoint.url()}/pleroma/admin/#/reports/index">View Reports in AdminFE</a>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
new()
|
new()
|
||||||
|
@ -87,7 +87,7 @@ def new_unapproved_registration(to, account) do
|
||||||
html_body = """
|
html_body = """
|
||||||
<p>New account for review: <a href="#{account.ap_id}">@#{account.nickname}</a></p>
|
<p>New account for review: <a href="#{account.ap_id}">@#{account.nickname}</a></p>
|
||||||
<blockquote>#{HTML.strip_tags(account.registration_reason)}</blockquote>
|
<blockquote>#{HTML.strip_tags(account.registration_reason)}</blockquote>
|
||||||
<a href="#{Pleroma.Web.base_url()}/pleroma/admin/#/users/#{account.id}/">Visit AdminFE</a>
|
<a href="#{Pleroma.Web.Endpoint.url()}/pleroma/admin/#/users/#{account.id}/">Visit AdminFE</a>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
new()
|
new()
|
||||||
|
|
|
@ -5,15 +5,22 @@
|
||||||
defmodule Pleroma.Emails.UserEmail do
|
defmodule Pleroma.Emails.UserEmail do
|
||||||
@moduledoc "User emails"
|
@moduledoc "User emails"
|
||||||
|
|
||||||
use Phoenix.Swoosh, view: Pleroma.Web.EmailView, layout: {Pleroma.Web.LayoutView, :email}
|
|
||||||
|
|
||||||
alias Pleroma.Config
|
alias Pleroma.Config
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Web.Endpoint
|
alias Pleroma.Web.Endpoint
|
||||||
alias Pleroma.Web.Router
|
alias Pleroma.Web.Router
|
||||||
|
|
||||||
|
import Swoosh.Email
|
||||||
|
import Phoenix.Swoosh, except: [render_body: 3]
|
||||||
import Pleroma.Config.Helpers, only: [instance_name: 0, sender: 0]
|
import Pleroma.Config.Helpers, only: [instance_name: 0, sender: 0]
|
||||||
|
|
||||||
|
def render_body(email, template, assigns \\ %{}) do
|
||||||
|
email
|
||||||
|
|> put_new_layout({Pleroma.Web.LayoutView, :email})
|
||||||
|
|> put_new_view(Pleroma.Web.EmailView)
|
||||||
|
|> Phoenix.Swoosh.render_body(template, assigns)
|
||||||
|
end
|
||||||
|
|
||||||
defp recipient(email, nil), do: email
|
defp recipient(email, nil), do: email
|
||||||
defp recipient(email, name), do: {name, email}
|
defp recipient(email, name), do: {name, email}
|
||||||
defp recipient(%User{} = user), do: recipient(user.email, user.name)
|
defp recipient(%User{} = user), do: recipient(user.email, user.name)
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
defmodule Pleroma.Emoji.Formatter do
|
defmodule Pleroma.Emoji.Formatter do
|
||||||
alias Pleroma.Emoji
|
alias Pleroma.Emoji
|
||||||
alias Pleroma.HTML
|
alias Pleroma.HTML
|
||||||
alias Pleroma.Web
|
alias Pleroma.Web.Endpoint
|
||||||
alias Pleroma.Web.MediaProxy
|
alias Pleroma.Web.MediaProxy
|
||||||
|
|
||||||
def emojify(text) do
|
def emojify(text) do
|
||||||
|
@ -44,7 +44,7 @@ def get_emoji_map(text) when is_binary(text) do
|
||||||
Emoji.get_all()
|
Emoji.get_all()
|
||||||
|> Enum.filter(fn {emoji, %Emoji{}} -> String.contains?(text, ":#{emoji}:") end)
|
|> Enum.filter(fn {emoji, %Emoji{}} -> String.contains?(text, ":#{emoji}:") end)
|
||||||
|> Enum.reduce(%{}, fn {name, %Emoji{file: file}}, acc ->
|
|> Enum.reduce(%{}, fn {name, %Emoji{file: file}}, acc ->
|
||||||
Map.put(acc, name, to_string(URI.merge(Web.base_url(), file)))
|
Map.put(acc, name, to_string(URI.merge(Endpoint.url(), file)))
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -62,7 +62,7 @@ def mention_handler("@" <> nickname, buffer, opts, acc) do
|
||||||
|
|
||||||
def hashtag_handler("#" <> tag = tag_text, _buffer, _opts, acc) do
|
def hashtag_handler("#" <> tag = tag_text, _buffer, _opts, acc) do
|
||||||
tag = String.downcase(tag)
|
tag = String.downcase(tag)
|
||||||
url = "#{Pleroma.Web.base_url()}/tag/#{tag}"
|
url = "#{Pleroma.Web.Endpoint.url()}/tag/#{tag}"
|
||||||
|
|
||||||
link =
|
link =
|
||||||
Phoenix.HTML.Tag.content_tag(:a, tag_text,
|
Phoenix.HTML.Tag.content_tag(:a, tag_text,
|
||||||
|
@ -121,6 +121,10 @@ def mentions_escape(text, options \\ []) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def markdown_to_html(text) do
|
||||||
|
Earmark.as_html!(text, %Earmark.Options{compact_output: true})
|
||||||
|
end
|
||||||
|
|
||||||
def html_escape({text, mentions, hashtags}, type) do
|
def html_escape({text, mentions, hashtags}, type) do
|
||||||
{html_escape(text, type), mentions, hashtags}
|
{html_escape(text, type), mentions, hashtags}
|
||||||
end
|
end
|
||||||
|
|
|
@ -11,9 +11,7 @@ defmodule Pleroma.Gun do
|
||||||
@callback await(pid(), reference()) :: {:response, :fin, 200, []}
|
@callback await(pid(), reference()) :: {:response, :fin, 200, []}
|
||||||
@callback set_owner(pid(), pid()) :: :ok
|
@callback set_owner(pid(), pid()) :: :ok
|
||||||
|
|
||||||
@api Pleroma.Config.get([Pleroma.Gun], Pleroma.Gun.API)
|
defp api, do: Pleroma.Config.get([Pleroma.Gun], Pleroma.Gun.API)
|
||||||
|
|
||||||
defp api, do: @api
|
|
||||||
|
|
||||||
def open(host, port, opts), do: api().open(host, port, opts)
|
def open(host, port, opts), do: api().open(host, port, opts)
|
||||||
|
|
||||||
|
|
|
@ -5,11 +5,11 @@
|
||||||
defmodule Pleroma.Gun.ConnectionPool.Reclaimer do
|
defmodule Pleroma.Gun.ConnectionPool.Reclaimer do
|
||||||
use GenServer, restart: :temporary
|
use GenServer, restart: :temporary
|
||||||
|
|
||||||
@registry Pleroma.Gun.ConnectionPool
|
defp registry, do: Pleroma.Gun.ConnectionPool
|
||||||
|
|
||||||
def start_monitor do
|
def start_monitor do
|
||||||
pid =
|
pid =
|
||||||
case :gen_server.start(__MODULE__, [], name: {:via, Registry, {@registry, "reclaimer"}}) do
|
case :gen_server.start(__MODULE__, [], name: {:via, Registry, {registry(), "reclaimer"}}) do
|
||||||
{:ok, pid} ->
|
{:ok, pid} ->
|
||||||
pid
|
pid
|
||||||
|
|
||||||
|
@ -46,7 +46,7 @@ def handle_continue(:reclaim, _) do
|
||||||
# {worker_pid, crf, last_reference} end)
|
# {worker_pid, crf, last_reference} end)
|
||||||
unused_conns =
|
unused_conns =
|
||||||
Registry.select(
|
Registry.select(
|
||||||
@registry,
|
registry(),
|
||||||
[
|
[
|
||||||
{{:_, :"$1", {:_, :"$2", :"$3", :"$4"}}, [{:==, :"$2", []}], [{{:"$1", :"$3", :"$4"}}]}
|
{{:_, :"$1", {:_, :"$2", :"$3", :"$4"}}, [{:==, :"$2", []}], [{{:"$1", :"$3", :"$4"}}]}
|
||||||
]
|
]
|
||||||
|
|
|
@ -6,10 +6,10 @@ defmodule Pleroma.Gun.ConnectionPool.Worker do
|
||||||
alias Pleroma.Gun
|
alias Pleroma.Gun
|
||||||
use GenServer, restart: :temporary
|
use GenServer, restart: :temporary
|
||||||
|
|
||||||
@registry Pleroma.Gun.ConnectionPool
|
defp registry, do: Pleroma.Gun.ConnectionPool
|
||||||
|
|
||||||
def start_link([key | _] = opts) do
|
def start_link([key | _] = opts) do
|
||||||
GenServer.start_link(__MODULE__, opts, name: {:via, Registry, {@registry, key}})
|
GenServer.start_link(__MODULE__, opts, name: {:via, Registry, {registry(), key}})
|
||||||
end
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
|
@ -24,7 +24,7 @@ def handle_continue({:connect, [key, uri, opts, client_pid]}, _) do
|
||||||
time = :erlang.monotonic_time(:millisecond)
|
time = :erlang.monotonic_time(:millisecond)
|
||||||
|
|
||||||
{_, _} =
|
{_, _} =
|
||||||
Registry.update_value(@registry, key, fn _ ->
|
Registry.update_value(registry(), key, fn _ ->
|
||||||
{conn_pid, [client_pid], 1, time}
|
{conn_pid, [client_pid], 1, time}
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
@ -65,7 +65,7 @@ def handle_call(:add_client, {client_pid, _}, %{key: key, protocol: protocol} =
|
||||||
time = :erlang.monotonic_time(:millisecond)
|
time = :erlang.monotonic_time(:millisecond)
|
||||||
|
|
||||||
{{conn_pid, used_by, _, _}, _} =
|
{{conn_pid, used_by, _, _}, _} =
|
||||||
Registry.update_value(@registry, key, fn {conn_pid, used_by, crf, last_reference} ->
|
Registry.update_value(registry(), key, fn {conn_pid, used_by, crf, last_reference} ->
|
||||||
{conn_pid, [client_pid | used_by], crf(time - last_reference, crf), time}
|
{conn_pid, [client_pid | used_by], crf(time - last_reference, crf), time}
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
@ -92,7 +92,7 @@ def handle_call(:add_client, {client_pid, _}, %{key: key, protocol: protocol} =
|
||||||
@impl true
|
@impl true
|
||||||
def handle_call(:remove_client, {client_pid, _}, %{key: key} = state) do
|
def handle_call(:remove_client, {client_pid, _}, %{key: key} = state) do
|
||||||
{{_conn_pid, used_by, _crf, _last_reference}, _} =
|
{{_conn_pid, used_by, _crf, _last_reference}, _} =
|
||||||
Registry.update_value(@registry, key, fn {conn_pid, used_by, crf, last_reference} ->
|
Registry.update_value(registry(), key, fn {conn_pid, used_by, crf, last_reference} ->
|
||||||
{conn_pid, List.delete(used_by, client_pid), crf, last_reference}
|
{conn_pid, List.delete(used_by, client_pid), crf, last_reference}
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
|
|
@ -49,31 +49,6 @@ def filter_tags(html, scrubber) do
|
||||||
def filter_tags(html), do: filter_tags(html, nil)
|
def filter_tags(html), do: filter_tags(html, nil)
|
||||||
def strip_tags(html), do: filter_tags(html, FastSanitize.Sanitizer.StripTags)
|
def strip_tags(html), do: filter_tags(html, FastSanitize.Sanitizer.StripTags)
|
||||||
|
|
||||||
def get_cached_scrubbed_html_for_activity(
|
|
||||||
content,
|
|
||||||
scrubbers,
|
|
||||||
activity,
|
|
||||||
key \\ "",
|
|
||||||
callback \\ fn x -> x end
|
|
||||||
) do
|
|
||||||
key = "#{key}#{generate_scrubber_signature(scrubbers)}|#{activity.id}"
|
|
||||||
|
|
||||||
@cachex.fetch!(:scrubber_cache, key, fn _key ->
|
|
||||||
object = Pleroma.Object.normalize(activity, fetch: false)
|
|
||||||
ensure_scrubbed_html(content, scrubbers, object.data["fake"] || false, callback)
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
def get_cached_stripped_html_for_activity(content, activity, key) do
|
|
||||||
get_cached_scrubbed_html_for_activity(
|
|
||||||
content,
|
|
||||||
FastSanitize.Sanitizer.StripTags,
|
|
||||||
activity,
|
|
||||||
key,
|
|
||||||
&HtmlEntities.decode/1
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
def ensure_scrubbed_html(
|
def ensure_scrubbed_html(
|
||||||
content,
|
content,
|
||||||
scrubbers,
|
scrubbers,
|
||||||
|
@ -92,16 +67,6 @@ def ensure_scrubbed_html(
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp generate_scrubber_signature(scrubber) when is_atom(scrubber) do
|
|
||||||
generate_scrubber_signature([scrubber])
|
|
||||||
end
|
|
||||||
|
|
||||||
defp generate_scrubber_signature(scrubbers) do
|
|
||||||
Enum.reduce(scrubbers, "", fn scrubber, signature ->
|
|
||||||
"#{signature}#{to_string(scrubber)}"
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
def extract_first_external_url_from_object(%{data: %{"content" => content}} = object)
|
def extract_first_external_url_from_object(%{data: %{"content" => content}} = object)
|
||||||
when is_binary(content) do
|
when is_binary(content) do
|
||||||
unless object.data["fake"] do
|
unless object.data["fake"] do
|
||||||
|
|
|
@ -54,8 +54,8 @@ def pool_timeout(pool) do
|
||||||
Config.get([:pools, pool, :recv_timeout], default)
|
Config.get([:pools, pool, :recv_timeout], default)
|
||||||
end
|
end
|
||||||
|
|
||||||
@prefix Pleroma.Gun.ConnectionPool
|
|
||||||
def limiter_setup do
|
def limiter_setup do
|
||||||
|
prefix = Pleroma.Gun.ConnectionPool
|
||||||
wait = Config.get([:connections_pool, :connection_acquisition_wait])
|
wait = Config.get([:connections_pool, :connection_acquisition_wait])
|
||||||
retries = Config.get([:connections_pool, :connection_acquisition_retries])
|
retries = Config.get([:connections_pool, :connection_acquisition_retries])
|
||||||
|
|
||||||
|
@ -66,7 +66,7 @@ def limiter_setup do
|
||||||
max_waiting = Keyword.get(opts, :max_waiting, 10)
|
max_waiting = Keyword.get(opts, :max_waiting, 10)
|
||||||
|
|
||||||
result =
|
result =
|
||||||
ConcurrentLimiter.new(:"#{@prefix}.#{name}", max_running, max_waiting,
|
ConcurrentLimiter.new(:"#{prefix}.#{name}", max_running, max_waiting,
|
||||||
wait: wait,
|
wait: wait,
|
||||||
max_retries: retries
|
max_retries: retries
|
||||||
)
|
)
|
||||||
|
|
|
@ -5,8 +5,8 @@
|
||||||
defmodule Pleroma.HTTP.WebPush do
|
defmodule Pleroma.HTTP.WebPush do
|
||||||
@moduledoc false
|
@moduledoc false
|
||||||
|
|
||||||
def post(url, payload, headers) do
|
def post(url, payload, headers, options \\ []) do
|
||||||
list_headers = Map.to_list(headers)
|
list_headers = Map.to_list(headers)
|
||||||
Pleroma.HTTP.post(url, payload, list_headers)
|
Pleroma.HTTP.post(url, payload, list_headers, options)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -12,4 +12,10 @@ def put_if_present(map, key, value, value_function \\ &{:ok, &1}) when is_map(ma
|
||||||
_ -> map
|
_ -> map
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def safe_put_in(data, keys, value) when is_map(data) and is_list(keys) do
|
||||||
|
Kernel.put_in(data, keys, value)
|
||||||
|
rescue
|
||||||
|
_ -> data
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -366,7 +366,7 @@ def update_data(%Object{data: data} = object, attrs \\ %{}) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def local?(%Object{data: %{"id" => id}}) do
|
def local?(%Object{data: %{"id" => id}}) do
|
||||||
String.starts_with?(id, Pleroma.Web.base_url() <> "/")
|
String.starts_with?(id, Pleroma.Web.Endpoint.url() <> "/")
|
||||||
end
|
end
|
||||||
|
|
||||||
def replies(object, opts \\ []) do
|
def replies(object, opts \\ []) do
|
||||||
|
|
|
@ -71,6 +71,14 @@ def contain_origin_from_id(id, %{"id" => other_id} = _params) when is_binary(oth
|
||||||
compare_uris(id_uri, other_uri)
|
compare_uris(id_uri, other_uri)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Mastodon pin activities don't have an id, so we check the object field, which will be pinned.
|
||||||
|
def contain_origin_from_id(id, %{"object" => object}) when is_binary(object) do
|
||||||
|
id_uri = URI.parse(id)
|
||||||
|
object_uri = URI.parse(object)
|
||||||
|
|
||||||
|
compare_uris(id_uri, object_uri)
|
||||||
|
end
|
||||||
|
|
||||||
def contain_origin_from_id(_id, _data), do: :error
|
def contain_origin_from_id(_id, _data), do: :error
|
||||||
|
|
||||||
def contain_child(%{"object" => %{"id" => id, "attributedTo" => _} = object}),
|
def contain_child(%{"object" => %{"id" => id, "attributedTo" => _} = object}),
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
defmodule Pleroma.Object.Fetcher do
|
defmodule Pleroma.Object.Fetcher do
|
||||||
alias Pleroma.HTTP
|
alias Pleroma.HTTP
|
||||||
|
alias Pleroma.Maps
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
alias Pleroma.Object.Containment
|
alias Pleroma.Object.Containment
|
||||||
alias Pleroma.Repo
|
alias Pleroma.Repo
|
||||||
|
@ -101,6 +102,9 @@ def fetch_object_from_id(id, options \\ []) do
|
||||||
{:transmogrifier, {:error, {:reject, e}}} ->
|
{:transmogrifier, {:error, {:reject, e}}} ->
|
||||||
{:reject, e}
|
{:reject, e}
|
||||||
|
|
||||||
|
{:transmogrifier, {:reject, e}} ->
|
||||||
|
{:reject, e}
|
||||||
|
|
||||||
{:transmogrifier, _} = e ->
|
{:transmogrifier, _} = e ->
|
||||||
{:error, e}
|
{:error, e}
|
||||||
|
|
||||||
|
@ -124,12 +128,14 @@ def fetch_object_from_id(id, options \\ []) do
|
||||||
defp prepare_activity_params(data) do
|
defp prepare_activity_params(data) do
|
||||||
%{
|
%{
|
||||||
"type" => "Create",
|
"type" => "Create",
|
||||||
"to" => data["to"] || [],
|
|
||||||
"cc" => data["cc"] || [],
|
|
||||||
# Should we seriously keep this attributedTo thing?
|
# Should we seriously keep this attributedTo thing?
|
||||||
"actor" => data["actor"] || data["attributedTo"],
|
"actor" => data["actor"] || data["attributedTo"],
|
||||||
"object" => data
|
"object" => data
|
||||||
}
|
}
|
||||||
|
|> Maps.put_if_present("to", data["to"])
|
||||||
|
|> Maps.put_if_present("cc", data["cc"])
|
||||||
|
|> Maps.put_if_present("bto", data["bto"])
|
||||||
|
|> Maps.put_if_present("bcc", data["bcc"])
|
||||||
end
|
end
|
||||||
|
|
||||||
def fetch_object_from_id!(id, options \\ []) do
|
def fetch_object_from_id!(id, options \\ []) do
|
||||||
|
|
|
@ -23,6 +23,9 @@ defmodule Pleroma.Upload do
|
||||||
is once created permanent and changing it (especially in uploaders) is probably a bad idea!
|
is once created permanent and changing it (especially in uploaders) is probably a bad idea!
|
||||||
* `:tempfile` - path to the temporary file. Prefer in-place changes on the file rather than changing the
|
* `:tempfile` - path to the temporary file. Prefer in-place changes on the file rather than changing the
|
||||||
path as the temporary file is also tracked by `Plug.Upload{}` and automatically deleted once the request is over.
|
path as the temporary file is also tracked by `Plug.Upload{}` and automatically deleted once the request is over.
|
||||||
|
* `:width` - width of the media in pixels
|
||||||
|
* `:height` - height of the media in pixels
|
||||||
|
* `:blurhash` - string hash of the image encoded with the blurhash algorithm (https://blurha.sh/)
|
||||||
|
|
||||||
Related behaviors:
|
Related behaviors:
|
||||||
|
|
||||||
|
@ -32,6 +35,7 @@ defmodule Pleroma.Upload do
|
||||||
"""
|
"""
|
||||||
alias Ecto.UUID
|
alias Ecto.UUID
|
||||||
alias Pleroma.Config
|
alias Pleroma.Config
|
||||||
|
alias Pleroma.Maps
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
@type source ::
|
@type source ::
|
||||||
|
@ -53,9 +57,12 @@ defmodule Pleroma.Upload do
|
||||||
name: String.t(),
|
name: String.t(),
|
||||||
tempfile: String.t(),
|
tempfile: String.t(),
|
||||||
content_type: String.t(),
|
content_type: String.t(),
|
||||||
|
width: integer(),
|
||||||
|
height: integer(),
|
||||||
|
blurhash: String.t(),
|
||||||
path: String.t()
|
path: String.t()
|
||||||
}
|
}
|
||||||
defstruct [:id, :name, :tempfile, :content_type, :path]
|
defstruct [:id, :name, :tempfile, :content_type, :width, :height, :blurhash, :path]
|
||||||
|
|
||||||
defp get_description(opts, upload) do
|
defp get_description(opts, upload) do
|
||||||
case {opts[:description], Pleroma.Config.get([Pleroma.Upload, :default_description])} do
|
case {opts[:description], Pleroma.Config.get([Pleroma.Upload, :default_description])} do
|
||||||
|
@ -89,9 +96,12 @@ def store(upload, opts \\ []) do
|
||||||
"mediaType" => upload.content_type,
|
"mediaType" => upload.content_type,
|
||||||
"href" => url_from_spec(upload, opts.base_url, url_spec)
|
"href" => url_from_spec(upload, opts.base_url, url_spec)
|
||||||
}
|
}
|
||||||
|
|> Maps.put_if_present("width", upload.width)
|
||||||
|
|> Maps.put_if_present("height", upload.height)
|
||||||
],
|
],
|
||||||
"name" => description
|
"name" => description
|
||||||
}}
|
}
|
||||||
|
|> Maps.put_if_present("blurhash", upload.blurhash)}
|
||||||
else
|
else
|
||||||
{:description_limit, _} ->
|
{:description_limit, _} ->
|
||||||
{:error, :description_too_long}
|
{:error, :description_too_long}
|
||||||
|
@ -225,7 +235,7 @@ def base_url do
|
||||||
|
|
||||||
case uploader do
|
case uploader do
|
||||||
Pleroma.Uploaders.Local ->
|
Pleroma.Uploaders.Local ->
|
||||||
upload_base_url || Pleroma.Web.base_url() <> "/media/"
|
upload_base_url || Pleroma.Web.Endpoint.url() <> "/media/"
|
||||||
|
|
||||||
Pleroma.Uploaders.S3 ->
|
Pleroma.Uploaders.S3 ->
|
||||||
bucket = Config.get([Pleroma.Uploaders.S3, :bucket])
|
bucket = Config.get([Pleroma.Uploaders.S3, :bucket])
|
||||||
|
@ -251,7 +261,7 @@ def base_url do
|
||||||
end
|
end
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
public_endpoint || upload_base_url || Pleroma.Web.base_url() <> "/media/"
|
public_endpoint || upload_base_url || Pleroma.Web.Endpoint.url() <> "/media/"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Upload.Filter.AnalyzeMetadata do
|
||||||
|
@moduledoc """
|
||||||
|
Extracts metadata about the upload, such as width/height
|
||||||
|
"""
|
||||||
|
require Logger
|
||||||
|
|
||||||
|
@behaviour Pleroma.Upload.Filter
|
||||||
|
|
||||||
|
@spec filter(Pleroma.Upload.t()) ::
|
||||||
|
{:ok, :filtered, Pleroma.Upload.t()} | {:ok, :noop} | {:error, String.t()}
|
||||||
|
def filter(%Pleroma.Upload{tempfile: file, content_type: "image" <> _} = upload) do
|
||||||
|
try do
|
||||||
|
image =
|
||||||
|
file
|
||||||
|
|> Mogrify.open()
|
||||||
|
|> Mogrify.verbose()
|
||||||
|
|
||||||
|
upload =
|
||||||
|
upload
|
||||||
|
|> Map.put(:width, image.width)
|
||||||
|
|> Map.put(:height, image.height)
|
||||||
|
|> Map.put(:blurhash, get_blurhash(file))
|
||||||
|
|
||||||
|
{:ok, :filtered, upload}
|
||||||
|
rescue
|
||||||
|
e in ErlangError ->
|
||||||
|
Logger.warn("#{__MODULE__}: #{inspect(e)}")
|
||||||
|
{:ok, :noop}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def filter(_), do: {:ok, :noop}
|
||||||
|
|
||||||
|
defp get_blurhash(file) do
|
||||||
|
with {:ok, blurhash} <- :eblurhash.magick(file) do
|
||||||
|
blurhash
|
||||||
|
else
|
||||||
|
_ -> nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -35,7 +35,7 @@ defmodule Pleroma.Uploaders.Uploader do
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@type file_spec :: {:file | :url, String.t()}
|
@type file_spec :: {:file | :url, String.t()}
|
||||||
@callback put_file(Pleroma.Upload.t()) ::
|
@callback put_file(upload :: struct()) ::
|
||||||
:ok | {:ok, file_spec()} | {:error, String.t()} | :wait_callback
|
:ok | {:ok, file_spec()} | {:error, String.t()} | :wait_callback
|
||||||
|
|
||||||
@callback delete_file(file :: String.t()) :: :ok | {:error, String.t()}
|
@callback delete_file(file :: String.t()) :: :ok | {:error, String.t()}
|
||||||
|
@ -46,7 +46,7 @@ defmodule Pleroma.Uploaders.Uploader do
|
||||||
| {:error, Plug.Conn.t(), String.t()}
|
| {:error, Plug.Conn.t(), String.t()}
|
||||||
@optional_callbacks http_callback: 2
|
@optional_callbacks http_callback: 2
|
||||||
|
|
||||||
@spec put_file(module(), Pleroma.Upload.t()) :: {:ok, file_spec()} | {:error, String.t()}
|
@spec put_file(module(), upload :: struct()) :: {:ok, file_spec()} | {:error, String.t()}
|
||||||
def put_file(uploader, upload) do
|
def put_file(uploader, upload) do
|
||||||
case uploader.put_file(upload) do
|
case uploader.put_file(upload) do
|
||||||
:ok -> {:ok, {:file, upload.path}}
|
:ok -> {:ok, {:file, upload.path}}
|
||||||
|
|
|
@ -27,13 +27,13 @@ defmodule Pleroma.User do
|
||||||
alias Pleroma.Repo
|
alias Pleroma.Repo
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.UserRelationship
|
alias Pleroma.UserRelationship
|
||||||
alias Pleroma.Web
|
|
||||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||||
alias Pleroma.Web.ActivityPub.Builder
|
alias Pleroma.Web.ActivityPub.Builder
|
||||||
alias Pleroma.Web.ActivityPub.Pipeline
|
alias Pleroma.Web.ActivityPub.Pipeline
|
||||||
alias Pleroma.Web.ActivityPub.Utils
|
alias Pleroma.Web.ActivityPub.Utils
|
||||||
alias Pleroma.Web.CommonAPI
|
alias Pleroma.Web.CommonAPI
|
||||||
alias Pleroma.Web.CommonAPI.Utils, as: CommonUtils
|
alias Pleroma.Web.CommonAPI.Utils, as: CommonUtils
|
||||||
|
alias Pleroma.Web.Endpoint
|
||||||
alias Pleroma.Web.OAuth
|
alias Pleroma.Web.OAuth
|
||||||
alias Pleroma.Web.RelMe
|
alias Pleroma.Web.RelMe
|
||||||
alias Pleroma.Workers.BackgroundWorker
|
alias Pleroma.Workers.BackgroundWorker
|
||||||
|
@ -99,6 +99,7 @@ defmodule Pleroma.User do
|
||||||
field(:local, :boolean, default: true)
|
field(:local, :boolean, default: true)
|
||||||
field(:follower_address, :string)
|
field(:follower_address, :string)
|
||||||
field(:following_address, :string)
|
field(:following_address, :string)
|
||||||
|
field(:featured_address, :string)
|
||||||
field(:search_rank, :float, virtual: true)
|
field(:search_rank, :float, virtual: true)
|
||||||
field(:search_type, :integer, virtual: true)
|
field(:search_type, :integer, virtual: true)
|
||||||
field(:tags, {:array, :string}, default: [])
|
field(:tags, {:array, :string}, default: [])
|
||||||
|
@ -129,7 +130,6 @@ defmodule Pleroma.User do
|
||||||
field(:hide_followers, :boolean, default: false)
|
field(:hide_followers, :boolean, default: false)
|
||||||
field(:hide_follows, :boolean, default: false)
|
field(:hide_follows, :boolean, default: false)
|
||||||
field(:hide_favorites, :boolean, default: true)
|
field(:hide_favorites, :boolean, default: true)
|
||||||
field(:pinned_activities, {:array, :string}, default: [])
|
|
||||||
field(:email_notifications, :map, default: %{"digest" => false})
|
field(:email_notifications, :map, default: %{"digest" => false})
|
||||||
field(:mascot, :map, default: nil)
|
field(:mascot, :map, default: nil)
|
||||||
field(:emoji, :map, default: %{})
|
field(:emoji, :map, default: %{})
|
||||||
|
@ -147,6 +147,7 @@ defmodule Pleroma.User do
|
||||||
field(:accepts_chat_messages, :boolean, default: nil)
|
field(:accepts_chat_messages, :boolean, default: nil)
|
||||||
field(:last_active_at, :naive_datetime)
|
field(:last_active_at, :naive_datetime)
|
||||||
field(:disclose_client, :boolean, default: true)
|
field(:disclose_client, :boolean, default: true)
|
||||||
|
field(:pinned_objects, :map, default: %{})
|
||||||
|
|
||||||
embeds_one(
|
embeds_one(
|
||||||
:notification_settings,
|
:notification_settings,
|
||||||
|
@ -358,7 +359,7 @@ def avatar_url(user, options \\ []) do
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
unless options[:no_default] do
|
unless options[:no_default] do
|
||||||
Config.get([:assets, :default_user_avatar], "#{Web.base_url()}/images/avi.png")
|
Config.get([:assets, :default_user_avatar], "#{Endpoint.url()}/images/avi.png")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -366,13 +367,15 @@ def avatar_url(user, options \\ []) do
|
||||||
def banner_url(user, options \\ []) do
|
def banner_url(user, options \\ []) do
|
||||||
case user.banner do
|
case user.banner do
|
||||||
%{"url" => [%{"href" => href} | _]} -> href
|
%{"url" => [%{"href" => href} | _]} -> href
|
||||||
_ -> !options[:no_default] && "#{Web.base_url()}/images/banner.png"
|
_ -> !options[:no_default] && "#{Endpoint.url()}/images/banner.png"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Should probably be renamed or removed
|
# Should probably be renamed or removed
|
||||||
def ap_id(%User{nickname: nickname}), do: "#{Web.base_url()}/users/#{nickname}"
|
@spec ap_id(User.t()) :: String.t()
|
||||||
|
def ap_id(%User{nickname: nickname}), do: "#{Endpoint.url()}/users/#{nickname}"
|
||||||
|
|
||||||
|
@spec ap_followers(User.t()) :: String.t()
|
||||||
def ap_followers(%User{follower_address: fa}) when is_binary(fa), do: fa
|
def ap_followers(%User{follower_address: fa}) when is_binary(fa), do: fa
|
||||||
def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers"
|
def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers"
|
||||||
|
|
||||||
|
@ -380,6 +383,11 @@ def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers"
|
||||||
def ap_following(%User{following_address: fa}) when is_binary(fa), do: fa
|
def ap_following(%User{following_address: fa}) when is_binary(fa), do: fa
|
||||||
def ap_following(%User{} = user), do: "#{ap_id(user)}/following"
|
def ap_following(%User{} = user), do: "#{ap_id(user)}/following"
|
||||||
|
|
||||||
|
@spec ap_featured_collection(User.t()) :: String.t()
|
||||||
|
def ap_featured_collection(%User{featured_address: fa}) when is_binary(fa), do: fa
|
||||||
|
|
||||||
|
def ap_featured_collection(%User{} = user), do: "#{ap_id(user)}/collections/featured"
|
||||||
|
|
||||||
defp truncate_fields_param(params) do
|
defp truncate_fields_param(params) do
|
||||||
if Map.has_key?(params, :fields) do
|
if Map.has_key?(params, :fields) do
|
||||||
Map.put(params, :fields, Enum.map(params[:fields], &truncate_field/1))
|
Map.put(params, :fields, Enum.map(params[:fields], &truncate_field/1))
|
||||||
|
@ -442,6 +450,7 @@ def remote_user_changeset(struct \\ %User{local: false}, params) do
|
||||||
:uri,
|
:uri,
|
||||||
:follower_address,
|
:follower_address,
|
||||||
:following_address,
|
:following_address,
|
||||||
|
:featured_address,
|
||||||
:hide_followers,
|
:hide_followers,
|
||||||
:hide_follows,
|
:hide_follows,
|
||||||
:hide_followers_count,
|
:hide_followers_count,
|
||||||
|
@ -453,7 +462,8 @@ def remote_user_changeset(struct \\ %User{local: false}, params) do
|
||||||
:invisible,
|
:invisible,
|
||||||
:actor_type,
|
:actor_type,
|
||||||
:also_known_as,
|
:also_known_as,
|
||||||
:accepts_chat_messages
|
:accepts_chat_messages,
|
||||||
|
:pinned_objects
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|> cast(params, [:name], empty_values: [])
|
|> cast(params, [:name], empty_values: [])
|
||||||
|
@ -685,7 +695,7 @@ def register_changeset_ldap(struct, params = %{password: password})
|
||||||
|> validate_format(:nickname, local_nickname_regex())
|
|> validate_format(:nickname, local_nickname_regex())
|
||||||
|> put_ap_id()
|
|> put_ap_id()
|
||||||
|> unique_constraint(:ap_id)
|
|> unique_constraint(:ap_id)
|
||||||
|> put_following_and_follower_address()
|
|> put_following_and_follower_and_featured_address()
|
||||||
end
|
end
|
||||||
|
|
||||||
def register_changeset(struct, params \\ %{}, opts \\ []) do
|
def register_changeset(struct, params \\ %{}, opts \\ []) do
|
||||||
|
@ -746,7 +756,7 @@ def register_changeset(struct, params \\ %{}, opts \\ []) do
|
||||||
|> put_password_hash
|
|> put_password_hash
|
||||||
|> put_ap_id()
|
|> put_ap_id()
|
||||||
|> unique_constraint(:ap_id)
|
|> unique_constraint(:ap_id)
|
||||||
|> put_following_and_follower_address()
|
|> put_following_and_follower_and_featured_address()
|
||||||
end
|
end
|
||||||
|
|
||||||
def maybe_validate_required_email(changeset, true), do: changeset
|
def maybe_validate_required_email(changeset, true), do: changeset
|
||||||
|
@ -764,11 +774,16 @@ defp put_ap_id(changeset) do
|
||||||
put_change(changeset, :ap_id, ap_id)
|
put_change(changeset, :ap_id, ap_id)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp put_following_and_follower_address(changeset) do
|
defp put_following_and_follower_and_featured_address(changeset) do
|
||||||
followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
|
user = %User{nickname: get_field(changeset, :nickname)}
|
||||||
|
followers = ap_followers(user)
|
||||||
|
following = ap_following(user)
|
||||||
|
featured = ap_featured_collection(user)
|
||||||
|
|
||||||
changeset
|
changeset
|
||||||
|> put_change(:follower_address, followers)
|
|> put_change(:follower_address, followers)
|
||||||
|
|> put_change(:following_address, following)
|
||||||
|
|> put_change(:featured_address, featured)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp autofollow_users(user) do
|
defp autofollow_users(user) do
|
||||||
|
@ -2334,45 +2349,35 @@ def approval_changeset(user, set_approval: approved?) do
|
||||||
cast(user, %{is_approved: approved?}, [:is_approved])
|
cast(user, %{is_approved: approved?}, [:is_approved])
|
||||||
end
|
end
|
||||||
|
|
||||||
def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do
|
@spec add_pinned_object_id(User.t(), String.t()) :: {:ok, User.t()} | {:error, term()}
|
||||||
if id not in user.pinned_activities do
|
def add_pinned_object_id(%User{} = user, object_id) do
|
||||||
max_pinned_statuses = Config.get([:instance, :max_pinned_statuses], 0)
|
if !user.pinned_objects[object_id] do
|
||||||
params = %{pinned_activities: user.pinned_activities ++ [id]}
|
params = %{pinned_objects: Map.put(user.pinned_objects, object_id, NaiveDateTime.utc_now())}
|
||||||
|
|
||||||
# if pinned activity was scheduled for deletion, we remove job
|
|
||||||
if expiration = Pleroma.Workers.PurgeExpiredActivity.get_expiration(id) do
|
|
||||||
Oban.cancel_job(expiration.id)
|
|
||||||
end
|
|
||||||
|
|
||||||
user
|
user
|
||||||
|> cast(params, [:pinned_activities])
|
|> cast(params, [:pinned_objects])
|
||||||
|> validate_length(:pinned_activities,
|
|> validate_change(:pinned_objects, fn :pinned_objects, pinned_objects ->
|
||||||
max: max_pinned_statuses,
|
max_pinned_statuses = Config.get([:instance, :max_pinned_statuses], 0)
|
||||||
message: "You have already pinned the maximum number of statuses"
|
|
||||||
)
|
if Enum.count(pinned_objects) <= max_pinned_statuses do
|
||||||
|
[]
|
||||||
|
else
|
||||||
|
[pinned_objects: "You have already pinned the maximum number of statuses"]
|
||||||
|
end
|
||||||
|
end)
|
||||||
else
|
else
|
||||||
change(user)
|
change(user)
|
||||||
end
|
end
|
||||||
|> update_and_set_cache()
|
|> update_and_set_cache()
|
||||||
end
|
end
|
||||||
|
|
||||||
def remove_pinnned_activity(user, %Pleroma.Activity{id: id, data: data}) do
|
@spec remove_pinned_object_id(User.t(), String.t()) :: {:ok, t()} | {:error, term()}
|
||||||
params = %{pinned_activities: List.delete(user.pinned_activities, id)}
|
def remove_pinned_object_id(%User{} = user, object_id) do
|
||||||
|
|
||||||
# if pinned activity was scheduled for deletion, we reschedule it for deletion
|
|
||||||
if data["expires_at"] do
|
|
||||||
# MRF.ActivityExpirationPolicy used UTC timestamps for expires_at in original implementation
|
|
||||||
{:ok, expires_at} =
|
|
||||||
data["expires_at"] |> Pleroma.EctoType.ActivityPub.ObjectValidators.DateTime.cast()
|
|
||||||
|
|
||||||
Pleroma.Workers.PurgeExpiredActivity.enqueue(%{
|
|
||||||
activity_id: id,
|
|
||||||
expires_at: expires_at
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
user
|
user
|
||||||
|> cast(params, [:pinned_activities])
|
|> cast(
|
||||||
|
%{pinned_objects: Map.delete(user.pinned_objects, object_id)},
|
||||||
|
[:pinned_objects]
|
||||||
|
)
|
||||||
|> update_and_set_cache()
|
|> update_and_set_cache()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,8 @@ defmodule Pleroma.Utils do
|
||||||
eperm epipe erange erofs espipe esrch estale etxtbsy exdev
|
eperm epipe erange erofs espipe esrch estale etxtbsy exdev
|
||||||
)a
|
)a
|
||||||
|
|
||||||
|
@repo_timeout Pleroma.Config.get([Pleroma.Repo, :timeout], 15_000)
|
||||||
|
|
||||||
def compile_dir(dir) when is_binary(dir) do
|
def compile_dir(dir) when is_binary(dir) do
|
||||||
dir
|
dir
|
||||||
|> File.ls!()
|
|> File.ls!()
|
||||||
|
@ -63,4 +65,21 @@ def posix_error_message(code) when code in @posix_error_codes do
|
||||||
end
|
end
|
||||||
|
|
||||||
def posix_error_message(_), do: ""
|
def posix_error_message(_), do: ""
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Returns [timeout: integer] suitable for passing as an option to Repo functions.
|
||||||
|
|
||||||
|
This function detects if the execution was triggered from IEx shell, Mix task, or
|
||||||
|
./bin/pleroma_ctl and sets the timeout to :infinity, else returns the default timeout value.
|
||||||
|
"""
|
||||||
|
@spec query_timeout() :: [timeout: integer]
|
||||||
|
def query_timeout do
|
||||||
|
{parent, _, _, _} = Process.info(self(), :current_stacktrace) |> elem(1) |> Enum.fetch!(2)
|
||||||
|
|
||||||
|
cond do
|
||||||
|
parent |> to_string |> String.starts_with?("Elixir.Mix.Task") -> [timeout: :infinity]
|
||||||
|
parent == :erl_eval -> [timeout: :infinity]
|
||||||
|
true -> [timeout: @repo_timeout]
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -35,9 +35,10 @@ def controller do
|
||||||
import Plug.Conn
|
import Plug.Conn
|
||||||
|
|
||||||
import Pleroma.Web.Gettext
|
import Pleroma.Web.Gettext
|
||||||
import Pleroma.Web.Router.Helpers
|
|
||||||
import Pleroma.Web.TranslationHelpers
|
import Pleroma.Web.TranslationHelpers
|
||||||
|
|
||||||
|
alias Pleroma.Web.Router.Helpers, as: Routes
|
||||||
|
|
||||||
plug(:set_put_layout)
|
plug(:set_put_layout)
|
||||||
|
|
||||||
defp set_put_layout(conn, _) do
|
defp set_put_layout(conn, _) do
|
||||||
|
@ -131,7 +132,8 @@ def view do
|
||||||
|
|
||||||
import Pleroma.Web.ErrorHelpers
|
import Pleroma.Web.ErrorHelpers
|
||||||
import Pleroma.Web.Gettext
|
import Pleroma.Web.Gettext
|
||||||
import Pleroma.Web.Router.Helpers
|
|
||||||
|
alias Pleroma.Web.Router.Helpers, as: Routes
|
||||||
|
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
|
@ -229,20 +231,4 @@ def call(%Plug.Conn{} = conn, options) do
|
||||||
defmacro __using__(which) when is_atom(which) do
|
defmacro __using__(which) when is_atom(which) do
|
||||||
apply(__MODULE__, which, [])
|
apply(__MODULE__, which, [])
|
||||||
end
|
end
|
||||||
|
|
||||||
def base_url do
|
|
||||||
Pleroma.Web.Endpoint.url()
|
|
||||||
end
|
|
||||||
|
|
||||||
# TODO: Change to Phoenix.Router.routes/1 for Phoenix 1.6.0+
|
|
||||||
def get_api_routes do
|
|
||||||
Pleroma.Web.Router.__routes__()
|
|
||||||
|> Enum.reject(fn r -> r.plug == Pleroma.Web.Fallback.RedirectController end)
|
|
||||||
|> Enum.map(fn r ->
|
|
||||||
r.path
|
|
||||||
|> String.split("/", trim: true)
|
|
||||||
|> List.first()
|
|
||||||
end)
|
|
||||||
|> Enum.uniq()
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -88,7 +88,7 @@ defp increase_replies_count_if_reply(%{
|
||||||
|
|
||||||
defp increase_replies_count_if_reply(_create_data), do: :noop
|
defp increase_replies_count_if_reply(_create_data), do: :noop
|
||||||
|
|
||||||
@object_types ~w[ChatMessage Question Answer Audio Video Event Article]
|
@object_types ~w[ChatMessage Question Answer Audio Video Event Article Note]
|
||||||
@impl true
|
@impl true
|
||||||
def persist(%{"type" => type} = object, meta) when type in @object_types do
|
def persist(%{"type" => type} = object, meta) when type in @object_types do
|
||||||
with {:ok, object} <- Object.create(object) do
|
with {:ok, object} <- Object.create(object) do
|
||||||
|
@ -630,7 +630,7 @@ defp fetch_activities_for_user(user, reading_user, params) do
|
||||||
|> Map.put(:type, ["Create", "Announce"])
|
|> Map.put(:type, ["Create", "Announce"])
|
||||||
|> Map.put(:user, reading_user)
|
|> Map.put(:user, reading_user)
|
||||||
|> Map.put(:actor_id, user.ap_id)
|
|> Map.put(:actor_id, user.ap_id)
|
||||||
|> Map.put(:pinned_activity_ids, user.pinned_activities)
|
|> Map.put(:pinned_object_ids, Map.keys(user.pinned_objects))
|
||||||
|
|
||||||
params =
|
params =
|
||||||
if User.blocks?(reading_user, user) do
|
if User.blocks?(reading_user, user) do
|
||||||
|
@ -1075,8 +1075,18 @@ defp restrict_unlisted(query, %{restrict_unlisted: true}) do
|
||||||
|
|
||||||
defp restrict_unlisted(query, _), do: query
|
defp restrict_unlisted(query, _), do: query
|
||||||
|
|
||||||
defp restrict_pinned(query, %{pinned: true, pinned_activity_ids: ids}) do
|
defp restrict_pinned(query, %{pinned: true, pinned_object_ids: ids}) do
|
||||||
from(activity in query, where: activity.id in ^ids)
|
from(
|
||||||
|
[activity, object: o] in query,
|
||||||
|
where:
|
||||||
|
fragment(
|
||||||
|
"(?)->>'type' = 'Create' and coalesce((?)->'object'->>'id', (?)->>'object') = any (?)",
|
||||||
|
activity.data,
|
||||||
|
activity.data,
|
||||||
|
activity.data,
|
||||||
|
^ids
|
||||||
|
)
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp restrict_pinned(query, _), do: query
|
defp restrict_pinned(query, _), do: query
|
||||||
|
@ -1419,6 +1429,9 @@ defp object_to_user_data(data) do
|
||||||
invisible = data["invisible"] || false
|
invisible = data["invisible"] || false
|
||||||
actor_type = data["type"] || "Person"
|
actor_type = data["type"] || "Person"
|
||||||
|
|
||||||
|
featured_address = data["featured"]
|
||||||
|
{:ok, pinned_objects} = fetch_and_prepare_featured_from_ap_id(featured_address)
|
||||||
|
|
||||||
public_key =
|
public_key =
|
||||||
if is_map(data["publicKey"]) && is_binary(data["publicKey"]["publicKeyPem"]) do
|
if is_map(data["publicKey"]) && is_binary(data["publicKey"]["publicKeyPem"]) do
|
||||||
data["publicKey"]["publicKeyPem"]
|
data["publicKey"]["publicKeyPem"]
|
||||||
|
@ -1447,13 +1460,15 @@ defp object_to_user_data(data) do
|
||||||
name: data["name"],
|
name: data["name"],
|
||||||
follower_address: data["followers"],
|
follower_address: data["followers"],
|
||||||
following_address: data["following"],
|
following_address: data["following"],
|
||||||
|
featured_address: featured_address,
|
||||||
bio: data["summary"] || "",
|
bio: data["summary"] || "",
|
||||||
actor_type: actor_type,
|
actor_type: actor_type,
|
||||||
also_known_as: Map.get(data, "alsoKnownAs", []),
|
also_known_as: Map.get(data, "alsoKnownAs", []),
|
||||||
public_key: public_key,
|
public_key: public_key,
|
||||||
inbox: data["inbox"],
|
inbox: data["inbox"],
|
||||||
shared_inbox: shared_inbox,
|
shared_inbox: shared_inbox,
|
||||||
accepts_chat_messages: accepts_chat_messages
|
accepts_chat_messages: accepts_chat_messages,
|
||||||
|
pinned_objects: pinned_objects
|
||||||
}
|
}
|
||||||
|
|
||||||
# nickname can be nil because of virtual actors
|
# nickname can be nil because of virtual actors
|
||||||
|
@ -1591,6 +1606,41 @@ def maybe_handle_clashing_nickname(data) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def pin_data_from_featured_collection(%{
|
||||||
|
"type" => type,
|
||||||
|
"orderedItems" => objects
|
||||||
|
})
|
||||||
|
when type in ["OrderedCollection", "Collection"] do
|
||||||
|
Map.new(objects, fn %{"id" => object_ap_id} -> {object_ap_id, NaiveDateTime.utc_now()} end)
|
||||||
|
end
|
||||||
|
|
||||||
|
def fetch_and_prepare_featured_from_ap_id(nil) do
|
||||||
|
{:ok, %{}}
|
||||||
|
end
|
||||||
|
|
||||||
|
def fetch_and_prepare_featured_from_ap_id(ap_id) do
|
||||||
|
with {:ok, data} <- Fetcher.fetch_and_contain_remote_object_from_id(ap_id) do
|
||||||
|
{:ok, pin_data_from_featured_collection(data)}
|
||||||
|
else
|
||||||
|
e ->
|
||||||
|
Logger.error("Could not decode featured collection at fetch #{ap_id}, #{inspect(e)}")
|
||||||
|
{:ok, %{}}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def pinned_fetch_task(nil), do: nil
|
||||||
|
|
||||||
|
def pinned_fetch_task(%{pinned_objects: pins}) do
|
||||||
|
if Enum.all?(pins, fn {ap_id, _} ->
|
||||||
|
Object.get_cached_by_ap_id(ap_id) ||
|
||||||
|
match?({:ok, _object}, Fetcher.fetch_object_from_id(ap_id))
|
||||||
|
end) do
|
||||||
|
:ok
|
||||||
|
else
|
||||||
|
:error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def make_user_from_ap_id(ap_id) 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)
|
||||||
|
|
||||||
|
@ -1598,6 +1648,8 @@ def make_user_from_ap_id(ap_id) 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) do
|
with {:ok, data} <- fetch_and_prepare_user_from_ap_id(ap_id) do
|
||||||
|
{:ok, _pid} = Task.start(fn -> pinned_fetch_task(data) end)
|
||||||
|
|
||||||
if user do
|
if user do
|
||||||
user
|
user
|
||||||
|> User.remote_user_changeset(data)
|
|> User.remote_user_changeset(data)
|
||||||
|
|
|
@ -3,5 +3,5 @@
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Pleroma.Web.ActivityPub.ActivityPub.Persisting do
|
defmodule Pleroma.Web.ActivityPub.ActivityPub.Persisting do
|
||||||
@callback persist(map(), keyword()) :: {:ok, Activity.t() | Object.t()}
|
@callback persist(map(), keyword()) :: {:ok, struct()}
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,10 +3,6 @@
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Pleroma.Web.ActivityPub.ActivityPub.Streaming do
|
defmodule Pleroma.Web.ActivityPub.ActivityPub.Streaming do
|
||||||
alias Pleroma.Activity
|
@callback stream_out(struct()) :: any()
|
||||||
alias Pleroma.Object
|
@callback stream_out_participations(struct(), struct()) :: any()
|
||||||
alias Pleroma.User
|
|
||||||
|
|
||||||
@callback stream_out(Activity.t()) :: any()
|
|
||||||
@callback stream_out_participations(Object.t(), User.t()) :: any()
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -543,4 +543,12 @@ def upload_media(%{assigns: %{user: %User{} = user}} = conn, %{"file" => file} =
|
||||||
|> json(object.data)
|
|> json(object.data)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def pinned(conn, %{"nickname" => nickname}) do
|
||||||
|
with %User{} = user <- User.get_cached_by_nickname(nickname) do
|
||||||
|
conn
|
||||||
|
|> put_resp_header("content-type", "application/activity+json")
|
||||||
|
|> json(UserView.render("featured.json", %{user: user}))
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -223,7 +223,7 @@ def announce(actor, object, options \\ []) do
|
||||||
[actor.follower_address]
|
[actor.follower_address]
|
||||||
|
|
||||||
public? and Visibility.is_local_public?(object) ->
|
public? and Visibility.is_local_public?(object) ->
|
||||||
[actor.follower_address, object.data["actor"], Pleroma.Constants.as_local_public()]
|
[actor.follower_address, object.data["actor"], Utils.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()]
|
||||||
|
@ -273,4 +273,36 @@ defp object_action(actor, object) do
|
||||||
"context" => object.data["context"]
|
"context" => object.data["context"]
|
||||||
}, []}
|
}, []}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec pin(User.t(), Object.t()) :: {:ok, map(), keyword()}
|
||||||
|
def pin(%User{} = user, object) do
|
||||||
|
{:ok,
|
||||||
|
%{
|
||||||
|
"id" => Utils.generate_activity_id(),
|
||||||
|
"target" => pinned_url(user.nickname),
|
||||||
|
"object" => object.data["id"],
|
||||||
|
"actor" => user.ap_id,
|
||||||
|
"type" => "Add",
|
||||||
|
"to" => [Pleroma.Constants.as_public()],
|
||||||
|
"cc" => [user.follower_address]
|
||||||
|
}, []}
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec unpin(User.t(), Object.t()) :: {:ok, map, keyword()}
|
||||||
|
def unpin(%User{} = user, object) do
|
||||||
|
{:ok,
|
||||||
|
%{
|
||||||
|
"id" => Utils.generate_activity_id(),
|
||||||
|
"target" => pinned_url(user.nickname),
|
||||||
|
"object" => object.data["id"],
|
||||||
|
"actor" => user.ap_id,
|
||||||
|
"type" => "Remove",
|
||||||
|
"to" => [Pleroma.Constants.as_public()],
|
||||||
|
"cc" => [user.follower_address]
|
||||||
|
}, []}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp pinned_url(nickname) when is_binary(nickname) do
|
||||||
|
Pleroma.Web.Router.Helpers.activity_pub_url(Pleroma.Web.Endpoint, :pinned, nickname)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -6,7 +6,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.NoEmptyPolicy do
|
||||||
@moduledoc "Filter local activities which have no content"
|
@moduledoc "Filter local activities which have no content"
|
||||||
@behaviour Pleroma.Web.ActivityPub.MRF
|
@behaviour Pleroma.Web.ActivityPub.MRF
|
||||||
|
|
||||||
alias Pleroma.Web
|
alias Pleroma.Web.Endpoint
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def filter(%{"actor" => actor} = object) do
|
def filter(%{"actor" => actor} = object) do
|
||||||
|
@ -24,7 +24,7 @@ def filter(%{"actor" => actor} = object) do
|
||||||
def filter(object), do: {:ok, object}
|
def filter(object), do: {:ok, object}
|
||||||
|
|
||||||
defp is_local?(actor) do
|
defp is_local?(actor) do
|
||||||
if actor |> String.starts_with?("#{Web.base_url()}") do
|
if actor |> String.starts_with?("#{Endpoint.url()}") do
|
||||||
true
|
true
|
||||||
else
|
else
|
||||||
false
|
false
|
||||||
|
|
|
@ -177,6 +177,14 @@ defp check_banner_removal(%{host: actor_host} = _actor_info, %{"image" => _image
|
||||||
|
|
||||||
defp check_banner_removal(_actor_info, object), do: {:ok, object}
|
defp check_banner_removal(_actor_info, object), do: {:ok, object}
|
||||||
|
|
||||||
|
defp check_object(%{"object" => object} = activity) do
|
||||||
|
with {:ok, _object} <- filter(object) do
|
||||||
|
{:ok, activity}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp check_object(object), do: {:ok, object}
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def filter(%{"type" => "Delete", "actor" => actor} = object) do
|
def filter(%{"type" => "Delete", "actor" => actor} = object) do
|
||||||
%{host: actor_host} = URI.parse(actor)
|
%{host: actor_host} = URI.parse(actor)
|
||||||
|
@ -202,7 +210,8 @@ def filter(%{"actor" => actor} = object) do
|
||||||
{:ok, object} <- check_media_nsfw(actor_info, object),
|
{:ok, object} <- check_media_nsfw(actor_info, object),
|
||||||
{:ok, object} <- check_ftl_removal(actor_info, object),
|
{:ok, object} <- check_ftl_removal(actor_info, object),
|
||||||
{:ok, object} <- check_followers_only(actor_info, object),
|
{:ok, object} <- check_followers_only(actor_info, object),
|
||||||
{:ok, object} <- check_report_removal(actor_info, object) do
|
{:ok, object} <- check_report_removal(actor_info, object),
|
||||||
|
{:ok, object} <- check_object(object) do
|
||||||
{:ok, object}
|
{:ok, object}
|
||||||
else
|
else
|
||||||
{:reject, nil} -> {:reject, "[SimplePolicy]"}
|
{:reject, nil} -> {:reject, "[SimplePolicy]"}
|
||||||
|
@ -227,6 +236,19 @@ def filter(%{"id" => actor, "type" => obj_type} = object)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def filter(object) when is_binary(object) do
|
||||||
|
uri = URI.parse(object)
|
||||||
|
|
||||||
|
with {:ok, object} <- check_accept(uri, object),
|
||||||
|
{:ok, object} <- check_reject(uri, object) do
|
||||||
|
{:ok, object}
|
||||||
|
else
|
||||||
|
{:reject, nil} -> {:reject, "[SimplePolicy]"}
|
||||||
|
{:reject, _} = e -> e
|
||||||
|
_ -> {:reject, "[SimplePolicy]"}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def filter(object), do: {:ok, object}
|
def filter(object), do: {:ok, object}
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
|
|
|
@ -17,6 +17,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
|
||||||
alias Pleroma.Object.Containment
|
alias Pleroma.Object.Containment
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidators.AcceptRejectValidator
|
alias Pleroma.Web.ActivityPub.ObjectValidators.AcceptRejectValidator
|
||||||
|
alias Pleroma.Web.ActivityPub.ObjectValidators.AddRemoveValidator
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidators.AnnounceValidator
|
alias Pleroma.Web.ActivityPub.ObjectValidators.AnnounceValidator
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidators.AnswerValidator
|
alias Pleroma.Web.ActivityPub.ObjectValidators.AnswerValidator
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidators.ArticleNoteValidator
|
alias Pleroma.Web.ActivityPub.ObjectValidators.ArticleNoteValidator
|
||||||
|
@ -101,7 +102,7 @@ def validate(
|
||||||
%{"type" => "Create", "object" => %{"type" => objtype} = object} = create_activity,
|
%{"type" => "Create", "object" => %{"type" => objtype} = object} = create_activity,
|
||||||
meta
|
meta
|
||||||
)
|
)
|
||||||
when objtype in ~w[Question Answer Audio Video Event Article] do
|
when objtype in ~w[Question Answer Audio Video Event Article Note] do
|
||||||
with {:ok, object_data} <- cast_and_apply(object),
|
with {:ok, object_data} <- cast_and_apply(object),
|
||||||
meta = Keyword.put(meta, :object_data, object_data |> stringify_keys),
|
meta = Keyword.put(meta, :object_data, object_data |> stringify_keys),
|
||||||
{:ok, create_activity} <-
|
{:ok, create_activity} <-
|
||||||
|
@ -113,9 +114,35 @@ def validate(
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def validate(%{"type" => type} = object, meta)
|
||||||
|
when type in ~w[Event Question Audio Video Article Note] do
|
||||||
|
validator =
|
||||||
|
case type do
|
||||||
|
"Event" -> EventValidator
|
||||||
|
"Question" -> QuestionValidator
|
||||||
|
"Audio" -> AudioVideoValidator
|
||||||
|
"Video" -> AudioVideoValidator
|
||||||
|
"Article" -> ArticleNoteValidator
|
||||||
|
"Note" -> ArticleNoteValidator
|
||||||
|
end
|
||||||
|
|
||||||
|
with {:ok, object} <-
|
||||||
|
object
|
||||||
|
|> validator.cast_and_validate()
|
||||||
|
|> Ecto.Changeset.apply_action(:insert) do
|
||||||
|
object = stringify_keys(object)
|
||||||
|
|
||||||
|
# Insert copy of hashtags as strings for the non-hashtag table indexing
|
||||||
|
tag = (object["tag"] || []) ++ Object.hashtags(%Object{data: object})
|
||||||
|
object = Map.put(object, "tag", tag)
|
||||||
|
|
||||||
|
{:ok, object, meta}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def validate(%{"type" => type} = object, meta)
|
def validate(%{"type" => type} = object, meta)
|
||||||
when type in ~w[Accept Reject Follow Update Like EmojiReact Announce
|
when type in ~w[Accept Reject Follow Update Like EmojiReact Announce
|
||||||
Event ChatMessage Question Audio Video Article Answer] do
|
ChatMessage Answer] do
|
||||||
validator =
|
validator =
|
||||||
case type do
|
case type do
|
||||||
"Accept" -> AcceptRejectValidator
|
"Accept" -> AcceptRejectValidator
|
||||||
|
@ -125,12 +152,7 @@ def validate(%{"type" => type} = object, meta)
|
||||||
"Like" -> LikeValidator
|
"Like" -> LikeValidator
|
||||||
"EmojiReact" -> EmojiReactValidator
|
"EmojiReact" -> EmojiReactValidator
|
||||||
"Announce" -> AnnounceValidator
|
"Announce" -> AnnounceValidator
|
||||||
"Event" -> EventValidator
|
|
||||||
"ChatMessage" -> ChatMessageValidator
|
"ChatMessage" -> ChatMessageValidator
|
||||||
"Question" -> QuestionValidator
|
|
||||||
"Audio" -> AudioVideoValidator
|
|
||||||
"Video" -> AudioVideoValidator
|
|
||||||
"Article" -> ArticleNoteValidator
|
|
||||||
"Answer" -> AnswerValidator
|
"Answer" -> AnswerValidator
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -143,6 +165,16 @@ def validate(%{"type" => type} = object, meta)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def validate(%{"type" => type} = object, meta) when type in ~w(Add Remove) do
|
||||||
|
with {:ok, object} <-
|
||||||
|
object
|
||||||
|
|> AddRemoveValidator.cast_and_validate()
|
||||||
|
|> Ecto.Changeset.apply_action(:insert) do
|
||||||
|
object = stringify_keys(object)
|
||||||
|
{:ok, object, meta}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def cast_and_apply(%{"type" => "ChatMessage"} = object) do
|
def cast_and_apply(%{"type" => "ChatMessage"} = object) do
|
||||||
ChatMessageValidator.cast_and_apply(object)
|
ChatMessageValidator.cast_and_apply(object)
|
||||||
end
|
end
|
||||||
|
@ -163,13 +195,13 @@ def cast_and_apply(%{"type" => "Event"} = object) do
|
||||||
EventValidator.cast_and_apply(object)
|
EventValidator.cast_and_apply(object)
|
||||||
end
|
end
|
||||||
|
|
||||||
def cast_and_apply(%{"type" => "Article"} = object) do
|
def cast_and_apply(%{"type" => type} = object) when type in ~w[Article Note] do
|
||||||
ArticleNoteValidator.cast_and_apply(object)
|
ArticleNoteValidator.cast_and_apply(object)
|
||||||
end
|
end
|
||||||
|
|
||||||
def cast_and_apply(o), do: {:error, {:validator_not_set, o}}
|
def cast_and_apply(o), do: {:error, {:validator_not_set, o}}
|
||||||
|
|
||||||
# is_struct/1 isn't present in Elixir 1.8.x
|
# is_struct/1 appears in Elixir 1.11
|
||||||
def stringify_keys(%{__struct__: _} = object) do
|
def stringify_keys(%{__struct__: _} = object) do
|
||||||
object
|
object
|
||||||
|> Map.from_struct()
|
|> Map.from_struct()
|
||||||
|
|
|
@ -27,7 +27,7 @@ def cast_data(data) do
|
||||||
|> cast(data, __schema__(:fields))
|
|> cast(data, __schema__(:fields))
|
||||||
end
|
end
|
||||||
|
|
||||||
def validate_data(cng) do
|
defp validate_data(cng) do
|
||||||
cng
|
cng
|
||||||
|> validate_required([:id, :type, :actor, :to, :cc, :object])
|
|> validate_required([:id, :type, :actor, :to, :cc, :object])
|
||||||
|> validate_inclusion(:type, ["Accept", "Reject"])
|
|> validate_inclusion(:type, ["Accept", "Reject"])
|
||||||
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.ActivityPub.ObjectValidators.AddRemoveValidator do
|
||||||
|
use Ecto.Schema
|
||||||
|
|
||||||
|
import Ecto.Changeset
|
||||||
|
import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
|
||||||
|
|
||||||
|
require Pleroma.Constants
|
||||||
|
|
||||||
|
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
||||||
|
alias Pleroma.User
|
||||||
|
|
||||||
|
@primary_key false
|
||||||
|
|
||||||
|
embedded_schema do
|
||||||
|
field(:id, ObjectValidators.ObjectID, primary_key: true)
|
||||||
|
field(:target)
|
||||||
|
field(:object, ObjectValidators.ObjectID)
|
||||||
|
field(:actor, ObjectValidators.ObjectID)
|
||||||
|
field(:type)
|
||||||
|
field(:to, ObjectValidators.Recipients, default: [])
|
||||||
|
field(:cc, ObjectValidators.Recipients, default: [])
|
||||||
|
end
|
||||||
|
|
||||||
|
def cast_and_validate(data) do
|
||||||
|
{:ok, actor} = User.get_or_fetch_by_ap_id(data["actor"])
|
||||||
|
|
||||||
|
{:ok, actor} = maybe_refetch_user(actor)
|
||||||
|
|
||||||
|
data
|
||||||
|
|> maybe_fix_data_for_mastodon(actor)
|
||||||
|
|> cast_data()
|
||||||
|
|> validate_data(actor)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp maybe_fix_data_for_mastodon(data, actor) do
|
||||||
|
# Mastodon sends pin/unpin objects without id, to, cc fields
|
||||||
|
data
|
||||||
|
|> Map.put_new("id", Pleroma.Web.ActivityPub.Utils.generate_activity_id())
|
||||||
|
|> Map.put_new("to", [Pleroma.Constants.as_public()])
|
||||||
|
|> Map.put_new("cc", [actor.follower_address])
|
||||||
|
end
|
||||||
|
|
||||||
|
defp cast_data(data) do
|
||||||
|
cast(%__MODULE__{}, data, __schema__(:fields))
|
||||||
|
end
|
||||||
|
|
||||||
|
defp validate_data(changeset, actor) do
|
||||||
|
changeset
|
||||||
|
|> validate_required([:id, :target, :object, :actor, :type, :to, :cc])
|
||||||
|
|> validate_inclusion(:type, ~w(Add Remove))
|
||||||
|
|> validate_actor_presence()
|
||||||
|
|> validate_collection_belongs_to_actor(actor)
|
||||||
|
|> validate_object_presence()
|
||||||
|
end
|
||||||
|
|
||||||
|
defp validate_collection_belongs_to_actor(changeset, actor) do
|
||||||
|
validate_change(changeset, :target, fn :target, target ->
|
||||||
|
if target == actor.featured_address do
|
||||||
|
[]
|
||||||
|
else
|
||||||
|
[target: "collection doesn't belong to actor"]
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp maybe_refetch_user(%User{featured_address: address} = user) when is_binary(address) do
|
||||||
|
{:ok, user}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp maybe_refetch_user(%User{ap_id: ap_id}) do
|
||||||
|
Pleroma.Web.ActivityPub.Transmogrifier.upgrade_user_from_ap_id(ap_id)
|
||||||
|
end
|
||||||
|
end
|
|
@ -50,7 +50,7 @@ def fix_after_cast(cng) do
|
||||||
cng
|
cng
|
||||||
end
|
end
|
||||||
|
|
||||||
def validate_data(data_cng) do
|
defp validate_data(data_cng) do
|
||||||
data_cng
|
data_cng
|
||||||
|> validate_inclusion(:type, ["Announce"])
|
|> validate_inclusion(:type, ["Announce"])
|
||||||
|> validate_required([:id, :type, :object, :actor, :to, :cc])
|
|> validate_required([:id, :type, :object, :actor, :to, :cc])
|
||||||
|
@ -68,7 +68,7 @@ def validate_announcable(cng) do
|
||||||
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
|
||||||
recipients = get_field(cng, :to) ++ get_field(cng, :cc)
|
recipients = get_field(cng, :to) ++ get_field(cng, :cc)
|
||||||
local_public = Pleroma.Constants.as_local_public()
|
local_public = Utils.as_local_public()
|
||||||
|
|
||||||
is_public =
|
is_public =
|
||||||
Enum.member?(recipients, Pleroma.Constants.as_public()) or
|
Enum.member?(recipients, Pleroma.Constants.as_public()) or
|
||||||
|
|
|
@ -6,6 +6,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AnswerValidator do
|
||||||
use Ecto.Schema
|
use Ecto.Schema
|
||||||
|
|
||||||
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
||||||
|
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
|
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
|
||||||
|
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
|
@ -23,6 +24,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AnswerValidator do
|
||||||
field(:name, :string)
|
field(:name, :string)
|
||||||
field(:inReplyTo, ObjectValidators.ObjectID)
|
field(:inReplyTo, ObjectValidators.ObjectID)
|
||||||
field(:attributedTo, ObjectValidators.ObjectID)
|
field(:attributedTo, ObjectValidators.ObjectID)
|
||||||
|
field(:context, :string)
|
||||||
|
|
||||||
# TODO: Remove actor on objects
|
# TODO: Remove actor on objects
|
||||||
field(:actor, ObjectValidators.ObjectID)
|
field(:actor, ObjectValidators.ObjectID)
|
||||||
|
@ -46,11 +48,16 @@ def cast_data(data) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def changeset(struct, data) do
|
def changeset(struct, data) do
|
||||||
|
data =
|
||||||
|
data
|
||||||
|
|> CommonFixes.fix_actor()
|
||||||
|
|> CommonFixes.fix_object_defaults()
|
||||||
|
|
||||||
struct
|
struct
|
||||||
|> cast(data, __schema__(:fields))
|
|> cast(data, __schema__(:fields))
|
||||||
end
|
end
|
||||||
|
|
||||||
def validate_data(data_cng) do
|
defp validate_data(data_cng) do
|
||||||
data_cng
|
data_cng
|
||||||
|> validate_inclusion(:type, ["Answer"])
|
|> validate_inclusion(:type, ["Answer"])
|
||||||
|> validate_required([:id, :inReplyTo, :name, :attributedTo, :actor])
|
|> validate_required([:id, :inReplyTo, :name, :attributedTo, :actor])
|
||||||
|
|
|
@ -9,6 +9,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNoteValidator do
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator
|
alias Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
|
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
|
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
|
||||||
|
alias Pleroma.Web.ActivityPub.ObjectValidators.TagValidator
|
||||||
alias Pleroma.Web.ActivityPub.Transmogrifier
|
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||||
|
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
|
@ -22,8 +23,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNoteValidator do
|
||||||
field(:cc, ObjectValidators.Recipients, default: [])
|
field(:cc, ObjectValidators.Recipients, default: [])
|
||||||
field(:bto, ObjectValidators.Recipients, default: [])
|
field(:bto, ObjectValidators.Recipients, default: [])
|
||||||
field(:bcc, ObjectValidators.Recipients, default: [])
|
field(:bcc, ObjectValidators.Recipients, default: [])
|
||||||
# TODO: Write type
|
embeds_many(:tag, TagValidator)
|
||||||
field(:tag, {:array, :map}, default: [])
|
|
||||||
field(:type, :string)
|
field(:type, :string)
|
||||||
|
|
||||||
field(:name, :string)
|
field(:name, :string)
|
||||||
|
@ -50,6 +50,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNoteValidator do
|
||||||
|
|
||||||
field(:likes, {:array, ObjectValidators.ObjectID}, default: [])
|
field(:likes, {:array, ObjectValidators.ObjectID}, default: [])
|
||||||
field(:announcements, {:array, ObjectValidators.ObjectID}, default: [])
|
field(:announcements, {:array, ObjectValidators.ObjectID}, default: [])
|
||||||
|
|
||||||
|
field(:replies, {:array, ObjectValidators.ObjectID}, default: [])
|
||||||
end
|
end
|
||||||
|
|
||||||
def cast_and_apply(data) do
|
def cast_and_apply(data) do
|
||||||
|
@ -65,36 +67,51 @@ def cast_and_validate(data) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def cast_data(data) do
|
def cast_data(data) do
|
||||||
data = fix(data)
|
|
||||||
|
|
||||||
%__MODULE__{}
|
%__MODULE__{}
|
||||||
|> changeset(data)
|
|> changeset(data)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp fix_url(%{"url" => url} = data) when is_map(url) do
|
defp fix_url(%{"url" => url} = data) when is_bitstring(url), do: data
|
||||||
Map.put(data, "url", url["href"])
|
defp fix_url(%{"url" => url} = data) when is_map(url), do: Map.put(data, "url", url["href"])
|
||||||
end
|
|
||||||
|
|
||||||
defp fix_url(data), do: data
|
defp fix_url(data), do: data
|
||||||
|
|
||||||
|
defp fix_tag(%{"tag" => tag} = data) when is_list(tag), do: data
|
||||||
|
defp fix_tag(%{"tag" => tag} = data) when is_map(tag), do: Map.put(data, "tag", [tag])
|
||||||
|
defp fix_tag(data), do: Map.drop(data, ["tag"])
|
||||||
|
|
||||||
|
defp fix_replies(%{"replies" => %{"first" => %{"items" => replies}}} = data)
|
||||||
|
when is_list(replies),
|
||||||
|
do: Map.put(data, "replies", replies)
|
||||||
|
|
||||||
|
defp fix_replies(%{"replies" => %{"items" => replies}} = data) when is_list(replies),
|
||||||
|
do: Map.put(data, "replies", replies)
|
||||||
|
|
||||||
|
defp fix_replies(%{"replies" => replies} = data) when is_bitstring(replies),
|
||||||
|
do: Map.drop(data, ["replies"])
|
||||||
|
|
||||||
|
defp fix_replies(data), do: data
|
||||||
|
|
||||||
defp fix(data) do
|
defp fix(data) do
|
||||||
data
|
data
|
||||||
|> CommonFixes.fix_defaults()
|
|
||||||
|> CommonFixes.fix_attribution()
|
|
||||||
|> CommonFixes.fix_actor()
|
|> CommonFixes.fix_actor()
|
||||||
|
|> CommonFixes.fix_object_defaults()
|
||||||
|> fix_url()
|
|> fix_url()
|
||||||
|
|> fix_tag()
|
||||||
|
|> fix_replies()
|
||||||
|> Transmogrifier.fix_emoji()
|
|> Transmogrifier.fix_emoji()
|
||||||
|
|> Transmogrifier.fix_content_map()
|
||||||
end
|
end
|
||||||
|
|
||||||
def changeset(struct, data) do
|
def changeset(struct, data) do
|
||||||
data = fix(data)
|
data = fix(data)
|
||||||
|
|
||||||
struct
|
struct
|
||||||
|> cast(data, __schema__(:fields) -- [:attachment])
|
|> cast(data, __schema__(:fields) -- [:attachment, :tag])
|
||||||
|> cast_embed(:attachment)
|
|> cast_embed(:attachment)
|
||||||
|
|> cast_embed(:tag)
|
||||||
end
|
end
|
||||||
|
|
||||||
def validate_data(data_cng) do
|
defp validate_data(data_cng) do
|
||||||
data_cng
|
data_cng
|
||||||
|> validate_inclusion(:type, ["Article", "Note"])
|
|> validate_inclusion(:type, ["Article", "Note"])
|
||||||
|> validate_required([:id, :actor, :attributedTo, :type, :context, :context_id])
|
|> validate_required([:id, :actor, :attributedTo, :type, :context, :context_id])
|
||||||
|
|
|
@ -6,7 +6,6 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator do
|
||||||
use Ecto.Schema
|
use Ecto.Schema
|
||||||
|
|
||||||
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidators.UrlObjectValidator
|
|
||||||
|
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
|
|
||||||
|
@ -21,6 +20,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator do
|
||||||
field(:type, :string)
|
field(:type, :string)
|
||||||
field(:href, ObjectValidators.Uri)
|
field(:href, ObjectValidators.Uri)
|
||||||
field(:mediaType, :string, default: "application/octet-stream")
|
field(:mediaType, :string, default: "application/octet-stream")
|
||||||
|
field(:width, :integer)
|
||||||
|
field(:height, :integer)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -52,7 +53,7 @@ def url_changeset(struct, data) do
|
||||||
data = fix_media_type(data)
|
data = fix_media_type(data)
|
||||||
|
|
||||||
struct
|
struct
|
||||||
|> cast(data, [:type, :href, :mediaType])
|
|> cast(data, [:type, :href, :mediaType, :width, :height])
|
||||||
|> validate_inclusion(:type, ["Link"])
|
|> validate_inclusion(:type, ["Link"])
|
||||||
|> validate_required([:type, :href, :mediaType])
|
|> validate_required([:type, :href, :mediaType])
|
||||||
end
|
end
|
||||||
|
@ -60,7 +61,7 @@ def url_changeset(struct, data) do
|
||||||
def fix_media_type(data) do
|
def fix_media_type(data) do
|
||||||
data = Map.put_new(data, "mediaType", data["mimeType"])
|
data = Map.put_new(data, "mediaType", data["mimeType"])
|
||||||
|
|
||||||
if MIME.valid?(data["mediaType"]) do
|
if is_bitstring(data["mediaType"]) && MIME.extensions(data["mediaType"]) != [] do
|
||||||
data
|
data
|
||||||
else
|
else
|
||||||
Map.put(data, "mediaType", "application/octet-stream")
|
Map.put(data, "mediaType", "application/octet-stream")
|
||||||
|
@ -90,7 +91,7 @@ defp fix_url(data) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def validate_data(cng) do
|
defp validate_data(cng) do
|
||||||
cng
|
cng
|
||||||
|> validate_inclusion(:type, ~w[Document Audio Image Video])
|
|> validate_inclusion(:type, ~w[Document Audio Image Video])
|
||||||
|> validate_required([:mediaType, :url, :type])
|
|> validate_required([:mediaType, :url, :type])
|
||||||
|
|
|
@ -5,11 +5,11 @@
|
||||||
defmodule Pleroma.Web.ActivityPub.ObjectValidators.AudioVideoValidator do
|
defmodule Pleroma.Web.ActivityPub.ObjectValidators.AudioVideoValidator do
|
||||||
use Ecto.Schema
|
use Ecto.Schema
|
||||||
|
|
||||||
alias Pleroma.EarmarkRenderer
|
|
||||||
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator
|
alias Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
|
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
|
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
|
||||||
|
alias Pleroma.Web.ActivityPub.ObjectValidators.TagValidator
|
||||||
alias Pleroma.Web.ActivityPub.Transmogrifier
|
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||||
|
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
|
@ -23,8 +23,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AudioVideoValidator do
|
||||||
field(:cc, ObjectValidators.Recipients, default: [])
|
field(:cc, ObjectValidators.Recipients, default: [])
|
||||||
field(:bto, ObjectValidators.Recipients, default: [])
|
field(:bto, ObjectValidators.Recipients, default: [])
|
||||||
field(:bcc, ObjectValidators.Recipients, default: [])
|
field(:bcc, ObjectValidators.Recipients, default: [])
|
||||||
# TODO: Write type
|
embeds_many(:tag, TagValidator)
|
||||||
field(:tag, {:array, :map}, default: [])
|
|
||||||
field(:type, :string)
|
field(:type, :string)
|
||||||
|
|
||||||
field(:name, :string)
|
field(:name, :string)
|
||||||
|
@ -110,7 +109,7 @@ defp fix_content(%{"mediaType" => "text/markdown", "content" => content} = data)
|
||||||
when is_binary(content) do
|
when is_binary(content) do
|
||||||
content =
|
content =
|
||||||
content
|
content
|
||||||
|> Earmark.as_html!(%Earmark.Options{renderer: EarmarkRenderer})
|
|> Pleroma.Formatter.markdown_to_html()
|
||||||
|> Pleroma.HTML.filter_tags()
|
|> Pleroma.HTML.filter_tags()
|
||||||
|
|
||||||
Map.put(data, "content", content)
|
Map.put(data, "content", content)
|
||||||
|
@ -120,9 +119,8 @@ defp fix_content(data), do: data
|
||||||
|
|
||||||
defp fix(data) do
|
defp fix(data) do
|
||||||
data
|
data
|
||||||
|> CommonFixes.fix_defaults()
|
|
||||||
|> CommonFixes.fix_attribution()
|
|
||||||
|> CommonFixes.fix_actor()
|
|> CommonFixes.fix_actor()
|
||||||
|
|> CommonFixes.fix_object_defaults()
|
||||||
|> Transmogrifier.fix_emoji()
|
|> Transmogrifier.fix_emoji()
|
||||||
|> fix_url()
|
|> fix_url()
|
||||||
|> fix_content()
|
|> fix_content()
|
||||||
|
@ -132,11 +130,12 @@ def changeset(struct, data) do
|
||||||
data = fix(data)
|
data = fix(data)
|
||||||
|
|
||||||
struct
|
struct
|
||||||
|> cast(data, __schema__(:fields) -- [:attachment])
|
|> cast(data, __schema__(:fields) -- [:attachment, :tag])
|
||||||
|> cast_embed(:attachment)
|
|> cast_embed(:attachment)
|
||||||
|
|> cast_embed(:tag)
|
||||||
end
|
end
|
||||||
|
|
||||||
def validate_data(data_cng) do
|
defp validate_data(data_cng) do
|
||||||
data_cng
|
data_cng
|
||||||
|> validate_inclusion(:type, ["Audio", "Video"])
|
|> validate_inclusion(:type, ["Audio", "Video"])
|
||||||
|> validate_required([:id, :actor, :attributedTo, :type, :context, :attachment])
|
|> validate_required([:id, :actor, :attributedTo, :type, :context, :attachment])
|
||||||
|
|
|
@ -26,7 +26,7 @@ def cast_data(data) do
|
||||||
|> cast(data, __schema__(:fields))
|
|> cast(data, __schema__(:fields))
|
||||||
end
|
end
|
||||||
|
|
||||||
def validate_data(cng) do
|
defp validate_data(cng) do
|
||||||
cng
|
cng
|
||||||
|> validate_required([:id, :type, :actor, :to, :cc, :object])
|
|> validate_required([:id, :type, :actor, :to, :cc, :object])
|
||||||
|> validate_inclusion(:type, ["Block"])
|
|> validate_inclusion(:type, ["Block"])
|
||||||
|
|
|
@ -67,7 +67,7 @@ def changeset(struct, data) do
|
||||||
|> cast_embed(:attachment)
|
|> cast_embed(:attachment)
|
||||||
end
|
end
|
||||||
|
|
||||||
def validate_data(data_cng) do
|
defp validate_data(data_cng) do
|
||||||
data_cng
|
data_cng
|
||||||
|> validate_inclusion(:type, ["ChatMessage"])
|
|> validate_inclusion(:type, ["ChatMessage"])
|
||||||
|> validate_required([:id, :actor, :to, :type, :published])
|
|> validate_required([:id, :actor, :to, :type, :published])
|
||||||
|
|
|
@ -3,26 +3,55 @@
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes do
|
defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes do
|
||||||
|
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
||||||
alias Pleroma.Object.Containment
|
alias Pleroma.Object.Containment
|
||||||
|
alias Pleroma.User
|
||||||
|
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||||
alias Pleroma.Web.ActivityPub.Utils
|
alias Pleroma.Web.ActivityPub.Utils
|
||||||
|
|
||||||
# based on Pleroma.Web.ActivityPub.Utils.lazy_put_objects_defaults
|
def cast_and_filter_recipients(message, field, follower_collection, field_fallback \\ []) do
|
||||||
def fix_defaults(data) do
|
{:ok, data} = ObjectValidators.Recipients.cast(message[field] || field_fallback)
|
||||||
|
|
||||||
|
data =
|
||||||
|
Enum.reject(data, fn x ->
|
||||||
|
String.ends_with?(x, "/followers") and x != follower_collection
|
||||||
|
end)
|
||||||
|
|
||||||
|
Map.put(message, field, data)
|
||||||
|
end
|
||||||
|
|
||||||
|
def fix_object_defaults(data) do
|
||||||
%{data: %{"id" => context}, id: context_id} =
|
%{data: %{"id" => context}, id: context_id} =
|
||||||
Utils.create_context(data["context"] || data["conversation"])
|
Utils.create_context(data["context"] || data["conversation"])
|
||||||
|
|
||||||
|
%User{follower_address: follower_collection} = User.get_cached_by_ap_id(data["attributedTo"])
|
||||||
|
|
||||||
data
|
data
|
||||||
|> Map.put("context", context)
|
|> Map.put("context", context)
|
||||||
|> Map.put("context_id", context_id)
|
|> Map.put("context_id", context_id)
|
||||||
|
|> cast_and_filter_recipients("to", follower_collection)
|
||||||
|
|> cast_and_filter_recipients("cc", follower_collection)
|
||||||
|
|> cast_and_filter_recipients("bto", follower_collection)
|
||||||
|
|> cast_and_filter_recipients("bcc", follower_collection)
|
||||||
|
|> Transmogrifier.fix_implicit_addressing(follower_collection)
|
||||||
end
|
end
|
||||||
|
|
||||||
def fix_attribution(data) do
|
def fix_activity_addressing(activity, _meta) do
|
||||||
data
|
%User{follower_address: follower_collection} = User.get_cached_by_ap_id(activity["actor"])
|
||||||
|> Map.put_new("actor", data["attributedTo"])
|
|
||||||
|
activity
|
||||||
|
|> cast_and_filter_recipients("to", follower_collection)
|
||||||
|
|> cast_and_filter_recipients("cc", follower_collection)
|
||||||
|
|> cast_and_filter_recipients("bto", follower_collection)
|
||||||
|
|> cast_and_filter_recipients("bcc", follower_collection)
|
||||||
|
|> Transmogrifier.fix_implicit_addressing(follower_collection)
|
||||||
end
|
end
|
||||||
|
|
||||||
def fix_actor(data) do
|
def fix_actor(data) do
|
||||||
actor = Containment.get_actor(data)
|
actor =
|
||||||
|
data
|
||||||
|
|> Map.put_new("actor", data["attributedTo"])
|
||||||
|
|> Containment.get_actor()
|
||||||
|
|
||||||
data
|
data
|
||||||
|> Map.put("actor", actor)
|
|> Map.put("actor", actor)
|
||||||
|
|
|
@ -9,11 +9,13 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations do
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
|
||||||
|
@spec validate_any_presence(Ecto.Changeset.t(), [atom()]) :: Ecto.Changeset.t()
|
||||||
def validate_any_presence(cng, fields) do
|
def validate_any_presence(cng, fields) do
|
||||||
non_empty =
|
non_empty =
|
||||||
fields
|
fields
|
||||||
|> Enum.map(fn field -> get_field(cng, field) end)
|
|> Enum.map(fn field -> get_field(cng, field) end)
|
||||||
|> Enum.any?(fn
|
|> Enum.any?(fn
|
||||||
|
nil -> false
|
||||||
[] -> false
|
[] -> false
|
||||||
_ -> true
|
_ -> true
|
||||||
end)
|
end)
|
||||||
|
@ -29,6 +31,7 @@ def validate_any_presence(cng, fields) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec validate_actor_presence(Ecto.Changeset.t(), keyword()) :: Ecto.Changeset.t()
|
||||||
def validate_actor_presence(cng, options \\ []) do
|
def validate_actor_presence(cng, options \\ []) do
|
||||||
field_name = Keyword.get(options, :field_name, :actor)
|
field_name = Keyword.get(options, :field_name, :actor)
|
||||||
|
|
||||||
|
@ -47,6 +50,7 @@ def validate_actor_presence(cng, options \\ []) do
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec validate_object_presence(Ecto.Changeset.t(), keyword()) :: Ecto.Changeset.t()
|
||||||
def validate_object_presence(cng, options \\ []) do
|
def validate_object_presence(cng, options \\ []) do
|
||||||
field_name = Keyword.get(options, :field_name, :object)
|
field_name = Keyword.get(options, :field_name, :object)
|
||||||
allowed_types = Keyword.get(options, :allowed_types, false)
|
allowed_types = Keyword.get(options, :allowed_types, false)
|
||||||
|
@ -68,6 +72,7 @@ def validate_object_presence(cng, options \\ []) do
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec validate_object_or_user_presence(Ecto.Changeset.t(), keyword()) :: Ecto.Changeset.t()
|
||||||
def validate_object_or_user_presence(cng, options \\ []) do
|
def validate_object_or_user_presence(cng, options \\ []) do
|
||||||
field_name = Keyword.get(options, :field_name, :object)
|
field_name = Keyword.get(options, :field_name, :object)
|
||||||
options = Keyword.put(options, :field_name, field_name)
|
options = Keyword.put(options, :field_name, field_name)
|
||||||
|
@ -83,6 +88,7 @@ def validate_object_or_user_presence(cng, options \\ []) do
|
||||||
if actor_cng.valid?, do: actor_cng, else: object_cng
|
if actor_cng.valid?, do: actor_cng, else: object_cng
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec validate_host_match(Ecto.Changeset.t(), [atom()]) :: Ecto.Changeset.t()
|
||||||
def validate_host_match(cng, fields \\ [:id, :actor]) do
|
def validate_host_match(cng, fields \\ [:id, :actor]) do
|
||||||
if same_domain?(cng, fields) do
|
if same_domain?(cng, fields) do
|
||||||
cng
|
cng
|
||||||
|
@ -95,6 +101,7 @@ def validate_host_match(cng, fields \\ [:id, :actor]) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec validate_fields_match(Ecto.Changeset.t(), [atom()]) :: Ecto.Changeset.t()
|
||||||
def validate_fields_match(cng, fields) do
|
def validate_fields_match(cng, fields) do
|
||||||
if map_unique?(cng, fields) do
|
if map_unique?(cng, fields) do
|
||||||
cng
|
cng
|
||||||
|
@ -122,12 +129,14 @@ defp map_unique?(cng, fields, func \\ & &1) do
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec same_domain?(Ecto.Changeset.t(), [atom()]) :: boolean()
|
||||||
def same_domain?(cng, fields \\ [:actor, :object]) do
|
def same_domain?(cng, fields \\ [:actor, :object]) do
|
||||||
map_unique?(cng, fields, fn value -> URI.parse(value).host end)
|
map_unique?(cng, fields, fn value -> URI.parse(value).host end)
|
||||||
end
|
end
|
||||||
|
|
||||||
# This figures out if a user is able to create, delete or modify something
|
# This figures out if a user is able to create, delete or modify something
|
||||||
# based on the domain and superuser status
|
# based on the domain and superuser status
|
||||||
|
@spec validate_modification_rights(Ecto.Changeset.t()) :: Ecto.Changeset.t()
|
||||||
def validate_modification_rights(cng) do
|
def validate_modification_rights(cng) do
|
||||||
actor = User.get_cached_by_ap_id(get_field(cng, :actor))
|
actor = User.get_cached_by_ap_id(get_field(cng, :actor))
|
||||||
|
|
||||||
|
|
|
@ -39,7 +39,7 @@ def cast_and_validate(data, meta \\ []) do
|
||||||
|> validate_data(meta)
|
|> validate_data(meta)
|
||||||
end
|
end
|
||||||
|
|
||||||
def validate_data(cng, meta \\ []) do
|
defp validate_data(cng, meta) do
|
||||||
cng
|
cng
|
||||||
|> validate_required([:id, :actor, :to, :type, :object])
|
|> validate_required([:id, :actor, :to, :type, :object])
|
||||||
|> validate_inclusion(:type, ["Create"])
|
|> validate_inclusion(:type, ["Create"])
|
||||||
|
|
|
@ -10,8 +10,10 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CreateGenericValidator do
|
||||||
|
|
||||||
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
|
alias Pleroma.User
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
|
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
|
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
|
||||||
|
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||||
|
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
|
|
||||||
|
@ -23,6 +25,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CreateGenericValidator do
|
||||||
field(:type, :string)
|
field(:type, :string)
|
||||||
field(:to, ObjectValidators.Recipients, default: [])
|
field(:to, ObjectValidators.Recipients, default: [])
|
||||||
field(:cc, ObjectValidators.Recipients, default: [])
|
field(:cc, ObjectValidators.Recipients, default: [])
|
||||||
|
field(:bto, ObjectValidators.Recipients, default: [])
|
||||||
|
field(:bcc, ObjectValidators.Recipients, default: [])
|
||||||
field(:object, ObjectValidators.ObjectID)
|
field(:object, ObjectValidators.ObjectID)
|
||||||
field(:expires_at, ObjectValidators.DateTime)
|
field(:expires_at, ObjectValidators.DateTime)
|
||||||
|
|
||||||
|
@ -54,39 +58,37 @@ def changeset(struct, data) do
|
||||||
|> cast(data, __schema__(:fields))
|
|> cast(data, __schema__(:fields))
|
||||||
end
|
end
|
||||||
|
|
||||||
defp fix_context(data, meta) do
|
# CommonFixes.fix_activity_addressing adapted for Create specific behavior
|
||||||
if object = meta[:object_data] do
|
defp fix_addressing(data, object) do
|
||||||
Map.put_new(data, "context", object["context"])
|
%User{follower_address: follower_collection} = User.get_cached_by_ap_id(data["actor"])
|
||||||
else
|
|
||||||
data
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp fix_addressing(data, meta) do
|
|
||||||
if object = meta[:object_data] do
|
|
||||||
data
|
|
||||||
|> Map.put_new("to", object["to"] || [])
|
|
||||||
|> Map.put_new("cc", object["cc"] || [])
|
|
||||||
else
|
|
||||||
data
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp fix(data, meta) do
|
|
||||||
data
|
data
|
||||||
|> fix_context(meta)
|
|> CommonFixes.cast_and_filter_recipients("to", follower_collection, object["to"])
|
||||||
|> fix_addressing(meta)
|
|> CommonFixes.cast_and_filter_recipients("cc", follower_collection, object["cc"])
|
||||||
|> CommonFixes.fix_actor()
|
|> CommonFixes.cast_and_filter_recipients("bto", follower_collection, object["bto"])
|
||||||
|
|> CommonFixes.cast_and_filter_recipients("bcc", follower_collection, object["bcc"])
|
||||||
|
|> Transmogrifier.fix_implicit_addressing(follower_collection)
|
||||||
end
|
end
|
||||||
|
|
||||||
def validate_data(cng, meta \\ []) do
|
def fix(data, meta) do
|
||||||
|
object = meta[:object_data]
|
||||||
|
|
||||||
|
data
|
||||||
|
|> CommonFixes.fix_actor()
|
||||||
|
|> Map.put_new("context", object["context"])
|
||||||
|
|> fix_addressing(object)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp validate_data(cng, meta) do
|
||||||
|
object = meta[:object_data]
|
||||||
|
|
||||||
cng
|
cng
|
||||||
|> validate_required([:actor, :type, :object])
|
|> validate_required([:actor, :type, :object, :to, :cc])
|
||||||
|> validate_inclusion(:type, ["Create"])
|
|> validate_inclusion(:type, ["Create"])
|
||||||
|> CommonValidations.validate_actor_presence()
|
|> CommonValidations.validate_actor_presence()
|
||||||
|> CommonValidations.validate_any_presence([:to, :cc])
|
|> validate_actors_match(object)
|
||||||
|> validate_actors_match(meta)
|
|> validate_context_match(object)
|
||||||
|> validate_context_match(meta)
|
|> validate_addressing_match(object)
|
||||||
|> validate_object_nonexistence()
|
|> validate_object_nonexistence()
|
||||||
|> validate_object_containment()
|
|> validate_object_containment()
|
||||||
end
|
end
|
||||||
|
@ -118,8 +120,8 @@ def validate_object_nonexistence(cng) do
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
def validate_actors_match(cng, meta) do
|
def validate_actors_match(cng, object) do
|
||||||
attributed_to = meta[:object_data]["attributedTo"] || meta[:object_data]["actor"]
|
attributed_to = object["attributedTo"] || object["actor"]
|
||||||
|
|
||||||
cng
|
cng
|
||||||
|> validate_change(:actor, fn :actor, actor ->
|
|> validate_change(:actor, fn :actor, actor ->
|
||||||
|
@ -131,7 +133,7 @@ def validate_actors_match(cng, meta) do
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
def validate_context_match(cng, %{object_data: %{"context" => object_context}}) do
|
def validate_context_match(cng, %{"context" => object_context}) do
|
||||||
cng
|
cng
|
||||||
|> validate_change(:context, fn :context, context ->
|
|> validate_change(:context, fn :context, context ->
|
||||||
if context == object_context do
|
if context == object_context do
|
||||||
|
@ -142,5 +144,18 @@ def validate_context_match(cng, %{object_data: %{"context" => object_context}})
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
def validate_context_match(cng, _), do: cng
|
def validate_addressing_match(cng, object) do
|
||||||
|
[:to, :cc, :bcc, :bto]
|
||||||
|
|> Enum.reduce(cng, fn field, cng ->
|
||||||
|
object_data = object[to_string(field)]
|
||||||
|
|
||||||
|
validate_change(cng, field, fn field, data ->
|
||||||
|
if data == object_data do
|
||||||
|
[]
|
||||||
|
else
|
||||||
|
[{field, "field doesn't match with object (#{inspect(object_data)})"}]
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,29 +0,0 @@
|
||||||
# Pleroma: A lightweight social networking server
|
|
||||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
|
|
||||||
defmodule Pleroma.Web.ActivityPub.ObjectValidators.CreateNoteValidator do
|
|
||||||
use Ecto.Schema
|
|
||||||
|
|
||||||
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidators.NoteValidator
|
|
||||||
|
|
||||||
import Ecto.Changeset
|
|
||||||
|
|
||||||
@primary_key false
|
|
||||||
|
|
||||||
embedded_schema do
|
|
||||||
field(:id, ObjectValidators.ObjectID, primary_key: true)
|
|
||||||
field(:actor, ObjectValidators.ObjectID)
|
|
||||||
field(:type, :string)
|
|
||||||
field(:to, ObjectValidators.Recipients, default: [])
|
|
||||||
field(:cc, ObjectValidators.Recipients, default: [])
|
|
||||||
field(:bto, ObjectValidators.Recipients, default: [])
|
|
||||||
field(:bcc, ObjectValidators.Recipients, default: [])
|
|
||||||
embeds_one(:object, NoteValidator)
|
|
||||||
end
|
|
||||||
|
|
||||||
def cast_data(data) do
|
|
||||||
cast(%__MODULE__{}, data, __schema__(:fields))
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -53,7 +53,7 @@ def add_deleted_activity_id(cng) do
|
||||||
Tombstone
|
Tombstone
|
||||||
Video
|
Video
|
||||||
}
|
}
|
||||||
def validate_data(cng) do
|
defp validate_data(cng) do
|
||||||
cng
|
cng
|
||||||
|> validate_required([:id, :type, :actor, :to, :cc, :object])
|
|> validate_required([:id, :type, :actor, :to, :cc, :object])
|
||||||
|> validate_inclusion(:type, ["Delete"])
|
|> validate_inclusion(:type, ["Delete"])
|
||||||
|
|
|
@ -70,7 +70,7 @@ def validate_emoji(cng) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def validate_data(data_cng) do
|
defp validate_data(data_cng) do
|
||||||
data_cng
|
data_cng
|
||||||
|> validate_inclusion(:type, ["EmojiReact"])
|
|> validate_inclusion(:type, ["EmojiReact"])
|
||||||
|> validate_required([:id, :type, :object, :actor, :context, :to, :cc, :content])
|
|> validate_required([:id, :type, :object, :actor, :context, :to, :cc, :content])
|
||||||
|
|
|
@ -9,6 +9,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EventValidator do
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator
|
alias Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
|
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
|
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
|
||||||
|
alias Pleroma.Web.ActivityPub.ObjectValidators.TagValidator
|
||||||
alias Pleroma.Web.ActivityPub.Transmogrifier
|
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||||
|
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
|
@ -23,8 +24,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EventValidator do
|
||||||
field(:cc, ObjectValidators.Recipients, default: [])
|
field(:cc, ObjectValidators.Recipients, default: [])
|
||||||
field(:bto, ObjectValidators.Recipients, default: [])
|
field(:bto, ObjectValidators.Recipients, default: [])
|
||||||
field(:bcc, ObjectValidators.Recipients, default: [])
|
field(:bcc, ObjectValidators.Recipients, default: [])
|
||||||
# TODO: Write type
|
embeds_many(:tag, TagValidator)
|
||||||
field(:tag, {:array, :map}, default: [])
|
|
||||||
field(:type, :string)
|
field(:type, :string)
|
||||||
|
|
||||||
field(:name, :string)
|
field(:name, :string)
|
||||||
|
@ -72,8 +72,8 @@ def cast_data(data) do
|
||||||
|
|
||||||
defp fix(data) do
|
defp fix(data) do
|
||||||
data
|
data
|
||||||
|> CommonFixes.fix_defaults()
|
|> CommonFixes.fix_actor()
|
||||||
|> CommonFixes.fix_attribution()
|
|> CommonFixes.fix_object_defaults()
|
||||||
|> Transmogrifier.fix_emoji()
|
|> Transmogrifier.fix_emoji()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -81,11 +81,12 @@ def changeset(struct, data) do
|
||||||
data = fix(data)
|
data = fix(data)
|
||||||
|
|
||||||
struct
|
struct
|
||||||
|> cast(data, __schema__(:fields) -- [:attachment])
|
|> cast(data, __schema__(:fields) -- [:attachment, :tag])
|
||||||
|> cast_embed(:attachment)
|
|> cast_embed(:attachment)
|
||||||
|
|> cast_embed(:tag)
|
||||||
end
|
end
|
||||||
|
|
||||||
def validate_data(data_cng) do
|
defp validate_data(data_cng) do
|
||||||
data_cng
|
data_cng
|
||||||
|> validate_inclusion(:type, ["Event"])
|
|> validate_inclusion(:type, ["Event"])
|
||||||
|> validate_required([:id, :actor, :attributedTo, :type, :context, :context_id])
|
|> validate_required([:id, :actor, :attributedTo, :type, :context, :context_id])
|
||||||
|
|
|
@ -27,7 +27,7 @@ def cast_data(data) do
|
||||||
|> cast(data, __schema__(:fields))
|
|> cast(data, __schema__(:fields))
|
||||||
end
|
end
|
||||||
|
|
||||||
def validate_data(cng) do
|
defp validate_data(cng) do
|
||||||
cng
|
cng
|
||||||
|> validate_required([:id, :type, :actor, :to, :cc, :object])
|
|> validate_required([:id, :type, :actor, :to, :cc, :object])
|
||||||
|> validate_inclusion(:type, ["Follow"])
|
|> validate_inclusion(:type, ["Follow"])
|
||||||
|
|
|
@ -76,7 +76,7 @@ def fix_recipients(cng) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def validate_data(data_cng) do
|
defp validate_data(data_cng) do
|
||||||
data_cng
|
data_cng
|
||||||
|> validate_inclusion(:type, ["Like"])
|
|> validate_inclusion(:type, ["Like"])
|
||||||
|> validate_required([:id, :type, :object, :actor, :context, :to, :cc])
|
|> validate_required([:id, :type, :object, :actor, :context, :to, :cc])
|
||||||
|
|
|
@ -10,6 +10,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.QuestionValidator do
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
|
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
|
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidators.QuestionOptionsValidator
|
alias Pleroma.Web.ActivityPub.ObjectValidators.QuestionOptionsValidator
|
||||||
|
alias Pleroma.Web.ActivityPub.ObjectValidators.TagValidator
|
||||||
alias Pleroma.Web.ActivityPub.Transmogrifier
|
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||||
|
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
|
@ -24,8 +25,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.QuestionValidator do
|
||||||
field(:cc, ObjectValidators.Recipients, default: [])
|
field(:cc, ObjectValidators.Recipients, default: [])
|
||||||
field(:bto, ObjectValidators.Recipients, default: [])
|
field(:bto, ObjectValidators.Recipients, default: [])
|
||||||
field(:bcc, ObjectValidators.Recipients, default: [])
|
field(:bcc, ObjectValidators.Recipients, default: [])
|
||||||
# TODO: Write type
|
embeds_many(:tag, TagValidator)
|
||||||
field(:tag, {:array, :map}, default: [])
|
|
||||||
field(:type, :string)
|
field(:type, :string)
|
||||||
field(:content, :string)
|
field(:content, :string)
|
||||||
field(:context, :string)
|
field(:context, :string)
|
||||||
|
@ -83,8 +83,8 @@ defp fix_closed(data) do
|
||||||
|
|
||||||
defp fix(data) do
|
defp fix(data) do
|
||||||
data
|
data
|
||||||
|> CommonFixes.fix_defaults()
|
|> CommonFixes.fix_actor()
|
||||||
|> CommonFixes.fix_attribution()
|
|> CommonFixes.fix_object_defaults()
|
||||||
|> Transmogrifier.fix_emoji()
|
|> Transmogrifier.fix_emoji()
|
||||||
|> fix_closed()
|
|> fix_closed()
|
||||||
end
|
end
|
||||||
|
@ -93,13 +93,14 @@ def changeset(struct, data) do
|
||||||
data = fix(data)
|
data = fix(data)
|
||||||
|
|
||||||
struct
|
struct
|
||||||
|> cast(data, __schema__(:fields) -- [:anyOf, :oneOf, :attachment])
|
|> cast(data, __schema__(:fields) -- [:anyOf, :oneOf, :attachment, :tag])
|
||||||
|> cast_embed(:attachment)
|
|> cast_embed(:attachment)
|
||||||
|> cast_embed(:anyOf)
|
|> cast_embed(:anyOf)
|
||||||
|> cast_embed(:oneOf)
|
|> cast_embed(:oneOf)
|
||||||
|
|> cast_embed(:tag)
|
||||||
end
|
end
|
||||||
|
|
||||||
def validate_data(data_cng) do
|
defp validate_data(data_cng) do
|
||||||
data_cng
|
data_cng
|
||||||
|> validate_inclusion(:type, ["Question"])
|
|> validate_inclusion(:type, ["Question"])
|
||||||
|> validate_required([:id, :actor, :attributedTo, :type, :context, :context_id])
|
|> validate_required([:id, :actor, :attributedTo, :type, :context, :context_id])
|
||||||
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.ActivityPub.ObjectValidators.TagValidator do
|
||||||
|
use Ecto.Schema
|
||||||
|
|
||||||
|
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
||||||
|
|
||||||
|
import Ecto.Changeset
|
||||||
|
|
||||||
|
@primary_key false
|
||||||
|
embedded_schema do
|
||||||
|
# Common
|
||||||
|
field(:type, :string)
|
||||||
|
field(:name, :string)
|
||||||
|
|
||||||
|
# Mention, Hashtag
|
||||||
|
field(:href, ObjectValidators.Uri)
|
||||||
|
|
||||||
|
# Emoji
|
||||||
|
embeds_one :icon, IconObjectValidator, primary_key: false do
|
||||||
|
field(:type, :string)
|
||||||
|
field(:url, ObjectValidators.Uri)
|
||||||
|
end
|
||||||
|
|
||||||
|
field(:updated, ObjectValidators.DateTime)
|
||||||
|
field(:id, ObjectValidators.Uri)
|
||||||
|
end
|
||||||
|
|
||||||
|
def cast_and_validate(data) do
|
||||||
|
data
|
||||||
|
|> cast_data()
|
||||||
|
end
|
||||||
|
|
||||||
|
def cast_data(data) do
|
||||||
|
%__MODULE__{}
|
||||||
|
|> changeset(data)
|
||||||
|
end
|
||||||
|
|
||||||
|
def changeset(struct, %{"type" => "Mention"} = data) do
|
||||||
|
struct
|
||||||
|
|> cast(data, [:type, :name, :href])
|
||||||
|
|> validate_required([:type, :href])
|
||||||
|
end
|
||||||
|
|
||||||
|
def changeset(struct, %{"type" => "Hashtag", "name" => name} = data) do
|
||||||
|
name =
|
||||||
|
cond do
|
||||||
|
"#" <> name -> name
|
||||||
|
name -> name
|
||||||
|
end
|
||||||
|
|> String.downcase()
|
||||||
|
|
||||||
|
data = Map.put(data, "name", name)
|
||||||
|
|
||||||
|
struct
|
||||||
|
|> cast(data, [:type, :name, :href])
|
||||||
|
|> validate_required([:type, :name])
|
||||||
|
end
|
||||||
|
|
||||||
|
def changeset(struct, %{"type" => "Emoji"} = data) do
|
||||||
|
data = Map.put(data, "name", String.trim(data["name"], ":"))
|
||||||
|
|
||||||
|
struct
|
||||||
|
|> cast(data, [:type, :name, :updated, :id])
|
||||||
|
|> cast_embed(:icon, with: &icon_changeset/2)
|
||||||
|
|> validate_required([:type, :name, :icon])
|
||||||
|
end
|
||||||
|
|
||||||
|
def icon_changeset(struct, data) do
|
||||||
|
struct
|
||||||
|
|> cast(data, [:type, :url])
|
||||||
|
|> validate_inclusion(:type, ~w[Image])
|
||||||
|
|> validate_required([:type, :url])
|
||||||
|
end
|
||||||
|
end
|
|
@ -38,7 +38,7 @@ def changeset(struct, data) do
|
||||||
|> cast(data, __schema__(:fields))
|
|> cast(data, __schema__(:fields))
|
||||||
end
|
end
|
||||||
|
|
||||||
def validate_data(data_cng) do
|
defp validate_data(data_cng) do
|
||||||
data_cng
|
data_cng
|
||||||
|> validate_inclusion(:type, ["Undo"])
|
|> validate_inclusion(:type, ["Undo"])
|
||||||
|> validate_required([:id, :type, :object, :actor, :to, :cc])
|
|> validate_required([:id, :type, :object, :actor, :to, :cc])
|
||||||
|
|
|
@ -28,7 +28,7 @@ def cast_data(data) do
|
||||||
|> cast(data, __schema__(:fields))
|
|> cast(data, __schema__(:fields))
|
||||||
end
|
end
|
||||||
|
|
||||||
def validate_data(cng) do
|
defp validate_data(cng) do
|
||||||
cng
|
cng
|
||||||
|> validate_required([:id, :type, :actor, :to, :cc, :object])
|
|> validate_required([:id, :type, :actor, :to, :cc, :object])
|
||||||
|> validate_inclusion(:type, ["Update"])
|
|> validate_inclusion(:type, ["Update"])
|
||||||
|
|
|
@ -7,6 +7,7 @@ defmodule Pleroma.Web.ActivityPub.Pipeline do
|
||||||
alias Pleroma.Config
|
alias Pleroma.Config
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
alias Pleroma.Repo
|
alias Pleroma.Repo
|
||||||
|
alias Pleroma.Utils
|
||||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||||
alias Pleroma.Web.ActivityPub.MRF
|
alias Pleroma.Web.ActivityPub.MRF
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidator
|
alias Pleroma.Web.ActivityPub.ObjectValidator
|
||||||
|
@ -14,19 +15,19 @@ defmodule Pleroma.Web.ActivityPub.Pipeline do
|
||||||
alias Pleroma.Web.ActivityPub.Visibility
|
alias Pleroma.Web.ActivityPub.Visibility
|
||||||
alias Pleroma.Web.Federator
|
alias Pleroma.Web.Federator
|
||||||
|
|
||||||
@side_effects Config.get([:pipeline, :side_effects], SideEffects)
|
defp side_effects, do: Config.get([:pipeline, :side_effects], SideEffects)
|
||||||
@federator Config.get([:pipeline, :federator], Federator)
|
defp federator, do: Config.get([:pipeline, :federator], Federator)
|
||||||
@object_validator Config.get([:pipeline, :object_validator], ObjectValidator)
|
defp object_validator, do: Config.get([:pipeline, :object_validator], ObjectValidator)
|
||||||
@mrf Config.get([:pipeline, :mrf], MRF)
|
defp mrf, do: Config.get([:pipeline, :mrf], MRF)
|
||||||
@activity_pub Config.get([:pipeline, :activity_pub], ActivityPub)
|
defp activity_pub, do: Config.get([:pipeline, :activity_pub], ActivityPub)
|
||||||
@config Config.get([:pipeline, :config], Config)
|
defp config, do: Config.get([:pipeline, :config], Config)
|
||||||
|
|
||||||
@spec common_pipeline(map(), keyword()) ::
|
@spec common_pipeline(map(), keyword()) ::
|
||||||
{:ok, Activity.t() | Object.t(), keyword()} | {:error, any()}
|
{:ok, Activity.t() | Object.t(), keyword()} | {:error, any()}
|
||||||
def common_pipeline(object, meta) do
|
def common_pipeline(object, meta) do
|
||||||
case Repo.transaction(fn -> do_common_pipeline(object, meta) end) do
|
case Repo.transaction(fn -> do_common_pipeline(object, meta) end, Utils.query_timeout()) do
|
||||||
{:ok, {:ok, activity, meta}} ->
|
{:ok, {:ok, activity, meta}} ->
|
||||||
@side_effects.handle_after_transaction(meta)
|
side_effects().handle_after_transaction(meta)
|
||||||
{:ok, activity, meta}
|
{:ok, activity, meta}
|
||||||
|
|
||||||
{:ok, value} ->
|
{:ok, value} ->
|
||||||
|
@ -40,19 +41,17 @@ def common_pipeline(object, meta) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def do_common_pipeline(object, meta) do
|
def do_common_pipeline(%{__struct__: _}, _meta), do: {:error, :is_struct}
|
||||||
with {_, {:ok, validated_object, meta}} <-
|
|
||||||
{:validate_object, @object_validator.validate(object, meta)},
|
def do_common_pipeline(message, meta) do
|
||||||
{_, {:ok, mrfd_object, meta}} <-
|
with {_, {:ok, message, meta}} <- {:validate, object_validator().validate(message, meta)},
|
||||||
{:mrf_object, @mrf.pipeline_filter(validated_object, meta)},
|
{_, {:ok, message, meta}} <- {:mrf, mrf().pipeline_filter(message, meta)},
|
||||||
{_, {:ok, activity, meta}} <-
|
{_, {:ok, message, meta}} <- {:persist, activity_pub().persist(message, meta)},
|
||||||
{:persist_object, @activity_pub.persist(mrfd_object, meta)},
|
{_, {:ok, message, meta}} <- {:side_effects, side_effects().handle(message, meta)},
|
||||||
{_, {:ok, activity, meta}} <-
|
{_, {:ok, _}} <- {:federation, maybe_federate(message, meta)} do
|
||||||
{:execute_side_effects, @side_effects.handle(activity, meta)},
|
{:ok, message, meta}
|
||||||
{_, {:ok, _}} <- {:federation, maybe_federate(activity, meta)} do
|
|
||||||
{:ok, activity, meta}
|
|
||||||
else
|
else
|
||||||
{:mrf_object, {:reject, message, _}} -> {:reject, message}
|
{:mrf, {:reject, message, _}} -> {:reject, message}
|
||||||
e -> {:error, e}
|
e -> {:error, e}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -61,7 +60,7 @@ defp maybe_federate(%Object{}, _), do: {:ok, :not_federated}
|
||||||
|
|
||||||
defp maybe_federate(%Activity{} = activity, meta) do
|
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 and local and not Visibility.is_local_public?(activity) do
|
if !do_not_federate and local and not Visibility.is_local_public?(activity) do
|
||||||
activity =
|
activity =
|
||||||
|
@ -71,7 +70,7 @@ defp maybe_federate(%Activity{} = activity, meta) do
|
||||||
activity
|
activity
|
||||||
end
|
end
|
||||||
|
|
||||||
@federator.publish(activity)
|
federator().publish(activity)
|
||||||
{:ok, :federated}
|
{:ok, :federated}
|
||||||
else
|
else
|
||||||
{:ok, :not_federated}
|
{:ok, :not_federated}
|
||||||
|
|
|
@ -272,7 +272,7 @@ def gather_webfinger_links(%User{} = user) do
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
"rel" => "http://ostatus.org/schema/1.0/subscribe",
|
"rel" => "http://ostatus.org/schema/1.0/subscribe",
|
||||||
"template" => "#{Pleroma.Web.base_url()}/ostatus_subscribe?acct={uri}"
|
"template" => "#{Pleroma.Web.Endpoint.url()}/ostatus_subscribe?acct={uri}"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
|
@ -203,6 +203,19 @@ def handle(%{data: %{"type" => "Create"}} = activity, meta) do
|
||||||
Object.increase_replies_count(in_reply_to)
|
Object.increase_replies_count(in_reply_to)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
reply_depth = (meta[:depth] || 0) + 1
|
||||||
|
|
||||||
|
# FIXME: Force inReplyTo to replies
|
||||||
|
if Pleroma.Web.Federator.allowed_thread_distance?(reply_depth) and
|
||||||
|
object.data["replies"] != nil do
|
||||||
|
for reply_id <- object.data["replies"] do
|
||||||
|
Pleroma.Workers.RemoteFetcherWorker.enqueue("fetch_remote", %{
|
||||||
|
"id" => reply_id,
|
||||||
|
"depth" => reply_depth
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
ConcurrentLimiter.limit(Pleroma.Web.RichMedia.Helpers, fn ->
|
ConcurrentLimiter.limit(Pleroma.Web.RichMedia.Helpers, fn ->
|
||||||
Task.start(fn -> Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity) end)
|
Task.start(fn -> Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity) end)
|
||||||
end)
|
end)
|
||||||
|
@ -276,10 +289,10 @@ def handle(%{data: %{"type" => "Delete", "object" => deleted_object}} = object,
|
||||||
result =
|
result =
|
||||||
case deleted_object do
|
case deleted_object do
|
||||||
%Object{} ->
|
%Object{} ->
|
||||||
with {:ok, deleted_object, activity} <- Object.delete(deleted_object),
|
with {:ok, deleted_object, _activity} <- Object.delete(deleted_object),
|
||||||
{_, actor} when is_binary(actor) <- {:actor, deleted_object.data["actor"]},
|
{_, actor} when is_binary(actor) <- {:actor, deleted_object.data["actor"]},
|
||||||
%User{} = user <- User.get_cached_by_ap_id(actor) do
|
%User{} = user <- User.get_cached_by_ap_id(actor) do
|
||||||
User.remove_pinnned_activity(user, activity)
|
User.remove_pinned_object_id(user, deleted_object.data["id"])
|
||||||
|
|
||||||
{:ok, user} = ActivityPub.decrease_note_count_if_public(user, deleted_object)
|
{:ok, user} = ActivityPub.decrease_note_count_if_public(user, deleted_object)
|
||||||
|
|
||||||
|
@ -312,6 +325,63 @@ def handle(%{data: %{"type" => "Delete", "object" => deleted_object}} = object,
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Tasks this handles:
|
||||||
|
# - adds pin to user
|
||||||
|
# - removes expiration job for pinned activity, if was set for expiration
|
||||||
|
@impl true
|
||||||
|
def handle(%{data: %{"type" => "Add"} = data} = object, meta) do
|
||||||
|
with %User{} = user <- User.get_cached_by_ap_id(data["actor"]),
|
||||||
|
{:ok, _user} <- User.add_pinned_object_id(user, data["object"]) do
|
||||||
|
# if pinned activity was scheduled for deletion, we remove job
|
||||||
|
if expiration = Pleroma.Workers.PurgeExpiredActivity.get_expiration(meta[:activity_id]) do
|
||||||
|
Oban.cancel_job(expiration.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
{:ok, object, meta}
|
||||||
|
else
|
||||||
|
nil ->
|
||||||
|
{:error, :user_not_found}
|
||||||
|
|
||||||
|
{:error, changeset} ->
|
||||||
|
if changeset.errors[:pinned_objects] do
|
||||||
|
{:error, :pinned_statuses_limit_reached}
|
||||||
|
else
|
||||||
|
changeset.errors
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Tasks this handles:
|
||||||
|
# - removes pin from user
|
||||||
|
# - removes corresponding Add activity
|
||||||
|
# - if activity had expiration, recreates activity expiration job
|
||||||
|
@impl true
|
||||||
|
def handle(%{data: %{"type" => "Remove"} = data} = object, meta) do
|
||||||
|
with %User{} = user <- User.get_cached_by_ap_id(data["actor"]),
|
||||||
|
{:ok, _user} <- User.remove_pinned_object_id(user, data["object"]) do
|
||||||
|
data["object"]
|
||||||
|
|> Activity.add_by_params_query(user.ap_id, user.featured_address)
|
||||||
|
|> Repo.delete_all()
|
||||||
|
|
||||||
|
# if pinned activity was scheduled for deletion, we reschedule it for deletion
|
||||||
|
if meta[:expires_at] do
|
||||||
|
# MRF.ActivityExpirationPolicy used UTC timestamps for expires_at in original implementation
|
||||||
|
{:ok, expires_at} =
|
||||||
|
Pleroma.EctoType.ActivityPub.ObjectValidators.DateTime.cast(meta[:expires_at])
|
||||||
|
|
||||||
|
Pleroma.Workers.PurgeExpiredActivity.enqueue(%{
|
||||||
|
activity_id: meta[:activity_id],
|
||||||
|
expires_at: expires_at
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
{:ok, object, meta}
|
||||||
|
else
|
||||||
|
nil -> {:error, :user_not_found}
|
||||||
|
error -> error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# Nothing to do
|
# Nothing to do
|
||||||
@impl true
|
@impl true
|
||||||
def handle(object, meta) do
|
def handle(object, meta) do
|
||||||
|
@ -366,7 +436,7 @@ def handle_object_creation(%{"type" => "Answer"} = object_map, meta) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_object_creation(%{"type" => objtype} = object, meta)
|
def handle_object_creation(%{"type" => objtype} = object, meta)
|
||||||
when objtype in ~w[Audio Video Question Event Article] do
|
when objtype in ~w[Audio Video Question Event Article Note] do
|
||||||
with {:ok, object, meta} <- Pipeline.common_pipeline(object, meta) do
|
with {:ok, object, meta} <- Pipeline.common_pipeline(object, meta) do
|
||||||
{:ok, object, meta}
|
{:ok, object, meta}
|
||||||
end
|
end
|
||||||
|
|
|
@ -43,7 +43,6 @@ def fix_object(object, options \\ []) do
|
||||||
|> fix_content_map()
|
|> fix_content_map()
|
||||||
|> fix_addressing()
|
|> fix_addressing()
|
||||||
|> fix_summary()
|
|> fix_summary()
|
||||||
|> fix_type(options)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def fix_summary(%{"summary" => nil} = object) do
|
def fix_summary(%{"summary" => nil} = object) do
|
||||||
|
@ -72,17 +71,21 @@ def fix_addressing_list(map, field) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def fix_explicit_addressing(
|
# if directMessage flag is set to true, leave the addressing alone
|
||||||
%{"to" => to, "cc" => cc} = object,
|
def fix_explicit_addressing(%{"directMessage" => true} = object, _follower_collection),
|
||||||
explicit_mentions,
|
do: object
|
||||||
follower_collection
|
|
||||||
) do
|
|
||||||
explicit_to = Enum.filter(to, fn x -> x in explicit_mentions end)
|
|
||||||
|
|
||||||
|
def fix_explicit_addressing(%{"to" => to, "cc" => cc} = object, follower_collection) do
|
||||||
|
explicit_mentions =
|
||||||
|
Utils.determine_explicit_mentions(object) ++
|
||||||
|
[Pleroma.Constants.as_public(), follower_collection]
|
||||||
|
|
||||||
|
explicit_to = Enum.filter(to, fn x -> x in explicit_mentions end)
|
||||||
explicit_cc = Enum.filter(to, fn x -> x not in explicit_mentions end)
|
explicit_cc = Enum.filter(to, fn x -> x not in explicit_mentions end)
|
||||||
|
|
||||||
final_cc =
|
final_cc =
|
||||||
(cc ++ explicit_cc)
|
(cc ++ explicit_cc)
|
||||||
|
|> Enum.filter(& &1)
|
||||||
|> Enum.reject(fn x -> String.ends_with?(x, "/followers") and x != follower_collection end)
|
|> Enum.reject(fn x -> String.ends_with?(x, "/followers") and x != follower_collection end)
|
||||||
|> Enum.uniq()
|
|> Enum.uniq()
|
||||||
|
|
||||||
|
@ -91,29 +94,6 @@ def fix_explicit_addressing(
|
||||||
|> Map.put("cc", final_cc)
|
|> Map.put("cc", final_cc)
|
||||||
end
|
end
|
||||||
|
|
||||||
def fix_explicit_addressing(object, _explicit_mentions, _followers_collection), do: object
|
|
||||||
|
|
||||||
# if directMessage flag is set to true, leave the addressing alone
|
|
||||||
def fix_explicit_addressing(%{"directMessage" => true} = object), do: object
|
|
||||||
|
|
||||||
def fix_explicit_addressing(object) do
|
|
||||||
explicit_mentions = Utils.determine_explicit_mentions(object)
|
|
||||||
|
|
||||||
%User{follower_address: follower_collection} =
|
|
||||||
object
|
|
||||||
|> Containment.get_actor()
|
|
||||||
|> User.get_cached_by_ap_id()
|
|
||||||
|
|
||||||
explicit_mentions =
|
|
||||||
explicit_mentions ++
|
|
||||||
[
|
|
||||||
Pleroma.Constants.as_public(),
|
|
||||||
follower_collection
|
|
||||||
]
|
|
||||||
|
|
||||||
fix_explicit_addressing(object, explicit_mentions, follower_collection)
|
|
||||||
end
|
|
||||||
|
|
||||||
# if as:Public is addressed, then make sure the followers collection is also addressed
|
# if as:Public is addressed, then make sure the followers collection is also addressed
|
||||||
# so that the activities will be delivered to local users.
|
# so that the activities will be delivered to local users.
|
||||||
def fix_implicit_addressing(%{"to" => to, "cc" => cc} = object, followers_collection) do
|
def fix_implicit_addressing(%{"to" => to, "cc" => cc} = object, followers_collection) do
|
||||||
|
@ -137,19 +117,19 @@ def fix_implicit_addressing(%{"to" => to, "cc" => cc} = object, followers_collec
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def fix_implicit_addressing(object, _), do: object
|
|
||||||
|
|
||||||
def fix_addressing(object) do
|
def fix_addressing(object) do
|
||||||
{:ok, %User{} = user} = User.get_or_fetch_by_ap_id(object["actor"])
|
{:ok, %User{follower_address: follower_collection}} =
|
||||||
followers_collection = User.ap_followers(user)
|
object
|
||||||
|
|> Containment.get_actor()
|
||||||
|
|> User.get_or_fetch_by_ap_id()
|
||||||
|
|
||||||
object
|
object
|
||||||
|> fix_addressing_list("to")
|
|> fix_addressing_list("to")
|
||||||
|> fix_addressing_list("cc")
|
|> fix_addressing_list("cc")
|
||||||
|> fix_addressing_list("bto")
|
|> fix_addressing_list("bto")
|
||||||
|> fix_addressing_list("bcc")
|
|> fix_addressing_list("bcc")
|
||||||
|> fix_explicit_addressing()
|
|> fix_explicit_addressing(follower_collection)
|
||||||
|> fix_implicit_addressing(followers_collection)
|
|> fix_implicit_addressing(follower_collection)
|
||||||
end
|
end
|
||||||
|
|
||||||
def fix_actor(%{"attributedTo" => actor} = object) do
|
def fix_actor(%{"attributedTo" => actor} = object) do
|
||||||
|
@ -223,10 +203,17 @@ def fix_attachments(%{"attachment" => attachment} = object) when is_list(attachm
|
||||||
|
|
||||||
media_type =
|
media_type =
|
||||||
cond do
|
cond do
|
||||||
is_map(url) && MIME.valid?(url["mediaType"]) -> url["mediaType"]
|
is_map(url) && MIME.extensions(url["mediaType"]) != [] ->
|
||||||
MIME.valid?(data["mediaType"]) -> data["mediaType"]
|
url["mediaType"]
|
||||||
MIME.valid?(data["mimeType"]) -> data["mimeType"]
|
|
||||||
true -> nil
|
is_bitstring(data["mediaType"]) && MIME.extensions(data["mediaType"]) != [] ->
|
||||||
|
data["mediaType"]
|
||||||
|
|
||||||
|
is_bitstring(data["mimeType"]) && MIME.extensions(data["mimeType"]) != [] ->
|
||||||
|
data["mimeType"]
|
||||||
|
|
||||||
|
true ->
|
||||||
|
nil
|
||||||
end
|
end
|
||||||
|
|
||||||
href =
|
href =
|
||||||
|
@ -244,6 +231,8 @@ def fix_attachments(%{"attachment" => attachment} = object) when is_list(attachm
|
||||||
"type" => Map.get(url || %{}, "type", "Link")
|
"type" => Map.get(url || %{}, "type", "Link")
|
||||||
}
|
}
|
||||||
|> Maps.put_if_present("mediaType", media_type)
|
|> Maps.put_if_present("mediaType", media_type)
|
||||||
|
|> Maps.put_if_present("width", (url || %{})["width"] || data["width"])
|
||||||
|
|> Maps.put_if_present("height", (url || %{})["height"] || data["height"])
|
||||||
|
|
||||||
%{
|
%{
|
||||||
"url" => [attachment_url],
|
"url" => [attachment_url],
|
||||||
|
@ -340,19 +329,18 @@ def fix_content_map(%{"contentMap" => content_map} = object) do
|
||||||
|
|
||||||
def fix_content_map(object), do: object
|
def fix_content_map(object), do: object
|
||||||
|
|
||||||
def fix_type(object, options \\ [])
|
defp fix_type(%{"type" => "Note", "inReplyTo" => reply_id, "name" => _} = object, options)
|
||||||
|
when is_binary(reply_id) do
|
||||||
|
options = Keyword.put(options, :fetch, true)
|
||||||
|
|
||||||
def fix_type(%{"inReplyTo" => reply_id, "name" => _} = object, options)
|
with %Object{data: %{"type" => "Question"}} <- Object.normalize(reply_id, options) do
|
||||||
when is_binary(reply_id) do
|
|
||||||
with true <- Federator.allowed_thread_distance?(options[:depth]),
|
|
||||||
{:ok, %{data: %{"type" => "Question"} = _} = _} <- get_obj_helper(reply_id, options) do
|
|
||||||
Map.put(object, "type", "Answer")
|
Map.put(object, "type", "Answer")
|
||||||
else
|
else
|
||||||
_ -> object
|
_ -> object
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def fix_type(object, _), do: object
|
defp fix_type(object, _options), do: object
|
||||||
|
|
||||||
# Reduce the object list to find the reported user.
|
# Reduce the object list to find the reported user.
|
||||||
defp get_reported(objects) do
|
defp get_reported(objects) do
|
||||||
|
@ -423,10 +411,9 @@ def handle_incoming(%{"id" => id}, _options) when is_binary(id) and byte_size(id
|
||||||
# - tags
|
# - tags
|
||||||
# - emoji
|
# - emoji
|
||||||
def handle_incoming(
|
def handle_incoming(
|
||||||
%{"type" => "Create", "object" => %{"type" => objtype} = object} = data,
|
%{"type" => "Create", "object" => %{"type" => "Page"} = object} = data,
|
||||||
options
|
options
|
||||||
)
|
) do
|
||||||
when objtype in ~w{Note Page} do
|
|
||||||
actor = Containment.get_actor(data)
|
actor = Containment.get_actor(data)
|
||||||
|
|
||||||
with nil <- Activity.get_create_by_object_ap_id(object["id"]),
|
with nil <- Activity.get_create_by_object_ap_id(object["id"]),
|
||||||
|
@ -518,14 +505,23 @@ def handle_incoming(
|
||||||
|
|
||||||
def handle_incoming(
|
def handle_incoming(
|
||||||
%{"type" => "Create", "object" => %{"type" => objtype, "id" => obj_id}} = data,
|
%{"type" => "Create", "object" => %{"type" => objtype, "id" => obj_id}} = data,
|
||||||
_options
|
options
|
||||||
)
|
)
|
||||||
when objtype in ~w{Question Answer ChatMessage Audio Video Event Article} do
|
when objtype in ~w{Question Answer ChatMessage Audio Video Event Article Note} do
|
||||||
data = Map.put(data, "object", strip_internal_fields(data["object"]))
|
fetch_options = Keyword.put(options, :depth, (options[:depth] || 0) + 1)
|
||||||
|
|
||||||
|
object =
|
||||||
|
data["object"]
|
||||||
|
|> strip_internal_fields()
|
||||||
|
|> fix_type(fetch_options)
|
||||||
|
|> fix_in_reply_to(fetch_options)
|
||||||
|
|
||||||
|
data = Map.put(data, "object", object)
|
||||||
|
options = Keyword.put(options, :local, false)
|
||||||
|
|
||||||
with {:ok, %User{}} <- ObjectValidator.fetch_actor(data),
|
with {:ok, %User{}} <- ObjectValidator.fetch_actor(data),
|
||||||
nil <- Activity.get_create_by_object_ap_id(obj_id),
|
nil <- Activity.get_create_by_object_ap_id(obj_id),
|
||||||
{:ok, activity, _} <- Pipeline.common_pipeline(data, local: false) do
|
{:ok, activity, _} <- Pipeline.common_pipeline(data, options) do
|
||||||
{:ok, activity}
|
{:ok, activity}
|
||||||
else
|
else
|
||||||
%Activity{} = activity -> {:ok, activity}
|
%Activity{} = activity -> {:ok, activity}
|
||||||
|
@ -534,7 +530,7 @@ def handle_incoming(
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_incoming(%{"type" => type} = data, _options)
|
def handle_incoming(%{"type" => type} = data, _options)
|
||||||
when type in ~w{Like EmojiReact Announce} do
|
when type in ~w{Like EmojiReact Announce Add Remove} do
|
||||||
with :ok <- ObjectValidator.fetch_actor_and_object(data),
|
with :ok <- ObjectValidator.fetch_actor_and_object(data),
|
||||||
{:ok, activity, _meta} <-
|
{:ok, activity, _meta} <-
|
||||||
Pipeline.common_pipeline(data, local: false) do
|
Pipeline.common_pipeline(data, local: false) do
|
||||||
|
@ -564,7 +560,7 @@ def handle_incoming(
|
||||||
Pipeline.common_pipeline(data, local: false) do
|
Pipeline.common_pipeline(data, local: false) do
|
||||||
{:ok, activity}
|
{:ok, activity}
|
||||||
else
|
else
|
||||||
{:error, {:validate_object, _}} = e ->
|
{:error, {:validate, _}} = e ->
|
||||||
# Check if we have a create activity for this
|
# Check if we have a create activity for this
|
||||||
with {:ok, object_id} <- ObjectValidators.ObjectID.cast(data["object"]),
|
with {:ok, object_id} <- ObjectValidators.ObjectID.cast(data["object"]),
|
||||||
%Activity{data: %{"actor" => actor}} <-
|
%Activity{data: %{"actor" => actor}} <-
|
||||||
|
@ -949,7 +945,7 @@ def prepare_attachments(object) do
|
||||||
object
|
object
|
||||||
|> Map.get("attachment", [])
|
|> Map.get("attachment", [])
|
||||||
|> Enum.map(fn data ->
|
|> Enum.map(fn data ->
|
||||||
[%{"mediaType" => media_type, "href" => href} | _] = data["url"]
|
[%{"mediaType" => media_type, "href" => href} = url | _] = data["url"]
|
||||||
|
|
||||||
%{
|
%{
|
||||||
"url" => href,
|
"url" => href,
|
||||||
|
@ -957,6 +953,9 @@ def prepare_attachments(object) do
|
||||||
"name" => data["name"],
|
"name" => data["name"],
|
||||||
"type" => "Document"
|
"type" => "Document"
|
||||||
}
|
}
|
||||||
|
|> Maps.put_if_present("width", url["width"])
|
||||||
|
|> Maps.put_if_present("height", url["height"])
|
||||||
|
|> Maps.put_if_present("blurhash", data["blurhash"])
|
||||||
end)
|
end)
|
||||||
|
|
||||||
Map.put(object, "attachment", attachments)
|
Map.put(object, "attachment", attachments)
|
||||||
|
@ -1000,6 +999,7 @@ 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),
|
{: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
|
||||||
|
{:ok, _pid} = Task.start(fn -> ActivityPub.pinned_fetch_task(user) end)
|
||||||
TransmogrifierWorker.enqueue("user_upgrade", %{"user_id" => user.id})
|
TransmogrifierWorker.enqueue("user_upgrade", %{"user_id" => user.id})
|
||||||
{:ok, user}
|
{:ok, user}
|
||||||
else
|
else
|
||||||
|
|
|
@ -12,7 +12,6 @@ defmodule Pleroma.Web.ActivityPub.Utils do
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
alias Pleroma.Repo
|
alias Pleroma.Repo
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Web
|
|
||||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||||
alias Pleroma.Web.ActivityPub.Visibility
|
alias Pleroma.Web.ActivityPub.Visibility
|
||||||
alias Pleroma.Web.AdminAPI.AccountView
|
alias Pleroma.Web.AdminAPI.AccountView
|
||||||
|
@ -38,6 +37,8 @@ defmodule Pleroma.Web.ActivityPub.Utils do
|
||||||
@supported_report_states ~w(open closed resolved)
|
@supported_report_states ~w(open closed resolved)
|
||||||
@valid_visibilities ~w(public unlisted private direct)
|
@valid_visibilities ~w(public unlisted private direct)
|
||||||
|
|
||||||
|
def as_local_public, do: Endpoint.url() <> "/#Public"
|
||||||
|
|
||||||
# Some implementations send the actor URI as the actor field, others send the entire actor object,
|
# Some implementations send the actor URI as the actor field, others send the entire actor object,
|
||||||
# so figure out what the actor's URI is based on what we have.
|
# so figure out what the actor's URI is based on what we have.
|
||||||
def get_ap_id(%{"id" => id} = _), do: id
|
def get_ap_id(%{"id" => id} = _), do: id
|
||||||
|
@ -96,8 +97,11 @@ def maybe_splice_recipient(ap_id, params) do
|
||||||
!label_in_collection?(ap_id, params["cc"])
|
!label_in_collection?(ap_id, params["cc"])
|
||||||
|
|
||||||
if need_splice? do
|
if need_splice? do
|
||||||
cc_list = extract_list(params["cc"])
|
cc = [ap_id | extract_list(params["cc"])]
|
||||||
Map.put(params, "cc", [ap_id | cc_list])
|
|
||||||
|
params
|
||||||
|
|> Map.put("cc", cc)
|
||||||
|
|> Maps.safe_put_in(["object", "cc"], cc)
|
||||||
else
|
else
|
||||||
params
|
params
|
||||||
end
|
end
|
||||||
|
@ -107,7 +111,7 @@ def make_json_ld_header do
|
||||||
%{
|
%{
|
||||||
"@context" => [
|
"@context" => [
|
||||||
"https://www.w3.org/ns/activitystreams",
|
"https://www.w3.org/ns/activitystreams",
|
||||||
"#{Web.base_url()}/schemas/litepub-0.1.jsonld",
|
"#{Endpoint.url()}/schemas/litepub-0.1.jsonld",
|
||||||
%{
|
%{
|
||||||
"@language" => "und"
|
"@language" => "und"
|
||||||
}
|
}
|
||||||
|
@ -132,7 +136,7 @@ def generate_object_id do
|
||||||
end
|
end
|
||||||
|
|
||||||
def generate_id(type) do
|
def generate_id(type) do
|
||||||
"#{Web.base_url()}/#{type}/#{UUID.generate()}"
|
"#{Endpoint.url()}/#{type}/#{UUID.generate()}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_notified_from_object(%{"type" => type} = object) when type in @supported_object_types do
|
def get_notified_from_object(%{"type" => type} = object) when type in @supported_object_types do
|
||||||
|
|
|
@ -6,8 +6,10 @@ defmodule Pleroma.Web.ActivityPub.UserView do
|
||||||
use Pleroma.Web, :view
|
use Pleroma.Web, :view
|
||||||
|
|
||||||
alias Pleroma.Keys
|
alias Pleroma.Keys
|
||||||
|
alias Pleroma.Object
|
||||||
alias Pleroma.Repo
|
alias Pleroma.Repo
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
alias Pleroma.Web.ActivityPub.ObjectView
|
||||||
alias Pleroma.Web.ActivityPub.Transmogrifier
|
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||||
alias Pleroma.Web.ActivityPub.Utils
|
alias Pleroma.Web.ActivityPub.Utils
|
||||||
alias Pleroma.Web.Endpoint
|
alias Pleroma.Web.Endpoint
|
||||||
|
@ -97,6 +99,7 @@ def render("user.json", %{user: user}) do
|
||||||
"followers" => "#{user.ap_id}/followers",
|
"followers" => "#{user.ap_id}/followers",
|
||||||
"inbox" => "#{user.ap_id}/inbox",
|
"inbox" => "#{user.ap_id}/inbox",
|
||||||
"outbox" => "#{user.ap_id}/outbox",
|
"outbox" => "#{user.ap_id}/outbox",
|
||||||
|
"featured" => "#{user.ap_id}/collections/featured",
|
||||||
"preferredUsername" => user.nickname,
|
"preferredUsername" => user.nickname,
|
||||||
"name" => user.name,
|
"name" => user.name,
|
||||||
"summary" => user.bio,
|
"summary" => user.bio,
|
||||||
|
@ -245,6 +248,25 @@ def render("activity_collection_page.json", %{
|
||||||
|> Map.merge(pagination)
|
|> Map.merge(pagination)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def render("featured.json", %{
|
||||||
|
user: %{featured_address: featured_address, pinned_objects: pinned_objects}
|
||||||
|
}) do
|
||||||
|
objects =
|
||||||
|
pinned_objects
|
||||||
|
|> Enum.sort_by(fn {_, pinned_at} -> pinned_at end, &>=/2)
|
||||||
|
|> Enum.map(fn {id, _} ->
|
||||||
|
ObjectView.render("object.json", %{object: Object.get_cached_by_ap_id(id)})
|
||||||
|
end)
|
||||||
|
|
||||||
|
%{
|
||||||
|
"id" => featured_address,
|
||||||
|
"type" => "OrderedCollection",
|
||||||
|
"orderedItems" => objects,
|
||||||
|
"totalItems" => length(objects)
|
||||||
|
}
|
||||||
|
|> Map.merge(Utils.make_json_ld_header())
|
||||||
|
end
|
||||||
|
|
||||||
defp maybe_put_total_items(map, false, _total), do: map
|
defp maybe_put_total_items(map, false, _total), do: map
|
||||||
|
|
||||||
defp maybe_put_total_items(map, true, total) do
|
defp maybe_put_total_items(map, true, total) do
|
||||||
|
|
|
@ -20,14 +20,14 @@ def is_public?(%{"directMessage" => true}), do: false
|
||||||
|
|
||||||
def is_public?(data) do
|
def is_public?(data) do
|
||||||
Utils.label_in_message?(Pleroma.Constants.as_public(), data) or
|
Utils.label_in_message?(Pleroma.Constants.as_public(), data) or
|
||||||
Utils.label_in_message?(Pleroma.Constants.as_local_public(), data)
|
Utils.label_in_message?(Utils.as_local_public(), data)
|
||||||
end
|
end
|
||||||
|
|
||||||
def is_local_public?(%Object{data: data}), do: is_local_public?(data)
|
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?(%Activity{data: data}), do: is_local_public?(data)
|
||||||
|
|
||||||
def is_local_public?(data) do
|
def is_local_public?(data) do
|
||||||
Utils.label_in_message?(Pleroma.Constants.as_local_public(), data) and
|
Utils.label_in_message?(Utils.as_local_public(), data) and
|
||||||
not Utils.label_in_message?(Pleroma.Constants.as_public(), data)
|
not Utils.label_in_message?(Pleroma.Constants.as_public(), data)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -127,7 +127,7 @@ 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 ->
|
Utils.as_local_public() in to ->
|
||||||
"local"
|
"local"
|
||||||
|
|
||||||
# this should use the sql for the object's activity
|
# this should use the sql for the object's activity
|
||||||
|
|
|
@ -13,7 +13,6 @@ defmodule Pleroma.Web.AdminAPI.OAuthAppController do
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
plug(Pleroma.Web.ApiSpec.CastAndValidate)
|
plug(Pleroma.Web.ApiSpec.CastAndValidate)
|
||||||
plug(:put_view, Pleroma.Web.MastodonAPI.AppView)
|
|
||||||
|
|
||||||
plug(
|
plug(
|
||||||
OAuthScopesPlug,
|
OAuthScopesPlug,
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.AdminAPI.OAuthAppView do
|
||||||
|
use Pleroma.Web, :view
|
||||||
|
alias Pleroma.Web.MastodonAPI
|
||||||
|
|
||||||
|
def render(view, opts), do: MastodonAPI.AppView.render(view, opts)
|
||||||
|
end
|
|
@ -105,6 +105,7 @@ def show_operation do
|
||||||
responses: %{
|
responses: %{
|
||||||
200 => Operation.response("Media", "application/json", Attachment),
|
200 => Operation.response("Media", "application/json", Attachment),
|
||||||
401 => Operation.response("Media", "application/json", ApiError),
|
401 => Operation.response("Media", "application/json", ApiError),
|
||||||
|
403 => Operation.response("Media", "application/json", ApiError),
|
||||||
422 => Operation.response("Media", "application/json", ApiError)
|
422 => Operation.response("Media", "application/json", ApiError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -182,7 +182,34 @@ def pin_operation do
|
||||||
parameters: [id_param()],
|
parameters: [id_param()],
|
||||||
responses: %{
|
responses: %{
|
||||||
200 => status_response(),
|
200 => status_response(),
|
||||||
400 => Operation.response("Error", "application/json", ApiError)
|
400 =>
|
||||||
|
Operation.response("Bad Request", "application/json", %Schema{
|
||||||
|
allOf: [ApiError],
|
||||||
|
title: "Unprocessable Entity",
|
||||||
|
example: %{
|
||||||
|
"error" => "You have already pinned the maximum number of statuses"
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
404 =>
|
||||||
|
Operation.response("Not found", "application/json", %Schema{
|
||||||
|
allOf: [ApiError],
|
||||||
|
title: "Unprocessable Entity",
|
||||||
|
example: %{
|
||||||
|
"error" => "Record not found"
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
422 =>
|
||||||
|
Operation.response(
|
||||||
|
"Unprocessable Entity",
|
||||||
|
"application/json",
|
||||||
|
%Schema{
|
||||||
|
allOf: [ApiError],
|
||||||
|
title: "Unprocessable Entity",
|
||||||
|
example: %{
|
||||||
|
"error" => "Someone else's status cannot be pinned"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
@ -197,7 +224,22 @@ def unpin_operation do
|
||||||
parameters: [id_param()],
|
parameters: [id_param()],
|
||||||
responses: %{
|
responses: %{
|
||||||
200 => status_response(),
|
200 => status_response(),
|
||||||
400 => Operation.response("Error", "application/json", ApiError)
|
400 =>
|
||||||
|
Operation.response("Bad Request", "application/json", %Schema{
|
||||||
|
allOf: [ApiError],
|
||||||
|
title: "Unprocessable Entity",
|
||||||
|
example: %{
|
||||||
|
"error" => "You have already pinned the maximum number of statuses"
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
404 =>
|
||||||
|
Operation.response("Not found", "application/json", %Schema{
|
||||||
|
allOf: [ApiError],
|
||||||
|
title: "Unprocessable Entity",
|
||||||
|
example: %{
|
||||||
|
"error" => "Record not found"
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
|
@ -115,7 +115,8 @@ def hashtag_operation do
|
||||||
],
|
],
|
||||||
operationId: "TimelineController.hashtag",
|
operationId: "TimelineController.hashtag",
|
||||||
responses: %{
|
responses: %{
|
||||||
200 => Operation.response("Array of Status", "application/json", array_of_statuses())
|
200 => Operation.response("Array of Status", "application/json", array_of_statuses()),
|
||||||
|
401 => Operation.response("Error", "application/json", ApiError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue