forked from AkkomaGang/akkoma
Merge remote-tracking branch 'origin/develop' into moderators
This commit is contained in:
commit
3f8fc34593
4120 changed files with 5692 additions and 12722 deletions
|
@ -134,6 +134,7 @@ unit-testing-rum:
|
||||||
- mix test --preload-modules
|
- mix test --preload-modules
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
|
image: elixir:1.12
|
||||||
stage: test
|
stage: test
|
||||||
only:
|
only:
|
||||||
changes:
|
changes:
|
||||||
|
@ -243,7 +244,7 @@ stop_review_app:
|
||||||
|
|
||||||
amd64:
|
amd64:
|
||||||
stage: release
|
stage: release
|
||||||
image: elixir:1.10.3
|
image: elixir:1.10.4
|
||||||
only: &release-only
|
only: &release-only
|
||||||
- stable@pleroma/pleroma
|
- stable@pleroma/pleroma
|
||||||
- develop@pleroma/pleroma
|
- develop@pleroma/pleroma
|
||||||
|
@ -281,7 +282,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.4-alpine
|
||||||
cache: *release-cache
|
cache: *release-cache
|
||||||
variables: *release-variables
|
variables: *release-variables
|
||||||
before_script: &before-release-musl
|
before_script: &before-release-musl
|
||||||
|
@ -297,7 +298,7 @@ arm:
|
||||||
only: *release-only
|
only: *release-only
|
||||||
tags:
|
tags:
|
||||||
- arm32-specified
|
- arm32-specified
|
||||||
image: arm32v7/elixir:1.10.3
|
image: arm32v7/elixir:1.10.4
|
||||||
cache: *release-cache
|
cache: *release-cache
|
||||||
variables: *release-variables
|
variables: *release-variables
|
||||||
before_script: *before-release
|
before_script: *before-release
|
||||||
|
@ -309,7 +310,7 @@ arm-musl:
|
||||||
only: *release-only
|
only: *release-only
|
||||||
tags:
|
tags:
|
||||||
- arm32-specified
|
- arm32-specified
|
||||||
image: arm32v7/elixir:1.10.3-alpine
|
image: arm32v7/elixir:1.10.4-alpine
|
||||||
cache: *release-cache
|
cache: *release-cache
|
||||||
variables: *release-variables
|
variables: *release-variables
|
||||||
before_script: *before-release-musl
|
before_script: *before-release-musl
|
||||||
|
@ -321,7 +322,7 @@ arm64:
|
||||||
only: *release-only
|
only: *release-only
|
||||||
tags:
|
tags:
|
||||||
- arm
|
- arm
|
||||||
image: arm64v8/elixir:1.10.3
|
image: arm64v8/elixir:1.10.4
|
||||||
cache: *release-cache
|
cache: *release-cache
|
||||||
variables: *release-variables
|
variables: *release-variables
|
||||||
before_script: *before-release
|
before_script: *before-release
|
||||||
|
@ -333,7 +334,7 @@ arm64-musl:
|
||||||
only: *release-only
|
only: *release-only
|
||||||
tags:
|
tags:
|
||||||
- arm
|
- arm
|
||||||
image: arm64v8/elixir:1.10.3-alpine
|
image: arm64v8/elixir:1.10.4-alpine
|
||||||
cache: *release-cache
|
cache: *release-cache
|
||||||
variables: *release-variables
|
variables: *release-variables
|
||||||
before_script: *before-release-musl
|
before_script: *before-release-musl
|
||||||
|
@ -351,8 +352,8 @@ docker:
|
||||||
IMAGE_TAG_SLUG: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG
|
IMAGE_TAG_SLUG: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG
|
||||||
IMAGE_TAG_LATEST: $CI_REGISTRY_IMAGE:latest
|
IMAGE_TAG_LATEST: $CI_REGISTRY_IMAGE:latest
|
||||||
IMAGE_TAG_LATEST_STABLE: $CI_REGISTRY_IMAGE:latest-stable
|
IMAGE_TAG_LATEST_STABLE: $CI_REGISTRY_IMAGE:latest-stable
|
||||||
DOCKER_BUILDX_URL: https://github.com/docker/buildx/releases/download/v0.4.1/buildx-v0.4.1.linux-amd64
|
DOCKER_BUILDX_URL: https://github.com/docker/buildx/releases/download/v0.6.3/buildx-v0.6.3.linux-amd64
|
||||||
DOCKER_BUILDX_HASH: 71a7d01439aa8c165a25b59c44d3f016fddbd98b
|
DOCKER_BUILDX_HASH: 980e6b9655f971991fbbb5fd6cd19f1672386195
|
||||||
before_script: &before-docker
|
before_script: &before-docker
|
||||||
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
|
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
|
||||||
- docker pull $IMAGE_TAG_SLUG || true
|
- docker pull $IMAGE_TAG_SLUG || true
|
||||||
|
|
57
CHANGELOG.md
57
CHANGELOG.md
|
@ -6,9 +6,47 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
- MastoFE
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Allow users to remove their emails if instance does not need email to register
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- `activeMonth` and `activeHalfyear` fields in NodeInfo usage.users object
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Subscription(Bell) Notifications: Don't create from Pipeline Ingested replies
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
## 2.4.1 - 2021-08-29
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Make `mix pleroma.database set_text_search_config` run concurrently and indefinitely
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- AdminAPI: Missing configuration description for StealEmojiPolicy
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- MastodonAPI: Stream out Create activities
|
||||||
|
- MRF ObjectAgePolicy: Fix pattern matching on "published"
|
||||||
|
- TwitterAPI: Make `change_password` and `change_email` require params on body instead of query
|
||||||
|
- Subscription(Bell) Notifications: Don't create from Pipeline Ingested replies
|
||||||
|
- AdminAPI: Fix rendering reports containing a `nil` object
|
||||||
|
- Mastodon API: Activity Search fallbacks on status fetching after a DB Timeout/Error
|
||||||
|
- Mastodon API: Fix crash in Streamer related to reblogging
|
||||||
|
- AdminAPI: List available frontends when `static/frontends` folder is missing
|
||||||
|
- Make activity search properly use language-aware GIN indexes
|
||||||
|
- AdminAPI: Fix suggestions for MRF Policies
|
||||||
|
|
||||||
|
## 2.4.0 - 2021-08-08
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- **Breaking:** Configuration: `:chat, enabled` moved to `:shout, enabled` and `:instance, chat_limit` moved to `:shout, limit`
|
- **Breaking:** Configuration: `:chat, enabled` moved to `:shout, enabled` and `:instance, chat_limit` moved to `:shout, limit`
|
||||||
|
- **Breaking** Entries for simple_policy, transparency_exclusions and quarantined_instances now list both the instance and a reason.
|
||||||
- Support for Erlang/OTP 24
|
- Support for Erlang/OTP 24
|
||||||
- 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.
|
- HTTPSecurityPlug now sends a response header to opt out of Google's FLoC (Federated Learning of Cohorts) targeted advertising.
|
||||||
|
@ -22,8 +60,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- 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`.
|
- Return OAuth token `id` (primary key) in POST `/oauth/token`.
|
||||||
- AdminAPI: return `created_at` date with users.
|
- AdminAPI: return `created_at` date with users.
|
||||||
|
- AdminAPI: add DELETE `/api/v1/pleroma/admin/instances/:instance` to delete all content from a remote instance.
|
||||||
- `AnalyzeMetadata` upload filter for extracting image/video attachment dimensions and generating blurhashes for images. Blurhashes for videos are not generated at this time.
|
- `AnalyzeMetadata` upload filter for extracting image/video attachment dimensions and generating blurhashes for images. Blurhashes for videos are not generated at this time.
|
||||||
- Attachment dimensions and blurhashes are federated when available.
|
- Attachment dimensions and blurhashes are federated when available.
|
||||||
|
- Mastodon API: support `poll` notification.
|
||||||
- Pinned posts federation
|
- Pinned posts federation
|
||||||
- AdminAPI: allow moderators to manage reports, users, invites, and custom emojis
|
- AdminAPI: allow moderators to manage reports, users, invites, and custom emojis
|
||||||
|
|
||||||
|
@ -32,17 +72,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- Checking activated Upload Filters for required commands.
|
- Checking activated Upload Filters for required commands.
|
||||||
- Remote users can no longer reappear after being deleted.
|
- Remote users can no longer reappear after being deleted.
|
||||||
- Deactivated users may now be deleted.
|
- Deactivated users may now be deleted.
|
||||||
|
- Deleting an activity with a lot of likes/boosts no longer causes a database timeout.
|
||||||
- Mix task `pleroma.database prune_objects`
|
- Mix task `pleroma.database prune_objects`
|
||||||
- Fixed rendering of JSON errors on ActivityPub endpoints.
|
- Fixed rendering of JSON errors on ActivityPub endpoints.
|
||||||
- Linkify: Parsing crash with URLs ending in unbalanced closed paren, no path separator, and no query parameters
|
- Linkify: Parsing crash with URLs ending in unbalanced closed paren, no path separator, and no query parameters
|
||||||
|
|
||||||
### Removed
|
|
||||||
- **Breaking**: Remove deprecated `/api/qvitter/statuses/notifications/read` (replaced by `/api/v1/pleroma/notifications/read`)
|
|
||||||
|
|
||||||
## Unreleased (Patch)
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- Try to save exported ConfigDB settings (migrate_from_db) in the system temp directory if default location is not writable.
|
- Try to save exported ConfigDB settings (migrate_from_db) in the system temp directory if default location is not writable.
|
||||||
- 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
|
||||||
|
@ -51,7 +84,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- 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.
|
- 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.
|
- Fixed some Markdown issues, including trailing slash in links.
|
||||||
|
|
||||||
## [2.3.0] - 2020-03-01
|
### Removed
|
||||||
|
- **Breaking**: Remove deprecated `/api/qvitter/statuses/notifications/read` (replaced by `/api/v1/pleroma/notifications/read`)
|
||||||
|
|
||||||
|
## [2.3.0] - 2021-03-01
|
||||||
|
|
||||||
### Security
|
### Security
|
||||||
|
|
||||||
|
@ -101,6 +137,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- Support pagination of blocks and mutes.
|
- Support pagination of blocks and mutes.
|
||||||
- Account backup.
|
- Account backup.
|
||||||
- Configuration: Add `:instance, autofollowing_nicknames` setting to provide a way to make accounts automatically follow new users that register on the local Pleroma instance.
|
- Configuration: Add `:instance, autofollowing_nicknames` setting to provide a way to make accounts automatically follow new users that register on the local Pleroma instance.
|
||||||
|
- `[:activitypub, :blockers_visible]` config to control visibility of blockers.
|
||||||
- Ability to view remote timelines, with ex. `/api/v1/timelines/public?instance=lain.com` and streams `public:remote` and `public:remote:media`.
|
- Ability to view remote timelines, with ex. `/api/v1/timelines/public?instance=lain.com` and streams `public:remote` and `public:remote:media`.
|
||||||
- The site title is now injected as a `title` tag like preloads or metadata.
|
- The site title is now injected as a `title` tag like preloads or metadata.
|
||||||
- Password reset tokens now are not accepted after a certain age.
|
- Password reset tokens now are not accepted after a certain age.
|
||||||
|
@ -152,7 +189,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- Mastodon API: Support for expires_in/expires_at in the Filters.
|
- Mastodon API: Support for expires_in/expires_at in the Filters.
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## [2.2.2] - 2020-01-18
|
## [2.2.2] - 2021-01-18
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ RUN apk add git gcc g++ musl-dev make cmake file-dev &&\
|
||||||
mkdir release &&\
|
mkdir release &&\
|
||||||
mix release --path release
|
mix release --path release
|
||||||
|
|
||||||
FROM alpine:3.11
|
FROM alpine:3.14
|
||||||
|
|
||||||
ARG BUILD_DATE
|
ARG BUILD_DATE
|
||||||
ARG VCS_REF
|
ARG VCS_REF
|
||||||
|
@ -31,8 +31,7 @@ LABEL maintainer="ops@pleroma.social" \
|
||||||
ARG HOME=/opt/pleroma
|
ARG HOME=/opt/pleroma
|
||||||
ARG DATA=/var/lib/pleroma
|
ARG DATA=/var/lib/pleroma
|
||||||
|
|
||||||
RUN echo "http://nl.alpinelinux.org/alpine/latest-stable/community" >> /etc/apk/repositories &&\
|
RUN apk update &&\
|
||||||
apk update &&\
|
|
||||||
apk add exiftool ffmpeg 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 &&\
|
||||||
|
|
|
@ -30,7 +30,7 @@ If your platform is not supported, or you just want to be able to edit the sourc
|
||||||
- [OpenBSD (fi)](https://docs-develop.pleroma.social/backend/installation/openbsd_fi/)
|
- [OpenBSD (fi)](https://docs-develop.pleroma.social/backend/installation/openbsd_fi/)
|
||||||
|
|
||||||
### OS/Distro packages
|
### OS/Distro packages
|
||||||
Currently Pleroma is not packaged by any OS/Distros, but if you want to package it for one, we can guide you through the process on our [community channels](#community-channels). If you want to change default options in your Pleroma package, please **discuss it with us first**.
|
Currently Pleroma is packaged for [YunoHost](https://yunohost.org). If you want to package Pleroma for any OS/Distros, we can guide you through the process on our [community channels](#community-channels). If you want to change default options in your Pleroma package, please **discuss it with us first**.
|
||||||
|
|
||||||
### Docker
|
### Docker
|
||||||
While we don’t provide docker files, other people have written very good ones. Take a look at <https://github.com/angristan/docker-pleroma> or <https://glitch.sh/sn0w/pleroma-docker>.
|
While we don’t provide docker files, other people have written very good ones. Take a look at <https://github.com/angristan/docker-pleroma> or <https://glitch.sh/sn0w/pleroma-docker>.
|
||||||
|
|
|
@ -394,7 +394,7 @@ defp get_actor(group, users), do: Enum.random(users[group])
|
||||||
|
|
||||||
defp other_data(actor, content) do
|
defp other_data(actor, content) do
|
||||||
%{host: host} = URI.parse(actor.ap_id)
|
%{host: host} = URI.parse(actor.ap_id)
|
||||||
datetime = DateTime.utc_now()
|
datetime = DateTime.utc_now() |> to_string()
|
||||||
context_id = "https://#{host}/contexts/#{UUID.generate()}"
|
context_id = "https://#{host}/contexts/#{UUID.generate()}"
|
||||||
activity_id = "https://#{host}/activities/#{UUID.generate()}"
|
activity_id = "https://#{host}/activities/#{UUID.generate()}"
|
||||||
object_id = "https://#{host}/objects/#{UUID.generate()}"
|
object_id = "https://#{host}/objects/#{UUID.generate()}"
|
||||||
|
|
|
@ -99,15 +99,16 @@ defp hashtag_fetching(params, user, local_only) do
|
||||||
|> Enum.map(&String.downcase(&1))
|
|> Enum.map(&String.downcase(&1))
|
||||||
|
|
||||||
_activities =
|
_activities =
|
||||||
params
|
%{
|
||||||
|> Map.put(:type, "Create")
|
type: "Create",
|
||||||
|> Map.put(:local_only, local_only)
|
local_only: local_only,
|
||||||
|> Map.put(:blocking_user, user)
|
blocking_user: user,
|
||||||
|> Map.put(:muting_user, user)
|
muting_user: user,
|
||||||
|> Map.put(:user, user)
|
user: user,
|
||||||
|> Map.put(:tag, tags)
|
tag: tags,
|
||||||
|> Map.put(:tag_all, tag_all)
|
tag_all: tag_all,
|
||||||
|> Map.put(:tag_reject, tag_reject)
|
tag_reject: tag_reject,
|
||||||
|
}
|
||||||
|> Pleroma.Web.ActivityPub.ActivityPub.fetch_public_activities()
|
|> Pleroma.Web.ActivityPub.ActivityPub.fetch_public_activities()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -17,14 +17,14 @@ def run(_args) do
|
||||||
# Let the user make 100 posts
|
# Let the user make 100 posts
|
||||||
|
|
||||||
1..100
|
1..100
|
||||||
|> Enum.each(fn i -> CommonAPI.post(user, %{"status" => to_string(i)}) end)
|
|> Enum.each(fn i -> CommonAPI.post(user, %{status: to_string(i)}) end)
|
||||||
|
|
||||||
# Let 10 random users post
|
# Let 10 random users post
|
||||||
posts =
|
posts =
|
||||||
users
|
users
|
||||||
|> Enum.take_random(10)
|
|> Enum.take_random(10)
|
||||||
|> Enum.map(fn {:ok, random_user} ->
|
|> Enum.map(fn {:ok, random_user} ->
|
||||||
{:ok, activity} = CommonAPI.post(random_user, %{"status" => "."})
|
{:ok, activity} = CommonAPI.post(random_user, %{status: "."})
|
||||||
activity
|
activity
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
@ -42,7 +42,7 @@ def run(_args) do
|
||||||
|> Conn.assign(:user, reading_user)
|
|> Conn.assign(:user, reading_user)
|
||||||
|> Conn.assign(:skip_link_headers, true)
|
|> Conn.assign(:skip_link_headers, true)
|
||||||
|
|
||||||
Pleroma.Web.MastodonAPI.AccountController.statuses(conn, %{"id" => user.id})
|
Pleroma.Web.MastodonAPI.AccountController.statuses(conn, %{id: user.id})
|
||||||
end
|
end
|
||||||
},
|
},
|
||||||
inputs: %{"user" => user, "no user" => nil},
|
inputs: %{"user" => user, "no user" => nil},
|
||||||
|
@ -50,7 +50,7 @@ def run(_args) do
|
||||||
)
|
)
|
||||||
|
|
||||||
users
|
users
|
||||||
|> Enum.each(fn {:ok, follower, user} -> Pleroma.User.follow(follower, user) end)
|
|> Enum.each(fn {:ok, follower} -> Pleroma.User.follow(follower, user) end)
|
||||||
|
|
||||||
Benchee.run(
|
Benchee.run(
|
||||||
%{
|
%{
|
||||||
|
@ -60,7 +60,7 @@ def run(_args) do
|
||||||
|> Conn.assign(:user, reading_user)
|
|> Conn.assign(:user, reading_user)
|
||||||
|> Conn.assign(:skip_link_headers, true)
|
|> Conn.assign(:skip_link_headers, true)
|
||||||
|
|
||||||
Pleroma.Web.MastodonAPI.AccountController.statuses(conn, %{"id" => user.id})
|
Pleroma.Web.MastodonAPI.AccountController.statuses(conn, %{id: user.id})
|
||||||
end
|
end
|
||||||
},
|
},
|
||||||
inputs: %{"user" => user, "no user" => nil},
|
inputs: %{"user" => user, "no user" => nil},
|
||||||
|
|
|
@ -4,8 +4,7 @@
|
||||||
# you can enable the server option below.
|
# you can enable the server option below.
|
||||||
config :pleroma, Pleroma.Web.Endpoint,
|
config :pleroma, Pleroma.Web.Endpoint,
|
||||||
http: [port: 4001],
|
http: [port: 4001],
|
||||||
url: [port: 4001],
|
url: [port: 4001]
|
||||||
server: true
|
|
||||||
|
|
||||||
# Disable captha for tests
|
# Disable captha for tests
|
||||||
config :pleroma, Pleroma.Captcha,
|
config :pleroma, Pleroma.Captcha,
|
||||||
|
@ -44,7 +43,7 @@
|
||||||
pool_size: 10
|
pool_size: 10
|
||||||
|
|
||||||
# Reduce hash rounds for testing
|
# Reduce hash rounds for testing
|
||||||
config :pbkdf2_elixir, rounds: 1
|
config :pleroma, :password, iterations: 1
|
||||||
|
|
||||||
config :tesla, adapter: Tesla.Mock
|
config :tesla, adapter: Tesla.Mock
|
||||||
|
|
||||||
|
|
|
@ -139,6 +139,7 @@
|
||||||
],
|
],
|
||||||
protocol: "https",
|
protocol: "https",
|
||||||
secret_key_base: "aK4Abxf29xU9TTDKre9coZPUgevcVCFQJe/5xP/7Lt4BEif6idBIbjupVbOrbKxl",
|
secret_key_base: "aK4Abxf29xU9TTDKre9coZPUgevcVCFQJe/5xP/7Lt4BEif6idBIbjupVbOrbKxl",
|
||||||
|
live_view: [signing_salt: "U5ELgdEwTD3n1+D5s0rY0AMg8/y1STxZ3Zvsl3bWh+oBcGrYdil0rXqPMRd3Glcq"],
|
||||||
signing_salt: "CqaoopA2",
|
signing_salt: "CqaoopA2",
|
||||||
render_errors: [view: Pleroma.Web.ErrorView, accepts: ~w(json)],
|
render_errors: [view: Pleroma.Web.ErrorView, accepts: ~w(json)],
|
||||||
pubsub_server: Pleroma.PubSub,
|
pubsub_server: Pleroma.PubSub,
|
||||||
|
@ -321,9 +322,6 @@
|
||||||
subjectLineBehavior: "email",
|
subjectLineBehavior: "email",
|
||||||
theme: "pleroma-dark",
|
theme: "pleroma-dark",
|
||||||
webPushNotifications: false
|
webPushNotifications: false
|
||||||
},
|
|
||||||
masto_fe: %{
|
|
||||||
showInstanceSpecificPanel: true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
config :pleroma, :assets,
|
config :pleroma, :assets,
|
||||||
|
@ -352,6 +350,7 @@
|
||||||
config :pleroma, :activitypub,
|
config :pleroma, :activitypub,
|
||||||
unfollow_blocked: true,
|
unfollow_blocked: true,
|
||||||
outgoing_blocks: true,
|
outgoing_blocks: true,
|
||||||
|
blockers_visible: true,
|
||||||
follow_handshake_timeout: 500,
|
follow_handshake_timeout: 500,
|
||||||
note_replies_output_limit: 5,
|
note_replies_output_limit: 5,
|
||||||
sign_object_fetches: true,
|
sign_object_fetches: true,
|
||||||
|
@ -560,6 +559,7 @@
|
||||||
mailer: 10,
|
mailer: 10,
|
||||||
transmogrifier: 20,
|
transmogrifier: 20,
|
||||||
scheduled_activities: 10,
|
scheduled_activities: 10,
|
||||||
|
poll_notifications: 10,
|
||||||
background: 5,
|
background: 5,
|
||||||
remote_fetcher: 2,
|
remote_fetcher: 2,
|
||||||
attachments_cleanup: 1,
|
attachments_cleanup: 1,
|
||||||
|
|
|
@ -687,12 +687,14 @@
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
key: :quarantined_instances,
|
key: :quarantined_instances,
|
||||||
type: {:list, :string},
|
type: {:list, :tuple},
|
||||||
|
key_placeholder: "instance",
|
||||||
|
value_placeholder: "reason",
|
||||||
description:
|
description:
|
||||||
"List of ActivityPub instances where private (DMs, followers-only) activities will not be sent",
|
"List of ActivityPub instances where private (DMs, followers-only) activities will not be sent and the reason for doing so",
|
||||||
suggestions: [
|
suggestions: [
|
||||||
"quarantined.com",
|
{"quarantined.com", "Reason"},
|
||||||
"*.quarantined.com"
|
{"*.quarantined.com", "Reason"}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
|
@ -1162,7 +1164,7 @@
|
||||||
type: :group,
|
type: :group,
|
||||||
description:
|
description:
|
||||||
"This form can be used to configure a keyword list that keeps the configuration data for any " <>
|
"This form can be used to configure a keyword list that keeps the configuration data for any " <>
|
||||||
"kind of frontend. By default, settings for pleroma_fe and masto_fe are configured. If you want to " <>
|
"kind of frontend. By default, settings for pleroma_fe are configured. If you want to " <>
|
||||||
"add your own configuration your settings all fields must be complete.",
|
"add your own configuration your settings all fields must be complete.",
|
||||||
children: [
|
children: [
|
||||||
%{
|
%{
|
||||||
|
@ -1362,25 +1364,6 @@
|
||||||
suggestions: ["pleroma-dark"]
|
suggestions: ["pleroma-dark"]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
|
||||||
%{
|
|
||||||
key: :masto_fe,
|
|
||||||
label: "Masto FE",
|
|
||||||
type: :map,
|
|
||||||
description: "Settings for Masto FE",
|
|
||||||
suggestions: [
|
|
||||||
%{
|
|
||||||
showInstanceSpecificPanel: true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
children: [
|
|
||||||
%{
|
|
||||||
key: :showInstanceSpecificPanel,
|
|
||||||
label: "Show instance specific panel",
|
|
||||||
type: :boolean,
|
|
||||||
description: "Whenether to show the instance's specific panel"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -1687,6 +1670,11 @@
|
||||||
type: :boolean,
|
type: :boolean,
|
||||||
description: "Whether to federate blocks to other instances"
|
description: "Whether to federate blocks to other instances"
|
||||||
},
|
},
|
||||||
|
%{
|
||||||
|
key: :blockers_visible,
|
||||||
|
type: :boolean,
|
||||||
|
description: "Whether a user can see someone who has blocked them"
|
||||||
|
},
|
||||||
%{
|
%{
|
||||||
key: :sign_object_fetches,
|
key: :sign_object_fetches,
|
||||||
type: :boolean,
|
type: :boolean,
|
||||||
|
|
|
@ -62,6 +62,7 @@
|
||||||
import_config "dev.secret.exs"
|
import_config "dev.secret.exs"
|
||||||
else
|
else
|
||||||
IO.puts(
|
IO.puts(
|
||||||
|
:stderr,
|
||||||
"!!! RUNNING IN LOCALHOST DEV MODE! !!!\nFEDERATION WON'T WORK UNTIL YOU CONFIGURE A dev.secret.exs"
|
"!!! RUNNING IN LOCALHOST DEV MODE! !!!\nFEDERATION WON'T WORK UNTIL YOU CONFIGURE A dev.secret.exs"
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
|
@ -39,7 +39,7 @@ To add configuration to your config file, you can copy it from the base config.
|
||||||
* `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`: Permits remote instances to subscribe to all public posts of your instance. This may increase the visibility of your 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`: 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).
|
||||||
* `extended_nickname_format`: Set to `true` to use extended local nicknames format (allows underscores/dashes). This will break federation with
|
* `extended_nickname_format`: Set to `true` to use extended local nicknames format (allows underscores/dashes). This will break federation with
|
||||||
older software for theses nicknames.
|
older software for theses nicknames.
|
||||||
|
@ -135,15 +135,16 @@ To add configuration to your config file, you can copy it from the base config.
|
||||||
Configuring MRF policies is not enough for them to take effect. You have to enable them by specifying their module in `policies` under [:mrf](#mrf) section.
|
Configuring MRF policies is not enough for them to take effect. You have to enable them by specifying their module in `policies` under [:mrf](#mrf) section.
|
||||||
|
|
||||||
#### :mrf_simple
|
#### :mrf_simple
|
||||||
* `media_removal`: List of instances to remove media from.
|
* `media_removal`: List of instances to strip media attachments from and the reason for doing so.
|
||||||
* `media_nsfw`: List of instances to put media as NSFW(sensitive) from.
|
* `media_nsfw`: List of instances to tag all media as NSFW (sensitive) from and the reason for doing so.
|
||||||
* `federated_timeline_removal`: List of instances to remove from Federated (aka The Whole Known Network) Timeline.
|
* `federated_timeline_removal`: List of instances to remove from the Federated Timeline (aka The Whole Known Network) and the reason for doing so.
|
||||||
* `reject`: List of instances to reject any activities from.
|
* `reject`: List of instances to reject activities (except deletes) from and the reason for doing so.
|
||||||
* `accept`: List of instances to accept any activities from.
|
* `accept`: List of instances to only accept activities (except deletes) from and the reason for doing so.
|
||||||
* `followers_only`: List of instances to decrease post visibility to only the followers, including for DM mentions.
|
* `followers_only`: Force posts from the given instances to be visible by followers only and the reason for doing so.
|
||||||
* `report_removal`: List of instances to reject reports from.
|
* `report_removal`: List of instances to reject reports from and the reason for doing so.
|
||||||
* `avatar_removal`: List of instances to strip avatars from.
|
* `avatar_removal`: List of instances to strip avatars from and the reason for doing so.
|
||||||
* `banner_removal`: List of instances to strip banners from.
|
* `banner_removal`: List of instances to strip banners from and the reason for doing so.
|
||||||
|
* `reject_deletes`: List of instances to reject deletions from and the reason for doing so.
|
||||||
|
|
||||||
#### :mrf_subchain
|
#### :mrf_subchain
|
||||||
This policy processes messages through an alternate pipeline when a given message matches certain criteria.
|
This policy processes messages through an alternate pipeline when a given message matches certain criteria.
|
||||||
|
@ -229,6 +230,7 @@ Notes:
|
||||||
### :activitypub
|
### :activitypub
|
||||||
* `unfollow_blocked`: Whether blocks result in people getting unfollowed
|
* `unfollow_blocked`: Whether blocks result in people getting unfollowed
|
||||||
* `outgoing_blocks`: Whether to federate blocks to other instances
|
* `outgoing_blocks`: Whether to federate blocks to other instances
|
||||||
|
* `blockers_visible`: Whether a user can see the posts of users who blocked them
|
||||||
* `deny_follow_blocked`: Whether to disallow following an account that has blocked the user in question
|
* `deny_follow_blocked`: Whether to disallow following an account that has blocked the user in question
|
||||||
* `sign_object_fetches`: Sign object fetches with HTTP signatures
|
* `sign_object_fetches`: Sign object fetches with HTTP signatures
|
||||||
* `authorized_fetch_mode`: Require HTTP signatures for AP fetches
|
* `authorized_fetch_mode`: Require HTTP signatures for AP fetches
|
||||||
|
@ -246,7 +248,7 @@ Notes:
|
||||||
|
|
||||||
### :frontend_configurations
|
### :frontend_configurations
|
||||||
|
|
||||||
This can be used to configure a keyword list that keeps the configuration data for any kind of frontend. By default, settings for `pleroma_fe` and `masto_fe` are configured. You can find the documentation for `pleroma_fe` configuration into [Pleroma-FE configuration and customization for instance administrators](/frontend/CONFIGURATION/#options).
|
This can be used to configure a keyword list that keeps the configuration data for any kind of frontend. By default, settings for `pleroma_fe` are configured. You can find the documentation for `pleroma_fe` configuration into [Pleroma-FE configuration and customization for instance administrators](/frontend/CONFIGURATION/#options).
|
||||||
|
|
||||||
Frontends can access these settings at `/api/v1/pleroma/frontend_configurations`
|
Frontends can access these settings at `/api/v1/pleroma/frontend_configurations`
|
||||||
|
|
||||||
|
@ -257,9 +259,6 @@ config :pleroma, :frontend_configurations,
|
||||||
pleroma_fe: %{
|
pleroma_fe: %{
|
||||||
theme: "pleroma-dark",
|
theme: "pleroma-dark",
|
||||||
# ... see /priv/static/static/config.json for the available keys.
|
# ... see /priv/static/static/config.json for the available keys.
|
||||||
},
|
|
||||||
masto_fe: %{
|
|
||||||
showInstanceSpecificPanel: true
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ Pleroma's full text search feature is powered by PostgreSQL's native [text searc
|
||||||
|
|
||||||
## Setup and test the new search config
|
## Setup and test the new search config
|
||||||
|
|
||||||
In most cases, you would need an extension installed to support parsing CJK text. Here are a few extension you may choose from, or you are more than welcome to share additional ones you found working for you with the rest of Pleroma community.
|
In most cases, you would need an extension installed to support parsing CJK text. Here are a few extensions you may choose from, or you are more than welcome to share additional ones you found working for you with the rest of Pleroma community.
|
||||||
|
|
||||||
* [a generic n-gram parser](https://github.com/huangjimmy/pg_cjk_parser) supports Simplifed/Traditional Chinese, Japanese, and Korean
|
* [a generic n-gram parser](https://github.com/huangjimmy/pg_cjk_parser) supports Simplifed/Traditional Chinese, Japanese, and Korean
|
||||||
* [a Korean parser](https://github.com/i0seph/textsearch_ko) based on mecab
|
* [a Korean parser](https://github.com/i0seph/textsearch_ko) based on mecab
|
||||||
|
@ -34,7 +34,7 @@ Check output of the query, and see if it matches your expectation.
|
||||||
mix pleroma.database set_text_search_config YOUR.CONFIG
|
mix pleroma.database set_text_search_config YOUR.CONFIG
|
||||||
```
|
```
|
||||||
|
|
||||||
Note: index update may take a while.
|
Note: index update may take a while, and it can be done while the instance is up and running, so you may restart db connection as soon as you see `Recreate index` in task output.
|
||||||
|
|
||||||
## Restart database connection
|
## Restart database connection
|
||||||
Since some changes above will only apply with a new database connection, you will have to restart either Pleroma or PostgreSQL process, or use `pg_terminate_backend` SQL command without restarting either.
|
Since some changes above will only apply with a new database connection, you will have to restart either Pleroma or PostgreSQL process, or use `pg_terminate_backend` SQL command without restarting either.
|
||||||
|
|
|
@ -55,18 +55,18 @@ Servers should be configured as lists.
|
||||||
|
|
||||||
### Example
|
### Example
|
||||||
|
|
||||||
This example will enable `SimplePolicy`, block media from `illegalporn.biz`, mark media as NSFW from `porn.biz` and `porn.business`, reject messages from `spam.com`, remove messages from `spam.university` from the federated timeline and block reports (flags) from `whiny.whiner`:
|
This example will enable `SimplePolicy`, block media from `illegalporn.biz`, mark media as NSFW from `porn.biz` and `porn.business`, reject messages from `spam.com`, remove messages from `spam.university` from the federated timeline and block reports (flags) from `whiny.whiner`. We also give a reason why the moderation was done:
|
||||||
|
|
||||||
```elixir
|
```elixir
|
||||||
config :pleroma, :mrf,
|
config :pleroma, :mrf,
|
||||||
policies: [Pleroma.Web.ActivityPub.MRF.SimplePolicy]
|
policies: [Pleroma.Web.ActivityPub.MRF.SimplePolicy]
|
||||||
|
|
||||||
config :pleroma, :mrf_simple,
|
config :pleroma, :mrf_simple,
|
||||||
media_removal: ["illegalporn.biz"],
|
media_removal: [{"illegalporn.biz", "Media can contain illegal contant"}],
|
||||||
media_nsfw: ["porn.biz", "porn.business"],
|
media_nsfw: [{"porn.biz", "unmarked nsfw media"}, {"porn.business", "A lot of unmarked nsfw media"}],
|
||||||
reject: ["spam.com"],
|
reject: [{"spam.com", "They keep spamming our users"}],
|
||||||
federated_timeline_removal: ["spam.university"],
|
federated_timeline_removal: [{"spam.university", "Annoying low-quality posts who otherwise fill up TWKN"}],
|
||||||
report_removal: ["whiny.whiner"]
|
report_removal: [{"whiny.whiner", "Keep spamming us with irrelevant reports"}]
|
||||||
```
|
```
|
||||||
|
|
||||||
### Use with Care
|
### Use with Care
|
||||||
|
|
|
@ -261,6 +261,46 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## `PATCH /api/v1/pleroma/admin/users/suggest`
|
||||||
|
|
||||||
|
### Suggest a user
|
||||||
|
|
||||||
|
Adds the user(s) to follower recommendations.
|
||||||
|
|
||||||
|
- Params:
|
||||||
|
- `nicknames`: nicknames array
|
||||||
|
- Response:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
users: [
|
||||||
|
{
|
||||||
|
// user object
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## `PATCH /api/v1/pleroma/admin/users/unsuggest`
|
||||||
|
|
||||||
|
### Unsuggest a user
|
||||||
|
|
||||||
|
Removes the user(s) from follower recommendations.
|
||||||
|
|
||||||
|
- Params:
|
||||||
|
- `nicknames`: nicknames array
|
||||||
|
- Response:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
users: [
|
||||||
|
{
|
||||||
|
// user object
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## `GET /api/v1/pleroma/admin/users/:nickname_or_id`
|
## `GET /api/v1/pleroma/admin/users/:nickname_or_id`
|
||||||
|
|
||||||
### Retrive the details of a user
|
### Retrive the details of a user
|
||||||
|
@ -319,6 +359,22 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## `DELETE /api/v1/pleroma/admin/instances/:instance`
|
||||||
|
|
||||||
|
### Delete all users and activities from a remote instance
|
||||||
|
|
||||||
|
Note: this will trigger a job to remove instance content in the background.
|
||||||
|
It may take some time.
|
||||||
|
|
||||||
|
- Params:
|
||||||
|
- `instance`: remote instance host
|
||||||
|
- Response:
|
||||||
|
- The `instance` name as a string
|
||||||
|
|
||||||
|
```json
|
||||||
|
"lain.com"
|
||||||
|
```
|
||||||
|
|
||||||
## `GET /api/v1/pleroma/admin/statuses`
|
## `GET /api/v1/pleroma/admin/statuses`
|
||||||
|
|
||||||
### Retrives all latest statuses
|
### Retrives all latest statuses
|
||||||
|
|
|
@ -17,10 +17,3 @@ Great! Now you can explore the fediverse! Open the login page for your Pleroma i
|
||||||
|
|
||||||
### Pleroma-FE
|
### Pleroma-FE
|
||||||
The default front-end used by Pleroma is Pleroma-FE. You can find more information on what it is and how to use it in the [Introduction to Pleroma-FE](../frontend).
|
The default front-end used by Pleroma is Pleroma-FE. You can find more information on what it is and how to use it in the [Introduction to Pleroma-FE](../frontend).
|
||||||
|
|
||||||
### Mastodon interface
|
|
||||||
If the Pleroma interface isn't your thing, or you're just trying something new but you want to keep using the familiar Mastodon interface, we got that too!
|
|
||||||
Just add a "/web" after your instance url (e.g. <https://pleroma.soykaf.com/web>) and you'll end on the Mastodon web interface, but with a Pleroma backend! MAGIC!
|
|
||||||
The Mastodon interface is from the Glitch-soc fork. For more information on the Mastodon interface you can check the [Mastodon](https://docs.joinmastodon.org/) and [Glitch-soc](https://glitch-soc.github.io/docs/) documentation.
|
|
||||||
|
|
||||||
Remember, what you see is only the frontend part of Mastodon, the backend is still Pleroma.
|
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
# Installing on Alpine Linux
|
# Installing on Alpine Linux
|
||||||
|
|
||||||
|
{! backend/installation/otp_vs_from_source_source.include !}
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
This guide is a step-by-step installation guide for Alpine Linux. The instructions were verified against Alpine v3.10 standard image. You might miss additional dependencies if you use `netboot` instead.
|
This guide is a step-by-step installation guide for Alpine Linux. The instructions were verified against Alpine v3.10 standard image. You might miss additional dependencies if you use `netboot` instead.
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
# Installing on Arch Linux
|
# Installing on Arch Linux
|
||||||
|
|
||||||
|
{! backend/installation/otp_vs_from_source_source.include !}
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
This guide will assume that you have administrative rights, either as root or a user with [sudo permissions](https://wiki.archlinux.org/index.php/Sudo). If you want to run this guide with root, ignore the `sudo` at the beginning of the lines, unless it calls a user like `sudo -Hu pleroma`; in this case, use `su <username> -s $SHELL -c 'command'` instead.
|
This guide will assume that you have administrative rights, either as root or a user with [sudo permissions](https://wiki.archlinux.org/index.php/Sudo). If you want to run this guide with root, ignore the `sudo` at the beginning of the lines, unless it calls a user like `sudo -Hu pleroma`; in this case, use `su <username> -s $SHELL -c 'command'` instead.
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
# Installing on Debian Based Distributions
|
# Installing on Debian Based Distributions
|
||||||
|
|
||||||
|
{! backend/installation/otp_vs_from_source_source.include !}
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
This guide will assume you are on Debian 11 (“bullseye”) or later. This guide should also work with Ubuntu 18.04 (“Bionic Beaver”) and later. It also assumes that you have administrative rights, either as root or a user with [sudo permissions](https://www.digitalocean.com/community/tutorials/how-to-add-delete-and-grant-sudo-privileges-to-users-on-a-debian-vps). If you want to run this guide with root, ignore the `sudo` at the beginning of the lines, unless it calls a user like `sudo -Hu pleroma`; in this case, use `su <username> -s $SHELL -c 'command'` instead.
|
This guide will assume you are on Debian 11 (“bullseye”) or later. This guide should also work with Ubuntu 18.04 (“Bionic Beaver”) and later. It also assumes that you have administrative rights, either as root or a user with [sudo permissions](https://www.digitalocean.com/community/tutorials/how-to-add-delete-and-grant-sudo-privileges-to-users-on-a-debian-vps). If you want to run this guide with root, ignore the `sudo` at the beginning of the lines, unless it calls a user like `sudo -Hu pleroma`; in this case, use `su <username> -s $SHELL -c 'command'` instead.
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
* GNU make
|
* GNU make
|
||||||
* CMake
|
* CMake
|
||||||
|
|
||||||
## Optionnal dependencies
|
## Optional dependencies
|
||||||
|
|
||||||
* ImageMagick
|
* ImageMagick
|
||||||
* FFmpeg
|
* FFmpeg
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
# Installing on Gentoo GNU/Linux
|
# Installing on Gentoo GNU/Linux
|
||||||
|
|
||||||
|
{! backend/installation/otp_vs_from_source_source.include !}
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
This guide will assume that you have administrative rights, either as root or a user with [sudo permissions](https://wiki.gentoo.org/wiki/Sudo). Lines that begin with `#` indicate that they should be run as the superuser. Lines using `$` should be run as the indicated user, e.g. `pleroma$` should be run as the `pleroma` user.
|
This guide will assume that you have administrative rights, either as root or a user with [sudo permissions](https://wiki.gentoo.org/wiki/Sudo). Lines that begin with `#` indicate that they should be run as the superuser. Lines using `$` should be run as the indicated user, e.g. `pleroma$` should be run as the `pleroma` user.
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
# Switching a from-source install to OTP releases
|
# Switching a from-source install to OTP releases
|
||||||
|
|
||||||
## What are OTP releases?
|
{! backend/installation/otp_vs_from_source.include !}
|
||||||
OTP releases are as close as you can get to binary releases with Erlang/Elixir. The release is self-contained, and provides everything needed to boot it, it is easily administered via the provided shell script to open up a remote console, start/stop/restart the release, start in the background, send remote commands, and more.
|
|
||||||
|
In this guide we cover how you can migrate from a from source installation to one using OTP releases.
|
||||||
|
|
||||||
## Pre-requisites
|
## Pre-requisites
|
||||||
You will be running commands as root. If you aren't root already, please elevate your priviledges by executing `sudo su`/`su`.
|
You will be running commands as root. If you aren't root already, please elevate your priviledges by executing `sudo su`/`su`.
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
# Installing on Linux using OTP releases
|
# Installing on Linux using OTP releases
|
||||||
|
|
||||||
|
{! backend/installation/otp_vs_from_source.include !}
|
||||||
|
|
||||||
|
This guide covers a installation using an OTP release. To install Pleroma from source, please check out the corresponding guide for your distro.
|
||||||
|
|
||||||
## Pre-requisites
|
## Pre-requisites
|
||||||
* A machine running Linux with GNU (e.g. Debian, Ubuntu) or musl (e.g. Alpine) libc and `x86_64`, `aarch64` or `armv7l` CPU, you have root access to. If you are not sure if it's compatible see [Detecting flavour section](#detecting-flavour) below
|
* A machine running Linux with GNU (e.g. Debian, Ubuntu) or musl (e.g. Alpine) libc and `x86_64`, `aarch64` or `armv7l` CPU, you have root access to. If you are not sure if it's compatible see [Detecting flavour section](#detecting-flavour) below
|
||||||
* A (sub)domain pointed to the machine
|
* A (sub)domain pointed to the machine
|
||||||
|
|
3
docs/installation/otp_vs_from_source.include
Normal file
3
docs/installation/otp_vs_from_source.include
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
## OTP releases vs from-source installations
|
||||||
|
|
||||||
|
There are two ways to install Pleroma. You can use OTP releases or do a from-source installation. OTP releases are as close as you can get to binary releases with Erlang/Elixir. The release is self-contained, and provides everything needed to boot it, it is easily administered via the provided shell script to open up a remote console, start/stop/restart the release, start in the background, send remote commands, and more. With from source installations you install Pleroma from source, meaning you have to install certain dependencies like Erlang+Elixir and compile Pleroma yourself.
|
3
docs/installation/otp_vs_from_source_source.include
Normal file
3
docs/installation/otp_vs_from_source_source.include
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
{! backend/installation/otp_vs_from_source.include !}
|
||||||
|
|
||||||
|
This guide covers a from-source installation. To install using OTP releases, please check out [the OTP guide](./otp_en.md).
|
9
docs/installation/yunohost_en.md
Normal file
9
docs/installation/yunohost_en.md
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
# Installing on Yunohost
|
||||||
|
|
||||||
|
[YunoHost](https://yunohost.org) is a server operating system aimed at self-hosting. The YunoHost community maintains a package of Pleroma which allows you to install Pleroma on YunoHost. You can install it via the normal way through the admin web interface, or through the CLI. More information can be found at [the repo of the package](https://github.com/YunoHost-Apps/pleroma_ynh).
|
||||||
|
|
||||||
|
## Questions
|
||||||
|
|
||||||
|
Questions and problems related to the YunoHost parts can be done through the [regular YunoHost channels](https://yunohost.org/en/help).
|
||||||
|
|
||||||
|
For questions about Pleroma, ask in [#pleroma:libera.chat](https://matrix.to/#/#pleroma:libera.chat) via Matrix or **#pleroma** on **libera.chat** via IRC.
|
|
@ -1,48 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
# Pleroma: A lightweight social networking server
|
|
||||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
project_id="74"
|
|
||||||
project_branch="rebase/glitch-soc"
|
|
||||||
static_dir="instance/static"
|
|
||||||
# For bundling:
|
|
||||||
# project_branch="pleroma"
|
|
||||||
# static_dir="priv/static"
|
|
||||||
|
|
||||||
if [ ! -d "${static_dir}" ]
|
|
||||||
then
|
|
||||||
echo "Error: ${static_dir} directory is missing, are you sure you are running this script at the root of pleroma’s repository?"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
last_modified="$(curl --fail -s -I 'https://git.pleroma.social/api/v4/projects/'${project_id}'/jobs/artifacts/'${project_branch}'/download?job=build' | grep '^Last-Modified:' | cut -d: -f2-)"
|
|
||||||
|
|
||||||
echo "branch:${project_branch}"
|
|
||||||
echo "Last-Modified:${last_modified}"
|
|
||||||
|
|
||||||
artifact="mastofe.zip"
|
|
||||||
|
|
||||||
if [ "${last_modified}x" = "x" ]
|
|
||||||
then
|
|
||||||
echo "ERROR: Couldn't get the modification date of the latest build archive, maybe it expired, exiting..."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -e mastofe.timestamp ] && [ "$(cat mastofe.timestamp)" = "${last_modified}" ]
|
|
||||||
then
|
|
||||||
echo "MastoFE is up-to-date, exiting..."
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
curl --fail -c - "https://git.pleroma.social/api/v4/projects/${project_id}/jobs/artifacts/${project_branch}/download?job=build" -o "${artifact}" || exit
|
|
||||||
|
|
||||||
# TODO: Update the emoji as well
|
|
||||||
rm -fr "${static_dir}/sw.js" "${static_dir}/packs" || exit
|
|
||||||
unzip -q "${artifact}" || exit
|
|
||||||
|
|
||||||
cp public/assets/sw.js "${static_dir}/sw.js" || exit
|
|
||||||
cp -r public/packs "${static_dir}/packs" || exit
|
|
||||||
|
|
||||||
echo "${last_modified}" > mastofe.timestamp
|
|
||||||
rm -fr public
|
|
||||||
rm -i "${artifact}"
|
|
|
@ -286,9 +286,7 @@ defp migrate_from_db(opts) do
|
||||||
file = File.open!(tmp_config_path)
|
file = File.open!(tmp_config_path)
|
||||||
|
|
||||||
shell_info(
|
shell_info(
|
||||||
"Saving database configuration settings to #{tmp_config_path}. Copy it to the #{
|
"Saving database configuration settings to #{tmp_config_path}. Copy it to the #{Path.dirname(config_path)} manually."
|
||||||
Path.dirname(config_path)
|
|
||||||
} manually."
|
|
||||||
)
|
)
|
||||||
|
|
||||||
write_config(file, tmp_config_path, opts)
|
write_config(file, tmp_config_path, opts)
|
||||||
|
|
|
@ -209,7 +209,9 @@ def run(["set_text_search_config", tsconfig]) do
|
||||||
new.fts_content := to_tsvector(new.data->>'content');
|
new.fts_content := to_tsvector(new.data->>'content');
|
||||||
RETURN new;
|
RETURN new;
|
||||||
END
|
END
|
||||||
$$ LANGUAGE plpgsql"
|
$$ LANGUAGE plpgsql",
|
||||||
|
[],
|
||||||
|
timeout: :infinity
|
||||||
)
|
)
|
||||||
|
|
||||||
shell_info("Refresh RUM index")
|
shell_info("Refresh RUM index")
|
||||||
|
@ -219,7 +221,9 @@ def run(["set_text_search_config", tsconfig]) do
|
||||||
|
|
||||||
Ecto.Adapters.SQL.query!(
|
Ecto.Adapters.SQL.query!(
|
||||||
Pleroma.Repo,
|
Pleroma.Repo,
|
||||||
"CREATE INDEX objects_fts ON objects USING gin(to_tsvector('#{tsconfig}', data->>'content')); "
|
"CREATE INDEX CONCURRENTLY objects_fts ON objects USING gin(to_tsvector('#{tsconfig}', data->>'content')); ",
|
||||||
|
[],
|
||||||
|
timeout: :infinity
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -199,6 +199,7 @@ def run(["gen" | rest]) do
|
||||||
secret = :crypto.strong_rand_bytes(64) |> Base.encode64() |> binary_part(0, 64)
|
secret = :crypto.strong_rand_bytes(64) |> Base.encode64() |> binary_part(0, 64)
|
||||||
jwt_secret = :crypto.strong_rand_bytes(64) |> Base.encode64() |> binary_part(0, 64)
|
jwt_secret = :crypto.strong_rand_bytes(64) |> Base.encode64() |> binary_part(0, 64)
|
||||||
signing_salt = :crypto.strong_rand_bytes(8) |> Base.encode64() |> binary_part(0, 8)
|
signing_salt = :crypto.strong_rand_bytes(8) |> Base.encode64() |> binary_part(0, 8)
|
||||||
|
lv_signing_salt = :crypto.strong_rand_bytes(8) |> Base.encode64() |> binary_part(0, 8)
|
||||||
{web_push_public_key, web_push_private_key} = :crypto.generate_key(:ecdh, :prime256v1)
|
{web_push_public_key, web_push_private_key} = :crypto.generate_key(:ecdh, :prime256v1)
|
||||||
template_dir = Application.app_dir(:pleroma, "priv") <> "/templates"
|
template_dir = Application.app_dir(:pleroma, "priv") <> "/templates"
|
||||||
|
|
||||||
|
@ -217,6 +218,7 @@ def run(["gen" | rest]) do
|
||||||
secret: secret,
|
secret: secret,
|
||||||
jwt_secret: jwt_secret,
|
jwt_secret: jwt_secret,
|
||||||
signing_salt: signing_salt,
|
signing_salt: signing_salt,
|
||||||
|
lv_signing_salt: lv_signing_salt,
|
||||||
web_push_public_key: Base.url_encode64(web_push_public_key, padding: false),
|
web_push_public_key: Base.url_encode64(web_push_public_key, padding: false),
|
||||||
web_push_private_key: Base.url_encode64(web_push_private_key, padding: false),
|
web_push_private_key: Base.url_encode64(web_push_private_key, padding: false),
|
||||||
db_configurable?: db_configurable?,
|
db_configurable?: db_configurable?,
|
||||||
|
|
|
@ -51,9 +51,7 @@ def run(["new", nickname, email | rest]) do
|
||||||
A user will be created with the following information:
|
A user will be created with the following information:
|
||||||
- nickname: #{nickname}
|
- nickname: #{nickname}
|
||||||
- email: #{email}
|
- email: #{email}
|
||||||
- password: #{
|
- password: #{if(generated_password?, do: "[generated; a reset link will be created]", else: password)}
|
||||||
if(generated_password?, do: "[generated; a reset link will be created]", else: password)
|
|
||||||
}
|
|
||||||
- name: #{name}
|
- name: #{name}
|
||||||
- bio: #{bio}
|
- bio: #{bio}
|
||||||
- moderator: #{if(moderator?, do: "true", else: "false")}
|
- moderator: #{if(moderator?, do: "true", else: "false")}
|
||||||
|
@ -114,15 +112,9 @@ def run(["reset_password", nickname]) do
|
||||||
{:ok, token} <- Pleroma.PasswordResetToken.create_token(user) do
|
{:ok, token} <- Pleroma.PasswordResetToken.create_token(user) do
|
||||||
shell_info("Generated password reset token for #{user.nickname}")
|
shell_info("Generated password reset token for #{user.nickname}")
|
||||||
|
|
||||||
IO.puts(
|
IO.puts("URL: #{Pleroma.Web.Router.Helpers.reset_password_url(Pleroma.Web.Endpoint,
|
||||||
"URL: #{
|
|
||||||
Pleroma.Web.Router.Helpers.reset_password_url(
|
|
||||||
Pleroma.Web.Endpoint,
|
|
||||||
:reset,
|
:reset,
|
||||||
token.token
|
token.token)}")
|
||||||
)
|
|
||||||
}"
|
|
||||||
)
|
|
||||||
else
|
else
|
||||||
_ ->
|
_ ->
|
||||||
shell_error("No local user #{nickname}")
|
shell_error("No local user #{nickname}")
|
||||||
|
@ -321,9 +313,7 @@ def run(["invites"]) do
|
||||||
end
|
end
|
||||||
|
|
||||||
shell_info(
|
shell_info(
|
||||||
"ID: #{invite.id} | Token: #{invite.token} | Token type: #{invite.invite_type} | Used: #{
|
"ID: #{invite.id} | Token: #{invite.token} | Token type: #{invite.invite_type} | Used: #{invite.used}#{expire_info}#{using_info}"
|
||||||
invite.used
|
|
||||||
}#{expire_info}#{using_info}"
|
|
||||||
)
|
)
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
@ -424,9 +414,7 @@ def run(["list"]) do
|
||||||
users
|
users
|
||||||
|> Enum.each(fn user ->
|
|> Enum.each(fn user ->
|
||||||
shell_info(
|
shell_info(
|
||||||
"#{user.nickname} moderator: #{user.is_moderator}, admin: #{user.is_admin}, locked: #{
|
"#{user.nickname} moderator: #{user.is_moderator}, admin: #{user.is_admin}, locked: #{user.is_locked}, is_active: #{user.is_active}"
|
||||||
user.is_locked
|
|
||||||
}, is_active: #{user.is_active}"
|
|
||||||
)
|
)
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
|
@ -302,7 +302,7 @@ def delete_all_by_object_ap_id(id) when is_binary(id) do
|
||||||
|> Queries.by_object_id()
|
|> Queries.by_object_id()
|
||||||
|> Queries.exclude_type("Delete")
|
|> Queries.exclude_type("Delete")
|
||||||
|> select([u], u)
|
|> select([u], u)
|
||||||
|> Repo.delete_all()
|
|> Repo.delete_all(timeout: :infinity)
|
||||||
|> elem(1)
|
|> elem(1)
|
||||||
|> Enum.find(fn
|
|> Enum.find(fn
|
||||||
%{data: %{"type" => "Create", "object" => ap_id}} when is_binary(ap_id) -> ap_id == id
|
%{data: %{"type" => "Create", "object" => ap_id}} when is_binary(ap_id) -> ap_id == id
|
||||||
|
@ -362,11 +362,9 @@ def following_requests_for_actor(%User{ap_id: ap_id}) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def restrict_deactivated_users(query) do
|
def restrict_deactivated_users(query) do
|
||||||
deactivated_users =
|
deactivated_users_query = from(u in User.Query.build(%{deactivated: true}), select: u.ap_id)
|
||||||
from(u in User.Query.build(%{deactivated: true}), select: u.ap_id)
|
|
||||||
|> Repo.all()
|
|
||||||
|
|
||||||
Activity.Queries.exclude_authors(query, deactivated_users)
|
from(activity in query, where: activity.actor not in subquery(deactivated_users_query))
|
||||||
end
|
end
|
||||||
|
|
||||||
defdelegate search(user, query, options \\ []), to: Pleroma.Activity.Search
|
defdelegate search(user, query, options \\ []), to: Pleroma.Activity.Search
|
||||||
|
|
|
@ -26,6 +26,7 @@ def search(user, search_query, options \\ []) do
|
||||||
:plain
|
:plain
|
||||||
end
|
end
|
||||||
|
|
||||||
|
try do
|
||||||
Activity
|
Activity
|
||||||
|> Activity.with_preloaded_object()
|
|> Activity.with_preloaded_object()
|
||||||
|> Activity.restrict_deactivated_users()
|
|> Activity.restrict_deactivated_users()
|
||||||
|
@ -39,6 +40,9 @@ def search(user, search_query, options \\ []) do
|
||||||
:offset
|
:offset
|
||||||
)
|
)
|
||||||
|> maybe_fetch(user, search_query)
|
|> maybe_fetch(user, search_query)
|
||||||
|
rescue
|
||||||
|
_ -> maybe_fetch([], user, search_query)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def maybe_restrict_author(query, %User{} = author) do
|
def maybe_restrict_author(query, %User{} = author) do
|
||||||
|
@ -61,10 +65,17 @@ defp restrict_public(q) do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp query_with(q, :gin, search_query, :plain) do
|
defp query_with(q, :gin, search_query, :plain) do
|
||||||
|
%{rows: [[tsc]]} =
|
||||||
|
Ecto.Adapters.SQL.query!(
|
||||||
|
Pleroma.Repo,
|
||||||
|
"select current_setting('default_text_search_config')::regconfig::oid;"
|
||||||
|
)
|
||||||
|
|
||||||
from([a, o] in q,
|
from([a, o] in q,
|
||||||
where:
|
where:
|
||||||
fragment(
|
fragment(
|
||||||
"to_tsvector(?->>'content') @@ plainto_tsquery(?)",
|
"to_tsvector(?::oid::regconfig, ?->>'content') @@ plainto_tsquery(?)",
|
||||||
|
^tsc,
|
||||||
o.data,
|
o.data,
|
||||||
^search_query
|
^search_query
|
||||||
)
|
)
|
||||||
|
@ -72,10 +83,17 @@ defp query_with(q, :gin, search_query, :plain) do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp query_with(q, :gin, search_query, :websearch) do
|
defp query_with(q, :gin, search_query, :websearch) do
|
||||||
|
%{rows: [[tsc]]} =
|
||||||
|
Ecto.Adapters.SQL.query!(
|
||||||
|
Pleroma.Repo,
|
||||||
|
"select current_setting('default_text_search_config')::regconfig::oid;"
|
||||||
|
)
|
||||||
|
|
||||||
from([a, o] in q,
|
from([a, o] in q,
|
||||||
where:
|
where:
|
||||||
fragment(
|
fragment(
|
||||||
"to_tsvector(?->>'content') @@ websearch_to_tsquery(?)",
|
"to_tsvector(?::oid::regconfig, ?->>'content') @@ websearch_to_tsquery(?)",
|
||||||
|
^tsc,
|
||||||
o.data,
|
o.data,
|
||||||
^search_query
|
^search_query
|
||||||
)
|
)
|
||||||
|
|
|
@ -19,9 +19,7 @@ def on_shell(username, _pubkey, _ip, _port) do
|
||||||
def on_connect(username, ip, port, method) do
|
def on_connect(username, ip, port, method) do
|
||||||
Logger.debug(fn ->
|
Logger.debug(fn ->
|
||||||
"""
|
"""
|
||||||
Incoming SSH shell #{inspect(self())} requested for #{username} from #{inspect(ip)}:#{
|
Incoming SSH shell #{inspect(self())} requested for #{username} from #{inspect(ip)}:#{inspect(port)} using #{inspect(method)}
|
||||||
inspect(port)
|
|
||||||
} using #{inspect(method)}
|
|
||||||
"""
|
"""
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
|
@ -20,6 +20,140 @@ defmodule Pleroma.Config.DeprecationWarnings do
|
||||||
"\n* `config :pleroma, :instance, mrf_transparency_exclusions` is now `config :pleroma, :mrf, transparency_exclusions`"}
|
"\n* `config :pleroma, :instance, mrf_transparency_exclusions` is now `config :pleroma, :mrf, transparency_exclusions`"}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def check_simple_policy_tuples do
|
||||||
|
has_strings =
|
||||||
|
Config.get([:mrf_simple])
|
||||||
|
|> Enum.any?(fn {_, v} -> Enum.any?(v, &is_binary/1) end)
|
||||||
|
|
||||||
|
if has_strings do
|
||||||
|
Logger.warn("""
|
||||||
|
!!!DEPRECATION WARNING!!!
|
||||||
|
Your config is using strings in the SimplePolicy configuration instead of tuples. They should work for now, but you are advised to change to the new configuration to prevent possible issues later:
|
||||||
|
|
||||||
|
```
|
||||||
|
config :pleroma, :mrf_simple,
|
||||||
|
media_removal: ["instance.tld"],
|
||||||
|
media_nsfw: ["instance.tld"],
|
||||||
|
federated_timeline_removal: ["instance.tld"],
|
||||||
|
report_removal: ["instance.tld"],
|
||||||
|
reject: ["instance.tld"],
|
||||||
|
followers_only: ["instance.tld"],
|
||||||
|
accept: ["instance.tld"],
|
||||||
|
avatar_removal: ["instance.tld"],
|
||||||
|
banner_removal: ["instance.tld"],
|
||||||
|
reject_deletes: ["instance.tld"]
|
||||||
|
```
|
||||||
|
|
||||||
|
Is now
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
config :pleroma, :mrf_simple,
|
||||||
|
media_removal: [{"instance.tld", "Reason for media removal"}],
|
||||||
|
media_nsfw: [{"instance.tld", "Reason for media nsfw"}],
|
||||||
|
federated_timeline_removal: [{"instance.tld", "Reason for federated timeline removal"}],
|
||||||
|
report_removal: [{"instance.tld", "Reason for report removal"}],
|
||||||
|
reject: [{"instance.tld", "Reason for reject"}],
|
||||||
|
followers_only: [{"instance.tld", "Reason for followers only"}],
|
||||||
|
accept: [{"instance.tld", "Reason for accept"}],
|
||||||
|
avatar_removal: [{"instance.tld", "Reason for avatar removal"}],
|
||||||
|
banner_removal: [{"instance.tld", "Reason for banner removal"}],
|
||||||
|
reject_deletes: [{"instance.tld", "Reason for reject deletes"}]
|
||||||
|
```
|
||||||
|
""")
|
||||||
|
|
||||||
|
new_config =
|
||||||
|
Config.get([:mrf_simple])
|
||||||
|
|> Enum.map(fn {k, v} ->
|
||||||
|
{k,
|
||||||
|
Enum.map(v, fn
|
||||||
|
{instance, reason} -> {instance, reason}
|
||||||
|
instance -> {instance, ""}
|
||||||
|
end)}
|
||||||
|
end)
|
||||||
|
|
||||||
|
Config.put([:mrf_simple], new_config)
|
||||||
|
|
||||||
|
:error
|
||||||
|
else
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def check_quarantined_instances_tuples do
|
||||||
|
has_strings = Config.get([:instance, :quarantined_instances]) |> Enum.any?(&is_binary/1)
|
||||||
|
|
||||||
|
if has_strings do
|
||||||
|
Logger.warn("""
|
||||||
|
!!!DEPRECATION WARNING!!!
|
||||||
|
Your config is using strings in the quarantined_instances configuration instead of tuples. They should work for now, but you are advised to change to the new configuration to prevent possible issues later:
|
||||||
|
|
||||||
|
```
|
||||||
|
config :pleroma, :instance,
|
||||||
|
quarantined_instances: ["instance.tld"]
|
||||||
|
```
|
||||||
|
|
||||||
|
Is now
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
config :pleroma, :instance,
|
||||||
|
quarantined_instances: [{"instance.tld", "Reason for quarantine"}]
|
||||||
|
```
|
||||||
|
""")
|
||||||
|
|
||||||
|
new_config =
|
||||||
|
Config.get([:instance, :quarantined_instances])
|
||||||
|
|> Enum.map(fn
|
||||||
|
{instance, reason} -> {instance, reason}
|
||||||
|
instance -> {instance, ""}
|
||||||
|
end)
|
||||||
|
|
||||||
|
Config.put([:instance, :quarantined_instances], new_config)
|
||||||
|
|
||||||
|
:error
|
||||||
|
else
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def check_transparency_exclusions_tuples do
|
||||||
|
has_strings = Config.get([:mrf, :transparency_exclusions]) |> Enum.any?(&is_binary/1)
|
||||||
|
|
||||||
|
if has_strings do
|
||||||
|
Logger.warn("""
|
||||||
|
!!!DEPRECATION WARNING!!!
|
||||||
|
Your config is using strings in the transparency_exclusions configuration instead of tuples. They should work for now, but you are advised to change to the new configuration to prevent possible issues later:
|
||||||
|
|
||||||
|
```
|
||||||
|
config :pleroma, :mrf,
|
||||||
|
transparency_exclusions: ["instance.tld"]
|
||||||
|
```
|
||||||
|
|
||||||
|
Is now
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
config :pleroma, :mrf,
|
||||||
|
transparency_exclusions: [{"instance.tld", "Reason to exlude transparency"}]
|
||||||
|
```
|
||||||
|
""")
|
||||||
|
|
||||||
|
new_config =
|
||||||
|
Config.get([:mrf, :transparency_exclusions])
|
||||||
|
|> Enum.map(fn
|
||||||
|
{instance, reason} -> {instance, reason}
|
||||||
|
instance -> {instance, ""}
|
||||||
|
end)
|
||||||
|
|
||||||
|
Config.put([:mrf, :transparency_exclusions], new_config)
|
||||||
|
|
||||||
|
:error
|
||||||
|
else
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def check_hellthread_threshold do
|
def check_hellthread_threshold do
|
||||||
if Config.get([:mrf_hellthread, :threshold]) do
|
if Config.get([:mrf_hellthread, :threshold]) do
|
||||||
Logger.warn("""
|
Logger.warn("""
|
||||||
|
@ -34,20 +168,24 @@ def check_hellthread_threshold do
|
||||||
end
|
end
|
||||||
|
|
||||||
def warn do
|
def warn do
|
||||||
with :ok <- check_hellthread_threshold(),
|
[
|
||||||
:ok <- check_old_mrf_config(),
|
check_hellthread_threshold(),
|
||||||
:ok <- check_media_proxy_whitelist_config(),
|
check_old_mrf_config(),
|
||||||
:ok <- check_welcome_message_config(),
|
check_media_proxy_whitelist_config(),
|
||||||
:ok <- check_gun_pool_options(),
|
check_welcome_message_config(),
|
||||||
:ok <- check_activity_expiration_config(),
|
check_gun_pool_options(),
|
||||||
:ok <- check_remote_ip_plug_name(),
|
check_activity_expiration_config(),
|
||||||
:ok <- check_uploders_s3_public_endpoint(),
|
check_remote_ip_plug_name(),
|
||||||
:ok <- check_old_chat_shoutbox() do
|
check_uploders_s3_public_endpoint(),
|
||||||
:ok
|
check_old_chat_shoutbox(),
|
||||||
else
|
check_quarantined_instances_tuples(),
|
||||||
_ ->
|
check_transparency_exclusions_tuples(),
|
||||||
:error
|
check_simple_policy_tuples()
|
||||||
end
|
]
|
||||||
|
|> Enum.reduce(:ok, fn
|
||||||
|
:ok, :ok -> :ok
|
||||||
|
_, _ -> :error
|
||||||
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
def check_welcome_message_config do
|
def check_welcome_message_config do
|
||||||
|
|
|
@ -21,9 +21,7 @@ def warn do
|
||||||
"""
|
"""
|
||||||
!!!OBAN CONFIG WARNING!!!
|
!!!OBAN CONFIG WARNING!!!
|
||||||
You are using old workers in Oban crontab settings, which were removed.
|
You are using old workers in Oban crontab settings, which were removed.
|
||||||
Please, remove setting from crontab in your config file (prod.secret.exs): #{
|
Please, remove setting from crontab in your config file (prod.secret.exs): #{inspect(setting)}
|
||||||
inspect(setting)
|
|
||||||
}
|
|
||||||
"""
|
"""
|
||||||
|> Logger.warn()
|
|> Logger.warn()
|
||||||
|
|
||||||
|
|
|
@ -148,9 +148,7 @@ defp update({group, key, value, merged}) do
|
||||||
rescue
|
rescue
|
||||||
error ->
|
error ->
|
||||||
error_msg =
|
error_msg =
|
||||||
"updating env causes error, group: #{inspect(group)}, key: #{inspect(key)}, value: #{
|
"updating env causes error, group: #{inspect(group)}, key: #{inspect(key)}, value: #{inspect(value)} error: #{inspect(error)}"
|
||||||
inspect(value)
|
|
||||||
} error: #{inspect(error)}"
|
|
||||||
|
|
||||||
Logger.warn(error_msg)
|
Logger.warn(error_msg)
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,8 @@
|
||||||
mute: 2,
|
mute: 2,
|
||||||
reblog_mute: 3,
|
reblog_mute: 3,
|
||||||
notification_mute: 4,
|
notification_mute: 4,
|
||||||
inverse_subscription: 5
|
inverse_subscription: 5,
|
||||||
|
suggestion_dismiss: 6
|
||||||
)
|
)
|
||||||
|
|
||||||
defenum(Pleroma.FollowingRelationship.State,
|
defenum(Pleroma.FollowingRelationship.State,
|
||||||
|
|
|
@ -60,9 +60,7 @@ def load do
|
||||||
|
|
||||||
if not Enum.empty?(files) do
|
if not Enum.empty?(files) do
|
||||||
Logger.warn(
|
Logger.warn(
|
||||||
"Found files in the emoji folder. These will be ignored, please move them to a subdirectory\nFound files: #{
|
"Found files in the emoji folder. These will be ignored, please move them to a subdirectory\nFound files: #{Enum.join(files, ", ")}"
|
||||||
Enum.join(files, ", ")
|
|
||||||
}"
|
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -105,6 +103,7 @@ defp load_pack(pack_dir, emoji_groups) do
|
||||||
pack_file = Path.join(pack_dir, "pack.json")
|
pack_file = Path.join(pack_dir, "pack.json")
|
||||||
|
|
||||||
if File.exists?(pack_file) do
|
if File.exists?(pack_file) do
|
||||||
|
Logger.info("Loading emoji pack from JSON: #{pack_file}")
|
||||||
contents = Jason.decode!(File.read!(pack_file))
|
contents = Jason.decode!(File.read!(pack_file))
|
||||||
|
|
||||||
contents["files"]
|
contents["files"]
|
||||||
|
@ -117,14 +116,13 @@ defp load_pack(pack_dir, emoji_groups) do
|
||||||
emoji_txt = Path.join(pack_dir, "emoji.txt")
|
emoji_txt = Path.join(pack_dir, "emoji.txt")
|
||||||
|
|
||||||
if File.exists?(emoji_txt) do
|
if File.exists?(emoji_txt) do
|
||||||
|
Logger.info("Loading emoji pack from emoji.txt: #{emoji_txt}")
|
||||||
load_from_file(emoji_txt, emoji_groups)
|
load_from_file(emoji_txt, emoji_groups)
|
||||||
else
|
else
|
||||||
extensions = Config.get([:emoji, :pack_extensions])
|
extensions = Config.get([:emoji, :pack_extensions])
|
||||||
|
|
||||||
Logger.info(
|
Logger.info(
|
||||||
"No emoji.txt found for pack \"#{pack_name}\", assuming all #{
|
"No emoji.txt found for pack \"#{pack_name}\", assuming all #{Enum.join(extensions, ", ")} files are emoji"
|
||||||
Enum.join(extensions, ", ")
|
|
||||||
} files are emoji"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
make_shortcode_to_file_map(pack_dir, extensions)
|
make_shortcode_to_file_map(pack_dir, extensions)
|
||||||
|
|
|
@ -57,9 +57,7 @@ defp do_open(uri, %{proxy: {proxy_host, proxy_port}} = opts) do
|
||||||
else
|
else
|
||||||
error ->
|
error ->
|
||||||
Logger.warn(
|
Logger.warn(
|
||||||
"Opening proxied connection to #{compose_uri_log(uri)} failed with error #{
|
"Opening proxied connection to #{compose_uri_log(uri)} failed with error #{inspect(error)}"
|
||||||
inspect(error)
|
|
||||||
}"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
error
|
error
|
||||||
|
@ -93,9 +91,7 @@ defp do_open(uri, %{proxy: {proxy_type, proxy_host, proxy_port}} = opts) do
|
||||||
else
|
else
|
||||||
error ->
|
error ->
|
||||||
Logger.warn(
|
Logger.warn(
|
||||||
"Opening socks proxied connection to #{compose_uri_log(uri)} failed with error #{
|
"Opening socks proxied connection to #{compose_uri_log(uri)} failed with error #{inspect(error)}"
|
||||||
inspect(error)
|
|
||||||
}"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
error
|
error
|
||||||
|
|
|
@ -8,6 +8,8 @@ defmodule Pleroma.Instances.Instance do
|
||||||
alias Pleroma.Instances
|
alias Pleroma.Instances
|
||||||
alias Pleroma.Instances.Instance
|
alias Pleroma.Instances.Instance
|
||||||
alias Pleroma.Repo
|
alias Pleroma.Repo
|
||||||
|
alias Pleroma.User
|
||||||
|
alias Pleroma.Workers.BackgroundWorker
|
||||||
|
|
||||||
use Ecto.Schema
|
use Ecto.Schema
|
||||||
|
|
||||||
|
@ -195,4 +197,24 @@ defp scrape_favicon(%URI{} = instance_uri) do
|
||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Deletes all users from an instance in a background task, thus also deleting
|
||||||
|
all of those users' activities and notifications.
|
||||||
|
"""
|
||||||
|
def delete_users_and_activities(host) when is_binary(host) do
|
||||||
|
BackgroundWorker.enqueue("delete_instance", %{"host" => host})
|
||||||
|
end
|
||||||
|
|
||||||
|
def perform(:delete_instance, host) when is_binary(host) do
|
||||||
|
User.Query.build(%{nickname: "@#{host}"})
|
||||||
|
|> Repo.chunk_stream(100, :batches)
|
||||||
|
|> Stream.each(fn users ->
|
||||||
|
users
|
||||||
|
|> Enum.each(fn user ->
|
||||||
|
User.perform(:delete, user)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|> Stream.run()
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -9,7 +9,7 @@ defmodule Pleroma.Maintenance do
|
||||||
def vacuum(args) do
|
def vacuum(args) do
|
||||||
case args do
|
case args do
|
||||||
"analyze" ->
|
"analyze" ->
|
||||||
Logger.info("Runnning VACUUM ANALYZE.")
|
Logger.info("Running VACUUM ANALYZE.")
|
||||||
|
|
||||||
Repo.query!(
|
Repo.query!(
|
||||||
"vacuum analyze;",
|
"vacuum analyze;",
|
||||||
|
@ -18,7 +18,7 @@ def vacuum(args) do
|
||||||
)
|
)
|
||||||
|
|
||||||
"full" ->
|
"full" ->
|
||||||
Logger.info("Runnning VACUUM FULL.")
|
Logger.info("Running VACUUM FULL.")
|
||||||
|
|
||||||
Logger.warn(
|
Logger.warn(
|
||||||
"Re-packing your entire database may take a while and will consume extra disk space during the process."
|
"Re-packing your entire database may take a while and will consume extra disk space during the process."
|
||||||
|
|
|
@ -338,6 +338,26 @@ def get_log_entry_message(%ModerationLog{
|
||||||
"@#{actor_nickname} approved users: #{users_to_nicknames_string(users)}"
|
"@#{actor_nickname} approved users: #{users_to_nicknames_string(users)}"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def get_log_entry_message(%ModerationLog{
|
||||||
|
data: %{
|
||||||
|
"actor" => %{"nickname" => actor_nickname},
|
||||||
|
"action" => "add_suggestion",
|
||||||
|
"subject" => users
|
||||||
|
}
|
||||||
|
}) do
|
||||||
|
"@#{actor_nickname} added suggested users: #{users_to_nicknames_string(users)}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_log_entry_message(%ModerationLog{
|
||||||
|
data: %{
|
||||||
|
"actor" => %{"nickname" => actor_nickname},
|
||||||
|
"action" => "remove_suggestion",
|
||||||
|
"subject" => users
|
||||||
|
}
|
||||||
|
}) do
|
||||||
|
"@#{actor_nickname} removed suggested users: #{users_to_nicknames_string(users)}"
|
||||||
|
end
|
||||||
|
|
||||||
def get_log_entry_message(%ModerationLog{
|
def get_log_entry_message(%ModerationLog{
|
||||||
data: %{
|
data: %{
|
||||||
"actor" => %{"nickname" => actor_nickname},
|
"actor" => %{"nickname" => actor_nickname},
|
||||||
|
@ -481,9 +501,7 @@ def get_log_entry_message(%ModerationLog{
|
||||||
"visibility" => visibility
|
"visibility" => visibility
|
||||||
}
|
}
|
||||||
}) do
|
}) do
|
||||||
"@#{actor_nickname} updated status ##{subject_id}, set sensitive: '#{sensitive}', visibility: '#{
|
"@#{actor_nickname} updated status ##{subject_id}, set sensitive: '#{sensitive}', visibility: '#{visibility}'"
|
||||||
visibility
|
|
||||||
}'"
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_log_entry_message(%ModerationLog{
|
def get_log_entry_message(%ModerationLog{
|
||||||
|
@ -523,9 +541,7 @@ def get_log_entry_message(%ModerationLog{
|
||||||
"subject" => subjects
|
"subject" => subjects
|
||||||
}
|
}
|
||||||
}) do
|
}) do
|
||||||
"@#{actor_nickname} re-sent confirmation email for users: #{
|
"@#{actor_nickname} re-sent confirmation email for users: #{users_to_nicknames_string(subjects)}"
|
||||||
users_to_nicknames_string(subjects)
|
|
||||||
}"
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_log_entry_message(%ModerationLog{
|
def get_log_entry_message(%ModerationLog{
|
||||||
|
|
|
@ -72,6 +72,7 @@ def unread_notifications_count(%User{id: user_id}) do
|
||||||
pleroma:emoji_reaction
|
pleroma:emoji_reaction
|
||||||
pleroma:report
|
pleroma:report
|
||||||
reblog
|
reblog
|
||||||
|
poll
|
||||||
}
|
}
|
||||||
|
|
||||||
def changeset(%Notification{} = notification, attrs) do
|
def changeset(%Notification{} = notification, attrs) do
|
||||||
|
@ -127,6 +128,7 @@ def for_user_query(user, opts \\ %{}) do
|
||||||
|> where([user_actor: user_actor], user_actor.is_active)
|
|> where([user_actor: user_actor], user_actor.is_active)
|
||||||
|> exclude_notification_muted(user, exclude_notification_muted_opts)
|
|> exclude_notification_muted(user, exclude_notification_muted_opts)
|
||||||
|> exclude_blocked(user, exclude_blocked_opts)
|
|> exclude_blocked(user, exclude_blocked_opts)
|
||||||
|
|> exclude_blockers(user)
|
||||||
|> exclude_filtered(user)
|
|> exclude_filtered(user)
|
||||||
|> exclude_visibility(opts)
|
|> exclude_visibility(opts)
|
||||||
end
|
end
|
||||||
|
@ -140,6 +142,17 @@ defp exclude_blocked(query, user, opts) do
|
||||||
|> FollowingRelationship.keep_following_or_not_domain_blocked(user)
|
|> FollowingRelationship.keep_following_or_not_domain_blocked(user)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp exclude_blockers(query, user) do
|
||||||
|
if Pleroma.Config.get([:activitypub, :blockers_visible]) == true do
|
||||||
|
query
|
||||||
|
else
|
||||||
|
blocker_ap_ids = User.incoming_relationships_ungrouped_ap_ids(user, [:block])
|
||||||
|
|
||||||
|
query
|
||||||
|
|> where([n, a], a.actor not in ^blocker_ap_ids)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
defp exclude_notification_muted(query, _, %{@include_muted_option => true}) do
|
defp exclude_notification_muted(query, _, %{@include_muted_option => true}) do
|
||||||
query
|
query
|
||||||
end
|
end
|
||||||
|
@ -379,7 +392,7 @@ defp do_create_notifications(%Activity{} = activity, options) do
|
||||||
notifications =
|
notifications =
|
||||||
Enum.map(potential_receivers, fn user ->
|
Enum.map(potential_receivers, fn user ->
|
||||||
do_send = do_send && user in enabled_receivers
|
do_send = do_send && user in enabled_receivers
|
||||||
create_notification(activity, user, do_send)
|
create_notification(activity, user, do_send: do_send)
|
||||||
end)
|
end)
|
||||||
|> Enum.reject(&is_nil/1)
|
|> Enum.reject(&is_nil/1)
|
||||||
|
|
||||||
|
@ -435,15 +448,18 @@ defp type_from_activity_object(%{data: %{"type" => "Create"}} = activity) do
|
||||||
end
|
end
|
||||||
|
|
||||||
# TODO move to sql, too.
|
# TODO move to sql, too.
|
||||||
def create_notification(%Activity{} = activity, %User{} = user, do_send \\ true) do
|
def create_notification(%Activity{} = activity, %User{} = user, opts \\ []) do
|
||||||
unless skip?(activity, user) do
|
do_send = Keyword.get(opts, :do_send, true)
|
||||||
|
type = Keyword.get(opts, :type, type_from_activity(activity))
|
||||||
|
|
||||||
|
unless skip?(activity, user, opts) do
|
||||||
{:ok, %{notification: notification}} =
|
{:ok, %{notification: notification}} =
|
||||||
Multi.new()
|
Multi.new()
|
||||||
|> Multi.insert(:notification, %Notification{
|
|> Multi.insert(:notification, %Notification{
|
||||||
user_id: user.id,
|
user_id: user.id,
|
||||||
activity: activity,
|
activity: activity,
|
||||||
seen: mark_as_read?(activity, user),
|
seen: mark_as_read?(activity, user),
|
||||||
type: type_from_activity(activity)
|
type: type
|
||||||
})
|
})
|
||||||
|> Marker.multi_set_last_read_id(user, "notifications")
|
|> Marker.multi_set_last_read_id(user, "notifications")
|
||||||
|> Repo.transaction()
|
|> Repo.transaction()
|
||||||
|
@ -457,6 +473,28 @@ def create_notification(%Activity{} = activity, %User{} = user, do_send \\ true)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def create_poll_notifications(%Activity{} = activity) do
|
||||||
|
with %Object{data: %{"type" => "Question", "actor" => actor} = data} <-
|
||||||
|
Object.normalize(activity) do
|
||||||
|
voters =
|
||||||
|
case data do
|
||||||
|
%{"voters" => voters} when is_list(voters) -> voters
|
||||||
|
_ -> []
|
||||||
|
end
|
||||||
|
|
||||||
|
notifications =
|
||||||
|
Enum.reduce([actor | voters], [], fn ap_id, acc ->
|
||||||
|
with %User{local: true} = user <- User.get_by_ap_id(ap_id) do
|
||||||
|
[create_notification(activity, user, type: "poll") | acc]
|
||||||
|
else
|
||||||
|
_ -> acc
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
{:ok, notifications}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Returns a tuple with 2 elements:
|
Returns a tuple with 2 elements:
|
||||||
{notification-enabled receivers, currently disabled receivers (blocking / [thread] muting)}
|
{notification-enabled receivers, currently disabled receivers (blocking / [thread] muting)}
|
||||||
|
@ -572,8 +610,10 @@ def exclude_thread_muter_ap_ids(ap_ids, %Activity{} = activity) do
|
||||||
Enum.uniq(ap_ids) -- thread_muter_ap_ids
|
Enum.uniq(ap_ids) -- thread_muter_ap_ids
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec skip?(Activity.t(), User.t()) :: boolean()
|
def skip?(activity, user, opts \\ [])
|
||||||
def skip?(%Activity{} = activity, %User{} = user) do
|
|
||||||
|
@spec skip?(Activity.t(), User.t(), Keyword.t()) :: boolean()
|
||||||
|
def skip?(%Activity{} = activity, %User{} = user, opts) do
|
||||||
[
|
[
|
||||||
:self,
|
:self,
|
||||||
:invisible,
|
:invisible,
|
||||||
|
@ -581,17 +621,21 @@ def skip?(%Activity{} = activity, %User{} = user) do
|
||||||
:recently_followed,
|
:recently_followed,
|
||||||
:filtered
|
:filtered
|
||||||
]
|
]
|
||||||
|> Enum.find(&skip?(&1, activity, user))
|
|> Enum.find(&skip?(&1, activity, user, opts))
|
||||||
end
|
end
|
||||||
|
|
||||||
def skip?(_, _), do: false
|
def skip?(_activity, _user, _opts), do: false
|
||||||
|
|
||||||
@spec skip?(atom(), Activity.t(), User.t()) :: boolean()
|
@spec skip?(atom(), Activity.t(), User.t(), Keyword.t()) :: boolean()
|
||||||
def skip?(:self, %Activity{} = activity, %User{} = user) do
|
def skip?(:self, %Activity{} = activity, %User{} = user, opts) do
|
||||||
activity.data["actor"] == user.ap_id
|
cond do
|
||||||
|
opts[:type] == "poll" -> false
|
||||||
|
activity.data["actor"] == user.ap_id -> true
|
||||||
|
true -> false
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def skip?(:invisible, %Activity{} = activity, _) do
|
def skip?(:invisible, %Activity{} = activity, _user, _opts) do
|
||||||
actor = activity.data["actor"]
|
actor = activity.data["actor"]
|
||||||
user = User.get_cached_by_ap_id(actor)
|
user = User.get_cached_by_ap_id(actor)
|
||||||
User.invisible?(user)
|
User.invisible?(user)
|
||||||
|
@ -600,15 +644,27 @@ def skip?(:invisible, %Activity{} = activity, _) do
|
||||||
def skip?(
|
def skip?(
|
||||||
:block_from_strangers,
|
:block_from_strangers,
|
||||||
%Activity{} = activity,
|
%Activity{} = activity,
|
||||||
%User{notification_settings: %{block_from_strangers: true}} = user
|
%User{notification_settings: %{block_from_strangers: true}} = user,
|
||||||
|
opts
|
||||||
) do
|
) do
|
||||||
actor = activity.data["actor"]
|
actor = activity.data["actor"]
|
||||||
follower = User.get_cached_by_ap_id(actor)
|
follower = User.get_cached_by_ap_id(actor)
|
||||||
!User.following?(follower, user)
|
|
||||||
|
cond do
|
||||||
|
opts[:type] == "poll" -> false
|
||||||
|
user.ap_id == actor -> false
|
||||||
|
!User.following?(follower, user) -> true
|
||||||
|
true -> false
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# To do: consider defining recency in hours and checking FollowingRelationship with a single SQL
|
# To do: consider defining recency in hours and checking FollowingRelationship with a single SQL
|
||||||
def skip?(:recently_followed, %Activity{data: %{"type" => "Follow"}} = activity, %User{} = user) do
|
def skip?(
|
||||||
|
:recently_followed,
|
||||||
|
%Activity{data: %{"type" => "Follow"}} = activity,
|
||||||
|
%User{} = user,
|
||||||
|
_opts
|
||||||
|
) do
|
||||||
actor = activity.data["actor"]
|
actor = activity.data["actor"]
|
||||||
|
|
||||||
Notification.for_user(user)
|
Notification.for_user(user)
|
||||||
|
@ -618,9 +674,10 @@ def skip?(:recently_followed, %Activity{data: %{"type" => "Follow"}} = activity,
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
def skip?(:filtered, %{data: %{"type" => type}}, _) when type in ["Follow", "Move"], do: false
|
def skip?(:filtered, %{data: %{"type" => type}}, _user, _opts) when type in ["Follow", "Move"],
|
||||||
|
do: false
|
||||||
|
|
||||||
def skip?(:filtered, activity, user) do
|
def skip?(:filtered, activity, user, _opts) do
|
||||||
object = Object.normalize(activity, fetch: false)
|
object = Object.normalize(activity, fetch: false)
|
||||||
|
|
||||||
cond do
|
cond do
|
||||||
|
@ -638,7 +695,7 @@ def skip?(:filtered, activity, user) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def skip?(_, _, _), do: false
|
def skip?(_type, _activity, _user, _opts), do: false
|
||||||
|
|
||||||
def mark_as_read?(activity, target_user) do
|
def mark_as_read?(activity, target_user) do
|
||||||
user = Activity.user_actor(activity)
|
user = Activity.user_actor(activity)
|
||||||
|
|
|
@ -29,9 +29,7 @@ def handle_event(
|
||||||
_
|
_
|
||||||
) do
|
) do
|
||||||
Logger.debug(fn ->
|
Logger.debug(fn ->
|
||||||
"Connection pool is exhausted (reached #{max_connections} connections). Starting idle connection cleanup to reclaim as much as #{
|
"Connection pool is exhausted (reached #{max_connections} connections). Starting idle connection cleanup to reclaim as much as #{reclaim_max} connections"
|
||||||
reclaim_max
|
|
||||||
} connections"
|
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -73,9 +71,7 @@ def handle_event(
|
||||||
_
|
_
|
||||||
) do
|
) do
|
||||||
Logger.warn(fn ->
|
Logger.warn(fn ->
|
||||||
"Pool worker for #{key}: Client #{inspect(client_pid)} died before releasing the connection with #{
|
"Pool worker for #{key}: Client #{inspect(client_pid)} died before releasing the connection with #{inspect(reason)}"
|
||||||
inspect(reason)
|
|
||||||
}"
|
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -124,7 +124,6 @@ defmodule Pleroma.User do
|
||||||
field(:is_moderator, :boolean, default: false)
|
field(:is_moderator, :boolean, default: false)
|
||||||
field(:is_admin, :boolean, default: false)
|
field(:is_admin, :boolean, default: false)
|
||||||
field(:show_role, :boolean, default: true)
|
field(:show_role, :boolean, default: true)
|
||||||
field(:mastofe_settings, :map, default: nil)
|
|
||||||
field(:uri, ObjectValidators.Uri, default: nil)
|
field(:uri, ObjectValidators.Uri, default: nil)
|
||||||
field(:hide_followers_count, :boolean, default: false)
|
field(:hide_followers_count, :boolean, default: false)
|
||||||
field(:hide_follows_count, :boolean, default: false)
|
field(:hide_follows_count, :boolean, default: false)
|
||||||
|
@ -149,6 +148,7 @@ defmodule Pleroma.User do
|
||||||
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: %{})
|
field(:pinned_objects, :map, default: %{})
|
||||||
|
field(:is_suggested, :boolean, default: false)
|
||||||
|
|
||||||
embeds_one(
|
embeds_one(
|
||||||
:notification_settings,
|
:notification_settings,
|
||||||
|
@ -1677,6 +1677,22 @@ def confirm(%User{is_confirmed: false} = user) do
|
||||||
|
|
||||||
def confirm(%User{} = user), do: {:ok, user}
|
def confirm(%User{} = user), do: {:ok, user}
|
||||||
|
|
||||||
|
def set_suggestion(users, is_suggested) when is_list(users) do
|
||||||
|
Repo.transaction(fn ->
|
||||||
|
Enum.map(users, fn user ->
|
||||||
|
with {:ok, user} <- set_suggestion(user, is_suggested), do: user
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_suggestion(%User{is_suggested: is_suggested} = user, is_suggested), do: {:ok, user}
|
||||||
|
|
||||||
|
def set_suggestion(%User{} = user, is_suggested) when is_boolean(is_suggested) do
|
||||||
|
user
|
||||||
|
|> change(is_suggested: is_suggested)
|
||||||
|
|> update_and_set_cache()
|
||||||
|
end
|
||||||
|
|
||||||
def update_notification_settings(%User{} = user, settings) do
|
def update_notification_settings(%User{} = user, settings) do
|
||||||
user
|
user
|
||||||
|> cast(%{notification_settings: settings}, [])
|
|> cast(%{notification_settings: settings}, [])
|
||||||
|
@ -1713,7 +1729,6 @@ def purge_user_changeset(user) do
|
||||||
ap_enabled: false,
|
ap_enabled: false,
|
||||||
is_moderator: false,
|
is_moderator: false,
|
||||||
is_admin: false,
|
is_admin: false,
|
||||||
mastofe_settings: nil,
|
|
||||||
mascot: nil,
|
mascot: nil,
|
||||||
emoji: %{},
|
emoji: %{},
|
||||||
pleroma_settings_store: %{},
|
pleroma_settings_store: %{},
|
||||||
|
@ -2248,7 +2263,7 @@ def get_delivered_users_by_object_id(object_id) do
|
||||||
def change_email(user, email) do
|
def change_email(user, email) do
|
||||||
user
|
user
|
||||||
|> cast(%{email: email}, [:email])
|
|> cast(%{email: email}, [:email])
|
||||||
|> validate_required([:email])
|
|> maybe_validate_required_email(false)
|
||||||
|> unique_constraint(:email)
|
|> unique_constraint(:email)
|
||||||
|> validate_format(:email, @email_regex)
|
|> validate_format(:email, @email_regex)
|
||||||
|> update_and_set_cache()
|
|> update_and_set_cache()
|
||||||
|
@ -2331,13 +2346,6 @@ def mascot_update(user, url) do
|
||||||
|> update_and_set_cache()
|
|> update_and_set_cache()
|
||||||
end
|
end
|
||||||
|
|
||||||
def mastodon_settings_update(user, settings) do
|
|
||||||
user
|
|
||||||
|> cast(%{mastofe_settings: settings}, [:mastofe_settings])
|
|
||||||
|> validate_required([:mastofe_settings])
|
|
||||||
|> update_and_set_cache()
|
|
||||||
end
|
|
||||||
|
|
||||||
@spec confirmation_changeset(User.t(), keyword()) :: Changeset.t()
|
@spec confirmation_changeset(User.t(), keyword()) :: Changeset.t()
|
||||||
def confirmation_changeset(user, set_confirmation: confirmed?) do
|
def confirmation_changeset(user, set_confirmation: confirmed?) do
|
||||||
params =
|
params =
|
||||||
|
@ -2483,8 +2491,8 @@ def update_last_active_at(%__MODULE__{local: true} = user) do
|
||||||
|> update_and_set_cache()
|
|> update_and_set_cache()
|
||||||
end
|
end
|
||||||
|
|
||||||
def active_user_count(weeks \\ 4) do
|
def active_user_count(days \\ 30) do
|
||||||
active_after = Timex.shift(NaiveDateTime.utc_now(), weeks: -weeks)
|
active_after = Timex.shift(NaiveDateTime.utc_now(), days: -days)
|
||||||
|
|
||||||
__MODULE__
|
__MODULE__
|
||||||
|> where([u], u.last_active_at >= ^active_after)
|
|> where([u], u.last_active_at >= ^active_after)
|
||||||
|
|
|
@ -46,6 +46,7 @@ defmodule Pleroma.User.Query do
|
||||||
unconfirmed: boolean(),
|
unconfirmed: boolean(),
|
||||||
is_admin: boolean(),
|
is_admin: boolean(),
|
||||||
is_moderator: boolean(),
|
is_moderator: boolean(),
|
||||||
|
is_suggested: boolean(),
|
||||||
super_users: boolean(),
|
super_users: boolean(),
|
||||||
invisible: boolean(),
|
invisible: boolean(),
|
||||||
internal: boolean(),
|
internal: boolean(),
|
||||||
|
@ -167,6 +168,10 @@ defp compose_query({:unconfirmed, _}, query) do
|
||||||
where(query, [u], u.is_confirmed == false)
|
where(query, [u], u.is_confirmed == false)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp compose_query({:is_suggested, bool}, query) do
|
||||||
|
where(query, [u], u.is_suggested == ^bool)
|
||||||
|
end
|
||||||
|
|
||||||
defp compose_query({:followers, %User{id: id}}, query) do
|
defp compose_query({:followers, %User{id: id}}, query) do
|
||||||
query
|
query
|
||||||
|> where([u], u.id != ^id)
|
|> where([u], u.id != ^id)
|
||||||
|
|
|
@ -25,6 +25,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||||
alias Pleroma.Web.Streamer
|
alias Pleroma.Web.Streamer
|
||||||
alias Pleroma.Web.WebFinger
|
alias Pleroma.Web.WebFinger
|
||||||
alias Pleroma.Workers.BackgroundWorker
|
alias Pleroma.Workers.BackgroundWorker
|
||||||
|
alias Pleroma.Workers.PollWorker
|
||||||
|
|
||||||
import Ecto.Query
|
import Ecto.Query
|
||||||
import Pleroma.Web.ActivityPub.Utils
|
import Pleroma.Web.ActivityPub.Utils
|
||||||
|
@ -288,6 +289,7 @@ defp do_create(%{to: to, actor: actor, context: context, object: object} = param
|
||||||
{:quick_insert, false, activity} <- {:quick_insert, quick_insert?, activity},
|
{:quick_insert, false, activity} <- {:quick_insert, quick_insert?, activity},
|
||||||
{:ok, _actor} <- increase_note_count_if_public(actor, activity),
|
{:ok, _actor} <- increase_note_count_if_public(actor, activity),
|
||||||
_ <- notify_and_stream(activity),
|
_ <- notify_and_stream(activity),
|
||||||
|
:ok <- maybe_schedule_poll_notifications(activity),
|
||||||
:ok <- maybe_federate(activity) do
|
:ok <- maybe_federate(activity) do
|
||||||
{:ok, activity}
|
{:ok, activity}
|
||||||
else
|
else
|
||||||
|
@ -302,6 +304,11 @@ defp do_create(%{to: to, actor: actor, context: context, object: object} = param
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp maybe_schedule_poll_notifications(activity) do
|
||||||
|
PollWorker.schedule_poll_end(activity)
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
|
||||||
@spec listen(map()) :: {:ok, Activity.t()} | {:error, any()}
|
@spec listen(map()) :: {:ok, Activity.t()} | {:error, any()}
|
||||||
def listen(%{to: to, actor: actor, context: context, object: object} = params) do
|
def listen(%{to: to, actor: actor, context: context, object: object} = params) do
|
||||||
additional = params[:additional] || %{}
|
additional = params[:additional] || %{}
|
||||||
|
@ -434,6 +441,7 @@ def fetch_activities_for_context_query(context, opts) do
|
||||||
|> maybe_preload_bookmarks(opts)
|
|> maybe_preload_bookmarks(opts)
|
||||||
|> maybe_set_thread_muted_field(opts)
|
|> maybe_set_thread_muted_field(opts)
|
||||||
|> restrict_blocked(opts)
|
|> restrict_blocked(opts)
|
||||||
|
|> restrict_blockers_visibility(opts)
|
||||||
|> restrict_recipients(recipients, opts[:user])
|
|> restrict_recipients(recipients, opts[:user])
|
||||||
|> restrict_filtered(opts)
|
|> restrict_filtered(opts)
|
||||||
|> where(
|
|> where(
|
||||||
|
@ -1021,7 +1029,10 @@ defp restrict_blocked(query, %{blocking_user: %User{} = user} = opts) do
|
||||||
|
|
||||||
from(
|
from(
|
||||||
[activity, object: o] in query,
|
[activity, object: o] in query,
|
||||||
|
# You don't block the author
|
||||||
where: fragment("not (? = ANY(?))", activity.actor, ^blocked_ap_ids),
|
where: fragment("not (? = ANY(?))", activity.actor, ^blocked_ap_ids),
|
||||||
|
|
||||||
|
# You don't block any recipients, and didn't author the post
|
||||||
where:
|
where:
|
||||||
fragment(
|
fragment(
|
||||||
"((not (? && ?)) or ? = ?)",
|
"((not (? && ?)) or ? = ?)",
|
||||||
|
@ -1030,12 +1041,18 @@ defp restrict_blocked(query, %{blocking_user: %User{} = user} = opts) do
|
||||||
activity.actor,
|
activity.actor,
|
||||||
^user.ap_id
|
^user.ap_id
|
||||||
),
|
),
|
||||||
|
|
||||||
|
# You don't block the domain of any recipients, and didn't author the post
|
||||||
where:
|
where:
|
||||||
fragment(
|
fragment(
|
||||||
"recipients_contain_blocked_domains(?, ?) = false",
|
"(recipients_contain_blocked_domains(?, ?) = false) or ? = ?",
|
||||||
activity.recipients,
|
activity.recipients,
|
||||||
^domain_blocks
|
^domain_blocks,
|
||||||
|
activity.actor,
|
||||||
|
^user.ap_id
|
||||||
),
|
),
|
||||||
|
|
||||||
|
# It's not a boost of a user you block
|
||||||
where:
|
where:
|
||||||
fragment(
|
fragment(
|
||||||
"not (?->>'type' = 'Announce' and ?->'to' \\?| ?)",
|
"not (?->>'type' = 'Announce' and ?->'to' \\?| ?)",
|
||||||
|
@ -1043,6 +1060,8 @@ defp restrict_blocked(query, %{blocking_user: %User{} = user} = opts) do
|
||||||
activity.data,
|
activity.data,
|
||||||
^blocked_ap_ids
|
^blocked_ap_ids
|
||||||
),
|
),
|
||||||
|
|
||||||
|
# You don't block the author's domain, and also don't follow the author
|
||||||
where:
|
where:
|
||||||
fragment(
|
fragment(
|
||||||
"(not (split_part(?, '/', 3) = ANY(?))) or ? = ANY(?)",
|
"(not (split_part(?, '/', 3) = ANY(?))) or ? = ANY(?)",
|
||||||
|
@ -1051,6 +1070,8 @@ defp restrict_blocked(query, %{blocking_user: %User{} = user} = opts) do
|
||||||
activity.actor,
|
activity.actor,
|
||||||
^following_ap_ids
|
^following_ap_ids
|
||||||
),
|
),
|
||||||
|
|
||||||
|
# Same as above, but checks the Object
|
||||||
where:
|
where:
|
||||||
fragment(
|
fragment(
|
||||||
"(not (split_part(?->>'actor', '/', 3) = ANY(?))) or (?->>'actor') = ANY(?)",
|
"(not (split_part(?->>'actor', '/', 3) = ANY(?))) or (?->>'actor') = ANY(?)",
|
||||||
|
@ -1064,6 +1085,31 @@ defp restrict_blocked(query, %{blocking_user: %User{} = user} = opts) do
|
||||||
|
|
||||||
defp restrict_blocked(query, _), do: query
|
defp restrict_blocked(query, _), do: query
|
||||||
|
|
||||||
|
defp restrict_blockers_visibility(query, %{blocking_user: %User{} = user}) do
|
||||||
|
if Config.get([:activitypub, :blockers_visible]) == true do
|
||||||
|
query
|
||||||
|
else
|
||||||
|
blocker_ap_ids = User.incoming_relationships_ungrouped_ap_ids(user, [:block])
|
||||||
|
|
||||||
|
from(
|
||||||
|
activity in query,
|
||||||
|
# The author doesn't block you
|
||||||
|
where: fragment("not (? = ANY(?))", activity.actor, ^blocker_ap_ids),
|
||||||
|
|
||||||
|
# It's not a boost of a user that blocks you
|
||||||
|
where:
|
||||||
|
fragment(
|
||||||
|
"not (?->>'type' = 'Announce' and ?->'to' \\?| ?)",
|
||||||
|
activity.data,
|
||||||
|
activity.data,
|
||||||
|
^blocker_ap_ids
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp restrict_blockers_visibility(query, _), do: query
|
||||||
|
|
||||||
defp restrict_unlisted(query, %{restrict_unlisted: true}) do
|
defp restrict_unlisted(query, %{restrict_unlisted: true}) do
|
||||||
from(
|
from(
|
||||||
activity in query,
|
activity in query,
|
||||||
|
@ -1290,6 +1336,7 @@ def fetch_activities_query(recipients, opts \\ %{}) do
|
||||||
|> restrict_state(opts)
|
|> restrict_state(opts)
|
||||||
|> restrict_favorited_by(opts)
|
|> restrict_favorited_by(opts)
|
||||||
|> restrict_blocked(restrict_blocked_opts)
|
|> restrict_blocked(restrict_blocked_opts)
|
||||||
|
|> restrict_blockers_visibility(opts)
|
||||||
|> restrict_muted(restrict_muted_opts)
|
|> restrict_muted(restrict_muted_opts)
|
||||||
|> restrict_filtered(opts)
|
|> restrict_filtered(opts)
|
||||||
|> restrict_media(opts)
|
|> restrict_media(opts)
|
||||||
|
@ -1590,9 +1637,7 @@ def maybe_handle_clashing_nickname(data) do
|
||||||
%User{} = old_user <- User.get_by_nickname(nickname),
|
%User{} = old_user <- User.get_by_nickname(nickname),
|
||||||
{_, false} <- {:ap_id_comparison, data[:ap_id] == old_user.ap_id} do
|
{_, false} <- {:ap_id_comparison, data[:ap_id] == old_user.ap_id} do
|
||||||
Logger.info(
|
Logger.info(
|
||||||
"Found an old user for #{nickname}, the old ap id is #{old_user.ap_id}, new one is #{
|
"Found an old user for #{nickname}, the old ap id is #{old_user.ap_id}, new one is #{data[:ap_id]}, renaming."
|
||||||
data[:ap_id]
|
|
||||||
}, renaming."
|
|
||||||
)
|
)
|
||||||
|
|
||||||
old_user
|
old_user
|
||||||
|
|
|
@ -283,15 +283,29 @@ def inbox(%{assigns: %{valid_signature: true}} = conn, params) do
|
||||||
json(conn, "ok")
|
json(conn, "ok")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def inbox(%{assigns: %{valid_signature: false}} = conn, _params) do
|
||||||
|
conn
|
||||||
|
|> put_status(:bad_request)
|
||||||
|
|> json("Invalid HTTP Signature")
|
||||||
|
end
|
||||||
|
|
||||||
# POST /relay/inbox -or- POST /internal/fetch/inbox
|
# POST /relay/inbox -or- POST /internal/fetch/inbox
|
||||||
def inbox(conn, params) do
|
def inbox(conn, %{"type" => "Create"} = params) do
|
||||||
if params["type"] == "Create" && FederatingPlug.federating?() do
|
if FederatingPlug.federating?() do
|
||||||
post_inbox_relayed_create(conn, params)
|
post_inbox_relayed_create(conn, params)
|
||||||
else
|
else
|
||||||
post_inbox_fallback(conn, params)
|
conn
|
||||||
|
|> put_status(:bad_request)
|
||||||
|
|> json("Not federating")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def inbox(conn, _params) do
|
||||||
|
conn
|
||||||
|
|> put_status(:bad_request)
|
||||||
|
|> json("error, missing HTTP Signature")
|
||||||
|
end
|
||||||
|
|
||||||
defp post_inbox_relayed_create(conn, params) do
|
defp post_inbox_relayed_create(conn, params) do
|
||||||
Logger.debug(
|
Logger.debug(
|
||||||
"Signature missing or not from author, relayed Create message, fetching object from source"
|
"Signature missing or not from author, relayed Create message, fetching object from source"
|
||||||
|
@ -302,23 +316,6 @@ defp post_inbox_relayed_create(conn, params) do
|
||||||
json(conn, "ok")
|
json(conn, "ok")
|
||||||
end
|
end
|
||||||
|
|
||||||
defp post_inbox_fallback(conn, params) do
|
|
||||||
headers = Enum.into(conn.req_headers, %{})
|
|
||||||
|
|
||||||
if headers["signature"] && params["actor"] &&
|
|
||||||
String.contains?(headers["signature"], params["actor"]) do
|
|
||||||
Logger.debug(
|
|
||||||
"Signature validation error for: #{params["actor"]}, make sure you are forwarding the HTTP Host header!"
|
|
||||||
)
|
|
||||||
|
|
||||||
Logger.debug(inspect(conn.req_headers))
|
|
||||||
end
|
|
||||||
|
|
||||||
conn
|
|
||||||
|> put_status(:bad_request)
|
|
||||||
|> json(dgettext("errors", "error"))
|
|
||||||
end
|
|
||||||
|
|
||||||
defp represent_service_actor(%User{} = user, conn) do
|
defp represent_service_actor(%User{} = user, conn) do
|
||||||
with {:ok, user} <- User.ensure_keys_present(user) do
|
with {:ok, user} <- User.ensure_keys_present(user) do
|
||||||
conn
|
conn
|
||||||
|
|
|
@ -15,6 +15,7 @@ defmodule Pleroma.Web.ActivityPub.Builder do
|
||||||
alias Pleroma.Web.ActivityPub.Relay
|
alias Pleroma.Web.ActivityPub.Relay
|
||||||
alias Pleroma.Web.ActivityPub.Utils
|
alias Pleroma.Web.ActivityPub.Utils
|
||||||
alias Pleroma.Web.ActivityPub.Visibility
|
alias Pleroma.Web.ActivityPub.Visibility
|
||||||
|
alias Pleroma.Web.CommonAPI.ActivityDraft
|
||||||
|
|
||||||
require Pleroma.Constants
|
require Pleroma.Constants
|
||||||
|
|
||||||
|
@ -125,6 +126,37 @@ def create(actor, object, recipients) do
|
||||||
|> Pleroma.Maps.put_if_present("context", context), []}
|
|> Pleroma.Maps.put_if_present("context", context), []}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec note(ActivityDraft.t()) :: {:ok, map(), keyword()}
|
||||||
|
def note(%ActivityDraft{} = draft) do
|
||||||
|
data =
|
||||||
|
%{
|
||||||
|
"type" => "Note",
|
||||||
|
"to" => draft.to,
|
||||||
|
"cc" => draft.cc,
|
||||||
|
"content" => draft.content_html,
|
||||||
|
"summary" => draft.summary,
|
||||||
|
"sensitive" => draft.sensitive,
|
||||||
|
"context" => draft.context,
|
||||||
|
"attachment" => draft.attachments,
|
||||||
|
"actor" => draft.user.ap_id,
|
||||||
|
"tag" => Keyword.values(draft.tags) |> Enum.uniq()
|
||||||
|
}
|
||||||
|
|> add_in_reply_to(draft.in_reply_to)
|
||||||
|
|> Map.merge(draft.extra)
|
||||||
|
|
||||||
|
{:ok, data, []}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp add_in_reply_to(object, nil), do: object
|
||||||
|
|
||||||
|
defp add_in_reply_to(object, in_reply_to) do
|
||||||
|
with %Object{} = in_reply_to_object <- Object.normalize(in_reply_to, fetch: false) do
|
||||||
|
Map.put(object, "inReplyTo", in_reply_to_object.data["id"])
|
||||||
|
else
|
||||||
|
_ -> object
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def chat_message(actor, recipient, content, opts \\ []) do
|
def chat_message(actor, recipient, content, opts \\ []) do
|
||||||
basic = %{
|
basic = %{
|
||||||
"id" => Utils.generate_object_id(),
|
"id" => Utils.generate_object_id(),
|
||||||
|
|
|
@ -21,7 +21,7 @@ defmodule Pleroma.Web.ActivityPub.MRF do
|
||||||
type: [:module, {:list, :module}],
|
type: [:module, {:list, :module}],
|
||||||
description:
|
description:
|
||||||
"A list of MRF policies enabled. Module names are shortened (removed leading `Pleroma.Web.ActivityPub.MRF.` part), but on adding custom module you need to use full name.",
|
"A list of MRF policies enabled. Module names are shortened (removed leading `Pleroma.Web.ActivityPub.MRF.` part), but on adding custom module you need to use full name.",
|
||||||
suggestions: {:list_behaviour_implementations, Pleroma.Web.ActivityPub.MRF}
|
suggestions: {:list_behaviour_implementations, Pleroma.Web.ActivityPub.MRF.Policy}
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
key: :transparency,
|
key: :transparency,
|
||||||
|
@ -33,9 +33,11 @@ defmodule Pleroma.Web.ActivityPub.MRF do
|
||||||
%{
|
%{
|
||||||
key: :transparency_exclusions,
|
key: :transparency_exclusions,
|
||||||
label: "MRF transparency exclusions",
|
label: "MRF transparency exclusions",
|
||||||
type: {:list, :string},
|
type: {:list, :tuple},
|
||||||
|
key_placeholder: "instance",
|
||||||
|
value_placeholder: "reason",
|
||||||
description:
|
description:
|
||||||
"Exclude specific instance names from MRF transparency. The use of the exclusions feature will be disclosed in nodeinfo as a boolean value.",
|
"Exclude specific instance names from MRF transparency. The use of the exclusions feature will be disclosed in nodeinfo as a boolean value. You can also provide a reason for excluding these instance names. The instances and reasons won't be publicly disclosed.",
|
||||||
suggestions: [
|
suggestions: [
|
||||||
"exclusion.com"
|
"exclusion.com"
|
||||||
]
|
]
|
||||||
|
@ -100,6 +102,11 @@ def subdomain_match?(domains, host) do
|
||||||
Enum.any?(domains, fn domain -> Regex.match?(domain, host) end)
|
Enum.any?(domains, fn domain -> Regex.match?(domain, host) end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec instance_list_from_tuples([{String.t(), String.t()}]) :: [String.t()]
|
||||||
|
def instance_list_from_tuples(list) do
|
||||||
|
Enum.map(list, fn {instance, _} -> instance end)
|
||||||
|
end
|
||||||
|
|
||||||
def describe(policies) do
|
def describe(policies) do
|
||||||
{:ok, policy_configs} =
|
{:ok, policy_configs} =
|
||||||
policies
|
policies
|
||||||
|
@ -150,9 +157,7 @@ def config_descriptions(policies) do
|
||||||
[description | acc]
|
[description | acc]
|
||||||
else
|
else
|
||||||
Logger.warn(
|
Logger.warn(
|
||||||
"#{policy} config description doesn't have one or all required keys #{
|
"#{policy} config description doesn't have one or all required keys #{inspect(@required_description_keys)}"
|
||||||
inspect(@required_description_keys)
|
|
||||||
}"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
acc
|
acc
|
||||||
|
|
|
@ -159,6 +159,8 @@ def config_description do
|
||||||
%{
|
%{
|
||||||
key: :replace,
|
key: :replace,
|
||||||
type: {:list, :tuple},
|
type: {:list, :tuple},
|
||||||
|
key_placeholder: "instance",
|
||||||
|
value_placeholder: "reason",
|
||||||
description: """
|
description: """
|
||||||
**Pattern**: a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`.
|
**Pattern**: a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`.
|
||||||
|
|
||||||
|
|
|
@ -49,6 +49,8 @@ defp check_delist(message, actions) do
|
||||||
message
|
message
|
||||||
|> Map.put("to", to)
|
|> Map.put("to", to)
|
||||||
|> Map.put("cc", cc)
|
|> Map.put("cc", cc)
|
||||||
|
|> Kernel.put_in(["object", "to"], to)
|
||||||
|
|> Kernel.put_in(["object", "cc"], cc)
|
||||||
|
|
||||||
{:ok, message}
|
{:ok, message}
|
||||||
else
|
else
|
||||||
|
@ -70,6 +72,8 @@ defp check_strip_followers(message, actions) do
|
||||||
message
|
message
|
||||||
|> Map.put("to", to)
|
|> Map.put("to", to)
|
||||||
|> Map.put("cc", cc)
|
|> Map.put("cc", cc)
|
||||||
|
|> Kernel.put_in(["object", "to"], to)
|
||||||
|
|> Kernel.put_in(["object", "cc"], cc)
|
||||||
|
|
||||||
{:ok, message}
|
{:ok, message}
|
||||||
else
|
else
|
||||||
|
@ -82,7 +86,7 @@ defp check_strip_followers(message, actions) do
|
||||||
end
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def filter(%{"type" => "Create", "published" => _} = message) do
|
def filter(%{"type" => "Create", "object" => %{"published" => _}} = message) do
|
||||||
with actions <- Config.get([:mrf_object_age, :actions]),
|
with actions <- Config.get([:mrf_object_age, :actions]),
|
||||||
{:reject, _} <- check_date(message),
|
{:reject, _} <- check_date(message),
|
||||||
{:ok, message} <- check_reject(message, actions),
|
{:ok, message} <- check_reject(message, actions),
|
||||||
|
|
|
@ -47,7 +47,7 @@ def filter(object), do: {:ok, object}
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def describe,
|
def describe,
|
||||||
do: {:ok, %{mrf_rejectnonpublic: Config.get(:mrf_rejectnonpublic) |> Enum.into(%{})}}
|
do: {:ok, %{mrf_rejectnonpublic: Config.get(:mrf_rejectnonpublic) |> Map.new()}}
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def config_description do
|
def config_description do
|
||||||
|
|
|
@ -15,7 +15,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
|
||||||
|
|
||||||
defp check_accept(%{host: actor_host} = _actor_info, object) do
|
defp check_accept(%{host: actor_host} = _actor_info, object) do
|
||||||
accepts =
|
accepts =
|
||||||
Config.get([:mrf_simple, :accept])
|
instance_list(:accept)
|
||||||
|> MRF.subdomains_regex()
|
|> MRF.subdomains_regex()
|
||||||
|
|
||||||
cond do
|
cond do
|
||||||
|
@ -28,7 +28,7 @@ defp check_accept(%{host: actor_host} = _actor_info, object) do
|
||||||
|
|
||||||
defp check_reject(%{host: actor_host} = _actor_info, object) do
|
defp check_reject(%{host: actor_host} = _actor_info, object) do
|
||||||
rejects =
|
rejects =
|
||||||
Config.get([:mrf_simple, :reject])
|
instance_list(:reject)
|
||||||
|> MRF.subdomains_regex()
|
|> MRF.subdomains_regex()
|
||||||
|
|
||||||
if MRF.subdomain_match?(rejects, actor_host) do
|
if MRF.subdomain_match?(rejects, actor_host) do
|
||||||
|
@ -44,7 +44,7 @@ defp check_media_removal(
|
||||||
)
|
)
|
||||||
when length(child_attachment) > 0 do
|
when length(child_attachment) > 0 do
|
||||||
media_removal =
|
media_removal =
|
||||||
Config.get([:mrf_simple, :media_removal])
|
instance_list(:media_removal)
|
||||||
|> MRF.subdomains_regex()
|
|> MRF.subdomains_regex()
|
||||||
|
|
||||||
object =
|
object =
|
||||||
|
@ -68,7 +68,7 @@ defp check_media_nsfw(
|
||||||
} = object
|
} = object
|
||||||
) do
|
) do
|
||||||
media_nsfw =
|
media_nsfw =
|
||||||
Config.get([:mrf_simple, :media_nsfw])
|
instance_list(:media_nsfw)
|
||||||
|> MRF.subdomains_regex()
|
|> MRF.subdomains_regex()
|
||||||
|
|
||||||
object =
|
object =
|
||||||
|
@ -85,7 +85,7 @@ defp check_media_nsfw(_actor_info, object), do: {:ok, object}
|
||||||
|
|
||||||
defp check_ftl_removal(%{host: actor_host} = _actor_info, object) do
|
defp check_ftl_removal(%{host: actor_host} = _actor_info, object) do
|
||||||
timeline_removal =
|
timeline_removal =
|
||||||
Config.get([:mrf_simple, :federated_timeline_removal])
|
instance_list(:federated_timeline_removal)
|
||||||
|> MRF.subdomains_regex()
|
|> MRF.subdomains_regex()
|
||||||
|
|
||||||
object =
|
object =
|
||||||
|
@ -112,7 +112,7 @@ defp intersection(list1, list2) do
|
||||||
|
|
||||||
defp check_followers_only(%{host: actor_host} = _actor_info, object) do
|
defp check_followers_only(%{host: actor_host} = _actor_info, object) do
|
||||||
followers_only =
|
followers_only =
|
||||||
Config.get([:mrf_simple, :followers_only])
|
instance_list(:followers_only)
|
||||||
|> MRF.subdomains_regex()
|
|> MRF.subdomains_regex()
|
||||||
|
|
||||||
object =
|
object =
|
||||||
|
@ -137,7 +137,7 @@ defp check_followers_only(%{host: actor_host} = _actor_info, object) do
|
||||||
|
|
||||||
defp check_report_removal(%{host: actor_host} = _actor_info, %{"type" => "Flag"} = object) do
|
defp check_report_removal(%{host: actor_host} = _actor_info, %{"type" => "Flag"} = object) do
|
||||||
report_removal =
|
report_removal =
|
||||||
Config.get([:mrf_simple, :report_removal])
|
instance_list(:report_removal)
|
||||||
|> MRF.subdomains_regex()
|
|> MRF.subdomains_regex()
|
||||||
|
|
||||||
if MRF.subdomain_match?(report_removal, actor_host) do
|
if MRF.subdomain_match?(report_removal, actor_host) do
|
||||||
|
@ -151,7 +151,7 @@ defp check_report_removal(_actor_info, object), do: {:ok, object}
|
||||||
|
|
||||||
defp check_avatar_removal(%{host: actor_host} = _actor_info, %{"icon" => _icon} = object) do
|
defp check_avatar_removal(%{host: actor_host} = _actor_info, %{"icon" => _icon} = object) do
|
||||||
avatar_removal =
|
avatar_removal =
|
||||||
Config.get([:mrf_simple, :avatar_removal])
|
instance_list(:avatar_removal)
|
||||||
|> MRF.subdomains_regex()
|
|> MRF.subdomains_regex()
|
||||||
|
|
||||||
if MRF.subdomain_match?(avatar_removal, actor_host) do
|
if MRF.subdomain_match?(avatar_removal, actor_host) do
|
||||||
|
@ -165,7 +165,7 @@ defp check_avatar_removal(_actor_info, object), do: {:ok, object}
|
||||||
|
|
||||||
defp check_banner_removal(%{host: actor_host} = _actor_info, %{"image" => _image} = object) do
|
defp check_banner_removal(%{host: actor_host} = _actor_info, %{"image" => _image} = object) do
|
||||||
banner_removal =
|
banner_removal =
|
||||||
Config.get([:mrf_simple, :banner_removal])
|
instance_list(:banner_removal)
|
||||||
|> MRF.subdomains_regex()
|
|> MRF.subdomains_regex()
|
||||||
|
|
||||||
if MRF.subdomain_match?(banner_removal, actor_host) do
|
if MRF.subdomain_match?(banner_removal, actor_host) do
|
||||||
|
@ -185,12 +185,17 @@ defp check_object(%{"object" => object} = activity) do
|
||||||
|
|
||||||
defp check_object(object), do: {:ok, object}
|
defp check_object(object), do: {:ok, object}
|
||||||
|
|
||||||
|
defp instance_list(config_key) do
|
||||||
|
Config.get([:mrf_simple, config_key])
|
||||||
|
|> MRF.instance_list_from_tuples()
|
||||||
|
end
|
||||||
|
|
||||||
@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)
|
||||||
|
|
||||||
reject_deletes =
|
reject_deletes =
|
||||||
Config.get([:mrf_simple, :reject_deletes])
|
instance_list(:reject_deletes)
|
||||||
|> MRF.subdomains_regex()
|
|> MRF.subdomains_regex()
|
||||||
|
|
||||||
if MRF.subdomain_match?(reject_deletes, actor_host) do
|
if MRF.subdomain_match?(reject_deletes, actor_host) do
|
||||||
|
@ -253,14 +258,42 @@ def filter(object), do: {:ok, object}
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def describe do
|
def describe do
|
||||||
exclusions = Config.get([:mrf, :transparency_exclusions])
|
exclusions = Config.get([:mrf, :transparency_exclusions]) |> MRF.instance_list_from_tuples()
|
||||||
|
|
||||||
|
mrf_simple_excluded =
|
||||||
|
Config.get(:mrf_simple)
|
||||||
|
|> Enum.map(fn {rule, instances} ->
|
||||||
|
{rule, Enum.reject(instances, fn {host, _} -> host in exclusions end)}
|
||||||
|
end)
|
||||||
|
|
||||||
mrf_simple =
|
mrf_simple =
|
||||||
Config.get(:mrf_simple)
|
mrf_simple_excluded
|
||||||
|> Enum.map(fn {k, v} -> {k, Enum.reject(v, fn v -> v in exclusions end)} end)
|
|> Enum.map(fn {rule, instances} ->
|
||||||
|> Enum.into(%{})
|
{rule, Enum.map(instances, fn {host, _} -> host end)}
|
||||||
|
end)
|
||||||
|
|> Map.new()
|
||||||
|
|
||||||
{:ok, %{mrf_simple: mrf_simple}}
|
# This is for backwards compatibility. We originally didn't sent
|
||||||
|
# extra info like a reason why an instance was rejected/quarantined/etc.
|
||||||
|
# Because we didn't want to break backwards compatibility it was decided
|
||||||
|
# to add an extra "info" key.
|
||||||
|
mrf_simple_info =
|
||||||
|
mrf_simple_excluded
|
||||||
|
|> Enum.map(fn {rule, instances} ->
|
||||||
|
{rule, Enum.reject(instances, fn {_, reason} -> reason == "" end)}
|
||||||
|
end)
|
||||||
|
|> Enum.reject(fn {_, instances} -> instances == [] end)
|
||||||
|
|> Enum.map(fn {rule, instances} ->
|
||||||
|
instances =
|
||||||
|
instances
|
||||||
|
|> Enum.map(fn {host, reason} -> {host, %{"reason" => reason}} end)
|
||||||
|
|> Map.new()
|
||||||
|
|
||||||
|
{rule, instances}
|
||||||
|
end)
|
||||||
|
|> Map.new()
|
||||||
|
|
||||||
|
{:ok, %{mrf_simple: mrf_simple, mrf_simple_info: mrf_simple_info}}
|
||||||
end
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
|
@ -270,70 +303,67 @@ def config_description do
|
||||||
related_policy: "Pleroma.Web.ActivityPub.MRF.SimplePolicy",
|
related_policy: "Pleroma.Web.ActivityPub.MRF.SimplePolicy",
|
||||||
label: "MRF Simple",
|
label: "MRF Simple",
|
||||||
description: "Simple ingress policies",
|
description: "Simple ingress policies",
|
||||||
children: [
|
children:
|
||||||
|
[
|
||||||
%{
|
%{
|
||||||
key: :media_removal,
|
key: :media_removal,
|
||||||
type: {:list, :string},
|
description:
|
||||||
description: "List of instances to strip media attachments from",
|
"List of instances to strip media attachments from and the reason for doing so"
|
||||||
suggestions: ["example.com", "*.example.com"]
|
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
key: :media_nsfw,
|
key: :media_nsfw,
|
||||||
label: "Media NSFW",
|
label: "Media NSFW",
|
||||||
type: {:list, :string},
|
description:
|
||||||
description: "List of instances to tag all media as NSFW (sensitive) from",
|
"List of instances to tag all media as NSFW (sensitive) from and the reason for doing so"
|
||||||
suggestions: ["example.com", "*.example.com"]
|
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
key: :federated_timeline_removal,
|
key: :federated_timeline_removal,
|
||||||
type: {:list, :string},
|
|
||||||
description:
|
description:
|
||||||
"List of instances to remove from the Federated (aka The Whole Known Network) Timeline",
|
"List of instances to remove from the Federated (aka The Whole Known Network) Timeline and the reason for doing so"
|
||||||
suggestions: ["example.com", "*.example.com"]
|
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
key: :reject,
|
key: :reject,
|
||||||
type: {:list, :string},
|
description:
|
||||||
description: "List of instances to reject activities from (except deletes)",
|
"List of instances to reject activities from (except deletes) and the reason for doing so"
|
||||||
suggestions: ["example.com", "*.example.com"]
|
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
key: :accept,
|
key: :accept,
|
||||||
type: {:list, :string},
|
description:
|
||||||
description: "List of instances to only accept activities from (except deletes)",
|
"List of instances to only accept activities from (except deletes) and the reason for doing so"
|
||||||
suggestions: ["example.com", "*.example.com"]
|
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
key: :followers_only,
|
key: :followers_only,
|
||||||
type: {:list, :string},
|
description:
|
||||||
description: "Force posts from the given instances to be visible by followers only",
|
"Force posts from the given instances to be visible by followers only and the reason for doing so"
|
||||||
suggestions: ["example.com", "*.example.com"]
|
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
key: :report_removal,
|
key: :report_removal,
|
||||||
type: {:list, :string},
|
description: "List of instances to reject reports from and the reason for doing so"
|
||||||
description: "List of instances to reject reports from",
|
|
||||||
suggestions: ["example.com", "*.example.com"]
|
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
key: :avatar_removal,
|
key: :avatar_removal,
|
||||||
type: {:list, :string},
|
description: "List of instances to strip avatars from and the reason for doing so"
|
||||||
description: "List of instances to strip avatars from",
|
|
||||||
suggestions: ["example.com", "*.example.com"]
|
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
key: :banner_removal,
|
key: :banner_removal,
|
||||||
type: {:list, :string},
|
description: "List of instances to strip banners from and the reason for doing so"
|
||||||
description: "List of instances to strip banners from",
|
|
||||||
suggestions: ["example.com", "*.example.com"]
|
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
key: :reject_deletes,
|
key: :reject_deletes,
|
||||||
type: {:list, :string},
|
description: "List of instances to reject deletions from and the reason for doing so"
|
||||||
description: "List of instances to reject deletions from",
|
|
||||||
suggestions: ["example.com", "*.example.com"]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|> Enum.map(fn setting ->
|
||||||
|
Map.merge(
|
||||||
|
setting,
|
||||||
|
%{
|
||||||
|
type: {:list, :tuple},
|
||||||
|
key_placeholder: "instance",
|
||||||
|
value_placeholder: "reason",
|
||||||
|
suggestions: [{"example.com", "Some reason"}, {"*.example.com", "Another reason"}]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
end)
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -38,9 +38,7 @@ defp steal_emoji({shortcode, url}, emoji_dir_path) do
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
Logger.debug(
|
Logger.debug(
|
||||||
"MRF.StealEmojiPolicy: :#{shortcode}: at #{url} (#{byte_size(response.body)} B) over size limit (#{
|
"MRF.StealEmojiPolicy: :#{shortcode}: at #{url} (#{byte_size(response.body)} B) over size limit (#{size_limit} B)"
|
||||||
size_limit
|
|
||||||
} B)"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
nil
|
nil
|
||||||
|
@ -92,6 +90,51 @@ def filter(%{"object" => %{"emoji" => foreign_emojis, "actor" => actor}} = messa
|
||||||
|
|
||||||
def filter(message), do: {:ok, message}
|
def filter(message), do: {:ok, message}
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
@spec config_description :: %{
|
||||||
|
children: [
|
||||||
|
%{
|
||||||
|
description: <<_::272, _::_*256>>,
|
||||||
|
key: :hosts | :rejected_shortcodes | :size_limit,
|
||||||
|
suggestions: [any(), ...],
|
||||||
|
type: {:list, :string} | {:list, :string} | :integer
|
||||||
|
},
|
||||||
|
...
|
||||||
|
],
|
||||||
|
description: <<_::448>>,
|
||||||
|
key: :mrf_steal_emoji,
|
||||||
|
label: <<_::80>>,
|
||||||
|
related_policy: <<_::352>>
|
||||||
|
}
|
||||||
|
def config_description do
|
||||||
|
%{
|
||||||
|
key: :mrf_steal_emoji,
|
||||||
|
related_policy: "Pleroma.Web.ActivityPub.MRF.StealEmojiPolicy",
|
||||||
|
label: "MRF Emojis",
|
||||||
|
description: "Steals emojis from selected instances when it sees them.",
|
||||||
|
children: [
|
||||||
|
%{
|
||||||
|
key: :hosts,
|
||||||
|
type: {:list, :string},
|
||||||
|
description: "List of hosts to steal emojis from",
|
||||||
|
suggestions: [""]
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
key: :rejected_shortcodes,
|
||||||
|
type: {:list, :string},
|
||||||
|
description: "Regex-list of shortcodes to reject",
|
||||||
|
suggestions: [""]
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
key: :size_limit,
|
||||||
|
type: :integer,
|
||||||
|
description: "File size limit (in bytes), checked before an emoji is saved to the disk",
|
||||||
|
suggestions: ["100000"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def describe do
|
def describe do
|
||||||
{:ok, %{}}
|
{:ok, %{}}
|
||||||
|
|
|
@ -23,9 +23,7 @@ defp lookup_subchain(actor) do
|
||||||
def filter(%{"actor" => actor} = message) do
|
def filter(%{"actor" => actor} = message) do
|
||||||
with {:ok, match, subchain} <- lookup_subchain(actor) do
|
with {:ok, match, subchain} <- lookup_subchain(actor) do
|
||||||
Logger.debug(
|
Logger.debug(
|
||||||
"[SubchainPolicy] Matched #{actor} against #{inspect(match)} with subchain #{
|
"[SubchainPolicy] Matched #{actor} against #{inspect(match)} with subchain #{inspect(subchain)}"
|
||||||
inspect(subchain)
|
|
||||||
}"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
MRF.filter(subchain, message)
|
MRF.filter(subchain, message)
|
||||||
|
|
|
@ -37,7 +37,7 @@ def filter(object), do: {:ok, object}
|
||||||
def describe do
|
def describe do
|
||||||
mrf_user_allowlist =
|
mrf_user_allowlist =
|
||||||
Config.get([:mrf_user_allowlist], [])
|
Config.get([:mrf_user_allowlist], [])
|
||||||
|> Enum.into(%{}, fn {k, v} -> {k, length(v)} end)
|
|> Map.new(fn {k, v} -> {k, length(v)} end)
|
||||||
|
|
||||||
{:ok, %{mrf_user_allowlist: mrf_user_allowlist}}
|
{:ok, %{mrf_user_allowlist: mrf_user_allowlist}}
|
||||||
end
|
end
|
||||||
|
|
|
@ -39,7 +39,7 @@ def filter(message), do: {:ok, message}
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def describe,
|
def describe,
|
||||||
do: {:ok, %{mrf_vocabulary: Pleroma.Config.get(:mrf_vocabulary) |> Enum.into(%{})}}
|
do: {:ok, %{mrf_vocabulary: Pleroma.Config.get(:mrf_vocabulary) |> Map.new()}}
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def config_description do
|
def config_description do
|
||||||
|
|
|
@ -213,6 +213,7 @@ def stringify_keys(%{__struct__: _} = object) do
|
||||||
|
|
||||||
def stringify_keys(object) when is_map(object) do
|
def stringify_keys(object) when is_map(object) do
|
||||||
object
|
object
|
||||||
|
|> Enum.filter(fn {_, v} -> v != nil end)
|
||||||
|> Map.new(fn {key, val} -> {to_string(key), stringify_keys(val)} end)
|
|> Map.new(fn {key, val} -> {to_string(key), stringify_keys(val)} end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,6 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AcceptRejectValidator do
|
||||||
use Ecto.Schema
|
use Ecto.Schema
|
||||||
|
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
|
||||||
|
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
|
import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
|
||||||
|
@ -14,12 +13,13 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AcceptRejectValidator do
|
||||||
@primary_key false
|
@primary_key false
|
||||||
|
|
||||||
embedded_schema do
|
embedded_schema do
|
||||||
field(:id, ObjectValidators.ObjectID, primary_key: true)
|
quote do
|
||||||
field(:type, :string)
|
unquote do
|
||||||
field(:object, ObjectValidators.ObjectID)
|
import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
|
||||||
field(:actor, ObjectValidators.ObjectID)
|
message_fields()
|
||||||
field(:to, ObjectValidators.Recipients, default: [])
|
activity_fields()
|
||||||
field(:cc, ObjectValidators.Recipients, default: [])
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def cast_data(data) do
|
def cast_data(data) do
|
||||||
|
|
|
@ -10,19 +10,20 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AddRemoveValidator do
|
||||||
|
|
||||||
require Pleroma.Constants
|
require Pleroma.Constants
|
||||||
|
|
||||||
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
|
||||||
@primary_key false
|
@primary_key false
|
||||||
|
|
||||||
embedded_schema do
|
embedded_schema do
|
||||||
field(:id, ObjectValidators.ObjectID, primary_key: true)
|
|
||||||
field(:target)
|
field(:target)
|
||||||
field(:object, ObjectValidators.ObjectID)
|
|
||||||
field(:actor, ObjectValidators.ObjectID)
|
quote do
|
||||||
field(:type)
|
unquote do
|
||||||
field(:to, ObjectValidators.Recipients, default: [])
|
import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
|
||||||
field(:cc, ObjectValidators.Recipients, default: [])
|
message_fields()
|
||||||
|
activity_fields()
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def cast_and_validate(data) do
|
def cast_and_validate(data) do
|
||||||
|
|
|
@ -20,13 +20,15 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AnnounceValidator do
|
||||||
@primary_key false
|
@primary_key false
|
||||||
|
|
||||||
embedded_schema do
|
embedded_schema do
|
||||||
field(:id, ObjectValidators.ObjectID, primary_key: true)
|
quote do
|
||||||
field(:type, :string)
|
unquote do
|
||||||
field(:object, ObjectValidators.ObjectID)
|
import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
|
||||||
field(:actor, ObjectValidators.ObjectID)
|
message_fields()
|
||||||
|
activity_fields()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
field(:context, :string)
|
field(:context, :string)
|
||||||
field(:to, ObjectValidators.Recipients, default: [])
|
|
||||||
field(:cc, ObjectValidators.Recipients, default: [])
|
|
||||||
field(:published, ObjectValidators.DateTime)
|
field(:published, ObjectValidators.DateTime)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -15,12 +15,13 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AnswerValidator do
|
||||||
@derive Jason.Encoder
|
@derive Jason.Encoder
|
||||||
|
|
||||||
embedded_schema do
|
embedded_schema do
|
||||||
field(:id, ObjectValidators.ObjectID, primary_key: true)
|
quote do
|
||||||
field(:to, ObjectValidators.Recipients, default: [])
|
unquote do
|
||||||
field(:cc, ObjectValidators.Recipients, default: [])
|
import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
|
||||||
field(:bto, ObjectValidators.Recipients, default: [])
|
message_fields()
|
||||||
field(:bcc, ObjectValidators.Recipients, default: [])
|
end
|
||||||
field(:type, :string)
|
end
|
||||||
|
|
||||||
field(:name, :string)
|
field(:name, :string)
|
||||||
field(:inReplyTo, ObjectValidators.ObjectID)
|
field(:inReplyTo, ObjectValidators.ObjectID)
|
||||||
field(:attributedTo, ObjectValidators.ObjectID)
|
field(:attributedTo, ObjectValidators.ObjectID)
|
||||||
|
|
|
@ -6,10 +6,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidator do
|
||||||
use Ecto.Schema
|
use Ecto.Schema
|
||||||
|
|
||||||
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
||||||
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
|
||||||
|
@ -18,38 +16,14 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidator do
|
||||||
@derive Jason.Encoder
|
@derive Jason.Encoder
|
||||||
|
|
||||||
embedded_schema do
|
embedded_schema do
|
||||||
field(:id, ObjectValidators.ObjectID, primary_key: true)
|
quote do
|
||||||
field(:to, ObjectValidators.Recipients, default: [])
|
unquote do
|
||||||
field(:cc, ObjectValidators.Recipients, default: [])
|
import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
|
||||||
field(:bto, ObjectValidators.Recipients, default: [])
|
message_fields()
|
||||||
field(:bcc, ObjectValidators.Recipients, default: [])
|
object_fields()
|
||||||
embeds_many(:tag, TagValidator)
|
status_object_fields()
|
||||||
field(:type, :string)
|
end
|
||||||
|
end
|
||||||
field(:name, :string)
|
|
||||||
field(:summary, :string)
|
|
||||||
field(:content, :string)
|
|
||||||
|
|
||||||
field(:context, :string)
|
|
||||||
# short identifier for PleromaFE to group statuses by context
|
|
||||||
field(:context_id, :integer)
|
|
||||||
|
|
||||||
# TODO: Remove actor on objects
|
|
||||||
field(:actor, ObjectValidators.ObjectID)
|
|
||||||
|
|
||||||
field(:attributedTo, ObjectValidators.ObjectID)
|
|
||||||
field(:published, ObjectValidators.DateTime)
|
|
||||||
field(:emoji, ObjectValidators.Emoji, default: %{})
|
|
||||||
field(:sensitive, :boolean, default: false)
|
|
||||||
embeds_many(:attachment, AttachmentValidator)
|
|
||||||
field(:replies_count, :integer, default: 0)
|
|
||||||
field(:like_count, :integer, default: 0)
|
|
||||||
field(:announcement_count, :integer, default: 0)
|
|
||||||
field(:inReplyTo, ObjectValidators.ObjectID)
|
|
||||||
field(:url, ObjectValidators.Uri)
|
|
||||||
|
|
||||||
field(:likes, {:array, ObjectValidators.ObjectID}, default: [])
|
|
||||||
field(:announcements, {:array, ObjectValidators.ObjectID}, default: [])
|
|
||||||
|
|
||||||
field(:replies, {:array, ObjectValidators.ObjectID}, default: [])
|
field(:replies, {:array, ObjectValidators.ObjectID}, default: [])
|
||||||
end
|
end
|
||||||
|
|
|
@ -68,12 +68,14 @@ def fix_media_type(data) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp handle_href(href, mediaType) do
|
defp handle_href(href, mediaType, data) do
|
||||||
[
|
[
|
||||||
%{
|
%{
|
||||||
"href" => href,
|
"href" => href,
|
||||||
"type" => "Link",
|
"type" => "Link",
|
||||||
"mediaType" => mediaType
|
"mediaType" => mediaType,
|
||||||
|
"width" => data["width"],
|
||||||
|
"height" => data["height"]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
@ -81,10 +83,10 @@ defp handle_href(href, mediaType) do
|
||||||
defp fix_url(data) do
|
defp fix_url(data) do
|
||||||
cond do
|
cond do
|
||||||
is_binary(data["url"]) ->
|
is_binary(data["url"]) ->
|
||||||
Map.put(data, "url", handle_href(data["url"], data["mediaType"]))
|
Map.put(data, "url", handle_href(data["url"], data["mediaType"], data))
|
||||||
|
|
||||||
is_binary(data["href"]) and data["url"] == nil ->
|
is_binary(data["href"]) and data["url"] == nil ->
|
||||||
Map.put(data, "url", handle_href(data["href"], data["mediaType"]))
|
Map.put(data, "url", handle_href(data["href"], data["mediaType"], data))
|
||||||
|
|
||||||
true ->
|
true ->
|
||||||
data
|
data
|
||||||
|
|
|
@ -5,11 +5,8 @@
|
||||||
defmodule Pleroma.Web.ActivityPub.ObjectValidators.AudioVideoValidator do
|
defmodule Pleroma.Web.ActivityPub.ObjectValidators.AudioVideoValidator do
|
||||||
use Ecto.Schema
|
use Ecto.Schema
|
||||||
|
|
||||||
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
|
||||||
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
|
||||||
|
@ -18,38 +15,14 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AudioVideoValidator do
|
||||||
@derive Jason.Encoder
|
@derive Jason.Encoder
|
||||||
|
|
||||||
embedded_schema do
|
embedded_schema do
|
||||||
field(:id, ObjectValidators.ObjectID, primary_key: true)
|
quote do
|
||||||
field(:to, ObjectValidators.Recipients, default: [])
|
unquote do
|
||||||
field(:cc, ObjectValidators.Recipients, default: [])
|
import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
|
||||||
field(:bto, ObjectValidators.Recipients, default: [])
|
message_fields()
|
||||||
field(:bcc, ObjectValidators.Recipients, default: [])
|
object_fields()
|
||||||
embeds_many(:tag, TagValidator)
|
status_object_fields()
|
||||||
field(:type, :string)
|
end
|
||||||
|
end
|
||||||
field(:name, :string)
|
|
||||||
field(:summary, :string)
|
|
||||||
field(:content, :string)
|
|
||||||
|
|
||||||
field(:context, :string)
|
|
||||||
# short identifier for PleromaFE to group statuses by context
|
|
||||||
field(:context_id, :integer)
|
|
||||||
|
|
||||||
# TODO: Remove actor on objects
|
|
||||||
field(:actor, ObjectValidators.ObjectID)
|
|
||||||
|
|
||||||
field(:attributedTo, ObjectValidators.ObjectID)
|
|
||||||
field(:published, ObjectValidators.DateTime)
|
|
||||||
field(:emoji, ObjectValidators.Emoji, default: %{})
|
|
||||||
field(:sensitive, :boolean, default: false)
|
|
||||||
embeds_many(:attachment, AttachmentValidator)
|
|
||||||
field(:replies_count, :integer, default: 0)
|
|
||||||
field(:like_count, :integer, default: 0)
|
|
||||||
field(:announcement_count, :integer, default: 0)
|
|
||||||
field(:inReplyTo, ObjectValidators.ObjectID)
|
|
||||||
field(:url, ObjectValidators.Uri)
|
|
||||||
|
|
||||||
field(:likes, {:array, ObjectValidators.ObjectID}, default: [])
|
|
||||||
field(:announcements, {:array, ObjectValidators.ObjectID}, default: [])
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def cast_and_apply(data) do
|
def cast_and_apply(data) do
|
||||||
|
|
|
@ -5,20 +5,21 @@
|
||||||
defmodule Pleroma.Web.ActivityPub.ObjectValidators.BlockValidator do
|
defmodule Pleroma.Web.ActivityPub.ObjectValidators.BlockValidator do
|
||||||
use Ecto.Schema
|
use Ecto.Schema
|
||||||
|
|
||||||
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
alias Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
|
||||||
|
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
|
|
||||||
|
|
||||||
@primary_key false
|
@primary_key false
|
||||||
|
@derive Jason.Encoder
|
||||||
|
|
||||||
embedded_schema do
|
embedded_schema do
|
||||||
field(:id, ObjectValidators.ObjectID, primary_key: true)
|
quote do
|
||||||
field(:type, :string)
|
unquote do
|
||||||
field(:actor, ObjectValidators.ObjectID)
|
import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
|
||||||
field(:to, ObjectValidators.Recipients, default: [])
|
message_fields()
|
||||||
field(:cc, ObjectValidators.Recipients, default: [])
|
activity_fields()
|
||||||
field(:object, ObjectValidators.ObjectID)
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def cast_data(data) do
|
def cast_data(data) do
|
||||||
|
@ -30,8 +31,8 @@ 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"])
|
||||||
|> validate_actor_presence()
|
|> CommonValidations.validate_actor_presence()
|
||||||
|> validate_actor_presence(field_name: :object)
|
|> CommonValidations.validate_actor_presence(field_name: :object)
|
||||||
end
|
end
|
||||||
|
|
||||||
def cast_and_validate(data) do
|
def cast_and_validate(data) do
|
||||||
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
# 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.CommonFields do
|
||||||
|
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
||||||
|
alias Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator
|
||||||
|
alias Pleroma.Web.ActivityPub.ObjectValidators.TagValidator
|
||||||
|
|
||||||
|
# Activities and Objects, except (Create)ChatMessage
|
||||||
|
defmacro message_fields do
|
||||||
|
quote bind_quoted: binding() do
|
||||||
|
field(:type, :string)
|
||||||
|
field(:id, ObjectValidators.ObjectID, primary_key: true)
|
||||||
|
|
||||||
|
field(:to, ObjectValidators.Recipients, default: [])
|
||||||
|
field(:cc, ObjectValidators.Recipients, default: [])
|
||||||
|
field(:bto, ObjectValidators.Recipients, default: [])
|
||||||
|
field(:bcc, ObjectValidators.Recipients, default: [])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defmacro activity_fields do
|
||||||
|
quote bind_quoted: binding() do
|
||||||
|
field(:object, ObjectValidators.ObjectID)
|
||||||
|
field(:actor, ObjectValidators.ObjectID)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# All objects except Answer and CHatMessage
|
||||||
|
defmacro object_fields do
|
||||||
|
quote bind_quoted: binding() do
|
||||||
|
field(:content, :string)
|
||||||
|
|
||||||
|
field(:published, ObjectValidators.DateTime)
|
||||||
|
field(:emoji, ObjectValidators.Emoji, default: %{})
|
||||||
|
embeds_many(:attachment, AttachmentValidator)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Basically objects that aren't ChatMessage and Answer
|
||||||
|
defmacro status_object_fields do
|
||||||
|
quote bind_quoted: binding() do
|
||||||
|
# TODO: Remove actor on objects
|
||||||
|
field(:actor, ObjectValidators.ObjectID)
|
||||||
|
field(:attributedTo, ObjectValidators.ObjectID)
|
||||||
|
|
||||||
|
embeds_many(:tag, TagValidator)
|
||||||
|
|
||||||
|
field(:name, :string)
|
||||||
|
field(:summary, :string)
|
||||||
|
|
||||||
|
field(:context, :string)
|
||||||
|
# short identifier for PleromaFE to group statuses by context
|
||||||
|
field(:context_id, :integer)
|
||||||
|
|
||||||
|
field(:sensitive, :boolean, default: false)
|
||||||
|
field(:replies_count, :integer, default: 0)
|
||||||
|
field(:like_count, :integer, default: 0)
|
||||||
|
field(:announcement_count, :integer, default: 0)
|
||||||
|
field(:inReplyTo, ObjectValidators.ObjectID)
|
||||||
|
field(:url, ObjectValidators.Uri)
|
||||||
|
|
||||||
|
field(:likes, {:array, ObjectValidators.ObjectID}, default: [])
|
||||||
|
field(:announcements, {:array, ObjectValidators.ObjectID}, default: [])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -17,11 +17,16 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CreateChatMessageValidator do
|
||||||
@primary_key false
|
@primary_key false
|
||||||
|
|
||||||
embedded_schema do
|
embedded_schema do
|
||||||
|
quote do
|
||||||
|
unquote do
|
||||||
|
import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
|
||||||
|
activity_fields()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
field(:id, ObjectValidators.ObjectID, primary_key: true)
|
field(:id, ObjectValidators.ObjectID, primary_key: true)
|
||||||
field(:actor, ObjectValidators.ObjectID)
|
|
||||||
field(:type, :string)
|
field(:type, :string)
|
||||||
field(:to, ObjectValidators.Recipients, default: [])
|
field(:to, ObjectValidators.Recipients, default: [])
|
||||||
field(:object, ObjectValidators.ObjectID)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def cast_and_apply(data) do
|
def cast_and_apply(data) do
|
||||||
|
|
|
@ -20,14 +20,14 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CreateGenericValidator do
|
||||||
@primary_key false
|
@primary_key false
|
||||||
|
|
||||||
embedded_schema do
|
embedded_schema do
|
||||||
field(:id, ObjectValidators.ObjectID, primary_key: true)
|
quote do
|
||||||
field(:actor, ObjectValidators.ObjectID)
|
unquote do
|
||||||
field(:type, :string)
|
import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
|
||||||
field(:to, ObjectValidators.Recipients, default: [])
|
message_fields()
|
||||||
field(:cc, ObjectValidators.Recipients, default: [])
|
activity_fields()
|
||||||
field(:bto, ObjectValidators.Recipients, default: [])
|
end
|
||||||
field(:bcc, ObjectValidators.Recipients, default: [])
|
end
|
||||||
field(:object, ObjectValidators.ObjectID)
|
|
||||||
field(:expires_at, ObjectValidators.DateTime)
|
field(:expires_at, ObjectValidators.DateTime)
|
||||||
|
|
||||||
# Should be moved to object, done for CommonAPI.Utils.make_context
|
# Should be moved to object, done for CommonAPI.Utils.make_context
|
||||||
|
|
|
@ -15,13 +15,15 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.DeleteValidator do
|
||||||
@primary_key false
|
@primary_key false
|
||||||
|
|
||||||
embedded_schema do
|
embedded_schema do
|
||||||
field(:id, ObjectValidators.ObjectID, primary_key: true)
|
quote do
|
||||||
field(:type, :string)
|
unquote do
|
||||||
field(:actor, ObjectValidators.ObjectID)
|
import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
|
||||||
field(:to, ObjectValidators.Recipients, default: [])
|
message_fields()
|
||||||
field(:cc, ObjectValidators.Recipients, default: [])
|
activity_fields()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
field(:deleted_activity_id, ObjectValidators.ObjectID)
|
field(:deleted_activity_id, ObjectValidators.ObjectID)
|
||||||
field(:object, ObjectValidators.ObjectID)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def cast_data(data) do
|
def cast_data(data) do
|
||||||
|
|
|
@ -5,7 +5,6 @@
|
||||||
defmodule Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator do
|
defmodule Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator do
|
||||||
use Ecto.Schema
|
use Ecto.Schema
|
||||||
|
|
||||||
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
|
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
|
||||||
|
|
||||||
|
@ -15,14 +14,16 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator do
|
||||||
@primary_key false
|
@primary_key false
|
||||||
|
|
||||||
embedded_schema do
|
embedded_schema do
|
||||||
field(:id, ObjectValidators.ObjectID, primary_key: true)
|
quote do
|
||||||
field(:type, :string)
|
unquote do
|
||||||
field(:object, ObjectValidators.ObjectID)
|
import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
|
||||||
field(:actor, ObjectValidators.ObjectID)
|
message_fields()
|
||||||
|
activity_fields()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
field(:context, :string)
|
field(:context, :string)
|
||||||
field(:content, :string)
|
field(:content, :string)
|
||||||
field(:to, ObjectValidators.Recipients, default: [])
|
|
||||||
field(:cc, ObjectValidators.Recipients, default: [])
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def cast_and_validate(data) do
|
def cast_and_validate(data) do
|
||||||
|
|
|
@ -5,11 +5,8 @@
|
||||||
defmodule Pleroma.Web.ActivityPub.ObjectValidators.EventValidator do
|
defmodule Pleroma.Web.ActivityPub.ObjectValidators.EventValidator do
|
||||||
use Ecto.Schema
|
use Ecto.Schema
|
||||||
|
|
||||||
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
|
||||||
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
|
||||||
|
@ -19,38 +16,14 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EventValidator do
|
||||||
|
|
||||||
# Extends from NoteValidator
|
# Extends from NoteValidator
|
||||||
embedded_schema do
|
embedded_schema do
|
||||||
field(:id, ObjectValidators.ObjectID, primary_key: true)
|
quote do
|
||||||
field(:to, ObjectValidators.Recipients, default: [])
|
unquote do
|
||||||
field(:cc, ObjectValidators.Recipients, default: [])
|
import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
|
||||||
field(:bto, ObjectValidators.Recipients, default: [])
|
message_fields()
|
||||||
field(:bcc, ObjectValidators.Recipients, default: [])
|
object_fields()
|
||||||
embeds_many(:tag, TagValidator)
|
status_object_fields()
|
||||||
field(:type, :string)
|
end
|
||||||
|
end
|
||||||
field(:name, :string)
|
|
||||||
field(:summary, :string)
|
|
||||||
field(:content, :string)
|
|
||||||
|
|
||||||
field(:context, :string)
|
|
||||||
# short identifier for PleromaFE to group statuses by context
|
|
||||||
field(:context_id, :integer)
|
|
||||||
|
|
||||||
# TODO: Remove actor on objects
|
|
||||||
field(:actor, ObjectValidators.ObjectID)
|
|
||||||
|
|
||||||
field(:attributedTo, ObjectValidators.ObjectID)
|
|
||||||
field(:published, ObjectValidators.DateTime)
|
|
||||||
field(:emoji, ObjectValidators.Emoji, default: %{})
|
|
||||||
field(:sensitive, :boolean, default: false)
|
|
||||||
embeds_many(:attachment, AttachmentValidator)
|
|
||||||
field(:replies_count, :integer, default: 0)
|
|
||||||
field(:like_count, :integer, default: 0)
|
|
||||||
field(:announcement_count, :integer, default: 0)
|
|
||||||
field(:inReplyTo, ObjectValidators.ObjectID)
|
|
||||||
field(:url, ObjectValidators.Uri)
|
|
||||||
|
|
||||||
field(:likes, {:array, ObjectValidators.ObjectID}, default: [])
|
|
||||||
field(:announcements, {:array, ObjectValidators.ObjectID}, default: [])
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def cast_and_apply(data) do
|
def cast_and_apply(data) do
|
||||||
|
|
|
@ -5,20 +5,20 @@
|
||||||
defmodule Pleroma.Web.ActivityPub.ObjectValidators.FollowValidator do
|
defmodule Pleroma.Web.ActivityPub.ObjectValidators.FollowValidator do
|
||||||
use Ecto.Schema
|
use Ecto.Schema
|
||||||
|
|
||||||
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
|
||||||
|
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
|
import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
|
||||||
|
|
||||||
@primary_key false
|
@primary_key false
|
||||||
|
|
||||||
embedded_schema do
|
embedded_schema do
|
||||||
field(:id, ObjectValidators.ObjectID, primary_key: true)
|
quote do
|
||||||
field(:type, :string)
|
unquote do
|
||||||
field(:actor, ObjectValidators.ObjectID)
|
import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
|
||||||
field(:to, ObjectValidators.Recipients, default: [])
|
message_fields()
|
||||||
field(:cc, ObjectValidators.Recipients, default: [])
|
activity_fields()
|
||||||
field(:object, ObjectValidators.ObjectID)
|
end
|
||||||
|
end
|
||||||
|
|
||||||
field(:state, :string, default: "pending")
|
field(:state, :string, default: "pending")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,6 @@
|
||||||
defmodule Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator do
|
defmodule Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator do
|
||||||
use Ecto.Schema
|
use Ecto.Schema
|
||||||
|
|
||||||
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
|
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
|
||||||
alias Pleroma.Web.ActivityPub.Utils
|
alias Pleroma.Web.ActivityPub.Utils
|
||||||
|
@ -16,13 +15,15 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator do
|
||||||
@primary_key false
|
@primary_key false
|
||||||
|
|
||||||
embedded_schema do
|
embedded_schema do
|
||||||
field(:id, ObjectValidators.ObjectID, primary_key: true)
|
quote do
|
||||||
field(:type, :string)
|
unquote do
|
||||||
field(:object, ObjectValidators.ObjectID)
|
import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
|
||||||
field(:actor, ObjectValidators.ObjectID)
|
message_fields()
|
||||||
|
activity_fields()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
field(:context, :string)
|
field(:context, :string)
|
||||||
field(:to, ObjectValidators.Recipients, default: [])
|
|
||||||
field(:cc, ObjectValidators.Recipients, default: [])
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def cast_and_validate(data) do
|
def cast_and_validate(data) do
|
||||||
|
|
|
@ -6,11 +6,9 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.QuestionValidator do
|
||||||
use Ecto.Schema
|
use Ecto.Schema
|
||||||
|
|
||||||
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
||||||
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.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
|
||||||
|
@ -20,35 +18,14 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.QuestionValidator do
|
||||||
|
|
||||||
# Extends from NoteValidator
|
# Extends from NoteValidator
|
||||||
embedded_schema do
|
embedded_schema do
|
||||||
field(:id, ObjectValidators.ObjectID, primary_key: true)
|
quote do
|
||||||
field(:to, ObjectValidators.Recipients, default: [])
|
unquote do
|
||||||
field(:cc, ObjectValidators.Recipients, default: [])
|
import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
|
||||||
field(:bto, ObjectValidators.Recipients, default: [])
|
message_fields()
|
||||||
field(:bcc, ObjectValidators.Recipients, default: [])
|
object_fields()
|
||||||
embeds_many(:tag, TagValidator)
|
status_object_fields()
|
||||||
field(:type, :string)
|
end
|
||||||
field(:content, :string)
|
end
|
||||||
field(:context, :string)
|
|
||||||
|
|
||||||
# TODO: Remove actor on objects
|
|
||||||
field(:actor, ObjectValidators.ObjectID)
|
|
||||||
|
|
||||||
field(:attributedTo, ObjectValidators.ObjectID)
|
|
||||||
field(:summary, :string)
|
|
||||||
field(:published, ObjectValidators.DateTime)
|
|
||||||
field(:emoji, ObjectValidators.Emoji, default: %{})
|
|
||||||
field(:sensitive, :boolean, default: false)
|
|
||||||
embeds_many(:attachment, AttachmentValidator)
|
|
||||||
field(:replies_count, :integer, default: 0)
|
|
||||||
field(:like_count, :integer, default: 0)
|
|
||||||
field(:announcement_count, :integer, default: 0)
|
|
||||||
field(:inReplyTo, ObjectValidators.ObjectID)
|
|
||||||
field(:url, ObjectValidators.Uri)
|
|
||||||
# short identifier for PleromaFE to group statuses by context
|
|
||||||
field(:context_id, :integer)
|
|
||||||
|
|
||||||
field(:likes, {:array, ObjectValidators.ObjectID}, default: [])
|
|
||||||
field(:announcements, {:array, ObjectValidators.ObjectID}, default: [])
|
|
||||||
|
|
||||||
field(:closed, ObjectValidators.DateTime)
|
field(:closed, ObjectValidators.DateTime)
|
||||||
field(:voters, {:array, ObjectValidators.ObjectID}, default: [])
|
field(:voters, {:array, ObjectValidators.ObjectID}, default: [])
|
||||||
|
|
|
@ -6,7 +6,6 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.UndoValidator do
|
||||||
use Ecto.Schema
|
use Ecto.Schema
|
||||||
|
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
|
@ -15,12 +14,13 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.UndoValidator do
|
||||||
@primary_key false
|
@primary_key false
|
||||||
|
|
||||||
embedded_schema do
|
embedded_schema do
|
||||||
field(:id, ObjectValidators.ObjectID, primary_key: true)
|
quote do
|
||||||
field(:type, :string)
|
unquote do
|
||||||
field(:object, ObjectValidators.ObjectID)
|
import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
|
||||||
field(:actor, ObjectValidators.ObjectID)
|
message_fields()
|
||||||
field(:to, ObjectValidators.Recipients, default: [])
|
activity_fields()
|
||||||
field(:cc, ObjectValidators.Recipients, default: [])
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def cast_and_validate(data) do
|
def cast_and_validate(data) do
|
||||||
|
|
|
@ -13,11 +13,14 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.UpdateValidator do
|
||||||
@primary_key false
|
@primary_key false
|
||||||
|
|
||||||
embedded_schema do
|
embedded_schema do
|
||||||
field(:id, ObjectValidators.ObjectID, primary_key: true)
|
quote do
|
||||||
field(:type, :string)
|
unquote do
|
||||||
|
import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
|
||||||
|
message_fields()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
field(:actor, ObjectValidators.ObjectID)
|
field(:actor, ObjectValidators.ObjectID)
|
||||||
field(:to, ObjectValidators.Recipients, default: [])
|
|
||||||
field(:cc, ObjectValidators.Recipients, default: [])
|
|
||||||
# In this case, we save the full object in this activity instead of just a
|
# In this case, we save the full object in this activity instead of just a
|
||||||
# reference, so we can always see what was actually changed by this.
|
# reference, so we can always see what was actually changed by this.
|
||||||
field(:object, :map)
|
field(:object, :map)
|
||||||
|
|
|
@ -63,8 +63,7 @@ def publish_one(%{inbox: inbox, json: json, actor: %User{} = actor, id: id} = pa
|
||||||
date: date
|
date: date
|
||||||
})
|
})
|
||||||
|
|
||||||
with {:ok, %{status: code}} when code in 200..299 <-
|
with {:ok, %{status: code}} = result when code in 200..299 <-
|
||||||
result =
|
|
||||||
HTTP.post(
|
HTTP.post(
|
||||||
inbox,
|
inbox,
|
||||||
json,
|
json,
|
||||||
|
@ -112,6 +111,7 @@ defp should_federate?(inbox, public) do
|
||||||
|
|
||||||
quarantined_instances =
|
quarantined_instances =
|
||||||
Config.get([:instance, :quarantined_instances], [])
|
Config.get([:instance, :quarantined_instances], [])
|
||||||
|
|> Pleroma.Web.ActivityPub.MRF.instance_list_from_tuples()
|
||||||
|> Pleroma.Web.ActivityPub.MRF.subdomains_regex()
|
|> Pleroma.Web.ActivityPub.MRF.subdomains_regex()
|
||||||
|
|
||||||
!Pleroma.Web.ActivityPub.MRF.subdomain_match?(quarantined_instances, host)
|
!Pleroma.Web.ActivityPub.MRF.subdomain_match?(quarantined_instances, host)
|
||||||
|
|
|
@ -10,7 +10,6 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
|
||||||
collection, and so on.
|
collection, and so on.
|
||||||
"""
|
"""
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
alias Pleroma.Activity.Ir.Topics
|
|
||||||
alias Pleroma.Chat
|
alias Pleroma.Chat
|
||||||
alias Pleroma.Chat.MessageReference
|
alias Pleroma.Chat.MessageReference
|
||||||
alias Pleroma.FollowingRelationship
|
alias Pleroma.FollowingRelationship
|
||||||
|
@ -24,6 +23,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
|
||||||
alias Pleroma.Web.ActivityPub.Utils
|
alias Pleroma.Web.ActivityPub.Utils
|
||||||
alias Pleroma.Web.Push
|
alias Pleroma.Web.Push
|
||||||
alias Pleroma.Web.Streamer
|
alias Pleroma.Web.Streamer
|
||||||
|
alias Pleroma.Workers.PollWorker
|
||||||
|
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
|
@ -195,12 +195,12 @@ def handle(%{data: %{"type" => "Like"}} = object, meta) do
|
||||||
# - Set up notifications
|
# - Set up notifications
|
||||||
@impl true
|
@impl true
|
||||||
def handle(%{data: %{"type" => "Create"}} = activity, meta) do
|
def handle(%{data: %{"type" => "Create"}} = activity, meta) do
|
||||||
with {:ok, object, meta} <- handle_object_creation(meta[:object_data], meta),
|
with {:ok, object, meta} <- handle_object_creation(meta[:object_data], activity, meta),
|
||||||
%User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
|
%User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
|
||||||
{:ok, notifications} = Notification.create_notifications(activity, do_send: false)
|
{:ok, notifications} = Notification.create_notifications(activity, do_send: false)
|
||||||
{:ok, _user} = ActivityPub.increase_note_count_if_public(user, object)
|
{:ok, _user} = ActivityPub.increase_note_count_if_public(user, object)
|
||||||
|
|
||||||
if in_reply_to = object.data["inReplyTo"] && object.data["type"] != "Answer" do
|
if in_reply_to = object.data["type"] != "Answer" && object.data["inReplyTo"] do
|
||||||
Object.increase_replies_count(in_reply_to)
|
Object.increase_replies_count(in_reply_to)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -225,6 +225,8 @@ def handle(%{data: %{"type" => "Create"}} = activity, meta) do
|
||||||
meta
|
meta
|
||||||
|> add_notifications(notifications)
|
|> add_notifications(notifications)
|
||||||
|
|
||||||
|
ap_streamer().stream_out(activity)
|
||||||
|
|
||||||
{:ok, activity, meta}
|
{:ok, activity, meta}
|
||||||
else
|
else
|
||||||
e -> Repo.rollback(e)
|
e -> Repo.rollback(e)
|
||||||
|
@ -245,9 +247,7 @@ def handle(%{data: %{"type" => "Announce"}} = object, meta) do
|
||||||
if !User.is_internal_user?(user) do
|
if !User.is_internal_user?(user) do
|
||||||
Notification.create_notifications(object)
|
Notification.create_notifications(object)
|
||||||
|
|
||||||
object
|
ap_streamer().stream_out(object)
|
||||||
|> Topics.get_activity_topics()
|
|
||||||
|> Streamer.stream(object)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
{:ok, object, meta}
|
{:ok, object, meta}
|
||||||
|
@ -389,7 +389,7 @@ def handle(object, meta) do
|
||||||
{:ok, object, meta}
|
{:ok, object, meta}
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_object_creation(%{"type" => "ChatMessage"} = object, meta) do
|
def handle_object_creation(%{"type" => "ChatMessage"} = object, _activity, meta) do
|
||||||
with {:ok, object, meta} <- Pipeline.common_pipeline(object, meta) do
|
with {:ok, object, meta} <- Pipeline.common_pipeline(object, meta) do
|
||||||
actor = User.get_cached_by_ap_id(object.data["actor"])
|
actor = User.get_cached_by_ap_id(object.data["actor"])
|
||||||
recipient = User.get_cached_by_ap_id(hd(object.data["to"]))
|
recipient = User.get_cached_by_ap_id(hd(object.data["to"]))
|
||||||
|
@ -424,7 +424,14 @@ def handle_object_creation(%{"type" => "ChatMessage"} = object, meta) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_object_creation(%{"type" => "Answer"} = object_map, meta) do
|
def handle_object_creation(%{"type" => "Question"} = object, activity, meta) do
|
||||||
|
with {:ok, object, meta} <- Pipeline.common_pipeline(object, meta) do
|
||||||
|
PollWorker.schedule_poll_end(activity)
|
||||||
|
{:ok, object, meta}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_object_creation(%{"type" => "Answer"} = object_map, _activity, meta) do
|
||||||
with {:ok, object, meta} <- Pipeline.common_pipeline(object_map, meta) do
|
with {:ok, object, meta} <- Pipeline.common_pipeline(object_map, meta) do
|
||||||
Object.increase_vote_count(
|
Object.increase_vote_count(
|
||||||
object.data["inReplyTo"],
|
object.data["inReplyTo"],
|
||||||
|
@ -436,15 +443,15 @@ def handle_object_creation(%{"type" => "Answer"} = object_map, meta) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_object_creation(%{"type" => objtype} = object, meta)
|
def handle_object_creation(%{"type" => objtype} = object, _activity, meta)
|
||||||
when objtype in ~w[Audio Video Question Event Article Note Page] do
|
when objtype in ~w[Audio Video Event Article Note Page] 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
|
||||||
end
|
end
|
||||||
|
|
||||||
# Nothing to do
|
# Nothing to do
|
||||||
def handle_object_creation(object, meta) do
|
def handle_object_creation(object, _activity, meta) do
|
||||||
{:ok, object, meta}
|
{:ok, object, meta}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -49,7 +49,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
||||||
plug(
|
plug(
|
||||||
OAuthScopesPlug,
|
OAuthScopesPlug,
|
||||||
%{scopes: ["admin:read:statuses"]}
|
%{scopes: ["admin:read:statuses"]}
|
||||||
when action in [:list_user_statuses, :list_instance_statuses]
|
when action in [:list_user_statuses]
|
||||||
)
|
)
|
||||||
|
|
||||||
plug(
|
plug(
|
||||||
|
@ -81,24 +81,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
||||||
|
|
||||||
action_fallback(AdminAPI.FallbackController)
|
action_fallback(AdminAPI.FallbackController)
|
||||||
|
|
||||||
def list_instance_statuses(conn, %{"instance" => instance} = params) do
|
|
||||||
with_reblogs = params["with_reblogs"] == "true" || params["with_reblogs"] == true
|
|
||||||
{page, page_size} = page_params(params)
|
|
||||||
|
|
||||||
result =
|
|
||||||
ActivityPub.fetch_statuses(nil, %{
|
|
||||||
instance: instance,
|
|
||||||
limit: page_size,
|
|
||||||
offset: (page - 1) * page_size,
|
|
||||||
exclude_reblogs: not with_reblogs,
|
|
||||||
total: true
|
|
||||||
})
|
|
||||||
|
|
||||||
conn
|
|
||||||
|> put_view(AdminAPI.StatusView)
|
|
||||||
|> render("index.json", %{total: result[:total], activities: result[:items], as: :activity})
|
|
||||||
end
|
|
||||||
|
|
||||||
def list_user_statuses(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname} = params) do
|
def list_user_statuses(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname} = params) do
|
||||||
with_reblogs = params["with_reblogs"] == "true" || params["with_reblogs"] == true
|
with_reblogs = params["with_reblogs"] == "true" || params["with_reblogs"] == true
|
||||||
godmode = params["godmode"] == "true" || params["godmode"] == true
|
godmode = params["godmode"] == "true" || params["godmode"] == true
|
||||||
|
|
|
@ -35,6 +35,12 @@ def install(%{body_params: params} = conn, _params) do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp installed do
|
defp installed do
|
||||||
File.ls!(Pleroma.Frontend.dir())
|
frontend_directory = Pleroma.Frontend.dir()
|
||||||
|
|
||||||
|
if File.exists?(frontend_directory) do
|
||||||
|
File.ls!(frontend_directory)
|
||||||
|
else
|
||||||
|
[]
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
63
lib/pleroma/web/admin_api/controllers/instance_controller.ex
Normal file
63
lib/pleroma/web/admin_api/controllers/instance_controller.ex
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
# 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.InstanceController do
|
||||||
|
use Pleroma.Web, :controller
|
||||||
|
|
||||||
|
import Pleroma.Web.ControllerHelper, only: [fetch_integer_param: 3]
|
||||||
|
|
||||||
|
alias Pleroma.Instances.Instance
|
||||||
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||||
|
alias Pleroma.Web.AdminAPI
|
||||||
|
alias Pleroma.Web.Plugs.OAuthScopesPlug
|
||||||
|
|
||||||
|
require Logger
|
||||||
|
|
||||||
|
@default_page_size 50
|
||||||
|
|
||||||
|
plug(
|
||||||
|
OAuthScopesPlug,
|
||||||
|
%{scopes: ["admin:read:statuses"]}
|
||||||
|
when action in [:list_statuses]
|
||||||
|
)
|
||||||
|
|
||||||
|
plug(
|
||||||
|
OAuthScopesPlug,
|
||||||
|
%{scopes: ["admin:write:accounts", "admin:write:statuses"]}
|
||||||
|
when action in [:delete]
|
||||||
|
)
|
||||||
|
|
||||||
|
action_fallback(AdminAPI.FallbackController)
|
||||||
|
|
||||||
|
def list_statuses(conn, %{"instance" => instance} = params) do
|
||||||
|
with_reblogs = params["with_reblogs"] == "true" || params["with_reblogs"] == true
|
||||||
|
{page, page_size} = page_params(params)
|
||||||
|
|
||||||
|
result =
|
||||||
|
ActivityPub.fetch_statuses(nil, %{
|
||||||
|
instance: instance,
|
||||||
|
limit: page_size,
|
||||||
|
offset: (page - 1) * page_size,
|
||||||
|
exclude_reblogs: not with_reblogs,
|
||||||
|
total: true
|
||||||
|
})
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> put_view(AdminAPI.StatusView)
|
||||||
|
|> render("index.json", %{total: result[:total], activities: result[:items], as: :activity})
|
||||||
|
end
|
||||||
|
|
||||||
|
def delete(conn, %{"instance" => instance}) do
|
||||||
|
with {:ok, _job} <- Instance.delete_users_and_activities(instance) do
|
||||||
|
json(conn, instance)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp page_params(params) do
|
||||||
|
{
|
||||||
|
fetch_integer_param(params, "page", 1),
|
||||||
|
fetch_integer_param(params, "page_size", @default_page_size)
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
|
@ -35,7 +35,9 @@ defmodule Pleroma.Web.AdminAPI.UserController do
|
||||||
:toggle_activation,
|
:toggle_activation,
|
||||||
:activate,
|
:activate,
|
||||||
:deactivate,
|
:deactivate,
|
||||||
:approve
|
:approve,
|
||||||
|
:suggest,
|
||||||
|
:unsuggest
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -239,6 +241,32 @@ def approve(%{assigns: %{user: admin}, body_params: %{nicknames: nicknames}} = c
|
||||||
render(conn, "index.json", users: updated_users)
|
render(conn, "index.json", users: updated_users)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def suggest(%{assigns: %{user: admin}, body_params: %{nicknames: nicknames}} = conn, _) do
|
||||||
|
users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
|
||||||
|
{:ok, updated_users} = User.set_suggestion(users, true)
|
||||||
|
|
||||||
|
ModerationLog.insert_log(%{
|
||||||
|
actor: admin,
|
||||||
|
subject: users,
|
||||||
|
action: "add_suggestion"
|
||||||
|
})
|
||||||
|
|
||||||
|
render(conn, "index.json", users: updated_users)
|
||||||
|
end
|
||||||
|
|
||||||
|
def unsuggest(%{assigns: %{user: admin}, body_params: %{nicknames: nicknames}} = conn, _) do
|
||||||
|
users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
|
||||||
|
{:ok, updated_users} = User.set_suggestion(users, false)
|
||||||
|
|
||||||
|
ModerationLog.insert_log(%{
|
||||||
|
actor: admin,
|
||||||
|
subject: users,
|
||||||
|
action: "remove_suggestion"
|
||||||
|
})
|
||||||
|
|
||||||
|
render(conn, "index.json", users: updated_users)
|
||||||
|
end
|
||||||
|
|
||||||
def index(conn, params) do
|
def index(conn, params) do
|
||||||
{page, page_size} = page_params(params)
|
{page, page_size} = page_params(params)
|
||||||
filters = maybe_parse_filters(params[:filters])
|
filters = maybe_parse_filters(params[:filters])
|
||||||
|
|
|
@ -13,7 +13,9 @@ def extract_report_info(
|
||||||
account = User.get_cached_by_ap_id(account_ap_id)
|
account = User.get_cached_by_ap_id(account_ap_id)
|
||||||
|
|
||||||
statuses =
|
statuses =
|
||||||
Enum.map(status_ap_ids, fn
|
status_ap_ids
|
||||||
|
|> Enum.reject(&is_nil(&1))
|
||||||
|
|> Enum.map(fn
|
||||||
act when is_map(act) -> Activity.get_by_ap_id_with_object(act["id"])
|
act when is_map(act) -> Activity.get_by_ap_id_with_object(act["id"])
|
||||||
act when is_binary(act) -> Activity.get_by_ap_id_with_object(act)
|
act when is_binary(act) -> Activity.get_by_ap_id_with_object(act)
|
||||||
end)
|
end)
|
||||||
|
|
|
@ -80,6 +80,7 @@ def render("show.json", %{user: user}) do
|
||||||
"tags" => user.tags || [],
|
"tags" => user.tags || [],
|
||||||
"is_confirmed" => user.is_confirmed,
|
"is_confirmed" => user.is_confirmed,
|
||||||
"is_approved" => user.is_approved,
|
"is_approved" => user.is_approved,
|
||||||
|
"is_suggested" => user.is_suggested,
|
||||||
"url" => user.uri || user.ap_id,
|
"url" => user.uri || user.ap_id,
|
||||||
"registration_reason" => user.registration_reason,
|
"registration_reason" => user.registration_reason,
|
||||||
"actor_type" => user.actor_type,
|
"actor_type" => user.actor_type,
|
||||||
|
|
|
@ -216,7 +216,71 @@ def approve_operation do
|
||||||
request_body(
|
request_body(
|
||||||
"Parameters",
|
"Parameters",
|
||||||
%Schema{
|
%Schema{
|
||||||
description: "POST body for deleting multiple users",
|
description: "POST body for approving multiple users",
|
||||||
|
type: :object,
|
||||||
|
properties: %{
|
||||||
|
nicknames: %Schema{
|
||||||
|
type: :array,
|
||||||
|
items: %Schema{type: :string}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
),
|
||||||
|
responses: %{
|
||||||
|
200 =>
|
||||||
|
Operation.response("Response", "application/json", %Schema{
|
||||||
|
type: :object,
|
||||||
|
properties: %{user: %Schema{type: :array, items: user()}}
|
||||||
|
}),
|
||||||
|
403 => Operation.response("Forbidden", "application/json", ApiError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def suggest_operation do
|
||||||
|
%Operation{
|
||||||
|
tags: ["User administration"],
|
||||||
|
summary: "Suggest multiple users",
|
||||||
|
operationId: "AdminAPI.UserController.suggest",
|
||||||
|
security: [%{"oAuth" => ["admin:write:accounts"]}],
|
||||||
|
parameters: admin_api_params(),
|
||||||
|
requestBody:
|
||||||
|
request_body(
|
||||||
|
"Parameters",
|
||||||
|
%Schema{
|
||||||
|
description: "POST body for adding multiple suggested users",
|
||||||
|
type: :object,
|
||||||
|
properties: %{
|
||||||
|
nicknames: %Schema{
|
||||||
|
type: :array,
|
||||||
|
items: %Schema{type: :string}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
),
|
||||||
|
responses: %{
|
||||||
|
200 =>
|
||||||
|
Operation.response("Response", "application/json", %Schema{
|
||||||
|
type: :object,
|
||||||
|
properties: %{user: %Schema{type: :array, items: user()}}
|
||||||
|
}),
|
||||||
|
403 => Operation.response("Forbidden", "application/json", ApiError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def unsuggest_operation do
|
||||||
|
%Operation{
|
||||||
|
tags: ["User administration"],
|
||||||
|
summary: "Unsuggest multiple users",
|
||||||
|
operationId: "AdminAPI.UserController.unsuggest",
|
||||||
|
security: [%{"oAuth" => ["admin:write:accounts"]}],
|
||||||
|
parameters: admin_api_params(),
|
||||||
|
requestBody:
|
||||||
|
request_body(
|
||||||
|
"Parameters",
|
||||||
|
%Schema{
|
||||||
|
description: "POST body for removing multiple suggested users",
|
||||||
type: :object,
|
type: :object,
|
||||||
properties: %{
|
properties: %{
|
||||||
nicknames: %Schema{
|
nicknames: %Schema{
|
||||||
|
|
|
@ -195,7 +195,8 @@ defp notification_type do
|
||||||
"pleroma:chat_mention",
|
"pleroma:chat_mention",
|
||||||
"pleroma:report",
|
"pleroma:report",
|
||||||
"move",
|
"move",
|
||||||
"follow_request"
|
"follow_request",
|
||||||
|
"poll"
|
||||||
],
|
],
|
||||||
description: """
|
description: """
|
||||||
The type of event that resulted in the notification.
|
The type of event that resulted in the notification.
|
||||||
|
|
|
@ -8,6 +8,8 @@ defmodule Pleroma.Web.ApiSpec.TwitterUtilOperation do
|
||||||
alias Pleroma.Web.ApiSpec.Schemas.ApiError
|
alias Pleroma.Web.ApiSpec.Schemas.ApiError
|
||||||
alias Pleroma.Web.ApiSpec.Schemas.BooleanLike
|
alias Pleroma.Web.ApiSpec.Schemas.BooleanLike
|
||||||
|
|
||||||
|
import Pleroma.Web.ApiSpec.Helpers
|
||||||
|
|
||||||
def open_api_operation(action) do
|
def open_api_operation(action) do
|
||||||
operation = String.to_existing_atom("#{action}_operation")
|
operation = String.to_existing_atom("#{action}_operation")
|
||||||
apply(__MODULE__, operation, [])
|
apply(__MODULE__, operation, [])
|
||||||
|
@ -63,17 +65,7 @@ def change_password_operation do
|
||||||
summary: "Change account password",
|
summary: "Change account password",
|
||||||
security: [%{"oAuth" => ["write:accounts"]}],
|
security: [%{"oAuth" => ["write:accounts"]}],
|
||||||
operationId: "UtilController.change_password",
|
operationId: "UtilController.change_password",
|
||||||
parameters: [
|
requestBody: request_body("Parameters", change_password_request(), required: true),
|
||||||
Operation.parameter(:password, :query, :string, "Current password", required: true),
|
|
||||||
Operation.parameter(:new_password, :query, :string, "New password", required: true),
|
|
||||||
Operation.parameter(
|
|
||||||
:new_password_confirmation,
|
|
||||||
:query,
|
|
||||||
:string,
|
|
||||||
"New password, confirmation",
|
|
||||||
required: true
|
|
||||||
)
|
|
||||||
],
|
|
||||||
responses: %{
|
responses: %{
|
||||||
200 =>
|
200 =>
|
||||||
Operation.response("Success", "application/json", %Schema{
|
Operation.response("Success", "application/json", %Schema{
|
||||||
|
@ -86,17 +78,30 @@ def change_password_operation do
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp change_password_request do
|
||||||
|
%Schema{
|
||||||
|
title: "ChangePasswordRequest",
|
||||||
|
description: "POST body for changing the account's passowrd",
|
||||||
|
type: :object,
|
||||||
|
required: [:password, :new_password, :new_password_confirmation],
|
||||||
|
properties: %{
|
||||||
|
password: %Schema{type: :string, description: "Current password"},
|
||||||
|
new_password: %Schema{type: :string, description: "New password"},
|
||||||
|
new_password_confirmation: %Schema{
|
||||||
|
type: :string,
|
||||||
|
description: "New password, confirmation"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
def change_email_operation do
|
def change_email_operation do
|
||||||
%Operation{
|
%Operation{
|
||||||
tags: ["Account credentials"],
|
tags: ["Account credentials"],
|
||||||
summary: "Change account email",
|
summary: "Change account email",
|
||||||
security: [%{"oAuth" => ["write:accounts"]}],
|
security: [%{"oAuth" => ["write:accounts"]}],
|
||||||
operationId: "UtilController.change_email",
|
operationId: "UtilController.change_email",
|
||||||
parameters: [
|
requestBody: request_body("Parameters", change_email_request(), required: true),
|
||||||
Operation.parameter(:password, :query, :string, "Current password", required: true),
|
|
||||||
Operation.parameter(:email, :query, :string, "New email", required: true)
|
|
||||||
],
|
|
||||||
requestBody: nil,
|
|
||||||
responses: %{
|
responses: %{
|
||||||
200 =>
|
200 =>
|
||||||
Operation.response("Success", "application/json", %Schema{
|
Operation.response("Success", "application/json", %Schema{
|
||||||
|
@ -109,6 +114,22 @@ def change_email_operation do
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp change_email_request do
|
||||||
|
%Schema{
|
||||||
|
title: "ChangeEmailRequest",
|
||||||
|
description: "POST body for changing the account's email",
|
||||||
|
type: :object,
|
||||||
|
required: [:email, :password],
|
||||||
|
properties: %{
|
||||||
|
email: %Schema{
|
||||||
|
type: :string,
|
||||||
|
description: "New email. Set to blank to remove the user's email."
|
||||||
|
},
|
||||||
|
password: %Schema{type: :string, description: "Current password"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
def update_notificaton_settings_operation do
|
def update_notificaton_settings_operation do
|
||||||
%Operation{
|
%Operation{
|
||||||
tags: ["Accounts"],
|
tags: ["Accounts"],
|
||||||
|
@ -170,6 +191,7 @@ def delete_account_operation do
|
||||||
parameters: [
|
parameters: [
|
||||||
Operation.parameter(:password, :query, :string, "Password")
|
Operation.parameter(:password, :query, :string, "Password")
|
||||||
],
|
],
|
||||||
|
requestBody: request_body("Parameters", delete_account_request(), required: false),
|
||||||
responses: %{
|
responses: %{
|
||||||
200 =>
|
200 =>
|
||||||
Operation.response("Success", "application/json", %Schema{
|
Operation.response("Success", "application/json", %Schema{
|
||||||
|
@ -216,4 +238,22 @@ def remote_subscribe_operation do
|
||||||
responses: %{200 => Operation.response("Web Page", "test/html", %Schema{type: :string})}
|
responses: %{200 => Operation.response("Web Page", "test/html", %Schema{type: :string})}
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp delete_account_request do
|
||||||
|
%Schema{
|
||||||
|
title: "AccountDeleteRequest",
|
||||||
|
description: "POST body for deleting one's own account",
|
||||||
|
type: :object,
|
||||||
|
properties: %{
|
||||||
|
password: %Schema{
|
||||||
|
type: :string,
|
||||||
|
description: "The user's own password for confirmation.",
|
||||||
|
format: :password
|
||||||
|
}
|
||||||
|
},
|
||||||
|
example: %{
|
||||||
|
"password" => "prettyp0ony1313"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -487,9 +487,7 @@ def remove_mute(user_id, activity_id) do
|
||||||
else
|
else
|
||||||
{what, result} = error ->
|
{what, result} = error ->
|
||||||
Logger.warn(
|
Logger.warn(
|
||||||
"CommonAPI.remove_mute/2 failed. #{what}: #{result}, user_id: #{user_id}, activity_id: #{
|
"CommonAPI.remove_mute/2 failed. #{what}: #{result}, user_id: #{user_id}, activity_id: #{activity_id}"
|
||||||
activity_id
|
|
||||||
}"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
{:error, error}
|
{:error, error}
|
||||||
|
|
|
@ -6,6 +6,7 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
alias Pleroma.Conversation.Participation
|
alias Pleroma.Conversation.Participation
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
|
alias Pleroma.Web.ActivityPub.Builder
|
||||||
alias Pleroma.Web.CommonAPI
|
alias Pleroma.Web.CommonAPI
|
||||||
alias Pleroma.Web.CommonAPI.Utils
|
alias Pleroma.Web.CommonAPI.Utils
|
||||||
|
|
||||||
|
@ -213,8 +214,10 @@ defp object(draft) do
|
||||||
|
|
||||||
emoji = Map.merge(emoji, summary_emoji)
|
emoji = Map.merge(emoji, summary_emoji)
|
||||||
|
|
||||||
|
{:ok, note_data, _meta} = Builder.note(draft)
|
||||||
|
|
||||||
object =
|
object =
|
||||||
Utils.make_note_data(draft)
|
note_data
|
||||||
|> Map.put("emoji", emoji)
|
|> Map.put("emoji", emoji)
|
||||||
|> Map.put("source", draft.status)
|
|> Map.put("source", draft.status)
|
||||||
|> Map.put("generator", draft.params[:generator])
|
|> Map.put("generator", draft.params[:generator])
|
||||||
|
|
|
@ -291,33 +291,6 @@ def format_input(text, "text/markdown", options) do
|
||||||
|> Formatter.html_escape("text/html")
|
|> Formatter.html_escape("text/html")
|
||||||
end
|
end
|
||||||
|
|
||||||
def make_note_data(%ActivityDraft{} = draft) do
|
|
||||||
%{
|
|
||||||
"type" => "Note",
|
|
||||||
"to" => draft.to,
|
|
||||||
"cc" => draft.cc,
|
|
||||||
"content" => draft.content_html,
|
|
||||||
"summary" => draft.summary,
|
|
||||||
"sensitive" => draft.sensitive,
|
|
||||||
"context" => draft.context,
|
|
||||||
"attachment" => draft.attachments,
|
|
||||||
"actor" => draft.user.ap_id,
|
|
||||||
"tag" => Keyword.values(draft.tags) |> Enum.uniq()
|
|
||||||
}
|
|
||||||
|> add_in_reply_to(draft.in_reply_to)
|
|
||||||
|> Map.merge(draft.extra)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp add_in_reply_to(object, nil), do: object
|
|
||||||
|
|
||||||
defp add_in_reply_to(object, in_reply_to) do
|
|
||||||
with %Object{} = in_reply_to_object <- Object.normalize(in_reply_to, fetch: false) do
|
|
||||||
Map.put(object, "inReplyTo", in_reply_to_object.data["id"])
|
|
||||||
else
|
|
||||||
_ -> object
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def format_naive_asctime(date) do
|
def format_naive_asctime(date) do
|
||||||
date |> DateTime.from_naive!("Etc/UTC") |> format_asctime
|
date |> DateTime.from_naive!("Etc/UTC") |> format_asctime
|
||||||
end
|
end
|
||||||
|
@ -412,19 +385,14 @@ def maybe_notify_mentioned_recipients(
|
||||||
|
|
||||||
def maybe_notify_mentioned_recipients(recipients, _), do: recipients
|
def maybe_notify_mentioned_recipients(recipients, _), do: recipients
|
||||||
|
|
||||||
# Do not notify subscribers if author is making a reply
|
|
||||||
def maybe_notify_subscribers(recipients, %Activity{
|
|
||||||
object: %Object{data: %{"inReplyTo" => _ap_id}}
|
|
||||||
}) do
|
|
||||||
recipients
|
|
||||||
end
|
|
||||||
|
|
||||||
def maybe_notify_subscribers(
|
def maybe_notify_subscribers(
|
||||||
recipients,
|
recipients,
|
||||||
%Activity{data: %{"actor" => actor, "type" => type}} = activity
|
%Activity{data: %{"actor" => actor, "type" => "Create"}} = activity
|
||||||
)
|
) do
|
||||||
when type == "Create" do
|
# Do not notify subscribers if author is making a reply
|
||||||
with %User{} = user <- User.get_cached_by_ap_id(actor) do
|
with %Object{data: object} <- Object.normalize(activity, fetch: false),
|
||||||
|
nil <- object["inReplyTo"],
|
||||||
|
%User{} = user <- User.get_cached_by_ap_id(actor) do
|
||||||
subscriber_ids =
|
subscriber_ids =
|
||||||
user
|
user
|
||||||
|> User.subscriber_users()
|
|> User.subscriber_users()
|
||||||
|
|
|
@ -10,6 +10,7 @@ defmodule Pleroma.Web.Endpoint do
|
||||||
alias Pleroma.Config
|
alias Pleroma.Config
|
||||||
|
|
||||||
socket("/socket", Pleroma.Web.UserSocket)
|
socket("/socket", Pleroma.Web.UserSocket)
|
||||||
|
socket("/live", Phoenix.LiveView.Socket)
|
||||||
|
|
||||||
plug(Plug.Telemetry, event_prefix: [:phoenix, :endpoint])
|
plug(Plug.Telemetry, event_prefix: [:phoenix, :endpoint])
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,8 @@ defmodule Pleroma.Web.Feed.UserController do
|
||||||
def feed_redirect(%{assigns: %{format: "html"}} = conn, %{"nickname" => nickname}) do
|
def feed_redirect(%{assigns: %{format: "html"}} = conn, %{"nickname" => nickname}) do
|
||||||
with {_, %User{} = user} <- {:fetch_user, User.get_cached_by_nickname_or_id(nickname)} do
|
with {_, %User{} = user} <- {:fetch_user, User.get_cached_by_nickname_or_id(nickname)} do
|
||||||
Pleroma.Web.Fallback.RedirectController.redirector_with_meta(conn, %{user: user})
|
Pleroma.Web.Fallback.RedirectController.redirector_with_meta(conn, %{user: user})
|
||||||
|
else
|
||||||
|
_ -> Pleroma.Web.Fallback.RedirectController.redirector(conn, nil)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
14
lib/pleroma/web/manifest_controller.ex
Normal file
14
lib/pleroma/web/manifest_controller.ex
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.ManifestController do
|
||||||
|
use Pleroma.Web, :controller
|
||||||
|
|
||||||
|
plug(:skip_auth when action == :show)
|
||||||
|
|
||||||
|
@doc "GET /manifest.json"
|
||||||
|
def show(conn, _params) do
|
||||||
|
render(conn, "manifest.json")
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,61 +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.MastoFEController do
|
|
||||||
use Pleroma.Web, :controller
|
|
||||||
|
|
||||||
alias Pleroma.User
|
|
||||||
alias Pleroma.Web.MastodonAPI.AuthController
|
|
||||||
alias Pleroma.Web.OAuth.Token
|
|
||||||
alias Pleroma.Web.Plugs.OAuthScopesPlug
|
|
||||||
|
|
||||||
plug(OAuthScopesPlug, %{scopes: ["write:accounts"]} when action == :put_settings)
|
|
||||||
|
|
||||||
# Note: :index action handles attempt of unauthenticated access to private instance with redirect
|
|
||||||
plug(:skip_public_check when action == :index)
|
|
||||||
|
|
||||||
plug(
|
|
||||||
OAuthScopesPlug,
|
|
||||||
%{scopes: ["read"], fallback: :proceed_unauthenticated}
|
|
||||||
when action == :index
|
|
||||||
)
|
|
||||||
|
|
||||||
plug(:skip_auth when action == :manifest)
|
|
||||||
|
|
||||||
@doc "GET /web/*path"
|
|
||||||
def index(conn, _params) do
|
|
||||||
with %{assigns: %{user: %User{} = user, token: %Token{app_id: token_app_id} = token}} <- conn,
|
|
||||||
{:ok, %{id: ^token_app_id}} <- AuthController.local_mastofe_app() do
|
|
||||||
conn
|
|
||||||
|> put_layout(false)
|
|
||||||
|> render("index.html",
|
|
||||||
token: token.token,
|
|
||||||
user: user,
|
|
||||||
custom_emojis: Pleroma.Emoji.get_all()
|
|
||||||
)
|
|
||||||
else
|
|
||||||
_ ->
|
|
||||||
conn
|
|
||||||
|> put_session(:return_to, conn.request_path)
|
|
||||||
|> redirect(to: "/web/login")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc "GET /web/manifest.json"
|
|
||||||
def manifest(conn, _params) do
|
|
||||||
render(conn, "manifest.json")
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc "PUT /api/web/settings: Backend-obscure settings blob for MastoFE, don't parse/reuse elsewhere"
|
|
||||||
def put_settings(%{assigns: %{user: user}} = conn, %{"data" => settings} = _params) do
|
|
||||||
with {:ok, _} <- User.mastodon_settings_update(user, settings) do
|
|
||||||
json(conn, %{})
|
|
||||||
else
|
|
||||||
e ->
|
|
||||||
conn
|
|
||||||
|> put_status(:internal_server_error)
|
|
||||||
|> json(%{error: inspect(e)})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -21,8 +21,6 @@ defmodule Pleroma.Web.MastodonAPI.AppController do
|
||||||
|
|
||||||
plug(Pleroma.Web.ApiSpec.CastAndValidate)
|
plug(Pleroma.Web.ApiSpec.CastAndValidate)
|
||||||
|
|
||||||
@local_mastodon_name "Mastodon-Local"
|
|
||||||
|
|
||||||
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.AppOperation
|
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.AppOperation
|
||||||
|
|
||||||
@doc "POST /api/v1/apps"
|
@doc "POST /api/v1/apps"
|
||||||
|
@ -35,7 +33,6 @@ def create(%{body_params: params} = conn, _params) do
|
||||||
|> Map.put(:scopes, scopes)
|
|> Map.put(:scopes, scopes)
|
||||||
|
|
||||||
with cs <- App.register_changeset(%App{}, app_attrs),
|
with cs <- App.register_changeset(%App{}, app_attrs),
|
||||||
false <- cs.changes[:client_name] == @local_mastodon_name,
|
|
||||||
{:ok, app} <- Repo.insert(cs) do
|
{:ok, app} <- Repo.insert(cs) do
|
||||||
render(conn, "show.json", app: app)
|
render(conn, "show.json", app: app)
|
||||||
end
|
end
|
||||||
|
|
|
@ -7,77 +7,12 @@ defmodule Pleroma.Web.MastodonAPI.AuthController do
|
||||||
|
|
||||||
import Pleroma.Web.ControllerHelper, only: [json_response: 3]
|
import Pleroma.Web.ControllerHelper, only: [json_response: 3]
|
||||||
|
|
||||||
alias Pleroma.Helpers.AuthHelper
|
|
||||||
alias Pleroma.Helpers.UriHelper
|
|
||||||
alias Pleroma.User
|
|
||||||
alias Pleroma.Web.OAuth.App
|
|
||||||
alias Pleroma.Web.OAuth.Authorization
|
|
||||||
alias Pleroma.Web.OAuth.Token
|
|
||||||
alias Pleroma.Web.OAuth.Token.Strategy.Revoke, as: RevokeToken
|
|
||||||
alias Pleroma.Web.TwitterAPI.TwitterAPI
|
alias Pleroma.Web.TwitterAPI.TwitterAPI
|
||||||
|
|
||||||
action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
|
action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
|
||||||
|
|
||||||
plug(Pleroma.Web.Plugs.RateLimiter, [name: :password_reset] when action == :password_reset)
|
plug(Pleroma.Web.Plugs.RateLimiter, [name: :password_reset] when action == :password_reset)
|
||||||
|
|
||||||
@local_mastodon_name "Mastodon-Local"
|
|
||||||
|
|
||||||
@doc "GET /web/login"
|
|
||||||
# Local Mastodon FE login callback action
|
|
||||||
def login(conn, %{"code" => auth_token} = params) do
|
|
||||||
with {:ok, app} <- local_mastofe_app(),
|
|
||||||
{:ok, auth} <- Authorization.get_by_token(app, auth_token),
|
|
||||||
{:ok, oauth_token} <- Token.exchange_token(app, auth) do
|
|
||||||
redirect_to =
|
|
||||||
conn
|
|
||||||
|> local_mastodon_post_login_path()
|
|
||||||
|> UriHelper.modify_uri_params(%{"access_token" => oauth_token.token})
|
|
||||||
|
|
||||||
conn
|
|
||||||
|> AuthHelper.put_session_token(oauth_token.token)
|
|
||||||
|> redirect(to: redirect_to)
|
|
||||||
else
|
|
||||||
_ -> redirect_to_oauth_form(conn, params)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def login(conn, params) do
|
|
||||||
with %{assigns: %{user: %User{}, token: %Token{app_id: app_id}}} <- conn,
|
|
||||||
{:ok, %{id: ^app_id}} <- local_mastofe_app() do
|
|
||||||
redirect(conn, to: local_mastodon_post_login_path(conn))
|
|
||||||
else
|
|
||||||
_ -> redirect_to_oauth_form(conn, params)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp redirect_to_oauth_form(conn, _params) do
|
|
||||||
with {:ok, app} <- local_mastofe_app() do
|
|
||||||
path =
|
|
||||||
Routes.o_auth_path(conn, :authorize,
|
|
||||||
response_type: "code",
|
|
||||||
client_id: app.client_id,
|
|
||||||
redirect_uri: ".",
|
|
||||||
scope: Enum.join(app.scopes, " ")
|
|
||||||
)
|
|
||||||
|
|
||||||
redirect(conn, to: path)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc "DELETE /auth/sign_out"
|
|
||||||
def logout(conn, _) do
|
|
||||||
conn =
|
|
||||||
with %{assigns: %{token: %Token{} = oauth_token}} <- conn,
|
|
||||||
session_token = AuthHelper.get_session_token(conn),
|
|
||||||
{:ok, %Token{token: ^session_token}} <- RevokeToken.revoke(oauth_token) do
|
|
||||||
AuthHelper.delete_session_token(conn)
|
|
||||||
else
|
|
||||||
_ -> conn
|
|
||||||
end
|
|
||||||
|
|
||||||
redirect(conn, to: "/")
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc "POST /auth/password"
|
@doc "POST /auth/password"
|
||||||
def password_reset(conn, params) do
|
def password_reset(conn, params) do
|
||||||
nickname_or_email = params["email"] || params["nickname"]
|
nickname_or_email = params["email"] || params["nickname"]
|
||||||
|
@ -86,23 +21,4 @@ def password_reset(conn, params) do
|
||||||
|
|
||||||
json_response(conn, :no_content, "")
|
json_response(conn, :no_content, "")
|
||||||
end
|
end
|
||||||
|
|
||||||
defp local_mastodon_post_login_path(conn) do
|
|
||||||
case get_session(conn, :return_to) do
|
|
||||||
nil ->
|
|
||||||
Routes.masto_fe_path(conn, :index, ["getting-started"])
|
|
||||||
|
|
||||||
return_to ->
|
|
||||||
delete_session(conn, :return_to)
|
|
||||||
return_to
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@spec local_mastofe_app() :: {:ok, App.t()} | {:error, Ecto.Changeset.t()}
|
|
||||||
def local_mastofe_app do
|
|
||||||
App.get_or_make(
|
|
||||||
%{client_name: @local_mastodon_name, redirect_uris: "."},
|
|
||||||
["read", "write", "follow", "push", "admin"]
|
|
||||||
)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -50,6 +50,7 @@ def index(conn, %{account_id: account_id} = params) do
|
||||||
favourite
|
favourite
|
||||||
move
|
move
|
||||||
pleroma:emoji_reaction
|
pleroma:emoji_reaction
|
||||||
|
poll
|
||||||
}
|
}
|
||||||
def index(%{assigns: %{user: user}} = conn, params) do
|
def index(%{assigns: %{user: user}} = conn, params) do
|
||||||
params =
|
params =
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue