Fix merge conflicts with upstream
authorSean King <seanking2919@protonmail.com>
Fri, 4 Jun 2021 20:42:44 +0000 (14:42 -0600)
committerSean King <seanking2919@protonmail.com>
Fri, 4 Jun 2021 20:42:44 +0000 (14:42 -0600)
240 files changed:
.gitignore
.gitlab-ci.yml
CHANGELOG.md
Dockerfile
README.md
benchmarks/load_testing/activities.ex
config/benchmark.exs
config/config.exs
config/description.exs
config/dev.exs
config/dokku.exs
config/prod.exs
config/test.exs
docs/configuration/cheatsheet.md
docs/development/API/differences_in_mastoapi_responses.md
docs/installation/alpine_linux_en.md
docs/installation/arch_linux_en.md
docs/installation/debian_based_en.md
docs/installation/debian_based_jp.md
docs/installation/freebsd_en.md
docs/installation/gentoo_en.md
docs/installation/netbsd_en.md
docs/installation/openbsd_en.md
docs/installation/openbsd_fi.md
docs/installation/otp_en.md
lib/pleroma/activity.ex
lib/pleroma/activity/html.ex [new file with mode: 0644]
lib/pleroma/activity/queries.ex
lib/pleroma/application.ex
lib/pleroma/application_requirements.ex
lib/pleroma/config/deprecation_warnings.ex
lib/pleroma/config/loader.ex
lib/pleroma/config/release_runtime_provider.ex
lib/pleroma/config/transfer_task.ex
lib/pleroma/constants.ex
lib/pleroma/earmark_renderer.ex [deleted file]
lib/pleroma/ecto_type/activity_pub/object_validators/recipients.ex
lib/pleroma/emails/admin_email.ex
lib/pleroma/emails/user_email.ex
lib/pleroma/emoji/formatter.ex
lib/pleroma/formatter.ex
lib/pleroma/gun.ex
lib/pleroma/gun/connection_pool/reclaimer.ex
lib/pleroma/gun/connection_pool/worker.ex
lib/pleroma/html.ex
lib/pleroma/http/adapter_helper/gun.ex
lib/pleroma/http/web_push.ex
lib/pleroma/maps.ex
lib/pleroma/object.ex
lib/pleroma/object/containment.ex
lib/pleroma/object/fetcher.ex
lib/pleroma/upload.ex
lib/pleroma/upload/filter/analyze_metadata.ex [new file with mode: 0644]
lib/pleroma/uploaders/uploader.ex
lib/pleroma/user.ex
lib/pleroma/utils.ex
lib/pleroma/web.ex
lib/pleroma/web/activity_pub/activity_pub.ex
lib/pleroma/web/activity_pub/activity_pub/persisting.ex
lib/pleroma/web/activity_pub/activity_pub/streaming.ex
lib/pleroma/web/activity_pub/activity_pub_controller.ex
lib/pleroma/web/activity_pub/builder.ex
lib/pleroma/web/activity_pub/mrf/no_empty_policy.ex
lib/pleroma/web/activity_pub/mrf/simple_policy.ex
lib/pleroma/web/activity_pub/object_validator.ex
lib/pleroma/web/activity_pub/object_validators/accept_reject_validator.ex
lib/pleroma/web/activity_pub/object_validators/add_remove_validator.ex [new file with mode: 0644]
lib/pleroma/web/activity_pub/object_validators/announce_validator.ex
lib/pleroma/web/activity_pub/object_validators/answer_validator.ex
lib/pleroma/web/activity_pub/object_validators/article_note_validator.ex
lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex
lib/pleroma/web/activity_pub/object_validators/audio_video_validator.ex
lib/pleroma/web/activity_pub/object_validators/block_validator.ex
lib/pleroma/web/activity_pub/object_validators/chat_message_validator.ex
lib/pleroma/web/activity_pub/object_validators/common_fixes.ex
lib/pleroma/web/activity_pub/object_validators/common_validations.ex
lib/pleroma/web/activity_pub/object_validators/create_chat_message_validator.ex
lib/pleroma/web/activity_pub/object_validators/create_generic_validator.ex
lib/pleroma/web/activity_pub/object_validators/create_note_validator.ex [deleted file]
lib/pleroma/web/activity_pub/object_validators/delete_validator.ex
lib/pleroma/web/activity_pub/object_validators/emoji_react_validator.ex
lib/pleroma/web/activity_pub/object_validators/event_validator.ex
lib/pleroma/web/activity_pub/object_validators/follow_validator.ex
lib/pleroma/web/activity_pub/object_validators/like_validator.ex
lib/pleroma/web/activity_pub/object_validators/question_validator.ex
lib/pleroma/web/activity_pub/object_validators/tag_validator.ex [new file with mode: 0644]
lib/pleroma/web/activity_pub/object_validators/undo_validator.ex
lib/pleroma/web/activity_pub/object_validators/update_validator.ex
lib/pleroma/web/activity_pub/pipeline.ex
lib/pleroma/web/activity_pub/publisher.ex
lib/pleroma/web/activity_pub/side_effects.ex
lib/pleroma/web/activity_pub/transmogrifier.ex
lib/pleroma/web/activity_pub/utils.ex
lib/pleroma/web/activity_pub/views/user_view.ex
lib/pleroma/web/activity_pub/visibility.ex
lib/pleroma/web/admin_api/controllers/o_auth_app_controller.ex
lib/pleroma/web/admin_api/views/o_auth_app_view.ex [new file with mode: 0644]
lib/pleroma/web/api_spec/operations/media_operation.ex
lib/pleroma/web/api_spec/operations/status_operation.ex
lib/pleroma/web/api_spec/operations/timeline_operation.ex
lib/pleroma/web/api_spec/operations/twitter_util_operation.ex [new file with mode: 0644]
lib/pleroma/web/api_spec/operations/user_import_operation.ex
lib/pleroma/web/api_spec/schemas/status.ex
lib/pleroma/web/channels/user_socket.ex
lib/pleroma/web/common_api.ex
lib/pleroma/web/common_api/utils.ex
lib/pleroma/web/federator.ex
lib/pleroma/web/feed/feed_view.ex
lib/pleroma/web/feed/user_controller.ex
lib/pleroma/web/mastodon_api/controllers/auth_controller.ex [new file with mode: 0644]
lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex
lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex
lib/pleroma/web/mastodon_api/controllers/media_controller.ex
lib/pleroma/web/mastodon_api/controllers/search_controller.ex
lib/pleroma/web/mastodon_api/controllers/status_controller.ex
lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex
lib/pleroma/web/mastodon_api/views/account_view.ex
lib/pleroma/web/mastodon_api/views/custom_emoji_view.ex
lib/pleroma/web/mastodon_api/views/follow_request_view.ex [new file with mode: 0644]
lib/pleroma/web/mastodon_api/views/instance_view.ex
lib/pleroma/web/mastodon_api/views/media_view.ex [new file with mode: 0644]
lib/pleroma/web/mastodon_api/views/status_view.ex
lib/pleroma/web/mastodon_api/views/timeline_view.ex [new file with mode: 0644]
lib/pleroma/web/media_proxy.ex
lib/pleroma/web/metadata/utils.ex
lib/pleroma/web/nodeinfo/nodeinfo_controller.ex
lib/pleroma/web/o_auth/o_auth_controller.ex
lib/pleroma/web/o_auth/o_auth_view.ex
lib/pleroma/web/pleroma_api/controllers/account_controller.ex
lib/pleroma/web/pleroma_api/controllers/conversation_controller.ex
lib/pleroma/web/pleroma_api/controllers/notification_controller.ex
lib/pleroma/web/pleroma_api/views/account_view.ex [new file with mode: 0644]
lib/pleroma/web/pleroma_api/views/conversation_view.ex [new file with mode: 0644]
lib/pleroma/web/pleroma_api/views/notification_view.ex [new file with mode: 0644]
lib/pleroma/web/plugs/frontend_static.ex
lib/pleroma/web/plugs/http_security_plug.ex
lib/pleroma/web/router.ex
lib/pleroma/web/shout_channel.ex [moved from lib/pleroma/web/chat_channel.ex with 78% similarity]
lib/pleroma/web/static_fe/static_fe_controller.ex
lib/pleroma/web/templates/feed/feed/_activity.atom.eex
lib/pleroma/web/templates/feed/feed/_activity.rss.eex
lib/pleroma/web/templates/feed/feed/_tag_activity.atom.eex
lib/pleroma/web/templates/feed/feed/tag.atom.eex
lib/pleroma/web/templates/feed/feed/tag.rss.eex
lib/pleroma/web/templates/feed/feed/user.atom.eex
lib/pleroma/web/templates/feed/feed/user.rss.eex
lib/pleroma/web/templates/masto_fe/index.html.eex [new file with mode: 0644]
lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex
lib/pleroma/web/templates/o_auth/mfa/totp.html.eex
lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex
lib/pleroma/web/templates/o_auth/o_auth/register.html.eex
lib/pleroma/web/templates/o_auth/o_auth/show.html.eex
lib/pleroma/web/templates/twitter_api/password/reset.html.eex
lib/pleroma/web/templates/twitter_api/password/reset_failed.html.eex
lib/pleroma/web/templates/twitter_api/password/reset_success.html.eex
lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex
lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex
lib/pleroma/web/templates/twitter_api/remote_follow/follow_mfa.html.eex
lib/pleroma/web/templates/twitter_api/util/subscribe.html.eex
lib/pleroma/web/twitter_api/controller.ex
lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex
lib/pleroma/web/twitter_api/controllers/util_controller.ex
lib/pleroma/web/twitter_api/views/util_view.ex
lib/pleroma/web/views/masto_fe_view.ex [new file with mode: 0644]
lib/pleroma/web/web_finger.ex
mix.exs
mix.lock
priv/gettext/zh_Hant/LC_MESSAGES/errors.po [new file with mode: 0644]
priv/repo/migrations/20200806175913_rename_instance_chat.exs [new file with mode: 0644]
priv/repo/migrations/20210202110641_add_pinned_objects_to_users.exs [new file with mode: 0644]
priv/repo/migrations/20210203141144_add_featured_address_to_users.exs [new file with mode: 0644]
priv/repo/migrations/20210205145000_move_pinned_activities_into_pinned_objects.exs [new file with mode: 0644]
priv/repo/migrations/20210206045221_remove_pinned_activities_from_users.exs [new file with mode: 0644]
priv/scrubbers/default.ex
test/fixtures/activitypub-client-post-activity.json
test/fixtures/config/temp.exported_from_db.secret.exs [new file with mode: 0644]
test/fixtures/config/temp.secret.exs
test/fixtures/mastodon/collections/featured.json [new file with mode: 0644]
test/fixtures/statuses/masto-note.json [new file with mode: 0644]
test/fixtures/statuses/note.json [new file with mode: 0644]
test/fixtures/users_mock/masto_featured.json [new file with mode: 0644]
test/fixtures/users_mock/user.json [new file with mode: 0644]
test/mix/tasks/pleroma/config_test.exs
test/pleroma/activity_test.exs
test/pleroma/application_requirements_test.exs
test/pleroma/config/deprecation_warnings_test.exs
test/pleroma/config/release_runtime_provider_test.exs [new file with mode: 0644]
test/pleroma/config/transfer_task_test.exs
test/pleroma/earmark_renderer_test.exs [deleted file]
test/pleroma/ecto_type/activity_pub/object_validators/recipients_test.exs
test/pleroma/http/request_builder_test.exs
test/pleroma/notification_test.exs
test/pleroma/object/fetcher_test.exs
test/pleroma/repo/migrations/rename_instance_chat_test.exs [new file with mode: 0644]
test/pleroma/upload/filter/analyze_metadata_test.exs [new file with mode: 0644]
test/pleroma/user_test.exs
test/pleroma/web/activity_pub/activity_pub_controller_test.exs
test/pleroma/web/activity_pub/activity_pub_test.exs
test/pleroma/web/activity_pub/mrf/simple_policy_test.exs
test/pleroma/web/activity_pub/object_validators/attachment_validator_test.exs
test/pleroma/web/activity_pub/pipeline_test.exs
test/pleroma/web/activity_pub/publisher_test.exs
test/pleroma/web/activity_pub/transmogrifier/add_remove_handling_test.exs [new file with mode: 0644]
test/pleroma/web/activity_pub/transmogrifier/audio_handling_test.exs
test/pleroma/web/activity_pub/transmogrifier/event_handling_test.exs
test/pleroma/web/activity_pub/transmogrifier/note_handling_test.exs
test/pleroma/web/activity_pub/transmogrifier/video_handling_test.exs
test/pleroma/web/activity_pub/transmogrifier_test.exs
test/pleroma/web/admin_api/controllers/config_controller_test.exs
test/pleroma/web/admin_api/controllers/o_auth_app_controller_test.exs
test/pleroma/web/admin_api/controllers/user_controller_test.exs
test/pleroma/web/common_api/utils_test.exs
test/pleroma/web/common_api_test.exs
test/pleroma/web/federator_test.exs
test/pleroma/web/feed/tag_controller_test.exs
test/pleroma/web/feed/user_controller_test.exs
test/pleroma/web/mastodon_api/controllers/account_controller_test.exs
test/pleroma/web/mastodon_api/controllers/conversation_controller_test.exs
test/pleroma/web/mastodon_api/controllers/instance_controller_test.exs
test/pleroma/web/mastodon_api/controllers/media_controller_test.exs
test/pleroma/web/mastodon_api/controllers/search_controller_test.exs
test/pleroma/web/mastodon_api/controllers/status_controller_test.exs
test/pleroma/web/mastodon_api/controllers/timeline_controller_test.exs
test/pleroma/web/mastodon_api/masto_fe_controller_test.exs [new file with mode: 0644]
test/pleroma/web/mastodon_api/views/account_view_test.exs
test/pleroma/web/mastodon_api/views/status_view_test.exs
test/pleroma/web/media_proxy_test.exs
test/pleroma/web/o_auth/o_auth_controller_test.exs
test/pleroma/web/o_status/o_status_controller_test.exs
test/pleroma/web/pleroma_api/controllers/user_import_controller_test.exs
test/pleroma/web/plugs/frontend_static_plug_test.exs
test/pleroma/web/shout_channel_test.ex [moved from test/pleroma/web/chat_channel_test.exs with 81% similarity]
test/pleroma/web/static_fe/static_fe_controller_test.exs
test/pleroma/web/twitter_api/controller_test.exs
test/pleroma/web/twitter_api/remote_follow_controller_test.exs
test/pleroma/web/twitter_api/util_controller_test.exs
test/pleroma/web/web_finger/web_finger_controller_test.exs
test/pleroma/web/web_finger_test.exs
test/support/factory.ex
test/support/http_request_mock.ex

index f30f4cf5fb2fe583d8726b35e513dd67d5f4afb0..da73b6f361d2538265d19631fee046777d4ec734 100644 (file)
@@ -28,6 +28,7 @@ erl_crash.dump
 # variables.
 /config/*.secret.exs
 /config/generated_config.exs
+/config/runtime.exs
 /config/*.env
 
 
@@ -56,4 +57,4 @@ pleroma.iml
 
 # Editor temp files
 /*~
-/*#
\ No newline at end of file
+/*#
index 2bc5719718d80d141536e14d7a5d9b32e17e457e..b155c81bd7efe6083d5a4e4cee319c443297e054 100644 (file)
@@ -8,7 +8,9 @@ variables: &global_variables
   MIX_ENV: test
 
 cache: &global_cache_policy
-  key: ${CI_COMMIT_REF_SLUG}
+  key:
+    files:
+      - mix.lock
   paths:
     - deps
     - _build
@@ -22,6 +24,7 @@ stages:
   - docker
 
 before_script:
+  - rm -rf _build/*/lib/pleroma
   - apt-get update && apt-get install -y cmake
   - mix local.hex --force
   - mix local.rebar --force
@@ -29,13 +32,25 @@ before_script:
   - apt-get -qq update
   - apt-get install -y libmagic-dev
 
+after_script:
+  - rm -rf _build/*/lib/pleroma
+
 build:
   stage: build
+  only:
+    changes:
+      - "**/*.ex"
+      - "**/*.exs"
+      - "mix.lock"
   script:
   - mix compile --force
 
 spec-build:
   stage: test
+  only:
+    changes:
+      - "lib/pleroma/web/api_spec/**/*.ex"
+      - "lib/pleroma/web/api_spec.ex"
   artifacts:
     paths:
     - spec.json
@@ -58,6 +73,11 @@ benchmark:
 
 unit-testing:
   stage: test
+  only:
+    changes:
+      - "**/*.ex"
+      - "**/*.exs"
+      - "mix.lock"
   retry: 2
   cache: &testing_cache_policy
     <<: *global_cache_policy
@@ -91,6 +111,11 @@ unit-testing:
 
 unit-testing-rum:
   stage: test
+  only:
+    changes:
+      - "**/*.ex"
+      - "**/*.exs"
+      - "mix.lock"
   retry: 2
   cache: *testing_cache_policy
   services:
@@ -109,12 +134,22 @@ unit-testing-rum:
 
 lint:
   stage: test
+  only:
+    changes:
+      - "**/*.ex"
+      - "**/*.exs"
+      - "mix.lock"
   cache: *testing_cache_policy
   script:
     - mix format --check-formatted
 
 analysis:
   stage: test
+  only:
+    changes:
+      - "**/*.ex"
+      - "**/*.exs"
+      - "mix.lock"
   cache: *testing_cache_policy
   script:
     - mix credo --strict --only=warnings,todo,fixme,consistency,readability
@@ -171,8 +206,8 @@ spec-deploy:
     - apk add curl
   script:
     - curl -X POST -F"token=$API_DOCS_PIPELINE_TRIGGER" -F'ref=master' -F"variables[BRANCH]=$CI_COMMIT_REF_NAME" -F"variables[JOB_REF]=$CI_JOB_ID" https://git.pleroma.social/api/v4/projects/1130/trigger/pipeline
-  
+
+
 stop_review_app:
   image: alpine:3.9
   stage: deploy
@@ -231,7 +266,7 @@ amd64-musl:
   stage: release
   artifacts: *release-artifacts
   only: *release-only
-  image: elixir:1.10.3-alpine 
+  image: elixir:1.10.3-alpine
   cache: *release-cache
   variables: *release-variables
   before_script: &before-release-musl
index 4f590dbd3600fbfbad51fc9a3199075528c00e8f..72a67eb8df8b8817d69da4352dd611b3ff6e63ba 100644 (file)
@@ -12,11 +12,25 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 
 ### Changed
 
+- **Breaking:** Configuration: `:chat, enabled` moved to `:shout, enabled` and `:instance, chat_limit` moved to `:shout, limit`
 - The `application` metadata returned with statuses is no longer hardcoded. Apps that want to display these details will now have valid data for new posts after this change.
+- HTTPSecurityPlug now sends a response header to opt out of Google's FLoC (Federated Learning of Cohorts) targeted advertising.
+- Email address is now returned if requesting user is the owner of the user account so it can be exposed in client and FE user settings UIs.
 
 ### Added
 
 - MRF (`FollowBotPolicy`): New MRF Policy which makes a designated local Bot account attempt to follow all users in public Notes received by your instance. Users who require approving follower requests or have #nobot in their profile are excluded.
+- Return OAuth token `id` (primary key) in POST `/oauth/token`.
+- `AnalyzeMetadata` upload filter for extracting attachment dimensions and generating blurhashes.
+- Attachment dimensions and blurhashes are federated when available.
+- Pinned posts federation
+
+### Fixed
+- Don't crash so hard when email settings are invalid.
+- Checking activated Upload Filters for required commands.
+
+### Removed
+- **Breaking**: Remove deprecated `/api/qvitter/statuses/notifications/read` (replaced by `/api/v1/pleroma/notifications/read`)
 
 ## Unreleased (Patch)
 
@@ -26,6 +40,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - Uploading custom instance thumbnail via AdminAPI/AdminFE generated invalid URL to the image
 - Applying ConcurrentLimiter settings via AdminAPI
 - User login failures if their `notification_settings` were in a NULL state.
+- Mix task `pleroma.user delete_activities` query transaction timeout is now :infinity
+- MRF (`SimplePolicy`): Embedded objects are now checked. If any embedded object would be rejected, its parent is rejected. This fixes Announces leaking posts from blocked domains.
+- Fixed some Markdown issues, including trailing slash in links.
 
 ## [2.3.0] - 2020-03-01
 
index b1b5171af458f40f2997e613b1225cda0d424389..db1a6b45726ee35815f85a8926377eda9240df09 100644 (file)
@@ -33,7 +33,7 @@ ARG DATA=/var/lib/pleroma
 
 RUN echo "http://nl.alpinelinux.org/alpine/latest-stable/community" >> /etc/apk/repositories &&\
        apk update &&\
-       apk add exiftool imagemagick libmagic ncurses postgresql-client &&\
+       apk add exiftool ffmpeg imagemagick libmagic ncurses postgresql-client &&\
        adduser --system --shell /bin/false --home ${HOME} pleroma &&\
        mkdir -p ${DATA}/uploads &&\
        mkdir -p ${DATA}/static &&\
index 7a05b9e48158e76019ad4ab1dce8deccb5e963a6..6aa36d89a1302b0623f42fbca545f7ba7774c98e 100644 (file)
--- a/README.md
+++ b/README.md
@@ -50,5 +50,5 @@ If you are not developing Pleroma, it is better to use the OTP release, which co
 - Latest Git revision: <https://docs-develop.pleroma.social>
 
 ## Community Channels
-* IRC: **#pleroma** and **#pleroma-dev** on freenode, webchat is available at <https://irc.pleroma.social>
-* Matrix: <https://matrix.to/#/#freenode_#pleroma:matrix.org> and <https://matrix.to/#/#freenode_#pleroma-dev:matrix.org>
+* IRC: **#pleroma** and **#pleroma-dev** on libera.chat, webchat is available at <https://irc.pleroma.social>
+* Matrix: [#pleroma:libera.chat](https://matrix.to/#/#pleroma:libera.chat) and [#pleroma-dev:libera.chat](https://matrix.to/#/#pleroma-dev:libera.chat)
index f5c7bfce845856c79d216e263d11f30a34a9e449..b9f6b24da5a5cd79f43ef0e4a416e31e12c1e7d1 100644 (file)
@@ -299,7 +299,7 @@ defmodule Pleroma.LoadTesting.Activities do
       "url" => [
         %{
           "href" =>
-            "#{Pleroma.Web.base_url()}/media/b1b873552422a07bf53af01f3c231c841db4dfc42c35efde681abaf0f2a4eab7.jpg",
+            "#{Pleroma.Web.Endpoint.url()}/media/b1b873552422a07bf53af01f3c231c841db4dfc42c35efde681abaf0f2a4eab7.jpg",
           "mediaType" => "image/jpeg",
           "type" => "Link"
         }
index 5567ff26e4694fea7e699b8efccc544c1ff110e3..a4d048f1bb0a91dc89daeb5812a73ffe456f23b7 100644 (file)
@@ -1,4 +1,4 @@
-use Mix.Config
+import Config
 
 # We don't run a server during test. If one is required,
 # you can enable the server option below.
index 07565c557b96803ed19434bdb61234e6b84f6d22..c00d5dca9d78d5d3a15d6a4e34a444d43ea0b5a4 100644 (file)
@@ -41,7 +41,7 @@
 #
 # This configuration file is loaded before any dependency and
 # is restricted to this project.
-use Mix.Config
+import Config
 
 # General application configuration
 config :pleroma, ecto_repos: [Pleroma.Repo]
@@ -190,7 +190,6 @@ config :pleroma, :instance,
   instance_thumbnail: "/instance/thumbnail.jpeg",
   limit: 5_000,
   description_limit: 5_000,
-  chat_limit: 5_000,
   remote_limit: 100_000,
   upload_limit: 16_000_000,
   avatar_upload_limit: 2_000_000,
@@ -454,7 +453,9 @@ config :pleroma, :media_preview_proxy,
   image_quality: 85,
   min_content_length: 100 * 1024
 
-config :pleroma, :chat, enabled: true
+config :pleroma, :shout,
+  enabled: true,
+  limit: 5_000
 
 config :phoenix, :format_encoders, json: Jason
 
index bc3f11dded1cbea02880d8624fa83705296386d2..94907aaba35b71bcb994b6df995fb3d3a744ca77 100644 (file)
@@ -1,4 +1,4 @@
-use Mix.Config
+import Config
 
 websocket_config = [
   path: "/websocket",
@@ -544,14 +544,6 @@ config :pleroma, :config_description, [
           5_000
         ]
       },
-      %{
-        key: :chat_limit,
-        type: :integer,
-        description: "Character limit of the instance chat messages",
-        suggestions: [
-          5_000
-        ]
-      },
       %{
         key: :remote_limit,
         type: :integer,
@@ -682,7 +674,8 @@ config :pleroma, :config_description, [
       %{
         key: :allow_relay,
         type: :boolean,
-        description: "Enable Pleroma's Relay, which makes it possible to follow a whole instance"
+        description:
+          "Permits remote instances to subscribe to all public posts of your instance. (Important!) This may increase the visibility of your instance."
       },
       %{
         key: :public,
@@ -1182,7 +1175,6 @@ config :pleroma, :config_description, [
             alwaysShowSubjectInput: true,
             background: "/static/aurora_borealis.jpg",
             collapseMessageWithSubject: false,
-            disableChat: false,
             greentext: false,
             hideFilteredStatuses: false,
             hideMutedPosts: false,
@@ -1229,12 +1221,6 @@ config :pleroma, :config_description, [
             description:
               "When a message has a subject (aka Content Warning), collapse it by default"
           },
-          %{
-            key: :disableChat,
-            label: "PleromaFE Chat",
-            type: :boolean,
-            description: "Disables PleromaFE Chat component"
-          },
           %{
             key: :greentext,
             label: "Greentext",
@@ -2633,13 +2619,22 @@ config :pleroma, :config_description, [
   },
   %{
     group: :pleroma,
-    key: :chat,
+    key: :shout,
     type: :group,
-    description: "Pleroma chat settings",
+    description: "Pleroma shout settings",
     children: [
       %{
         key: :enabled,
-        type: :boolean
+        type: :boolean,
+        description: "Enables the backend Shoutbox chat feature."
+      },
+      %{
+        key: :limit,
+        type: :integer,
+        description: "Shout message character limit.",
+        suggestions: [
+          5_000
+        ]
       }
     ]
   },
index 4faaeff5bfbf7881f72468a3a33d44b4f8a6abf9..6b7ffb0e9064f7c3a19b19622c69398239219955 100644 (file)
@@ -1,4 +1,4 @@
-use Mix.Config
+import Config
 
 # For development, we disable any cache and enable
 # debugging and code reloading.
@@ -54,6 +54,10 @@ config :pleroma, Pleroma.Repo,
 
 config :pleroma, Pleroma.Web.ApiSpec.CastAndValidate, strict: true
 
+# Reduce recompilation time
+# https://dashbit.co/blog/speeding-up-re-compilation-of-elixir-projects
+config :phoenix, :plug_init_mode, :runtime
+
 if File.exists?("./config/dev.secret.exs") do
   import_config "dev.secret.exs"
 else
index 9ea0ec450161529855eed3e7dc44b0117b30cfbd..1cc396c3df0fe73bccbd1235518e92a08f9a6321 100644 (file)
@@ -1,4 +1,4 @@
-use Mix.Config
+import Config
 
 config :pleroma, Pleroma.Web.Endpoint,
   http: [
index adbce56066682508c0902086afe86bc2f38c8bec..968f596e00bc804bb71e06e112b2487f8939700f 100644 (file)
@@ -1,4 +1,4 @@
-use Mix.Config
+import Config
 
 # For production, we often load configuration from external
 # sources, such as your system environment. For this reason,
@@ -63,7 +63,12 @@ config :logger, :ex_syslogger, level: :info
 
 # Finally import the config/prod.secret.exs
 # which should be versioned separately.
-import_config "prod.secret.exs"
+if File.exists?("./config/prod.secret.exs") do
+  import_config "prod.secret.exs"
+else
+  "`config/prod.secret.exs` not found. You may want to create one by running `mix pleroma.instance gen`"
+  |> IO.warn([])
+end
 
 if File.exists?("./config/prod.exported_from_db.secret.exs"),
   do: import_config("prod.exported_from_db.secret.exs")
index 87396a88dbfe98e0764db7817c54476eb9c48ddb..d5c25f65e3cb2e533027df58be84befddca99834 100644 (file)
@@ -1,4 +1,4 @@
-use Mix.Config
+import Config
 
 # We don't run a server during test. If one is required,
 # you can enable the server option below.
@@ -133,6 +133,10 @@ config :pleroma, :side_effects,
   ap_streamer: Pleroma.Web.ActivityPub.ActivityPubMock,
   logger: Pleroma.LoggerMock
 
+# Reduce recompilation time
+# https://dashbit.co/blog/speeding-up-re-compilation-of-elixir-projects
+config :phoenix, :plug_init_mode, :runtime
+
 if File.exists?("./config/test.secret.exs") do
   import_config "test.secret.exs"
 else
index 5c3313a2cfec7dbeaf7db62e27156243bc440b33..59061aaa66a8413c58786fe9debda8a8104eb865 100644 (file)
@@ -8,9 +8,10 @@ For from source installations Pleroma configuration works by first importing the
 
 To add configuration to your config file, you can copy it from the base config. The latest version of it can be viewed [here](https://git.pleroma.social/pleroma/pleroma/blob/develop/config/config.exs). You can also use this file if you don't know how an option is supposed to be formatted.
 
-## :chat
+## :shout
 
-* `enabled` - Enables the backend chat. Defaults to `true`.
+* `enabled` - Enables the backend Shoutbox chat feature. Defaults to `true`.
+* `limit` - Shout character limit. Defaults to `5_000`
 
 ## :instance
 * `name`: The instance’s name.
@@ -19,7 +20,6 @@ To add configuration to your config file, you can copy it from the base config.
 * `description`: The instance’s description, can be seen in nodeinfo and ``/api/v1/instance``.
 * `limit`: Posts character limit (CW/Subject included in the counter).
 * `description_limit`: The character limit for image descriptions.
-* `chat_limit`: Character limit of the instance chat messages.
 * `remote_limit`: Hard character limit beyond which remote posts will be dropped.
 * `upload_limit`: File size limit of uploads (except for avatar, background, banner).
 * `avatar_upload_limit`: File size limit of user’s profile avatars.
@@ -37,7 +37,7 @@ To add configuration to your config file, you can copy it from the base config.
 * `federating`: Enable federation with other instances.
 * `federation_incoming_replies_max_depth`: Max. depth of reply-to activities fetching on incoming federation, to prevent out-of-memory situations while fetching very long threads. If set to `nil`, threads of any depth will be fetched. Lower this value if you experience out-of-memory crashes.
 * `federation_reachability_timeout_days`: Timeout (in days) of each external federation target being unreachable prior to pausing federating to it.
-* `allow_relay`: Enable Pleroma’s Relay, which makes it possible to follow a whole instance.
+* `allow_relay`: Permits remote instances to subscribe to all public posts of your instance. This may increase the visibility of your instance.
 * `public`: Makes the client API in authenticated mode-only except for user-profiles. Useful for disabling the Local Timeline and The Whole Known Network. Note that there is a dependent setting restricting or allowing unauthenticated access to specific resources, see `restrict_unauthenticated` for more details.
 * `quarantined_instances`: List of ActivityPub instances where private (DMs, followers-only) activities will not be send.
 * `allowed_post_formats`: MIME-type list of formats allowed to be posted (transformed into HTML).
index a14fcb416c39a95f97f9dd71a059622ca2297d41..6c1ecb559671e557032f59724a598baa8ceef543 100644 (file)
@@ -38,6 +38,7 @@ Has these additional fields under the `pleroma` object:
 - `thread_muted`: true if the thread the post belongs to is muted
 - `emoji_reactions`: A list with emoji / reaction maps. The format is `{name: "☕", count: 1, me: true}`. Contains no information about the reacting users, for that use the `/statuses/:id/reactions` endpoint.
 - `parent_visible`: If the parent of this post is visible to the user or not.
+- `pinned_at`: a datetime (iso8601) when status was pinned, `null` otherwise.
 
 ## Scheduled statuses
 
@@ -255,9 +256,29 @@ This information is returned in the `/api/v1/accounts/verify_credentials` endpoi
 
 *Pleroma supports refreshing tokens.*
 
-`POST /oauth/token`
+### POST `/oauth/token`
 
-Post here request with `grant_type=refresh_token` to obtain new access token. Returns an access token.
+You can obtain access tokens for a user in a few additional ways.
+
+#### Refreshing a token
+
+To obtain a new access token from a refresh token, pass `grant_type=refresh_token` with the following extra parameters:
+
+- `refresh_token`: The refresh token.
+
+#### Getting a token with a password
+
+To obtain a token from a user's password, pass `grant_type=password` with the following extra parameters:
+
+- `username`: Username to authenticate.
+- `password`: The user's password.
+
+#### Response body
+
+Additional fields are returned in the response:
+
+- `id`: The primary key of this token in Pleroma's database.
+- `me` (user tokens only): The ActivityPub ID of the user who owns the token.
 
 ## Account Registration
 
index 7eb1718f2f94f68923f65deb476eb3d9bb3e9da7..54859bf03a623c58f7a3bbf6f8f47984e4b7946f 100644 (file)
@@ -117,7 +117,7 @@ cd /opt/pleroma
 sudo -Hu pleroma mix deps.get
 ```
 
-* Generate the configuration: `sudo -Hu pleroma mix pleroma.instance gen`
+* Generate the configuration: `sudo -Hu pleroma MIX_ENV=prod mix pleroma.instance gen`
   * Answer with `yes` if it asks you to install `rebar3`.
   * This may take some time, because parts of pleroma get compiled first.
   * After that it will ask you a few questions about your instance and generates a configuration file in `config/generated_config.exs`.
@@ -240,4 +240,4 @@ sudo -Hu pleroma MIX_ENV=prod mix pleroma.user new <username> <your@emailaddress
 
 ## Questions
 
-Questions about the installation or didn’t it work as it should be, ask in [#pleroma:matrix.org](https://matrix.heldscal.la/#/room/#freenode_#pleroma:matrix.org) or IRC Channel **#pleroma** on **Freenode**.
+Questions about the installation or didn’t it work as it should be, ask in [#pleroma:libera.chat](https://matrix.to/#/#pleroma:libera.chat) via Matrix or **#pleroma** on **libera.chat** via IRC.
index da78c3205f86ba5bb05a40494f474f9c538cbd80..d11deb6210cc1e0c70c74db4fcbe61d41c5a150f 100644 (file)
@@ -92,7 +92,7 @@ cd /opt/pleroma
 sudo -Hu pleroma mix deps.get
 ```
 
-* Generate the configuration: `sudo -Hu pleroma mix pleroma.instance gen`
+* Generate the configuration: `sudo -Hu pleroma MIX_ENV=prod mix pleroma.instance gen`
   * Answer with `yes` if it asks you to install `rebar3`.
   * This may take some time, because parts of pleroma get compiled first.
   * After that it will ask you a few questions about your instance and generates a configuration file in `config/generated_config.exs`.
@@ -215,4 +215,4 @@ sudo -Hu pleroma MIX_ENV=prod mix pleroma.user new <username> <your@emailaddress
 
 ## Questions
 
-Questions about the installation or didn’t it work as it should be, ask in [#pleroma:matrix.org](https://matrix.heldscal.la/#/room/#freenode_#pleroma:matrix.org) or IRC Channel **#pleroma** on **Freenode**.
+Questions about the installation or didn’t it work as it should be, ask in [#pleroma:libera.chat](https://matrix.to/#/#pleroma:libera.chat) via Matrix or **#pleroma** on **libera.chat** via IRC.
index c5687a01e0db3b292fc60e1944bbf8d7db497d78..b8c2b8e867574487db7de24c2c3aee89883a8dbd 100644 (file)
@@ -90,7 +90,7 @@ cd /opt/pleroma
 sudo -Hu pleroma mix deps.get
 ```
 
-* Generate the configuration: `sudo -Hu pleroma mix pleroma.instance gen`
+* Generate the configuration: `sudo -Hu pleroma MIX_ENV=prod mix pleroma.instance gen`
   * Answer with `yes` if it asks you to install `rebar3`.
   * This may take some time, because parts of pleroma get compiled first.
   * After that it will ask you a few questions about your instance and generates a configuration file in `config/generated_config.exs`.
@@ -202,4 +202,4 @@ sudo -Hu pleroma MIX_ENV=prod mix pleroma.user new <username> <your@emailaddress
 
 ## Questions
 
-Questions about the installation or didn’t it work as it should be, ask in [#pleroma:matrix.org](https://matrix.heldscal.la/#/room/#freenode_#pleroma:matrix.org) or IRC Channel **#pleroma** on **Freenode**.
+Questions about the installation or didn’t it work as it should be, ask in [#pleroma:libera.chat](https://matrix.to/#/#pleroma:libera.chat) via Matrix or **#pleroma** on **libera.chat** via IRC.
index c4bbd47805893488e7332d88a16448b87309a859..3736e857fa20ea075041a100e4a27215702a8fa7 100644 (file)
@@ -89,7 +89,7 @@ sudo -Hu pleroma mix deps.get
 
 * コンフィギュレーションを生成します。
 ```
-sudo -Hu pleroma mix pleroma.instance gen
+sudo -Hu pleroma MIX_ENV=prod mix pleroma.instance gen
 ```
     * rebar3をインストールしてもよいか聞かれたら、yesを入力してください。
     * このときにpleromaの一部がコンパイルされるため、この処理には時間がかかります。
@@ -103,7 +103,7 @@ sudo -Hu pleroma mv config/{generated_config.exs,prod.secret.exs}
 
 * 先程のコマンドで、すでに `config/setup_db.psql` というファイルが作られています。このファイルをもとに、データベースを作成します。
 ```
-sudo -Hu pleroma mix pleroma.instance gen
+sudo -Hu pleroma MIX_ENV=prod mix pleroma.instance gen
 ```
 
 * そして、データベースのマイグレーションを実行します。
@@ -191,5 +191,5 @@ sudo -Hu pleroma MIX_ENV=prod mix pleroma.user new <username> <your@emailaddress
 
 インストールについて質問がある、もしくは、うまくいかないときは、以下のところで質問できます。
 
-* [#pleroma:matrix.org](https://matrix.heldscal.la/#/room/#freenode_#pleroma:matrix.org)
-* **Freenode** の **#pleroma** IRCチャンネル
+* [#pleroma:libera.chat](https://matrix.to/#/#pleroma:libera.chat)
+* **libera.chat** の **#pleroma** IRCチャンネル
index 2dc466eb8a61abc77552c3859ff45eb0cd3c16e3..39b8e8d664f7bd663b7c71410057c9cc2167bccd 100644 (file)
@@ -1,8 +1,8 @@
-# Installing on FreeBSD 
+# Installing on FreeBSD
 
 This document was written for FreeBSD 12.1, but should be work on future releases.
 
-## Required software 
+## Required software
 
 This assumes the target system has `pkg(8)`.
 
@@ -54,7 +54,7 @@ Configure Pleroma. Note that you need a domain name at this point:
 ```
 $ cd /home/pleroma/pleroma
 $ mix deps.get # Enter "y" when asked to install Hex
-$ mix pleroma.instance gen # You will be asked a few questions here.
+$ MIX_ENV=prod mix pleroma.instance gen # You will be asked a few questions here.
 $ cp config/generated_config.exs config/prod.secret.exs
 ```
 
@@ -213,4 +213,4 @@ incorrect timestamps. You should have ntpd running.
 
 ## Questions
 
-Questions about the installation or didn’t it work as it should be, ask in [#pleroma:matrix.org](https://matrix.heldscal.la/#/room/#freenode_#pleroma:matrix.org) or IRC Channel **#pleroma** on **Freenode**.
+Questions about the installation or didn’t it work as it should be, ask in [#pleroma:libera.chat](https://matrix.to/#/#pleroma:libera.chat) via Matrix or **#pleroma** on **libera.chat** via IRC.
index f2380ab72031f61a10ae186c2f60b4081563891f..d649393fc11842b4852a30b9bd6003649f9f203f 100644 (file)
@@ -54,7 +54,7 @@ Gentoo quite pointedly does not come with a cron daemon installed, and as such i
  # emerge --ask dev-db/postgresql dev-lang/elixir dev-vcs/git www-servers/nginx app-crypt/certbot app-crypt/certbot-nginx dev-util/cmake sys-apps/file
 ```
 
-If you would not like to install the optional packages, remove them from this line. 
+If you would not like to install the optional packages, remove them from this line.
 
 If you're running this from a low-powered virtual machine, it should work though it will take some time. There were no issues on a VPS with a single core and 1GB of RAM; if you are using an even more limited device and run into issues, you can try creating a swapfile or use a more powerful machine running Gentoo to [cross build](https://wiki.gentoo.org/wiki/Cross_build_environment). If you have a wait ahead of you, now would be a good time to take a break, strech a bit, refresh your beverage of choice and/or get a snack, and reply to Arch users' posts with "I use Gentoo btw" as we do.
 
@@ -79,12 +79,12 @@ The output from emerging postgresql should give you a command for initializing t
 ```
 
 * Start postgres and enable the system service
+
 ```shell
  # /etc/init.d/postgresql-11 start
  # rc-update add postgresql-11 default
  ```
+
 ### A note on licenses, the AGPL, and deployment procedures
 
 If you do not plan to make any modifications to your Pleroma instance, cloning directly from the main repo will get you what you need. However, if you plan on doing any contributions to upstream development, making changes or modifications to your instance, making custom themes, or want to play around--and let's be honest here, if you're using Gentoo that is most likely you--you will save yourself a lot of headache later if you take the time right now to fork the Pleroma repo and use that in the following section.
@@ -135,7 +135,7 @@ pleroma$ mix deps.get
 * Generate the configuration:
 
 ```shell
-pleroma$ mix pleroma.instance gen
+pleroma$ MIX_ENV=prod mix pleroma.instance gen
 ```
 
   * Answer with `yes` if it asks you to install `rebar3`.
@@ -241,7 +241,7 @@ First, ensure that the command you will be installing into your crontab works.
  # /usr/bin/certbot renew --nginx
 ```
 
-Assuming not much time has passed since you got certbot working a few steps ago, you should get a message for all domains you installed certificates for saying `Cert not yet due for renewal`. 
+Assuming not much time has passed since you got certbot working a few steps ago, you should get a message for all domains you installed certificates for saying `Cert not yet due for renewal`.
 
 Now, run crontab as a superuser with `crontab -e` or `sudo crontab -e` as appropriate, and add the following line to your cron:
 
@@ -298,4 +298,4 @@ If you opted to allow sudo for the `pleroma` user but would like to remove the a
 
 ## Questions
 
-Questions about the installation or didn’t it work as it should be, ask in [#pleroma:matrix.org](https://matrix.heldscal.la/#/room/#freenode_#pleroma:matrix.org) or IRC Channel **#pleroma** on **Freenode**.
+Questions about the installation or didn’t it work as it should be, ask in [#pleroma:libera.chat](https://matrix.to/#/#pleroma:libera.chat) via Matrix or **#pleroma** on **libera.chat** via IRC.
index 233cf28b75b309e8b3bcb8b258b98e0bdc787510..fc56e79ce74bd52a8822d31fb680ea4261544cff 100644 (file)
@@ -1,6 +1,6 @@
 # Installing on NetBSD
 
-## Required software 
+## Required software
 
 pkgin should have been installed by the NetBSD installer if you selected
 the right options. If it isn't installed, install it using pkg_add.
@@ -71,7 +71,7 @@ Configure Pleroma. Note that you need a domain name at this point:
 ```
 $ cd /home/pleroma/pleroma
 $ mix deps.get
-$ mix pleroma.instance gen # You will be asked a few questions here.
+$ MIX_ENV=prod mix pleroma.instance gen # You will be asked a few questions here.
 ```
 
 Since Postgres is configured, we can now initialize the database. There should
@@ -193,8 +193,6 @@ Run `# /etc/rc.d/pleroma start` to start Pleroma.
 
 Restart nginx with `# /etc/rc.d/nginx restart` and you should be up and running.
 
-If you need further help, contact niaa on freenode.
-
 Make sure your time is in sync, or other instances will receive your posts with
 incorrect timestamps. You should have ntpd running.
 
@@ -208,4 +206,4 @@ incorrect timestamps. You should have ntpd running.
 
 ## Questions
 
-Questions about the installation or didn’t it work as it should be, ask in [#pleroma:matrix.org](https://matrix.heldscal.la/#/room/#freenode_#pleroma:matrix.org) or IRC Channel **#pleroma** on **Freenode**.
+Questions about the installation or didn’t it work as it should be, ask in [#pleroma:libera.chat](https://matrix.to/#/#pleroma:libera.chat) via Matrix or **#pleroma** on **libera.chat** via IRC.
index 0e1269ca56b484fc3e8399c02b6050378acda815..95f0291804f42973ca7a9c278fd708cf910a6777 100644 (file)
@@ -239,7 +239,7 @@ Enter a shell as \_pleroma (as root `su _pleroma -`) and enter pleroma's install
 Then follow the main installation guide:
 
   * run `mix deps.get`
-  * run `mix pleroma.instance gen` and enter your instance's information when asked
+  * run `MIX_ENV=prod mix pleroma.instance gen` and enter your instance's information when asked
   * copy config/generated\_config.exs to config/prod.secret.exs. The default values should be sufficient but you should edit it and check that everything seems OK.
   * exit your current shell back to a root one and run `psql -U postgres -f /home/_pleroma/pleroma/config/setup_db.psql` to setup the database.
   * return to a \_pleroma shell into pleroma's installation directory (`su _pleroma -;cd ~/pleroma`) and run `MIX_ENV=prod mix ecto.migrate`
@@ -264,4 +264,4 @@ LC_ALL=en_US.UTF-8 MIX_ENV=prod mix pleroma.user new <username> <your@emailaddre
 
 ## Questions
 
-Questions about the installation or didn’t it work as it should be, ask in [#pleroma:matrix.org](https://matrix.heldscal.la/#/room/#freenode_#pleroma:matrix.org) or IRC Channel **#pleroma** on **Freenode**.
+Questions about the installation or didn’t it work as it should be, ask in [#pleroma:libera.chat](https://matrix.to/#/#pleroma:libera.chat) via Matrix or **#pleroma** on **libera.chat** via IRC.
index a61434147ccedf8636a1137bbc9a54361d4ec908..3c40b2d1a25bc8c53d4f90d3750be3e91d5ce395 100644 (file)
@@ -10,8 +10,8 @@ suositeltavaa tehdä komennon `doas` avulla, katso `doas (1)` ja `doas.conf (5)`
 Tästä eteenpäin oletuksena on, että domain "esimerkki.com" osoittaa
 serverin IP-osoitteeseen.
 
-Jos asennuksen kanssa on ongelmia, IRC-kanava #pleroma Freenodessa tai
-Matrix-kanava #freenode_#pleroma:matrix.org ovat hyviä paikkoja löytää apua
+Jos asennuksen kanssa on ongelmia, IRC-kanava #pleroma Libera.chat tai
+Matrix-kanava #pleroma:libera.chat ovat hyviä paikkoja löytää apua
 (englanniksi), `/msg eal kukkuu` jos haluat välttämättä puhua härmää.
 
 Asenna tarvittava ohjelmisto:
index 42e264e657c7a11182a7c76a54c0cdf9a3b6f2ae..8e43e3239c5b2cde4d94addb78c3fecb3aecf690 100644 (file)
@@ -232,7 +232,7 @@ At this point if you open your (sub)domain in a browser you should see a 502 err
 
 If everything worked, you should see Pleroma-FE when visiting your domain. If that didn't happen, try reviewing the installation steps, starting Pleroma in the foreground and seeing if there are any errrors.
 
-Still doesn't work? Feel free to contact us on [#pleroma on freenode](https://irc.pleroma.social) or via matrix at <https://matrix.heldscal.la/#/room/#freenode_#pleroma:matrix.org>, you can also [file an issue on our Gitlab](https://git.pleroma.social/pleroma/pleroma-support/issues/new)
+Questions about the installation or didn’t it work as it should be, ask in [#pleroma:libera.chat](https://matrix.to/#/#pleroma:libera.chat) via Matrix or **#pleroma** on **libera.chat** via IRC, you can also [file an issue on our Gitlab](https://git.pleroma.social/pleroma/pleroma-support/issues/new).
 
 ## Post installation
 
@@ -290,7 +290,7 @@ nginx -t
 
 ## Create your first user and set as admin
 ```sh
-cd /opt/pleroma/bin
+cd /opt/pleroma
 su pleroma -s $SHELL -lc "./bin/pleroma_ctl user new joeuser joeuser@sld.tld --admin"
 ```
 This will create an account withe the username of 'joeuser' with the email address of joeuser@sld.tld, and set that user's account as an admin. This will result in a link that you can paste into the browser, which logs you in and enables you to set the password.
@@ -301,4 +301,4 @@ This will create an account withe the username of 'joeuser' with the email addre
 
 ## Questions
 
-Questions about the installation or didn’t it work as it should be, ask in [#pleroma:matrix.org](https://matrix.heldscal.la/#/room/#freenode_#pleroma:matrix.org) or IRC Channel **#pleroma** on **Freenode**.
+Questions about the installation or didn’t it work as it should be, ask in [#pleroma:libera.chat](https://matrix.to/#/#pleroma:libera.chat) via Matrix or **#pleroma** on **libera.chat** via IRC, you can also [file an issue on our Gitlab](https://git.pleroma.social/pleroma/pleroma-support/issues/new).
index d594038849bbd6ed68bd7f774adb52cacec7b911..53beca5e6b0c7f6f057c916182ef2f44b30e05d2 100644 (file)
@@ -184,40 +184,48 @@ defmodule Pleroma.Activity do
     |> Repo.one()
   end
 
-  @spec get_by_id(String.t()) :: Activity.t() | nil
-  def get_by_id(id) do
-    case FlakeId.flake_id?(id) do
-      true ->
-        Activity
-        |> where([a], a.id == ^id)
-        |> restrict_deactivated_users()
-        |> Repo.one()
-
-      _ ->
-        nil
-    end
-  end
-
-  def get_by_id_with_user_actor(id) do
-    case FlakeId.flake_id?(id) do
-      true ->
-        Activity
-        |> where([a], a.id == ^id)
-        |> with_preloaded_user_actor()
-        |> Repo.one()
-
-      _ ->
-        nil
+  @doc """
+  Gets activity by ID, doesn't load activities from deactivated actors by default.
+  """
+  @spec get_by_id(String.t(), keyword()) :: t() | nil
+  def get_by_id(id, opts \\ [filter: [:restrict_deactivated]]), do: get_by_id_with_opts(id, opts)
+
+  @spec get_by_id_with_user_actor(String.t()) :: t() | nil
+  def get_by_id_with_user_actor(id), do: get_by_id_with_opts(id, preload: [:user_actor])
+
+  @spec get_by_id_with_object(String.t()) :: t() | nil
+  def get_by_id_with_object(id), do: get_by_id_with_opts(id, preload: [:object])
+
+  defp get_by_id_with_opts(id, opts) do
+    if FlakeId.flake_id?(id) do
+      query = Queries.by_id(id)
+
+      with_filters_query =
+        if is_list(opts[:filter]) do
+          Enum.reduce(opts[:filter], query, fn
+            {:type, type}, acc -> Queries.by_type(acc, type)
+            :restrict_deactivated, acc -> restrict_deactivated_users(acc)
+            _, acc -> acc
+          end)
+        else
+          query
+        end
+
+      with_preloads_query =
+        if is_list(opts[:preload]) do
+          Enum.reduce(opts[:preload], with_filters_query, fn
+            :user_actor, acc -> with_preloaded_user_actor(acc)
+            :object, acc -> with_preloaded_object(acc)
+            _, acc -> acc
+          end)
+        else
+          with_filters_query
+        end
+
+      Repo.one(with_preloads_query)
     end
   end
 
-  def get_by_id_with_object(id) do
-    Activity
-    |> where(id: ^id)
-    |> with_preloaded_object()
-    |> Repo.one()
-  end
-
   def all_by_ids_with_object(ids) do
     Activity
     |> where([a], a.id in ^ids)
@@ -269,6 +277,11 @@ defmodule Pleroma.Activity do
 
   def get_create_by_object_ap_id_with_object(_), do: nil
 
+  @spec create_by_id_with_object(String.t()) :: t() | nil
+  def create_by_id_with_object(id) do
+    get_by_id_with_opts(id, preload: [:object], filter: [type: "Create"])
+  end
+
   defp get_in_reply_to_activity_from_object(%Object{data: %{"inReplyTo" => ap_id}}) do
     get_create_by_object_ap_id_with_object(ap_id)
   end
@@ -368,12 +381,6 @@ defmodule Pleroma.Activity do
     end
   end
 
-  @spec pinned_by_actor?(Activity.t()) :: boolean()
-  def pinned_by_actor?(%Activity{} = activity) do
-    actor = user_actor(activity)
-    activity.id in actor.pinned_activities
-  end
-
   @spec get_by_object_ap_id_with_object(String.t()) :: t() | nil
   def get_by_object_ap_id_with_object(ap_id) when is_binary(ap_id) do
     ap_id
@@ -384,4 +391,13 @@ defmodule Pleroma.Activity do
   end
 
   def get_by_object_ap_id_with_object(_), do: nil
+
+  @spec add_by_params_query(String.t(), String.t(), String.t()) :: Ecto.Query.t()
+  def add_by_params_query(object_id, actor, target) do
+    object_id
+    |> Queries.by_object_id()
+    |> Queries.by_type("Add")
+    |> Queries.by_actor(actor)
+    |> where([a], fragment("?->>'target' = ?", a.data, ^target))
+  end
 end
diff --git a/lib/pleroma/activity/html.ex b/lib/pleroma/activity/html.ex
new file mode 100644 (file)
index 0000000..0bf3938
--- /dev/null
@@ -0,0 +1,45 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Activity.HTML do
+  alias Pleroma.HTML
+  alias Pleroma.Object
+
+  @cachex Pleroma.Config.get([:cachex, :provider], Cachex)
+
+  def get_cached_scrubbed_html_for_activity(
+        content,
+        scrubbers,
+        activity,
+        key \\ "",
+        callback \\ fn x -> x end
+      ) do
+    key = "#{key}#{generate_scrubber_signature(scrubbers)}|#{activity.id}"
+
+    @cachex.fetch!(:scrubber_cache, key, fn _key ->
+      object = Object.normalize(activity, fetch: false)
+      HTML.ensure_scrubbed_html(content, scrubbers, object.data["fake"] || false, callback)
+    end)
+  end
+
+  def get_cached_stripped_html_for_activity(content, activity, key) do
+    get_cached_scrubbed_html_for_activity(
+      content,
+      FastSanitize.Sanitizer.StripTags,
+      activity,
+      key,
+      &HtmlEntities.decode/1
+    )
+  end
+
+  defp generate_scrubber_signature(scrubber) when is_atom(scrubber) do
+    generate_scrubber_signature([scrubber])
+  end
+
+  defp generate_scrubber_signature(scrubbers) do
+    Enum.reduce(scrubbers, "", fn scrubber, signature ->
+      "#{signature}#{to_string(scrubber)}"
+    end)
+  end
+end
index a6b02a88944b23cbd07a003bd2ce097bcd4006ba..4632651b026da07cbf4ff66b434531d2cb13d2e2 100644 (file)
@@ -14,6 +14,11 @@ defmodule Pleroma.Activity.Queries do
   alias Pleroma.Activity
   alias Pleroma.User
 
+  @spec by_id(query(), String.t()) :: query()
+  def by_id(query \\ Activity, id) do
+    from(a in query, where: a.id == ^id)
+  end
+
   @spec by_ap_id(query, String.t()) :: query
   def by_ap_id(query \\ Activity, ap_id) do
     from(
index 06d399b2e3631493e0e7ca76ddc1236fd5fdb07d..9824e0a4ac3353ff195014cdcb591f6d1b809dda 100644 (file)
@@ -25,7 +25,7 @@ defmodule Pleroma.Application do
     if Process.whereis(Pleroma.Web.Endpoint) do
       case Config.get([:http, :user_agent], :default) do
         :default ->
-          info = "#{Pleroma.Web.base_url()} <#{Config.get([:instance, :email], "")}>"
+          info = "#{Pleroma.Web.Endpoint.url()} <#{Config.get([:instance, :email], "")}>"
           named_version() <> "; " <> info
 
         custom ->
@@ -102,7 +102,7 @@ defmodule Pleroma.Application do
         ] ++
         task_children(@mix_env) ++
         dont_run_in_test(@mix_env) ++
-        chat_child(chat_enabled?()) ++
+        shout_child(shout_enabled?()) ++
         [Pleroma.Gopher.Server]
 
     # See http://elixir-lang.org/docs/stable/elixir/Supervisor.html
@@ -216,7 +216,7 @@ defmodule Pleroma.Application do
       type: :worker
     }
 
-  defp chat_enabled?, do: Config.get([:chat, :enabled])
+  defp shout_enabled?, do: Config.get([:shout, :enabled])
 
   defp dont_run_in_test(env) when env in [:test, :benchmark], do: []
 
@@ -237,14 +237,14 @@ defmodule Pleroma.Application do
     ]
   end
 
-  defp chat_child(true) do
+  defp shout_child(true) do
     [
-      Pleroma.Web.ChatChannel.ChatChannelState,
+      Pleroma.Web.ShoutChannel.ShoutChannelState,
       {Phoenix.PubSub, [name: Pleroma.PubSub, adapter: Phoenix.PubSub.PG2]}
     ]
   end
 
-  defp chat_child(_), do: []
+  defp shout_child(_), do: []
 
   defp task_children(:test) do
     [
index 6ef65b263e7d5e542c4fe5c8c1a9b282ee828a59..ee6ee9516e7af24e45fecc8b1861cb72d91e828e 100644 (file)
@@ -34,15 +34,16 @@ defmodule Pleroma.ApplicationRequirements do
   defp check_welcome_message_config!(:ok) do
     if Pleroma.Config.get([:welcome, :email, :enabled], false) and
          not Pleroma.Emails.Mailer.enabled?() do
-      Logger.error("""
-      To send welcome email do you need to enable mail.
-      \nconfig :pleroma, Pleroma.Emails.Mailer, enabled: true
-      """)
+      Logger.warn("""
+      To send welcome emails, you need to enable the mailer.
+      Welcome emails will NOT be sent with the current config.
 
-      {:error, "The mail disabled."}
-    else
-      :ok
+      Enable the mailer:
+        config :pleroma, Pleroma.Emails.Mailer, enabled: true
+      """)
     end
+
+    :ok
   end
 
   defp check_welcome_message_config!(result), do: result
@@ -51,18 +52,21 @@ defmodule Pleroma.ApplicationRequirements do
   #
   def check_confirmation_accounts!(:ok) do
     if Pleroma.Config.get([:instance, :account_activation_required]) &&
-         not Pleroma.Config.get([Pleroma.Emails.Mailer, :enabled]) do
-      Logger.error(
-        "Account activation enabled, but no Mailer settings enabled.\n" <>
-          "Please set config :pleroma, :instance, account_activation_required: false\n" <>
-          "Otherwise setup and enable Mailer."
-      )
+         not Pleroma.Emails.Mailer.enabled?() do
+      Logger.warn("""
+      Account activation is required, but the mailer is disabled.
+      Users will NOT be able to confirm their accounts with this config.
+      Either disable account activation or enable the mailer.
 
-      {:error,
-       "Account activation enabled, but Mailer is disabled. Cannot send confirmation emails."}
-    else
-      :ok
+      Disable account activation:
+        config :pleroma, :instance, account_activation_required: false
+
+      Enable the mailer:
+        config :pleroma, Pleroma.Emails.Mailer, enabled: true
+      """)
     end
+
+    :ok
   end
 
   def check_confirmation_accounts!(result), do: result
@@ -160,9 +164,11 @@ defmodule Pleroma.ApplicationRequirements do
 
   defp check_system_commands!(:ok) do
     filter_commands_statuses = [
-      check_filter(Pleroma.Upload.Filters.Exiftool, "exiftool"),
-      check_filter(Pleroma.Upload.Filters.Mogrify, "mogrify"),
-      check_filter(Pleroma.Upload.Filters.Mogrifun, "mogrify")
+      check_filter(Pleroma.Upload.Filter.Exiftool, "exiftool"),
+      check_filter(Pleroma.Upload.Filter.Mogrify, "mogrify"),
+      check_filter(Pleroma.Upload.Filter.Mogrifun, "mogrify"),
+      check_filter(Pleroma.Upload.Filter.AnalyzeMetadata, "mogrify"),
+      check_filter(Pleroma.Upload.Filter.AnalyzeMetadata, "convert")
     ]
 
     preview_proxy_commands_status =
index 24aa5993bae0aefd1201c8d6daea8bcf47f31565..fedd58a7ef584bc09e9da5467d4583b80edde24e 100644 (file)
@@ -41,7 +41,8 @@ defmodule Pleroma.Config.DeprecationWarnings do
          :ok <- check_gun_pool_options(),
          :ok <- check_activity_expiration_config(),
          :ok <- check_remote_ip_plug_name(),
-         :ok <- check_uploders_s3_public_endpoint() do
+         :ok <- check_uploders_s3_public_endpoint(),
+         :ok <- check_old_chat_shoutbox() do
       :ok
     else
       _ ->
@@ -215,4 +216,27 @@ defmodule Pleroma.Config.DeprecationWarnings do
       :ok
     end
   end
+
+  @spec check_old_chat_shoutbox() :: :ok | nil
+  def check_old_chat_shoutbox do
+    instance_config = Pleroma.Config.get([:instance])
+    chat_config = Pleroma.Config.get([:chat]) || []
+
+    use_old_config =
+      Keyword.has_key?(instance_config, :chat_limit) or
+        Keyword.has_key?(chat_config, :enabled)
+
+    if use_old_config do
+      Logger.error("""
+      !!!DEPRECATION WARNING!!!
+      Your config is using the old namespace for the Shoutbox configuration. You need to convert to the new namespace. e.g.,
+      \n* `config :pleroma, :chat, enabled` and `config :pleroma, :instance, chat_limit` are now equal to:
+      \n* `config :pleroma, :shout, enabled` and `config :pleroma, :shout, limit`
+      """)
+
+      :error
+    else
+      :ok
+    end
+  end
 end
index b64d06707840788f6442f3ce68240370a7c8662a..9489f58c4f3018671df18616605bd60868c66e22 100644 (file)
@@ -3,19 +3,21 @@
 # SPDX-License-Identifier: AGPL-3.0-only
 
 defmodule Pleroma.Config.Loader do
-  @reject_keys [
-    Pleroma.Repo,
-    Pleroma.Web.Endpoint,
-    :env,
-    :configurable_from_database,
-    :database,
-    :swarm
-  ]
-
-  @reject_groups [
-    :postgrex,
-    :tesla
-  ]
+  defp reject_keys,
+    do: [
+      Pleroma.Repo,
+      Pleroma.Web.Endpoint,
+      :env,
+      :configurable_from_database,
+      :database,
+      :swarm
+    ]
+
+  defp reject_groups,
+    do: [
+      :postgrex,
+      :tesla
+    ]
 
   if Code.ensure_loaded?(Config.Reader) do
     @reader Config.Reader
@@ -52,7 +54,7 @@ defmodule Pleroma.Config.Loader do
   @spec filter_group(atom(), keyword()) :: keyword()
   def filter_group(group, configs) do
     Enum.reject(configs[group], fn {key, _v} ->
-      key in @reject_keys or group in @reject_groups or
+      key in reject_keys() or group in reject_groups() or
         (group == :phoenix and key == :serve_endpoints)
     end)
   end
index 8227195dcc58b2dde42e8baf92ec756843423dc1..e5e9d3dcd2c2a1019d990e76bfdaa9abb154e589 100644 (file)
@@ -1,6 +1,6 @@
 defmodule Pleroma.Config.ReleaseRuntimeProvider do
   @moduledoc """
-  Imports `runtime.exs` and `{env}.exported_from_db.secret.exs` for elixir releases.
+  Imports runtime config and `{env}.exported_from_db.secret.exs` for releases.
   """
   @behaviour Config.Provider
 
@@ -8,10 +8,11 @@ defmodule Pleroma.Config.ReleaseRuntimeProvider do
   def init(opts), do: opts
 
   @impl true
-  def load(config, _opts) do
+  def load(config, opts) do
     with_defaults = Config.Reader.merge(config, Pleroma.Config.Holder.release_defaults())
 
-    config_path = System.get_env("PLEROMA_CONFIG_PATH") || "/etc/pleroma/config.exs"
+    config_path =
+      opts[:config_path] || System.get_env("PLEROMA_CONFIG_PATH") || "/etc/pleroma/config.exs"
 
     with_runtime_config =
       if File.exists?(config_path) do
@@ -24,7 +25,7 @@ defmodule Pleroma.Config.ReleaseRuntimeProvider do
         warning = [
           IO.ANSI.red(),
           IO.ANSI.bright(),
-          "!!! #{config_path} not found! Please ensure it exists and that PLEROMA_CONFIG_PATH is unset or points to an existing file",
+          "!!! Config path is not declared! Please ensure it exists and that PLEROMA_CONFIG_PATH is unset or points to an existing file",
           IO.ANSI.reset()
         ]
 
@@ -33,13 +34,14 @@ defmodule Pleroma.Config.ReleaseRuntimeProvider do
       end
 
     exported_config_path =
-      config_path
-      |> Path.dirname()
-      |> Path.join("prod.exported_from_db.secret.exs")
+      opts[:exported_config_path] ||
+        config_path
+        |> Path.dirname()
+        |> Path.join("#{Pleroma.Config.get(:env)}.exported_from_db.secret.exs")
 
     with_exported =
       if File.exists?(exported_config_path) do
-        exported_config = Config.Reader.read!(with_runtime_config)
+        exported_config = Config.Reader.read!(exported_config_path)
         Config.Reader.merge(with_runtime_config, exported_config)
       else
         with_runtime_config
index aad45aab89127ad6f59d3b1147adbd2d8cd5aedc..d5c6081a240834a03222bf336f43b42398348582 100644 (file)
@@ -13,23 +13,25 @@ defmodule Pleroma.Config.TransferTask do
 
   @type env() :: :test | :benchmark | :dev | :prod
 
-  @reboot_time_keys [
-    {:pleroma, :hackney_pools},
-    {:pleroma, :chat},
-    {:pleroma, Oban},
-    {:pleroma, :rate_limit},
-    {:pleroma, :markup},
-    {:pleroma, :streamer},
-    {:pleroma, :pools},
-    {:pleroma, :connections_pool}
-  ]
-
-  @reboot_time_subkeys [
-    {:pleroma, Pleroma.Captcha, [:seconds_valid]},
-    {:pleroma, Pleroma.Upload, [:proxy_remote]},
-    {:pleroma, :instance, [:upload_limit]},
-    {:pleroma, :gopher, [:enabled]}
-  ]
+  defp reboot_time_keys,
+    do: [
+      {:pleroma, :hackney_pools},
+      {:pleroma, :shout},
+      {:pleroma, Oban},
+      {:pleroma, :rate_limit},
+      {:pleroma, :markup},
+      {:pleroma, :streamer},
+      {:pleroma, :pools},
+      {:pleroma, :connections_pool}
+    ]
+
+  defp reboot_time_subkeys,
+    do: [
+      {:pleroma, Pleroma.Captcha, [:seconds_valid]},
+      {:pleroma, Pleroma.Upload, [:proxy_remote]},
+      {:pleroma, :instance, [:upload_limit]},
+      {:pleroma, :gopher, [:enabled]}
+    ]
 
   def start_link(restart_pleroma? \\ true) do
     load_and_update_env([], restart_pleroma?)
@@ -165,12 +167,12 @@ defmodule Pleroma.Config.TransferTask do
   end
 
   defp group_and_key_need_reboot?(group, key) do
-    Enum.any?(@reboot_time_keys, fn {g, k} -> g == group and k == key end)
+    Enum.any?(reboot_time_keys(), fn {g, k} -> g == group and k == key end)
   end
 
   defp group_and_subkey_need_reboot?(group, key, value) do
     Keyword.keyword?(value) and
-      Enum.any?(@reboot_time_subkeys, fn {g, k, subkeys} ->
+      Enum.any?(reboot_time_subkeys(), fn {g, k, subkeys} ->
         g == group and k == key and
           Enum.any?(Keyword.keys(value), &(&1 in subkeys))
       end)
index b24338cc68dfb740cfff197b8f1843446e0bf87d..bf92f65cb170d1267fc5242765fcc521bde1861b 100644 (file)
@@ -27,6 +27,4 @@ defmodule Pleroma.Constants do
     do:
       ~w(index.html robots.txt static static-fe finmoji emoji packs sounds images instance sw.js sw-pleroma.js favicon.png schemas doc embed.js embed.css)
   )
-
-  def as_local_public, do: Pleroma.Web.base_url() <> "/#Public"
 end
diff --git a/lib/pleroma/earmark_renderer.ex b/lib/pleroma/earmark_renderer.ex
deleted file mode 100644 (file)
index 31cae3c..0000000
+++ /dev/null
@@ -1,256 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-#
-# This file is derived from Earmark, under the following copyright:
-# Copyright © 2014 Dave Thomas, The Pragmatic Programmers
-# SPDX-License-Identifier: Apache-2.0
-# Upstream: https://github.com/pragdave/earmark/blob/master/lib/earmark/html_renderer.ex
-defmodule Pleroma.EarmarkRenderer do
-  @moduledoc false
-
-  alias Earmark.Block
-  alias Earmark.Context
-  alias Earmark.HtmlRenderer
-  alias Earmark.Options
-
-  import Earmark.Inline, only: [convert: 3]
-  import Earmark.Helpers.HtmlHelpers
-  import Earmark.Message, only: [add_messages_from: 2, get_messages: 1, set_messages: 2]
-  import Earmark.Context, only: [append: 2, set_value: 2]
-  import Earmark.Options, only: [get_mapper: 1]
-
-  @doc false
-  def render(blocks, %Context{options: %Options{}} = context) do
-    messages = get_messages(context)
-
-    {contexts, html} =
-      get_mapper(context.options).(
-        blocks,
-        &render_block(&1, put_in(context.options.messages, []))
-      )
-      |> Enum.unzip()
-
-    all_messages =
-      contexts
-      |> Enum.reduce(messages, fn ctx, messages1 -> messages1 ++ get_messages(ctx) end)
-
-    {put_in(context.options.messages, all_messages), html |> IO.iodata_to_binary()}
-  end
-
-  #############
-  # Paragraph #
-  #############
-  defp render_block(%Block.Para{lnb: lnb, lines: lines, attrs: attrs}, context) do
-    lines = convert(lines, lnb, context)
-    add_attrs(lines, "<p>#{lines.value}</p>", attrs, [], lnb)
-  end
-
-  ########
-  # Html #
-  ########
-  defp render_block(%Block.Html{html: html}, context) do
-    {context, html}
-  end
-
-  defp render_block(%Block.HtmlComment{lines: lines}, context) do
-    {context, lines}
-  end
-
-  defp render_block(%Block.HtmlOneline{html: html}, context) do
-    {context, html}
-  end
-
-  #########
-  # Ruler #
-  #########
-  defp render_block(%Block.Ruler{lnb: lnb, attrs: attrs}, context) do
-    add_attrs(context, "<hr />", attrs, [], lnb)
-  end
-
-  ###########
-  # Heading #
-  ###########
-  defp render_block(
-         %Block.Heading{lnb: lnb, level: level, content: content, attrs: attrs},
-         context
-       ) do
-    converted = convert(content, lnb, context)
-    html = "<h#{level}>#{converted.value}</h#{level}>"
-    add_attrs(converted, html, attrs, [], lnb)
-  end
-
-  ##############
-  # Blockquote #
-  ##############
-
-  defp render_block(%Block.BlockQuote{lnb: lnb, blocks: blocks, attrs: attrs}, context) do
-    {context1, body} = render(blocks, context)
-    html = "<blockquote>#{body}</blockquote>"
-    add_attrs(context1, html, attrs, [], lnb)
-  end
-
-  #########
-  # Table #
-  #########
-
-  defp render_block(
-         %Block.Table{lnb: lnb, header: header, rows: rows, alignments: aligns, attrs: attrs},
-         context
-       ) do
-    {context1, html} = add_attrs(context, "<table>", attrs, [], lnb)
-    context2 = set_value(context1, html)
-
-    context3 =
-      if header do
-        append(add_trs(append(context2, "<thead>"), [header], "th", aligns, lnb), "</thead>")
-      else
-        # Maybe an error, needed append(context, html)
-        context2
-      end
-
-    context4 = append(add_trs(append(context3, "<tbody>"), rows, "td", aligns, lnb), "</tbody>")
-
-    {context4, [context4.value, "</table>"]}
-  end
-
-  ########
-  # Code #
-  ########
-
-  defp render_block(
-         %Block.Code{lnb: lnb, language: language, attrs: attrs} = block,
-         %Context{options: options} = context
-       ) do
-    class =
-      if language, do: ~s{ class="#{code_classes(language, options.code_class_prefix)}"}, else: ""
-
-    tag = ~s[<pre><code#{class}>]
-    lines = options.render_code.(block)
-    html = ~s[#{tag}#{lines}</code></pre>]
-    add_attrs(context, html, attrs, [], lnb)
-  end
-
-  #########
-  # Lists #
-  #########
-
-  defp render_block(
-         %Block.List{lnb: lnb, type: type, blocks: items, attrs: attrs, start: start},
-         context
-       ) do
-    {context1, content} = render(items, context)
-    html = "<#{type}#{start}>#{content}</#{type}>"
-    add_attrs(context1, html, attrs, [], lnb)
-  end
-
-  # format a single paragraph list item, and remove the para tags
-  defp render_block(
-         %Block.ListItem{lnb: lnb, blocks: blocks, spaced: false, attrs: attrs},
-         context
-       )
-       when length(blocks) == 1 do
-    {context1, content} = render(blocks, context)
-    content = Regex.replace(~r{</?p>}, content, "")
-    html = "<li>#{content}</li>"
-    add_attrs(context1, html, attrs, [], lnb)
-  end
-
-  # format a spaced list item
-  defp render_block(%Block.ListItem{lnb: lnb, blocks: blocks, attrs: attrs}, context) do
-    {context1, content} = render(blocks, context)
-    html = "<li>#{content}</li>"
-    add_attrs(context1, html, attrs, [], lnb)
-  end
-
-  ##################
-  # Footnote Block #
-  ##################
-
-  defp render_block(%Block.FnList{blocks: footnotes}, context) do
-    items =
-      Enum.map(footnotes, fn note ->
-        blocks = append_footnote_link(note)
-        %Block.ListItem{attrs: "#fn:#{note.number}", type: :ol, blocks: blocks}
-      end)
-
-    {context1, html} = render_block(%Block.List{type: :ol, blocks: items}, context)
-    {context1, Enum.join([~s[<div class="footnotes">], "<hr />", html, "</div>"])}
-  end
-
-  #######################################
-  # Isolated IALs are rendered as paras #
-  #######################################
-
-  defp render_block(%Block.Ial{verbatim: verbatim}, context) do
-    {context, "<p>{:#{verbatim}}</p>"}
-  end
-
-  ####################
-  # IDDef is ignored #
-  ####################
-
-  defp render_block(%Block.IdDef{}, context), do: {context, ""}
-
-  #####################################
-  # And here are the inline renderers #
-  #####################################
-
-  defdelegate br, to: HtmlRenderer
-  defdelegate codespan(text), to: HtmlRenderer
-  defdelegate em(text), to: HtmlRenderer
-  defdelegate strong(text), to: HtmlRenderer
-  defdelegate strikethrough(text), to: HtmlRenderer
-
-  defdelegate link(url, text), to: HtmlRenderer
-  defdelegate link(url, text, title), to: HtmlRenderer
-
-  defdelegate image(path, alt, title), to: HtmlRenderer
-
-  defdelegate footnote_link(ref, backref, number), to: HtmlRenderer
-
-  # Table rows
-  defp add_trs(context, rows, tag, aligns, lnb) do
-    numbered_rows =
-      rows
-      |> Enum.zip(Stream.iterate(lnb, &(&1 + 1)))
-
-    numbered_rows
-    |> Enum.reduce(context, fn {row, lnb}, ctx ->
-      append(add_tds(append(ctx, "<tr>"), row, tag, aligns, lnb), "</tr>")
-    end)
-  end
-
-  defp add_tds(context, row, tag, aligns, lnb) do
-    Enum.reduce(1..length(row), context, add_td_fn(row, tag, aligns, lnb))
-  end
-
-  defp add_td_fn(row, tag, aligns, lnb) do
-    fn n, ctx ->
-      style =
-        case Enum.at(aligns, n - 1, :default) do
-          :default -> ""
-          align -> " style=\"text-align: #{align}\""
-        end
-
-      col = Enum.at(row, n - 1)
-      converted = convert(col, lnb, set_messages(ctx, []))
-      append(add_messages_from(ctx, converted), "<#{tag}#{style}>#{converted.value}</#{tag}>")
-    end
-  end
-
-  ###############################
-  # Append Footnote Return Link #
-  ###############################
-
-  defdelegate append_footnote_link(note), to: HtmlRenderer
-  defdelegate append_footnote_link(note, fnlink), to: HtmlRenderer
-
-  defdelegate render_code(lines), to: HtmlRenderer
-
-  defp code_classes(language, prefix) do
-    ["" | String.split(prefix || "")]
-    |> Enum.map(fn pfx -> "#{pfx}#{language}" end)
-    |> Enum.join(" ")
-  end
-end
index af4b0e52779524291822a94ec552d97e020e0a88..06fed8fb3526fbf54f3f718d745146ece6d548a2 100644 (file)
@@ -13,21 +13,33 @@ defmodule Pleroma.EctoType.ActivityPub.ObjectValidators.Recipients do
     cast([object])
   end
 
+  def cast(object) when is_map(object) do
+    case ObjectID.cast(object) do
+      {:ok, data} -> {:ok, [data]}
+      _ -> :error
+    end
+  end
+
   def cast(data) when is_list(data) do
-    data
-    |> Enum.reduce_while({:ok, []}, fn element, {:ok, list} ->
-      case ObjectID.cast(element) do
-        {:ok, id} ->
-          {:cont, {:ok, [id | list]}}
-
-        _ ->
-          {:halt, :error}
-      end
-    end)
+    data =
+      data
+      |> Enum.reduce_while([], fn element, list ->
+        case ObjectID.cast(element) do
+          {:ok, id} ->
+            {:cont, [id | list]}
+
+          _ ->
+            {:cont, list}
+        end
+      end)
+      |> Enum.sort()
+      |> Enum.uniq()
+
+    {:ok, data}
   end
 
-  def cast(_) do
-    :error
+  def cast(data) do
+    {:error, data}
   end
 
   def dump(data) do
index 5fe74e2f7ce4ec5c4969e2a64144269ce26ceb72..88bc78aec52edce6a13d6fceb70443c36b712d7c 100644 (file)
@@ -73,7 +73,7 @@ defmodule Pleroma.Emails.AdminEmail do
     #{comment_html}
     #{statuses_html}
     <p>
-    <a href="#{Pleroma.Web.base_url()}/pleroma/admin/#/reports/index">View Reports in AdminFE</a>
+    <a href="#{Pleroma.Web.Endpoint.url()}/pleroma/admin/#/reports/index">View Reports in AdminFE</a>
     """
 
     new()
@@ -87,7 +87,7 @@ defmodule Pleroma.Emails.AdminEmail do
     html_body = """
     <p>New account for review: <a href="#{account.ap_id}">@#{account.nickname}</a></p>
     <blockquote>#{HTML.strip_tags(account.registration_reason)}</blockquote>
-    <a href="#{Pleroma.Web.base_url()}/pleroma/admin/#/users/#{account.id}/">Visit AdminFE</a>
+    <a href="#{Pleroma.Web.Endpoint.url()}/pleroma/admin/#/users/#{account.id}/">Visit AdminFE</a>
     """
 
     new()
index 52f3d419d7bc0715d97ea8ea1aacd9cda3d81056..e38c681bae9ad3cacaf5e826558d58e58c3d2eba 100644 (file)
@@ -5,15 +5,22 @@
 defmodule Pleroma.Emails.UserEmail do
   @moduledoc "User emails"
 
-  use Phoenix.Swoosh, view: Pleroma.Web.EmailView, layout: {Pleroma.Web.LayoutView, :email}
-
   alias Pleroma.Config
   alias Pleroma.User
   alias Pleroma.Web.Endpoint
   alias Pleroma.Web.Router
 
+  import Swoosh.Email
+  import Phoenix.Swoosh, except: [render_body: 3]
   import Pleroma.Config.Helpers, only: [instance_name: 0, sender: 0]
 
+  def render_body(email, template, assigns \\ %{}) do
+    email
+    |> put_new_layout({Pleroma.Web.LayoutView, :email})
+    |> put_new_view(Pleroma.Web.EmailView)
+    |> Phoenix.Swoosh.render_body(template, assigns)
+  end
+
   defp recipient(email, nil), do: email
   defp recipient(email, name), do: {name, email}
   defp recipient(%User{} = user), do: recipient(user.email, user.name)
index 50150e95169843dbca9d073298fe26f5f6da64d1..1914519528ed367d9852e91c9d79837cc69f9734 100644 (file)
@@ -5,7 +5,7 @@
 defmodule Pleroma.Emoji.Formatter do
   alias Pleroma.Emoji
   alias Pleroma.HTML
-  alias Pleroma.Web
+  alias Pleroma.Web.Endpoint
   alias Pleroma.Web.MediaProxy
 
   def emojify(text) do
@@ -44,7 +44,7 @@ defmodule Pleroma.Emoji.Formatter do
     Emoji.get_all()
     |> Enum.filter(fn {emoji, %Emoji{}} -> String.contains?(text, ":#{emoji}:") end)
     |> Enum.reduce(%{}, fn {name, %Emoji{file: file}}, acc ->
-      Map.put(acc, name, to_string(URI.merge(Web.base_url(), file)))
+      Map.put(acc, name, to_string(URI.merge(Endpoint.url(), file)))
     end)
   end
 
index 7a08e48a9c38565041933727a82da96ac8fe2ae2..ae37946ab79630af6111ae439a8849a33df4f379 100644 (file)
@@ -62,7 +62,7 @@ defmodule Pleroma.Formatter do
 
   def hashtag_handler("#" <> tag = tag_text, _buffer, _opts, acc) do
     tag = String.downcase(tag)
-    url = "#{Pleroma.Web.base_url()}/tag/#{tag}"
+    url = "#{Pleroma.Web.Endpoint.url()}/tag/#{tag}"
 
     link =
       Phoenix.HTML.Tag.content_tag(:a, tag_text,
@@ -121,6 +121,10 @@ defmodule Pleroma.Formatter do
     end
   end
 
+  def markdown_to_html(text) do
+    Earmark.as_html!(text, %Earmark.Options{compact_output: true})
+  end
+
   def html_escape({text, mentions, hashtags}, type) do
     {html_escape(text, type), mentions, hashtags}
   end
index f9c828facf2561c4af2265d62b4ac81b1abe25d5..bef1c98722be6bcac4f7193e30bea19d5c50eac3 100644 (file)
@@ -11,9 +11,7 @@ defmodule Pleroma.Gun do
   @callback await(pid(), reference()) :: {:response, :fin, 200, []}
   @callback set_owner(pid(), pid()) :: :ok
 
-  @api Pleroma.Config.get([Pleroma.Gun], Pleroma.Gun.API)
-
-  defp api, do: @api
+  defp api, do: Pleroma.Config.get([Pleroma.Gun], Pleroma.Gun.API)
 
   def open(host, port, opts), do: api().open(host, port, opts)
 
index c37b62bf2f21b7b812037045c90644a76124b030..4c643d7cbb607805c8f070e89e910bc3100b4ed5 100644 (file)
@@ -5,11 +5,11 @@
 defmodule Pleroma.Gun.ConnectionPool.Reclaimer do
   use GenServer, restart: :temporary
 
-  @registry Pleroma.Gun.ConnectionPool
+  defp registry, do: Pleroma.Gun.ConnectionPool
 
   def start_monitor do
     pid =
-      case :gen_server.start(__MODULE__, [], name: {:via, Registry, {@registry, "reclaimer"}}) do
+      case :gen_server.start(__MODULE__, [], name: {:via, Registry, {registry(), "reclaimer"}}) do
         {:ok, pid} ->
           pid
 
@@ -46,7 +46,7 @@ defmodule Pleroma.Gun.ConnectionPool.Reclaimer do
     #   {worker_pid, crf, last_reference} end)
     unused_conns =
       Registry.select(
-        @registry,
+        registry(),
         [
           {{:_, :"$1", {:_, :"$2", :"$3", :"$4"}}, [{:==, :"$2", []}], [{{:"$1", :"$3", :"$4"}}]}
         ]
index 02bfff2747ac3cfeaa1403aeb245a990b7c41bfa..a3fa75386946f198d4eb3158c65aa6c5b1daa277 100644 (file)
@@ -6,10 +6,10 @@ defmodule Pleroma.Gun.ConnectionPool.Worker do
   alias Pleroma.Gun
   use GenServer, restart: :temporary
 
-  @registry Pleroma.Gun.ConnectionPool
+  defp registry, do: Pleroma.Gun.ConnectionPool
 
   def start_link([key | _] = opts) do
-    GenServer.start_link(__MODULE__, opts, name: {:via, Registry, {@registry, key}})
+    GenServer.start_link(__MODULE__, opts, name: {:via, Registry, {registry(), key}})
   end
 
   @impl true
@@ -24,7 +24,7 @@ defmodule Pleroma.Gun.ConnectionPool.Worker do
       time = :erlang.monotonic_time(:millisecond)
 
       {_, _} =
-        Registry.update_value(@registry, key, fn _ ->
+        Registry.update_value(registry(), key, fn _ ->
           {conn_pid, [client_pid], 1, time}
         end)
 
@@ -65,7 +65,7 @@ defmodule Pleroma.Gun.ConnectionPool.Worker do
     time = :erlang.monotonic_time(:millisecond)
 
     {{conn_pid, used_by, _, _}, _} =
-      Registry.update_value(@registry, key, fn {conn_pid, used_by, crf, last_reference} ->
+      Registry.update_value(registry(), key, fn {conn_pid, used_by, crf, last_reference} ->
         {conn_pid, [client_pid | used_by], crf(time - last_reference, crf), time}
       end)
 
@@ -92,7 +92,7 @@ defmodule Pleroma.Gun.ConnectionPool.Worker do
   @impl true
   def handle_call(:remove_client, {client_pid, _}, %{key: key} = state) do
     {{_conn_pid, used_by, _crf, _last_reference}, _} =
-      Registry.update_value(@registry, key, fn {conn_pid, used_by, crf, last_reference} ->
+      Registry.update_value(registry(), key, fn {conn_pid, used_by, crf, last_reference} ->
         {conn_pid, List.delete(used_by, client_pid), crf, last_reference}
       end)
 
index 2dfdca6930bd2c147dccd09941cca62bf6054a9e..bee66169d7a8edfc0219c1a1dc48ef6c220a0040 100644 (file)
@@ -49,31 +49,6 @@ defmodule Pleroma.HTML do
   def filter_tags(html), do: filter_tags(html, nil)
   def strip_tags(html), do: filter_tags(html, FastSanitize.Sanitizer.StripTags)
 
-  def get_cached_scrubbed_html_for_activity(
-        content,
-        scrubbers,
-        activity,
-        key \\ "",
-        callback \\ fn x -> x end
-      ) do
-    key = "#{key}#{generate_scrubber_signature(scrubbers)}|#{activity.id}"
-
-    @cachex.fetch!(:scrubber_cache, key, fn _key ->
-      object = Pleroma.Object.normalize(activity, fetch: false)
-      ensure_scrubbed_html(content, scrubbers, object.data["fake"] || false, callback)
-    end)
-  end
-
-  def get_cached_stripped_html_for_activity(content, activity, key) do
-    get_cached_scrubbed_html_for_activity(
-      content,
-      FastSanitize.Sanitizer.StripTags,
-      activity,
-      key,
-      &HtmlEntities.decode/1
-    )
-  end
-
   def ensure_scrubbed_html(
         content,
         scrubbers,
@@ -92,16 +67,6 @@ defmodule Pleroma.HTML do
     end
   end
 
-  defp generate_scrubber_signature(scrubber) when is_atom(scrubber) do
-    generate_scrubber_signature([scrubber])
-  end
-
-  defp generate_scrubber_signature(scrubbers) do
-    Enum.reduce(scrubbers, "", fn scrubber, signature ->
-      "#{signature}#{to_string(scrubber)}"
-    end)
-  end
-
   def extract_first_external_url_from_object(%{data: %{"content" => content}} = object)
       when is_binary(content) do
     unless object.data["fake"] do
index 82c7fd65482f4e62a521809287cd28e8ef93ac2e..251539f3466f44c391194cb1a0e802a4cd544c80 100644 (file)
@@ -54,8 +54,8 @@ defmodule Pleroma.HTTP.AdapterHelper.Gun do
     Config.get([:pools, pool, :recv_timeout], default)
   end
 
-  @prefix Pleroma.Gun.ConnectionPool
   def limiter_setup do
+    prefix = Pleroma.Gun.ConnectionPool
     wait = Config.get([:connections_pool, :connection_acquisition_wait])
     retries = Config.get([:connections_pool, :connection_acquisition_retries])
 
@@ -66,7 +66,7 @@ defmodule Pleroma.HTTP.AdapterHelper.Gun do
       max_waiting = Keyword.get(opts, :max_waiting, 10)
 
       result =
-        ConcurrentLimiter.new(:"#{@prefix}.#{name}", max_running, max_waiting,
+        ConcurrentLimiter.new(:"#{prefix}.#{name}", max_running, max_waiting,
           wait: wait,
           max_retries: retries
         )
index 51f72fbf80029b4a1b7fc78a501c938dfa8284e9..16bbe6e8c3cde4d6fc44d25e561de5e51ef1d9d8 100644 (file)
@@ -5,8 +5,8 @@
 defmodule Pleroma.HTTP.WebPush do
   @moduledoc false
 
-  def post(url, payload, headers) do
+  def post(url, payload, headers, options \\ []) do
     list_headers = Map.to_list(headers)
-    Pleroma.HTTP.post(url, payload, list_headers)
+    Pleroma.HTTP.post(url, payload, list_headers, options)
   end
 end
index 0d2e94248d079c0f45a868d2345589ca7405ff95..b08b83305ca9154129c8e9ca45cbddff5301cf9f 100644 (file)
@@ -12,4 +12,10 @@ defmodule Pleroma.Maps do
       _ -> map
     end
   end
+
+  def safe_put_in(data, keys, value) when is_map(data) and is_list(keys) do
+    Kernel.put_in(data, keys, value)
+  rescue
+    _ -> data
+  end
 end
index 3ba749d1a36944e1120c53a07b28aca8d21e9003..c3ea1b98b5ff31d33635aabadde9148f22ba08b0 100644 (file)
@@ -366,7 +366,7 @@ defmodule Pleroma.Object do
   end
 
   def local?(%Object{data: %{"id" => id}}) do
-    String.starts_with?(id, Pleroma.Web.base_url() <> "/")
+    String.starts_with?(id, Pleroma.Web.Endpoint.url() <> "/")
   end
 
   def replies(object, opts \\ []) do
index fb0398f92cccc54a6b5dfbfc85a89be32ccb912c..040537acf1a802502fce0d945682050534ef8c53 100644 (file)
@@ -71,6 +71,14 @@ defmodule Pleroma.Object.Containment do
     compare_uris(id_uri, other_uri)
   end
 
+  # Mastodon pin activities don't have an id, so we check the object field, which will be pinned.
+  def contain_origin_from_id(id, %{"object" => object}) when is_binary(object) do
+    id_uri = URI.parse(id)
+    object_uri = URI.parse(object)
+
+    compare_uris(id_uri, object_uri)
+  end
+
   def contain_origin_from_id(_id, _data), do: :error
 
   def contain_child(%{"object" => %{"id" => id, "attributedTo" => _} = object}),
index bcccf1c4c9138af6355094d20d58b029cfff58a9..4ca67f0fda211e98b5e5f305c8ccf88f97cac2bc 100644 (file)
@@ -4,6 +4,7 @@
 
 defmodule Pleroma.Object.Fetcher do
   alias Pleroma.HTTP
+  alias Pleroma.Maps
   alias Pleroma.Object
   alias Pleroma.Object.Containment
   alias Pleroma.Repo
@@ -101,6 +102,9 @@ defmodule Pleroma.Object.Fetcher do
       {:transmogrifier, {:error, {:reject, e}}} ->
         {:reject, e}
 
+      {:transmogrifier, {:reject, e}} ->
+        {:reject, e}
+
       {:transmogrifier, _} = e ->
         {:error, e}
 
@@ -124,12 +128,14 @@ defmodule Pleroma.Object.Fetcher do
   defp prepare_activity_params(data) do
     %{
       "type" => "Create",
-      "to" => data["to"] || [],
-      "cc" => data["cc"] || [],
       # Should we seriously keep this attributedTo thing?
       "actor" => data["actor"] || data["attributedTo"],
       "object" => data
     }
+    |> Maps.put_if_present("to", data["to"])
+    |> Maps.put_if_present("cc", data["cc"])
+    |> Maps.put_if_present("bto", data["bto"])
+    |> Maps.put_if_present("bcc", data["bcc"])
   end
 
   def fetch_object_from_id!(id, options \\ []) do
index 6547113514b15fe3d74770f2fde3869930265223..17822dc5eb65959e5ad753f9550d0a119de058d8 100644 (file)
@@ -23,6 +23,9 @@ defmodule Pleroma.Upload do
     is once created permanent and changing it (especially in uploaders) is probably a bad idea!
   * `:tempfile` - path to the temporary file. Prefer in-place changes on the file rather than changing the
   path as the temporary file is also tracked by `Plug.Upload{}` and automatically deleted once the request is over.
+  * `:width` - width of the media in pixels
+  * `:height` - height of the media in pixels
+  * `:blurhash` - string hash of the image encoded with the blurhash algorithm (https://blurha.sh/)
 
   Related behaviors:
 
@@ -32,6 +35,7 @@ defmodule Pleroma.Upload do
   """
   alias Ecto.UUID
   alias Pleroma.Config
+  alias Pleroma.Maps
   require Logger
 
   @type source ::
@@ -53,9 +57,12 @@ defmodule Pleroma.Upload do
           name: String.t(),
           tempfile: String.t(),
           content_type: String.t(),
+          width: integer(),
+          height: integer(),
+          blurhash: String.t(),
           path: String.t()
         }
-  defstruct [:id, :name, :tempfile, :content_type, :path]
+  defstruct [:id, :name, :tempfile, :content_type, :width, :height, :blurhash, :path]
 
   defp get_description(opts, upload) do
     case {opts[:description], Pleroma.Config.get([Pleroma.Upload, :default_description])} do
@@ -89,9 +96,12 @@ defmodule Pleroma.Upload do
              "mediaType" => upload.content_type,
              "href" => url_from_spec(upload, opts.base_url, url_spec)
            }
+           |> Maps.put_if_present("width", upload.width)
+           |> Maps.put_if_present("height", upload.height)
          ],
          "name" => description
-       }}
+       }
+       |> Maps.put_if_present("blurhash", upload.blurhash)}
     else
       {:description_limit, _} ->
         {:error, :description_too_long}
@@ -225,7 +235,7 @@ defmodule Pleroma.Upload do
 
     case uploader do
       Pleroma.Uploaders.Local ->
-        upload_base_url || Pleroma.Web.base_url() <> "/media/"
+        upload_base_url || Pleroma.Web.Endpoint.url() <> "/media/"
 
       Pleroma.Uploaders.S3 ->
         bucket = Config.get([Pleroma.Uploaders.S3, :bucket])
@@ -251,7 +261,7 @@ defmodule Pleroma.Upload do
         end
 
       _ ->
-        public_endpoint || upload_base_url || Pleroma.Web.base_url() <> "/media/"
+        public_endpoint || upload_base_url || Pleroma.Web.Endpoint.url() <> "/media/"
     end
   end
 end
diff --git a/lib/pleroma/upload/filter/analyze_metadata.ex b/lib/pleroma/upload/filter/analyze_metadata.ex
new file mode 100644 (file)
index 0000000..8c23076
--- /dev/null
@@ -0,0 +1,45 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Upload.Filter.AnalyzeMetadata do
+  @moduledoc """
+  Extracts metadata about the upload, such as width/height
+  """
+  require Logger
+
+  @behaviour Pleroma.Upload.Filter
+
+  @spec filter(Pleroma.Upload.t()) ::
+          {:ok, :filtered, Pleroma.Upload.t()} | {:ok, :noop} | {:error, String.t()}
+  def filter(%Pleroma.Upload{tempfile: file, content_type: "image" <> _} = upload) do
+    try do
+      image =
+        file
+        |> Mogrify.open()
+        |> Mogrify.verbose()
+
+      upload =
+        upload
+        |> Map.put(:width, image.width)
+        |> Map.put(:height, image.height)
+        |> Map.put(:blurhash, get_blurhash(file))
+
+      {:ok, :filtered, upload}
+    rescue
+      e in ErlangError ->
+        Logger.warn("#{__MODULE__}: #{inspect(e)}")
+        {:ok, :noop}
+    end
+  end
+
+  def filter(_), do: {:ok, :noop}
+
+  defp get_blurhash(file) do
+    with {:ok, blurhash} <- :eblurhash.magick(file) do
+      blurhash
+    else
+      _ -> nil
+    end
+  end
+end
index 0be878ca282c0bf039b90a7395fd90bdb81e1d4c..deba548b7a9eeb2524e11173324435fa629e8cb8 100644 (file)
@@ -35,7 +35,7 @@ defmodule Pleroma.Uploaders.Uploader do
 
   """
   @type file_spec :: {:file | :url, String.t()}
-  @callback put_file(Pleroma.Upload.t()) ::
+  @callback put_file(upload :: struct()) ::
               :ok | {:ok, file_spec()} | {:error, String.t()} | :wait_callback
 
   @callback delete_file(file :: String.t()) :: :ok | {:error, String.t()}
@@ -46,7 +46,7 @@ defmodule Pleroma.Uploaders.Uploader do
               | {:error, Plug.Conn.t(), String.t()}
   @optional_callbacks http_callback: 2
 
-  @spec put_file(module(), Pleroma.Upload.t()) :: {:ok, file_spec()} | {:error, String.t()}
+  @spec put_file(module(), upload :: struct()) :: {:ok, file_spec()} | {:error, String.t()}
   def put_file(uploader, upload) do
     case uploader.put_file(upload) do
       :ok -> {:ok, {:file, upload.path}}
index a3d41fcd064fc41c55dadb101e265b4296aaec90..46bac283c79902a3b66d9f4790a26a2ae9e9fb81 100644 (file)
@@ -27,13 +27,13 @@ defmodule Pleroma.User do
   alias Pleroma.Repo
   alias Pleroma.User
   alias Pleroma.UserRelationship
-  alias Pleroma.Web
   alias Pleroma.Web.ActivityPub.ActivityPub
   alias Pleroma.Web.ActivityPub.Builder
   alias Pleroma.Web.ActivityPub.Pipeline
   alias Pleroma.Web.ActivityPub.Utils
   alias Pleroma.Web.CommonAPI
   alias Pleroma.Web.CommonAPI.Utils, as: CommonUtils
+  alias Pleroma.Web.Endpoint
   alias Pleroma.Web.OAuth
   alias Pleroma.Web.RelMe
   alias Pleroma.Workers.BackgroundWorker
@@ -99,6 +99,7 @@ defmodule Pleroma.User do
     field(:local, :boolean, default: true)
     field(:follower_address, :string)
     field(:following_address, :string)
+    field(:featured_address, :string)
     field(:search_rank, :float, virtual: true)
     field(:search_type, :integer, virtual: true)
     field(:tags, {:array, :string}, default: [])
@@ -129,7 +130,6 @@ defmodule Pleroma.User do
     field(:hide_followers, :boolean, default: false)
     field(:hide_follows, :boolean, default: false)
     field(:hide_favorites, :boolean, default: true)
-    field(:pinned_activities, {:array, :string}, default: [])
     field(:email_notifications, :map, default: %{"digest" => false})
     field(:mascot, :map, default: nil)
     field(:emoji, :map, default: %{})
@@ -147,6 +147,7 @@ defmodule Pleroma.User do
     field(:accepts_chat_messages, :boolean, default: nil)
     field(:last_active_at, :naive_datetime)
     field(:disclose_client, :boolean, default: true)
+    field(:pinned_objects, :map, default: %{})
 
     embeds_one(
       :notification_settings,
@@ -358,7 +359,7 @@ defmodule Pleroma.User do
 
       _ ->
         unless options[:no_default] do
-          Config.get([:assets, :default_user_avatar], "#{Web.base_url()}/images/avi.png")
+          Config.get([:assets, :default_user_avatar], "#{Endpoint.url()}/images/avi.png")
         end
     end
   end
@@ -366,13 +367,15 @@ defmodule Pleroma.User do
   def banner_url(user, options \\ []) do
     case user.banner do
       %{"url" => [%{"href" => href} | _]} -> href
-      _ -> !options[:no_default] && "#{Web.base_url()}/images/banner.png"
+      _ -> !options[:no_default] && "#{Endpoint.url()}/images/banner.png"
     end
   end
 
   # Should probably be renamed or removed
-  def ap_id(%User{nickname: nickname}), do: "#{Web.base_url()}/users/#{nickname}"
+  @spec ap_id(User.t()) :: String.t()
+  def ap_id(%User{nickname: nickname}), do: "#{Endpoint.url()}/users/#{nickname}"
 
+  @spec ap_followers(User.t()) :: String.t()
   def ap_followers(%User{follower_address: fa}) when is_binary(fa), do: fa
   def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers"
 
@@ -380,6 +383,11 @@ defmodule Pleroma.User do
   def ap_following(%User{following_address: fa}) when is_binary(fa), do: fa
   def ap_following(%User{} = user), do: "#{ap_id(user)}/following"
 
+  @spec ap_featured_collection(User.t()) :: String.t()
+  def ap_featured_collection(%User{featured_address: fa}) when is_binary(fa), do: fa
+
+  def ap_featured_collection(%User{} = user), do: "#{ap_id(user)}/collections/featured"
+
   defp truncate_fields_param(params) do
     if Map.has_key?(params, :fields) do
       Map.put(params, :fields, Enum.map(params[:fields], &truncate_field/1))
@@ -442,6 +450,7 @@ defmodule Pleroma.User do
         :uri,
         :follower_address,
         :following_address,
+        :featured_address,
         :hide_followers,
         :hide_follows,
         :hide_followers_count,
@@ -453,7 +462,8 @@ defmodule Pleroma.User do
         :invisible,
         :actor_type,
         :also_known_as,
-        :accepts_chat_messages
+        :accepts_chat_messages,
+        :pinned_objects
       ]
     )
     |> cast(params, [:name], empty_values: [])
@@ -685,7 +695,7 @@ defmodule Pleroma.User do
     |> validate_format(:nickname, local_nickname_regex())
     |> put_ap_id()
     |> unique_constraint(:ap_id)
-    |> put_following_and_follower_address()
+    |> put_following_and_follower_and_featured_address()
   end
 
   def register_changeset(struct, params \\ %{}, opts \\ []) do
@@ -746,7 +756,7 @@ defmodule Pleroma.User do
     |> put_password_hash
     |> put_ap_id()
     |> unique_constraint(:ap_id)
-    |> put_following_and_follower_address()
+    |> put_following_and_follower_and_featured_address()
   end
 
   def maybe_validate_required_email(changeset, true), do: changeset
@@ -764,11 +774,16 @@ defmodule Pleroma.User do
     put_change(changeset, :ap_id, ap_id)
   end
 
-  defp put_following_and_follower_address(changeset) do
-    followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
+  defp put_following_and_follower_and_featured_address(changeset) do
+    user = %User{nickname: get_field(changeset, :nickname)}
+    followers = ap_followers(user)
+    following = ap_following(user)
+    featured = ap_featured_collection(user)
 
     changeset
     |> put_change(:follower_address, followers)
+    |> put_change(:following_address, following)
+    |> put_change(:featured_address, featured)
   end
 
   defp autofollow_users(user) do
@@ -2334,45 +2349,35 @@ defmodule Pleroma.User do
     cast(user, %{is_approved: approved?}, [:is_approved])
   end
 
-  def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do
-    if id not in user.pinned_activities do
-      max_pinned_statuses = Config.get([:instance, :max_pinned_statuses], 0)
-      params = %{pinned_activities: user.pinned_activities ++ [id]}
-
-      # if pinned activity was scheduled for deletion, we remove job
-      if expiration = Pleroma.Workers.PurgeExpiredActivity.get_expiration(id) do
-        Oban.cancel_job(expiration.id)
-      end
+  @spec add_pinned_object_id(User.t(), String.t()) :: {:ok, User.t()} | {:error, term()}
+  def add_pinned_object_id(%User{} = user, object_id) do
+    if !user.pinned_objects[object_id] do
+      params = %{pinned_objects: Map.put(user.pinned_objects, object_id, NaiveDateTime.utc_now())}
 
       user
-      |> cast(params, [:pinned_activities])
-      |> validate_length(:pinned_activities,
-        max: max_pinned_statuses,
-        message: "You have already pinned the maximum number of statuses"
-      )
+      |> cast(params, [:pinned_objects])
+      |> validate_change(:pinned_objects, fn :pinned_objects, pinned_objects ->
+        max_pinned_statuses = Config.get([:instance, :max_pinned_statuses], 0)
+
+        if Enum.count(pinned_objects) <= max_pinned_statuses do
+          []
+        else
+          [pinned_objects: "You have already pinned the maximum number of statuses"]
+        end
+      end)
     else
       change(user)
     end
     |> update_and_set_cache()
   end
 
-  def remove_pinnned_activity(user, %Pleroma.Activity{id: id, data: data}) do
-    params = %{pinned_activities: List.delete(user.pinned_activities, id)}
-
-    # if pinned activity was scheduled for deletion, we reschedule it for deletion
-    if data["expires_at"] do
-      # MRF.ActivityExpirationPolicy used UTC timestamps for expires_at in original implementation
-      {:ok, expires_at} =
-        data["expires_at"] |> Pleroma.EctoType.ActivityPub.ObjectValidators.DateTime.cast()
-
-      Pleroma.Workers.PurgeExpiredActivity.enqueue(%{
-        activity_id: id,
-        expires_at: expires_at
-      })
-    end
-
+  @spec remove_pinned_object_id(User.t(), String.t()) :: {:ok, t()} | {:error, term()}
+  def remove_pinned_object_id(%User{} = user, object_id) do
     user
-    |> cast(params, [:pinned_activities])
+    |> cast(
+      %{pinned_objects: Map.delete(user.pinned_objects, object_id)},
+      [:pinned_objects]
+    )
     |> update_and_set_cache()
   end
 
index bc0c953322338b347316b5414f4d3f36b78cfffb..a446d3ae6f32a80d73a3780fe69e073f52aaccce 100644 (file)
@@ -11,6 +11,8 @@ defmodule Pleroma.Utils do
     eperm epipe erange erofs espipe esrch estale etxtbsy exdev
   )a
 
+  @repo_timeout Pleroma.Config.get([Pleroma.Repo, :timeout], 15_000)
+
   def compile_dir(dir) when is_binary(dir) do
     dir
     |> File.ls!()
@@ -63,4 +65,21 @@ defmodule Pleroma.Utils do
   end
 
   def posix_error_message(_), do: ""
+
+  @doc """
+  Returns [timeout: integer] suitable for passing as an option to Repo functions.
+
+  This function detects if the execution was triggered from IEx shell, Mix task, or
+  ./bin/pleroma_ctl and sets the timeout to :infinity, else returns the default timeout value.
+  """
+  @spec query_timeout() :: [timeout: integer]
+  def query_timeout do
+    {parent, _, _, _} = Process.info(self(), :current_stacktrace) |> elem(1) |> Enum.fetch!(2)
+
+    cond do
+      parent |> to_string |> String.starts_with?("Elixir.Mix.Task") -> [timeout: :infinity]
+      parent == :erl_eval -> [timeout: :infinity]
+      true -> [timeout: @repo_timeout]
+    end
+  end
 end
index 8630f244b8505dc5bba489fa88f72061f1b65396..d26931af95f088fe0e87051bc464081de9613665 100644 (file)
@@ -35,9 +35,10 @@ defmodule Pleroma.Web do
       import Plug.Conn
 
       import Pleroma.Web.Gettext
-      import Pleroma.Web.Router.Helpers
       import Pleroma.Web.TranslationHelpers
 
+      alias Pleroma.Web.Router.Helpers, as: Routes
+
       plug(:set_put_layout)
 
       defp set_put_layout(conn, _) do
@@ -131,7 +132,8 @@ defmodule Pleroma.Web do
 
       import Pleroma.Web.ErrorHelpers
       import Pleroma.Web.Gettext
-      import Pleroma.Web.Router.Helpers
+
+      alias Pleroma.Web.Router.Helpers, as: Routes
 
       require Logger
 
@@ -229,20 +231,4 @@ defmodule Pleroma.Web do
   defmacro __using__(which) when is_atom(which) do
     apply(__MODULE__, which, [])
   end
-
-  def base_url do
-    Pleroma.Web.Endpoint.url()
-  end
-
-  # TODO: Change to Phoenix.Router.routes/1 for Phoenix 1.6.0+
-  def get_api_routes do
-    Pleroma.Web.Router.__routes__()
-    |> Enum.reject(fn r -> r.plug == Pleroma.Web.Fallback.RedirectController end)
-    |> Enum.map(fn r ->
-      r.path
-      |> String.split("/", trim: true)
-      |> List.first()
-    end)
-    |> Enum.uniq()
-  end
 end
index efbf92c70c2539a479784a8091bf799fc8a2084e..18368943d1a910bbfe213e5ba6b8ccef96798ed8 100644 (file)
@@ -88,7 +88,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
 
   defp increase_replies_count_if_reply(_create_data), do: :noop
 
-  @object_types ~w[ChatMessage Question Answer Audio Video Event Article]
+  @object_types ~w[ChatMessage Question Answer Audio Video Event Article Note]
   @impl true
   def persist(%{"type" => type} = object, meta) when type in @object_types do
     with {:ok, object} <- Object.create(object) do
@@ -630,7 +630,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
       |> Map.put(:type, ["Create", "Announce"])
       |> Map.put(:user, reading_user)
       |> Map.put(:actor_id, user.ap_id)
-      |> Map.put(:pinned_activity_ids, user.pinned_activities)
+      |> Map.put(:pinned_object_ids, Map.keys(user.pinned_objects))
 
     params =
       if User.blocks?(reading_user, user) do
@@ -1075,8 +1075,18 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
 
   defp restrict_unlisted(query, _), do: query
 
-  defp restrict_pinned(query, %{pinned: true, pinned_activity_ids: ids}) do
-    from(activity in query, where: activity.id in ^ids)
+  defp restrict_pinned(query, %{pinned: true, pinned_object_ids: ids}) do
+    from(
+      [activity, object: o] in query,
+      where:
+        fragment(
+          "(?)->>'type' = 'Create' and coalesce((?)->'object'->>'id', (?)->>'object') = any (?)",
+          activity.data,
+          activity.data,
+          activity.data,
+          ^ids
+        )
+    )
   end
 
   defp restrict_pinned(query, _), do: query
@@ -1419,6 +1429,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
     invisible = data["invisible"] || false
     actor_type = data["type"] || "Person"
 
+    featured_address = data["featured"]
+    {:ok, pinned_objects} = fetch_and_prepare_featured_from_ap_id(featured_address)
+
     public_key =
       if is_map(data["publicKey"]) && is_binary(data["publicKey"]["publicKeyPem"]) do
         data["publicKey"]["publicKeyPem"]
@@ -1447,13 +1460,15 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
       name: data["name"],
       follower_address: data["followers"],
       following_address: data["following"],
+      featured_address: featured_address,
       bio: data["summary"] || "",
       actor_type: actor_type,
       also_known_as: Map.get(data, "alsoKnownAs", []),
       public_key: public_key,
       inbox: data["inbox"],
       shared_inbox: shared_inbox,
-      accepts_chat_messages: accepts_chat_messages
+      accepts_chat_messages: accepts_chat_messages,
+      pinned_objects: pinned_objects
     }
 
     # nickname can be nil because of virtual actors
@@ -1591,6 +1606,41 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
     end
   end
 
+  def pin_data_from_featured_collection(%{
+        "type" => type,
+        "orderedItems" => objects
+      })
+      when type in ["OrderedCollection", "Collection"] do
+    Map.new(objects, fn %{"id" => object_ap_id} -> {object_ap_id, NaiveDateTime.utc_now()} end)
+  end
+
+  def fetch_and_prepare_featured_from_ap_id(nil) do
+    {:ok, %{}}
+  end
+
+  def fetch_and_prepare_featured_from_ap_id(ap_id) do
+    with {:ok, data} <- Fetcher.fetch_and_contain_remote_object_from_id(ap_id) do
+      {:ok, pin_data_from_featured_collection(data)}
+    else
+      e ->
+        Logger.error("Could not decode featured collection at fetch #{ap_id}, #{inspect(e)}")
+        {:ok, %{}}
+    end
+  end
+
+  def pinned_fetch_task(nil), do: nil
+
+  def pinned_fetch_task(%{pinned_objects: pins}) do
+    if Enum.all?(pins, fn {ap_id, _} ->
+         Object.get_cached_by_ap_id(ap_id) ||
+           match?({:ok, _object}, Fetcher.fetch_object_from_id(ap_id))
+       end) do
+      :ok
+    else
+      :error
+    end
+  end
+
   def make_user_from_ap_id(ap_id) do
     user = User.get_cached_by_ap_id(ap_id)
 
@@ -1598,6 +1648,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
       Transmogrifier.upgrade_user_from_ap_id(ap_id)
     else
       with {:ok, data} <- fetch_and_prepare_user_from_ap_id(ap_id) do
+        {:ok, _pid} = Task.start(fn -> pinned_fetch_task(data) end)
+
         if user do
           user
           |> User.remote_user_changeset(data)
index 5ec8b7bab04912457ee805a53671a542471dd3e8..f39cd000acd174f0ef42d593c62016e7044620e9 100644 (file)
@@ -3,5 +3,5 @@
 # SPDX-License-Identifier: AGPL-3.0-only
 
 defmodule Pleroma.Web.ActivityPub.ActivityPub.Persisting do
-  @callback persist(map(), keyword()) :: {:ok, Activity.t() | Object.t()}
+  @callback persist(map(), keyword()) :: {:ok, struct()}
 end
index 983168bff394ffdf0e8e4b3fc0115abfade45959..33c7bf2bca0f1a8a138ead003ff708f38b00d18e 100644 (file)
@@ -3,10 +3,6 @@
 # SPDX-License-Identifier: AGPL-3.0-only
 
 defmodule Pleroma.Web.ActivityPub.ActivityPub.Streaming do
-  alias Pleroma.Activity
-  alias Pleroma.Object
-  alias Pleroma.User
-
-  @callback stream_out(Activity.t()) :: any()
-  @callback stream_out_participations(Object.t(), User.t()) :: any()
+  @callback stream_out(struct()) :: any()
+  @callback stream_out_participations(struct(), struct()) :: any()
 end
index 9d3dcc7f976122b2bc84dbb2589119bfb332cdb7..5aa3b281ad2aeca2a075baa82b9cf299b0603eb3 100644 (file)
@@ -543,4 +543,12 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
       |> json(object.data)
     end
   end
+
+  def pinned(conn, %{"nickname" => nickname}) do
+    with %User{} = user <- User.get_cached_by_nickname(nickname) do
+      conn
+      |> put_resp_header("content-type", "application/activity+json")
+      |> json(UserView.render("featured.json", %{user: user}))
+    end
+  end
 end
index f56bfc600ecce9b24c061b1485a21e376368bc41..cde4777103d3330aa3356f604698c1f6fe79345b 100644 (file)
@@ -223,7 +223,7 @@ defmodule Pleroma.Web.ActivityPub.Builder do
           [actor.follower_address]
 
         public? and Visibility.is_local_public?(object) ->
-          [actor.follower_address, object.data["actor"], Pleroma.Constants.as_local_public()]
+          [actor.follower_address, object.data["actor"], Utils.as_local_public()]
 
         public? ->
           [actor.follower_address, object.data["actor"], Pleroma.Constants.as_public()]
@@ -273,4 +273,36 @@ defmodule Pleroma.Web.ActivityPub.Builder do
        "context" => object.data["context"]
      }, []}
   end
+
+  @spec pin(User.t(), Object.t()) :: {:ok, map(), keyword()}
+  def pin(%User{} = user, object) do
+    {:ok,
+     %{
+       "id" => Utils.generate_activity_id(),
+       "target" => pinned_url(user.nickname),
+       "object" => object.data["id"],
+       "actor" => user.ap_id,
+       "type" => "Add",
+       "to" => [Pleroma.Constants.as_public()],
+       "cc" => [user.follower_address]
+     }, []}
+  end
+
+  @spec unpin(User.t(), Object.t()) :: {:ok, map, keyword()}
+  def unpin(%User{} = user, object) do
+    {:ok,
+     %{
+       "id" => Utils.generate_activity_id(),
+       "target" => pinned_url(user.nickname),
+       "object" => object.data["id"],
+       "actor" => user.ap_id,
+       "type" => "Remove",
+       "to" => [Pleroma.Constants.as_public()],
+       "cc" => [user.follower_address]
+     }, []}
+  end
+
+  defp pinned_url(nickname) when is_binary(nickname) do
+    Pleroma.Web.Router.Helpers.activity_pub_url(Pleroma.Web.Endpoint, :pinned, nickname)
+  end
 end
index 32bb1b64556d914ef0ea4dd7ee368ab5d8e13f19..f4c5db05ca805386239fc121381340c23eb71411 100644 (file)
@@ -6,7 +6,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.NoEmptyPolicy do
   @moduledoc "Filter local activities which have no content"
   @behaviour Pleroma.Web.ActivityPub.MRF
 
-  alias Pleroma.Web
+  alias Pleroma.Web.Endpoint
 
   @impl true
   def filter(%{"actor" => actor} = object) do
@@ -24,7 +24,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.NoEmptyPolicy do
   def filter(object), do: {:ok, object}
 
   defp is_local?(actor) do
-    if actor |> String.starts_with?("#{Web.base_url()}") do
+    if actor |> String.starts_with?("#{Endpoint.url()}") do
       true
     else
       false
index 62024c58c90929f9073ef72f00db9e2d0953ac5b..d40348cb1a764ada4bba67a017dca699162d3b88 100644 (file)
@@ -177,6 +177,14 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
 
   defp check_banner_removal(_actor_info, object), do: {:ok, object}
 
+  defp check_object(%{"object" => object} = activity) do
+    with {:ok, _object} <- filter(object) do
+      {:ok, activity}
+    end
+  end
+
+  defp check_object(object), do: {:ok, object}
+
   @impl true
   def filter(%{"type" => "Delete", "actor" => actor} = object) do
     %{host: actor_host} = URI.parse(actor)
@@ -202,7 +210,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
          {:ok, object} <- check_media_nsfw(actor_info, object),
          {:ok, object} <- check_ftl_removal(actor_info, object),
          {:ok, object} <- check_followers_only(actor_info, object),
-         {:ok, object} <- check_report_removal(actor_info, object) do
+         {:ok, object} <- check_report_removal(actor_info, object),
+         {:ok, object} <- check_object(object) do
       {:ok, object}
     else
       {:reject, nil} -> {:reject, "[SimplePolicy]"}
@@ -227,6 +236,19 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
     end
   end
 
+  def filter(object) when is_binary(object) do
+    uri = URI.parse(object)
+
+    with {:ok, object} <- check_accept(uri, object),
+         {:ok, object} <- check_reject(uri, object) do
+      {:ok, object}
+    else
+      {:reject, nil} -> {:reject, "[SimplePolicy]"}
+      {:reject, _} = e -> e
+      _ -> {:reject, "[SimplePolicy]"}
+    end
+  end
+
   def filter(object), do: {:ok, object}
 
   @impl true
index f757442039b54b3d2f33a918a5b3ff42ed0b4f91..248a12a36827ec26c6274f65871737734776ecfa 100644 (file)
@@ -17,6 +17,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
   alias Pleroma.Object.Containment
   alias Pleroma.User
   alias Pleroma.Web.ActivityPub.ObjectValidators.AcceptRejectValidator
+  alias Pleroma.Web.ActivityPub.ObjectValidators.AddRemoveValidator
   alias Pleroma.Web.ActivityPub.ObjectValidators.AnnounceValidator
   alias Pleroma.Web.ActivityPub.ObjectValidators.AnswerValidator
   alias Pleroma.Web.ActivityPub.ObjectValidators.ArticleNoteValidator
@@ -101,7 +102,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
         %{"type" => "Create", "object" => %{"type" => objtype} = object} = create_activity,
         meta
       )
-      when objtype in ~w[Question Answer Audio Video Event Article] do
+      when objtype in ~w[Question Answer Audio Video Event Article Note] do
     with {:ok, object_data} <- cast_and_apply(object),
          meta = Keyword.put(meta, :object_data, object_data |> stringify_keys),
          {:ok, create_activity} <-
@@ -113,9 +114,35 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
     end
   end
 
+  def validate(%{"type" => type} = object, meta)
+      when type in ~w[Event Question Audio Video Article Note] do
+    validator =
+      case type do
+        "Event" -> EventValidator
+        "Question" -> QuestionValidator
+        "Audio" -> AudioVideoValidator
+        "Video" -> AudioVideoValidator
+        "Article" -> ArticleNoteValidator
+        "Note" -> ArticleNoteValidator
+      end
+
+    with {:ok, object} <-
+           object
+           |> validator.cast_and_validate()
+           |> Ecto.Changeset.apply_action(:insert) do
+      object = stringify_keys(object)
+
+      # Insert copy of hashtags as strings for the non-hashtag table indexing
+      tag = (object["tag"] || []) ++ Object.hashtags(%Object{data: object})
+      object = Map.put(object, "tag", tag)
+
+      {:ok, object, meta}
+    end
+  end
+
   def validate(%{"type" => type} = object, meta)
       when type in ~w[Accept Reject Follow Update Like EmojiReact Announce
-      Event ChatMessage Question Audio Video Article Answer] do
+      ChatMessage Answer] do
     validator =
       case type do
         "Accept" -> AcceptRejectValidator
@@ -125,12 +152,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
         "Like" -> LikeValidator
         "EmojiReact" -> EmojiReactValidator
         "Announce" -> AnnounceValidator
-        "Event" -> EventValidator
         "ChatMessage" -> ChatMessageValidator
-        "Question" -> QuestionValidator
-        "Audio" -> AudioVideoValidator
-        "Video" -> AudioVideoValidator
-        "Article" -> ArticleNoteValidator
         "Answer" -> AnswerValidator
       end
 
@@ -143,6 +165,16 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
     end
   end
 
+  def validate(%{"type" => type} = object, meta) when type in ~w(Add Remove) do
+    with {:ok, object} <-
+           object
+           |> AddRemoveValidator.cast_and_validate()
+           |> Ecto.Changeset.apply_action(:insert) do
+      object = stringify_keys(object)
+      {:ok, object, meta}
+    end
+  end
+
   def cast_and_apply(%{"type" => "ChatMessage"} = object) do
     ChatMessageValidator.cast_and_apply(object)
   end
@@ -163,13 +195,13 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
     EventValidator.cast_and_apply(object)
   end
 
-  def cast_and_apply(%{"type" => "Article"} = object) do
+  def cast_and_apply(%{"type" => type} = object) when type in ~w[Article Note] do
     ArticleNoteValidator.cast_and_apply(object)
   end
 
   def cast_and_apply(o), do: {:error, {:validator_not_set, o}}
 
-  # is_struct/1 isn't present in Elixir 1.8.x
+  # is_struct/1 appears in Elixir 1.11
   def stringify_keys(%{__struct__: _} = object) do
     object
     |> Map.from_struct()
index d31e780c393ee2e3985a19cdf0f7c6877374c182..b577a1044d852baab57fa189f47f78af65d3b6cd 100644 (file)
@@ -27,7 +27,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AcceptRejectValidator do
     |> cast(data, __schema__(:fields))
   end
 
-  def validate_data(cng) do
+  defp validate_data(cng) do
     cng
     |> validate_required([:id, :type, :actor, :to, :cc, :object])
     |> validate_inclusion(:type, ["Accept", "Reject"])
diff --git a/lib/pleroma/web/activity_pub/object_validators/add_remove_validator.ex b/lib/pleroma/web/activity_pub/object_validators/add_remove_validator.ex
new file mode 100644 (file)
index 0000000..f885aab
--- /dev/null
@@ -0,0 +1,77 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.ObjectValidators.AddRemoveValidator do
+  use Ecto.Schema
+
+  import Ecto.Changeset
+  import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
+
+  require Pleroma.Constants
+
+  alias Pleroma.EctoType.ActivityPub.ObjectValidators
+  alias Pleroma.User
+
+  @primary_key false
+
+  embedded_schema do
+    field(:id, ObjectValidators.ObjectID, primary_key: true)
+    field(:target)
+    field(:object, ObjectValidators.ObjectID)
+    field(:actor, ObjectValidators.ObjectID)
+    field(:type)
+    field(:to, ObjectValidators.Recipients, default: [])
+    field(:cc, ObjectValidators.Recipients, default: [])
+  end
+
+  def cast_and_validate(data) do
+    {:ok, actor} = User.get_or_fetch_by_ap_id(data["actor"])
+
+    {:ok, actor} = maybe_refetch_user(actor)
+
+    data
+    |> maybe_fix_data_for_mastodon(actor)
+    |> cast_data()
+    |> validate_data(actor)
+  end
+
+  defp maybe_fix_data_for_mastodon(data, actor) do
+    # Mastodon sends pin/unpin objects without id, to, cc fields
+    data
+    |> Map.put_new("id", Pleroma.Web.ActivityPub.Utils.generate_activity_id())
+    |> Map.put_new("to", [Pleroma.Constants.as_public()])
+    |> Map.put_new("cc", [actor.follower_address])
+  end
+
+  defp cast_data(data) do
+    cast(%__MODULE__{}, data, __schema__(:fields))
+  end
+
+  defp validate_data(changeset, actor) do
+    changeset
+    |> validate_required([:id, :target, :object, :actor, :type, :to, :cc])
+    |> validate_inclusion(:type, ~w(Add Remove))
+    |> validate_actor_presence()
+    |> validate_collection_belongs_to_actor(actor)
+    |> validate_object_presence()
+  end
+
+  defp validate_collection_belongs_to_actor(changeset, actor) do
+    validate_change(changeset, :target, fn :target, target ->
+      if target == actor.featured_address do
+        []
+      else
+        [target: "collection doesn't belong to actor"]
+      end
+    end)
+  end
+
+  defp maybe_refetch_user(%User{featured_address: address} = user) when is_binary(address) do
+    {:ok, user}
+  end
+
+  defp maybe_refetch_user(%User{ap_id: ap_id}) do
+    Pleroma.Web.ActivityPub.Transmogrifier.upgrade_user_from_ap_id(ap_id)
+  end
+end
index b08a33e6869a01e8af4675d31f81432998bb6a85..a2f752ac3ce2836f46d08b8514423aadbab6e355 100644 (file)
@@ -50,7 +50,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AnnounceValidator do
     cng
   end
 
-  def validate_data(data_cng) do
+  defp validate_data(data_cng) do
     data_cng
     |> validate_inclusion(:type, ["Announce"])
     |> validate_required([:id, :type, :object, :actor, :to, :cc])
@@ -68,7 +68,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AnnounceValidator do
          false <- Visibility.is_public?(object) do
       same_actor = object.data["actor"] == actor.ap_id
       recipients = get_field(cng, :to) ++ get_field(cng, :cc)
-      local_public = Pleroma.Constants.as_local_public()
+      local_public = Utils.as_local_public()
 
       is_public =
         Enum.member?(recipients, Pleroma.Constants.as_public()) or
index 15e4413cde58b90bd6ad94adb0d0b921ba6bab6e..3451e1ff8937254f61de9afadaa7150273d9b879 100644 (file)
@@ -6,6 +6,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AnswerValidator do
   use Ecto.Schema
 
   alias Pleroma.EctoType.ActivityPub.ObjectValidators
+  alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
   alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
 
   import Ecto.Changeset
@@ -23,6 +24,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AnswerValidator do
     field(:name, :string)
     field(:inReplyTo, ObjectValidators.ObjectID)
     field(:attributedTo, ObjectValidators.ObjectID)
+    field(:context, :string)
 
     # TODO: Remove actor on objects
     field(:actor, ObjectValidators.ObjectID)
@@ -46,11 +48,16 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AnswerValidator do
   end
 
   def changeset(struct, data) do
+    data =
+      data
+      |> CommonFixes.fix_actor()
+      |> CommonFixes.fix_object_defaults()
+
     struct
     |> cast(data, __schema__(:fields))
   end
 
-  def validate_data(data_cng) do
+  defp validate_data(data_cng) do
     data_cng
     |> validate_inclusion(:type, ["Answer"])
     |> validate_required([:id, :inReplyTo, :name, :attributedTo, :actor])
index b0388ef3b8d2f8e3f8c24540b1d8edd6904a543e..193f85f49292750bebcb60acdce1677cc6b44bf0 100644 (file)
@@ -9,6 +9,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNoteValidator do
   alias Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator
   alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
   alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
+  alias Pleroma.Web.ActivityPub.ObjectValidators.TagValidator
   alias Pleroma.Web.ActivityPub.Transmogrifier
 
   import Ecto.Changeset
@@ -22,8 +23,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNoteValidator do
     field(:cc, ObjectValidators.Recipients, default: [])
     field(:bto, ObjectValidators.Recipients, default: [])
     field(:bcc, ObjectValidators.Recipients, default: [])
-    # TODO: Write type
-    field(:tag, {:array, :map}, default: [])
+    embeds_many(:tag, TagValidator)
     field(:type, :string)
 
     field(:name, :string)
@@ -50,6 +50,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNoteValidator do
 
     field(:likes, {:array, ObjectValidators.ObjectID}, default: [])
     field(:announcements, {:array, ObjectValidators.ObjectID}, default: [])
+
+    field(:replies, {:array, ObjectValidators.ObjectID}, default: [])
   end
 
   def cast_and_apply(data) do
@@ -65,36 +67,51 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNoteValidator do
   end
 
   def cast_data(data) do
-    data = fix(data)
-
     %__MODULE__{}
     |> changeset(data)
   end
 
-  defp fix_url(%{"url" => url} = data) when is_map(url) do
-    Map.put(data, "url", url["href"])
-  end
-
+  defp fix_url(%{"url" => url} = data) when is_bitstring(url), do: data
+  defp fix_url(%{"url" => url} = data) when is_map(url), do: Map.put(data, "url", url["href"])
   defp fix_url(data), do: data
 
+  defp fix_tag(%{"tag" => tag} = data) when is_list(tag), do: data
+  defp fix_tag(%{"tag" => tag} = data) when is_map(tag), do: Map.put(data, "tag", [tag])
+  defp fix_tag(data), do: Map.drop(data, ["tag"])
+
+  defp fix_replies(%{"replies" => %{"first" => %{"items" => replies}}} = data)
+       when is_list(replies),
+       do: Map.put(data, "replies", replies)
+
+  defp fix_replies(%{"replies" => %{"items" => replies}} = data) when is_list(replies),
+    do: Map.put(data, "replies", replies)
+
+  defp fix_replies(%{"replies" => replies} = data) when is_bitstring(replies),
+    do: Map.drop(data, ["replies"])
+
+  defp fix_replies(data), do: data
+
   defp fix(data) do
     data
-    |> CommonFixes.fix_defaults()
-    |> CommonFixes.fix_attribution()
     |> CommonFixes.fix_actor()
+    |> CommonFixes.fix_object_defaults()
     |> fix_url()
+    |> fix_tag()
+    |> fix_replies()
     |> Transmogrifier.fix_emoji()
+    |> Transmogrifier.fix_content_map()
   end
 
   def changeset(struct, data) do
     data = fix(data)
 
     struct
-    |> cast(data, __schema__(:fields) -- [:attachment])
+    |> cast(data, __schema__(:fields) -- [:attachment, :tag])
     |> cast_embed(:attachment)
+    |> cast_embed(:tag)
   end
 
-  def validate_data(data_cng) do
+  defp validate_data(data_cng) do
     data_cng
     |> validate_inclusion(:type, ["Article", "Note"])
     |> validate_required([:id, :actor, :attributedTo, :type, :context, :context_id])
index 3175427adc981fd87d7743f6da10a374fbe26078..837787b9fcd3955583d1121823cc84e1ccb3c1b8 100644 (file)
@@ -6,7 +6,6 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator do
   use Ecto.Schema
 
   alias Pleroma.EctoType.ActivityPub.ObjectValidators
-  alias Pleroma.Web.ActivityPub.ObjectValidators.UrlObjectValidator
 
   import Ecto.Changeset
 
@@ -21,6 +20,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator do
       field(:type, :string)
       field(:href, ObjectValidators.Uri)
       field(:mediaType, :string, default: "application/octet-stream")
+      field(:width, :integer)
+      field(:height, :integer)
     end
   end
 
@@ -52,7 +53,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator do
     data = fix_media_type(data)
 
     struct
-    |> cast(data, [:type, :href, :mediaType])
+    |> cast(data, [:type, :href, :mediaType, :width, :height])
     |> validate_inclusion(:type, ["Link"])
     |> validate_required([:type, :href, :mediaType])
   end
@@ -60,7 +61,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator do
   def fix_media_type(data) do
     data = Map.put_new(data, "mediaType", data["mimeType"])
 
-    if MIME.valid?(data["mediaType"]) do
+    if is_bitstring(data["mediaType"]) && MIME.extensions(data["mediaType"]) != [] do
       data
     else
       Map.put(data, "mediaType", "application/octet-stream")
@@ -90,7 +91,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator do
     end
   end
 
-  def validate_data(cng) do
+  defp validate_data(cng) do
     cng
     |> validate_inclusion(:type, ~w[Document Audio Image Video])
     |> validate_required([:mediaType, :url, :type])
index 4a96fef529899be4e80ac8c606df1b2dcbe15488..572687deb845d5b34d8ce5f4759d5155502033b4 100644 (file)
@@ -5,11 +5,11 @@
 defmodule Pleroma.Web.ActivityPub.ObjectValidators.AudioVideoValidator do
   use Ecto.Schema
 
-  alias Pleroma.EarmarkRenderer
   alias Pleroma.EctoType.ActivityPub.ObjectValidators
   alias Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator
   alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
   alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
+  alias Pleroma.Web.ActivityPub.ObjectValidators.TagValidator
   alias Pleroma.Web.ActivityPub.Transmogrifier
 
   import Ecto.Changeset
@@ -23,8 +23,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AudioVideoValidator do
     field(:cc, ObjectValidators.Recipients, default: [])
     field(:bto, ObjectValidators.Recipients, default: [])
     field(:bcc, ObjectValidators.Recipients, default: [])
-    # TODO: Write type
-    field(:tag, {:array, :map}, default: [])
+    embeds_many(:tag, TagValidator)
     field(:type, :string)
 
     field(:name, :string)
@@ -110,7 +109,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AudioVideoValidator do
        when is_binary(content) do
     content =
       content
-      |> Earmark.as_html!(%Earmark.Options{renderer: EarmarkRenderer})
+      |> Pleroma.Formatter.markdown_to_html()
       |> Pleroma.HTML.filter_tags()
 
     Map.put(data, "content", content)
@@ -120,9 +119,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AudioVideoValidator do
 
   defp fix(data) do
     data
-    |> CommonFixes.fix_defaults()
-    |> CommonFixes.fix_attribution()
     |> CommonFixes.fix_actor()
+    |> CommonFixes.fix_object_defaults()
     |> Transmogrifier.fix_emoji()
     |> fix_url()
     |> fix_content()
@@ -132,11 +130,12 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AudioVideoValidator do
     data = fix(data)
 
     struct
-    |> cast(data, __schema__(:fields) -- [:attachment])
+    |> cast(data, __schema__(:fields) -- [:attachment, :tag])
     |> cast_embed(:attachment)
+    |> cast_embed(:tag)
   end
 
-  def validate_data(data_cng) do
+  defp validate_data(data_cng) do
     data_cng
     |> validate_inclusion(:type, ["Audio", "Video"])
     |> validate_required([:id, :actor, :attributedTo, :type, :context, :attachment])
index c5f77bb76edb8c8cb485e59fdc08d8e00e81b803..88948135fc2f19fee402c5c6a5b9ad9a732927e0 100644 (file)
@@ -26,7 +26,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.BlockValidator do
     |> cast(data, __schema__(:fields))
   end
 
-  def validate_data(cng) do
+  defp validate_data(cng) do
     cng
     |> validate_required([:id, :type, :actor, :to, :cc, :object])
     |> validate_inclusion(:type, ["Block"])
index 1189778f20ff7ef06766f43546e6bdc72c8ee0fa..b153156b031cbe71e77bac71ea725366e5a66434 100644 (file)
@@ -67,7 +67,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ChatMessageValidator do
     |> cast_embed(:attachment)
   end
 
-  def validate_data(data_cng) do
+  defp validate_data(data_cng) do
     data_cng
     |> validate_inclusion(:type, ["ChatMessage"])
     |> validate_required([:id, :actor, :to, :type, :published])
index 5f2c633bc21627f6bc5c475c3a0155bb0b772e37..c958fcc5d2c680439e9ba08a3f9ba5ac2ab8c62a 100644 (file)
@@ -3,26 +3,55 @@
 # SPDX-License-Identifier: AGPL-3.0-only
 
 defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes do
+  alias Pleroma.EctoType.ActivityPub.ObjectValidators
   alias Pleroma.Object.Containment
+  alias Pleroma.User
+  alias Pleroma.Web.ActivityPub.Transmogrifier
   alias Pleroma.Web.ActivityPub.Utils
 
-  # based on Pleroma.Web.ActivityPub.Utils.lazy_put_objects_defaults
-  def fix_defaults(data) do
+  def cast_and_filter_recipients(message, field, follower_collection, field_fallback \\ []) do
+    {:ok, data} = ObjectValidators.Recipients.cast(message[field] || field_fallback)
+
+    data =
+      Enum.reject(data, fn x ->
+        String.ends_with?(x, "/followers") and x != follower_collection
+      end)
+
+    Map.put(message, field, data)
+  end
+
+  def fix_object_defaults(data) do
     %{data: %{"id" => context}, id: context_id} =
       Utils.create_context(data["context"] || data["conversation"])
 
+    %User{follower_address: follower_collection} = User.get_cached_by_ap_id(data["attributedTo"])
+
     data
     |> Map.put("context", context)
     |> Map.put("context_id", context_id)
+    |> cast_and_filter_recipients("to", follower_collection)
+    |> cast_and_filter_recipients("cc", follower_collection)
+    |> cast_and_filter_recipients("bto", follower_collection)
+    |> cast_and_filter_recipients("bcc", follower_collection)
+    |> Transmogrifier.fix_implicit_addressing(follower_collection)
   end
 
-  def fix_attribution(data) do
-    data
-    |> Map.put_new("actor", data["attributedTo"])
+  def fix_activity_addressing(activity, _meta) do
+    %User{follower_address: follower_collection} = User.get_cached_by_ap_id(activity["actor"])
+
+    activity
+    |> cast_and_filter_recipients("to", follower_collection)
+    |> cast_and_filter_recipients("cc", follower_collection)
+    |> cast_and_filter_recipients("bto", follower_collection)
+    |> cast_and_filter_recipients("bcc", follower_collection)
+    |> Transmogrifier.fix_implicit_addressing(follower_collection)
   end
 
   def fix_actor(data) do
-    actor = Containment.get_actor(data)
+    actor =
+      data
+      |> Map.put_new("actor", data["attributedTo"])
+      |> Containment.get_actor()
 
     data
     |> Map.put("actor", actor)
index 093549a45873124904298545313f2d223812109b..be50743481bc9c7d660e6435154e6c259239beb9 100644 (file)
@@ -9,11 +9,13 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations do
   alias Pleroma.Object
   alias Pleroma.User
 
+  @spec validate_any_presence(Ecto.Changeset.t(), [atom()]) :: Ecto.Changeset.t()
   def validate_any_presence(cng, fields) do
     non_empty =
       fields
       |> Enum.map(fn field -> get_field(cng, field) end)
       |> Enum.any?(fn
+        nil -> false
         [] -> false
         _ -> true
       end)
@@ -29,6 +31,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations do
     end
   end
 
+  @spec validate_actor_presence(Ecto.Changeset.t(), keyword()) :: Ecto.Changeset.t()
   def validate_actor_presence(cng, options \\ []) do
     field_name = Keyword.get(options, :field_name, :actor)
 
@@ -47,6 +50,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations do
     end)
   end
 
+  @spec validate_object_presence(Ecto.Changeset.t(), keyword()) :: Ecto.Changeset.t()
   def validate_object_presence(cng, options \\ []) do
     field_name = Keyword.get(options, :field_name, :object)
     allowed_types = Keyword.get(options, :allowed_types, false)
@@ -68,6 +72,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations do
     end)
   end
 
+  @spec validate_object_or_user_presence(Ecto.Changeset.t(), keyword()) :: Ecto.Changeset.t()
   def validate_object_or_user_presence(cng, options \\ []) do
     field_name = Keyword.get(options, :field_name, :object)
     options = Keyword.put(options, :field_name, field_name)
@@ -83,6 +88,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations do
     if actor_cng.valid?, do: actor_cng, else: object_cng
   end
 
+  @spec validate_host_match(Ecto.Changeset.t(), [atom()]) :: Ecto.Changeset.t()
   def validate_host_match(cng, fields \\ [:id, :actor]) do
     if same_domain?(cng, fields) do
       cng
@@ -95,6 +101,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations do
     end
   end
 
+  @spec validate_fields_match(Ecto.Changeset.t(), [atom()]) :: Ecto.Changeset.t()
   def validate_fields_match(cng, fields) do
     if map_unique?(cng, fields) do
       cng
@@ -122,12 +129,14 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations do
     end)
   end
 
+  @spec same_domain?(Ecto.Changeset.t(), [atom()]) :: boolean()
   def same_domain?(cng, fields \\ [:actor, :object]) do
     map_unique?(cng, fields, fn value -> URI.parse(value).host end)
   end
 
   # This figures out if a user is able to create, delete or modify something
   # based on the domain and superuser status
+  @spec validate_modification_rights(Ecto.Changeset.t()) :: Ecto.Changeset.t()
   def validate_modification_rights(cng) do
     actor = User.get_cached_by_ap_id(get_field(cng, :actor))
 
index 8384c16a7ee5002a2e9ed3599e7d9627382f2747..7a31a99bfeb8be15b0947886c24cff31a2f2d65a 100644 (file)
@@ -39,7 +39,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CreateChatMessageValidator do
     |> validate_data(meta)
   end
 
-  def validate_data(cng, meta \\ []) do
+  defp validate_data(cng, meta) do
     cng
     |> validate_required([:id, :actor, :to, :type, :object])
     |> validate_inclusion(:type, ["Create"])
index bf56a918cfd0242a81e93833e1956b1ff9ae2991..d2de53049d760edf2820b17e6c309c92a83e7b8f 100644 (file)
@@ -10,8 +10,10 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CreateGenericValidator do
 
   alias Pleroma.EctoType.ActivityPub.ObjectValidators
   alias Pleroma.Object
+  alias Pleroma.User
   alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
   alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
+  alias Pleroma.Web.ActivityPub.Transmogrifier
 
   import Ecto.Changeset
 
@@ -23,6 +25,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CreateGenericValidator do
     field(:type, :string)
     field(:to, ObjectValidators.Recipients, default: [])
     field(:cc, ObjectValidators.Recipients, default: [])
+    field(:bto, ObjectValidators.Recipients, default: [])
+    field(:bcc, ObjectValidators.Recipients, default: [])
     field(:object, ObjectValidators.ObjectID)
     field(:expires_at, ObjectValidators.DateTime)
 
@@ -54,39 +58,37 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CreateGenericValidator do
     |> cast(data, __schema__(:fields))
   end
 
-  defp fix_context(data, meta) do
-    if object = meta[:object_data] do
-      Map.put_new(data, "context", object["context"])
-    else
-      data
-    end
-  end
+  # CommonFixes.fix_activity_addressing adapted for Create specific behavior
+  defp fix_addressing(data, object) do
+    %User{follower_address: follower_collection} = User.get_cached_by_ap_id(data["actor"])
 
-  defp fix_addressing(data, meta) do
-    if object = meta[:object_data] do
-      data
-      |> Map.put_new("to", object["to"] || [])
-      |> Map.put_new("cc", object["cc"] || [])
-    else
-      data
-    end
+    data
+    |> CommonFixes.cast_and_filter_recipients("to", follower_collection, object["to"])
+    |> CommonFixes.cast_and_filter_recipients("cc", follower_collection, object["cc"])
+    |> CommonFixes.cast_and_filter_recipients("bto", follower_collection, object["bto"])
+    |> CommonFixes.cast_and_filter_recipients("bcc", follower_collection, object["bcc"])
+    |> Transmogrifier.fix_implicit_addressing(follower_collection)
   end
 
-  defp fix(data, meta) do
+  def fix(data, meta) do
+    object = meta[:object_data]
+
     data
-    |> fix_context(meta)
-    |> fix_addressing(meta)
     |> CommonFixes.fix_actor()
+    |> Map.put_new("context", object["context"])
+    |> fix_addressing(object)
   end
 
-  def validate_data(cng, meta \\ []) do
+  defp validate_data(cng, meta) do
+    object = meta[:object_data]
+
     cng
-    |> validate_required([:actor, :type, :object])
+    |> validate_required([:actor, :type, :object, :to, :cc])
     |> validate_inclusion(:type, ["Create"])
     |> CommonValidations.validate_actor_presence()
-    |> CommonValidations.validate_any_presence([:to, :cc])
-    |> validate_actors_match(meta)
-    |> validate_context_match(meta)
+    |> validate_actors_match(object)
+    |> validate_context_match(object)
+    |> validate_addressing_match(object)
     |> validate_object_nonexistence()
     |> validate_object_containment()
   end
@@ -118,8 +120,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CreateGenericValidator do
     end)
   end
 
-  def validate_actors_match(cng, meta) do
-    attributed_to = meta[:object_data]["attributedTo"] || meta[:object_data]["actor"]
+  def validate_actors_match(cng, object) do
+    attributed_to = object["attributedTo"] || object["actor"]
 
     cng
     |> validate_change(:actor, fn :actor, actor ->
@@ -131,7 +133,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CreateGenericValidator do
     end)
   end
 
-  def validate_context_match(cng, %{object_data: %{"context" => object_context}}) do
+  def validate_context_match(cng, %{"context" => object_context}) do
     cng
     |> validate_change(:context, fn :context, context ->
       if context == object_context do
@@ -142,5 +144,18 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CreateGenericValidator do
     end)
   end
 
-  def validate_context_match(cng, _), do: cng
+  def validate_addressing_match(cng, object) do
+    [:to, :cc, :bcc, :bto]
+    |> Enum.reduce(cng, fn field, cng ->
+      object_data = object[to_string(field)]
+
+      validate_change(cng, field, fn field, data ->
+        if data == object_data do
+          []
+        else
+          [{field, "field doesn't match with object (#{inspect(object_data)})"}]
+        end
+      end)
+    end)
+  end
 end
diff --git a/lib/pleroma/web/activity_pub/object_validators/create_note_validator.ex b/lib/pleroma/web/activity_pub/object_validators/create_note_validator.ex
deleted file mode 100644 (file)
index a85a029..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.ActivityPub.ObjectValidators.CreateNoteValidator do
-  use Ecto.Schema
-
-  alias Pleroma.EctoType.ActivityPub.ObjectValidators
-  alias Pleroma.Web.ActivityPub.ObjectValidators.NoteValidator
-
-  import Ecto.Changeset
-
-  @primary_key false
-
-  embedded_schema do
-    field(:id, ObjectValidators.ObjectID, primary_key: true)
-    field(:actor, ObjectValidators.ObjectID)
-    field(:type, :string)
-    field(:to, ObjectValidators.Recipients, default: [])
-    field(:cc, ObjectValidators.Recipients, default: [])
-    field(:bto, ObjectValidators.Recipients, default: [])
-    field(:bcc, ObjectValidators.Recipients, default: [])
-    embeds_one(:object, NoteValidator)
-  end
-
-  def cast_data(data) do
-    cast(%__MODULE__{}, data, __schema__(:fields))
-  end
-end
index fc1a79a729486baf040a011f46acd5374d87d66e..7da67bf16bf6d845ca53fa007d8fc444e2f9fa47 100644 (file)
@@ -53,7 +53,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.DeleteValidator do
     Tombstone
     Video
   }
-  def validate_data(cng) do
+  defp validate_data(cng) do
     cng
     |> validate_required([:id, :type, :actor, :to, :cc, :object])
     |> validate_inclusion(:type, ["Delete"])
index 1906e597e84beafdf0ad9c404dfc380659e82cd1..ec75665158b864a811f1283b48ff1bcd6a238f03 100644 (file)
@@ -70,7 +70,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator do
     end
   end
 
-  def validate_data(data_cng) do
+  defp validate_data(data_cng) do
     data_cng
     |> validate_inclusion(:type, ["EmojiReact"])
     |> validate_required([:id, :type, :object, :actor, :context, :to, :cc, :content])
index 2e26726f86803d89a898a353449a01fef986f36b..fee2e997afd967cc9bd63858c7503f65ee659f3d 100644 (file)
@@ -9,6 +9,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EventValidator do
   alias Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator
   alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
   alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
+  alias Pleroma.Web.ActivityPub.ObjectValidators.TagValidator
   alias Pleroma.Web.ActivityPub.Transmogrifier
 
   import Ecto.Changeset
@@ -23,8 +24,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EventValidator do
     field(:cc, ObjectValidators.Recipients, default: [])
     field(:bto, ObjectValidators.Recipients, default: [])
     field(:bcc, ObjectValidators.Recipients, default: [])
-    # TODO: Write type
-    field(:tag, {:array, :map}, default: [])
+    embeds_many(:tag, TagValidator)
     field(:type, :string)
 
     field(:name, :string)
@@ -72,8 +72,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EventValidator do
 
   defp fix(data) do
     data
-    |> CommonFixes.fix_defaults()
-    |> CommonFixes.fix_attribution()
+    |> CommonFixes.fix_actor()
+    |> CommonFixes.fix_object_defaults()
     |> Transmogrifier.fix_emoji()
   end
 
@@ -81,11 +81,12 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EventValidator do
     data = fix(data)
 
     struct
-    |> cast(data, __schema__(:fields) -- [:attachment])
+    |> cast(data, __schema__(:fields) -- [:attachment, :tag])
     |> cast_embed(:attachment)
+    |> cast_embed(:tag)
   end
 
-  def validate_data(data_cng) do
+  defp validate_data(data_cng) do
     data_cng
     |> validate_inclusion(:type, ["Event"])
     |> validate_required([:id, :actor, :attributedTo, :type, :context, :context_id])
index 6e428bacc991f6b670719ae03c8c1b29f7d1a802..239cee5e7a3f5ad97da792026127ad45a8d79157 100644 (file)
@@ -27,7 +27,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.FollowValidator do
     |> cast(data, __schema__(:fields))
   end
 
-  def validate_data(cng) do
+  defp validate_data(cng) do
     cng
     |> validate_required([:id, :type, :actor, :to, :cc, :object])
     |> validate_inclusion(:type, ["Follow"])
index 30c40b2380ad9f18b6d323aaf4bce83e86f83ec8..509da507b3493677b995881ece6b9e6591728bb4 100644 (file)
@@ -76,7 +76,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator do
     end
   end
 
-  def validate_data(data_cng) do
+  defp validate_data(data_cng) do
     data_cng
     |> validate_inclusion(:type, ["Like"])
     |> validate_required([:id, :type, :object, :actor, :context, :to, :cc])
index 6b746c99770aaf2f52756f4e4c1367cd81f585be..083d08ec4e3ce93da3aecac14d3e42271b21fda4 100644 (file)
@@ -10,6 +10,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.QuestionValidator do
   alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
   alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
   alias Pleroma.Web.ActivityPub.ObjectValidators.QuestionOptionsValidator
+  alias Pleroma.Web.ActivityPub.ObjectValidators.TagValidator
   alias Pleroma.Web.ActivityPub.Transmogrifier
 
   import Ecto.Changeset
@@ -24,8 +25,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.QuestionValidator do
     field(:cc, ObjectValidators.Recipients, default: [])
     field(:bto, ObjectValidators.Recipients, default: [])
     field(:bcc, ObjectValidators.Recipients, default: [])
-    # TODO: Write type
-    field(:tag, {:array, :map}, default: [])
+    embeds_many(:tag, TagValidator)
     field(:type, :string)
     field(:content, :string)
     field(:context, :string)
@@ -83,8 +83,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.QuestionValidator do
 
   defp fix(data) do
     data
-    |> CommonFixes.fix_defaults()
-    |> CommonFixes.fix_attribution()
+    |> CommonFixes.fix_actor()
+    |> CommonFixes.fix_object_defaults()
     |> Transmogrifier.fix_emoji()
     |> fix_closed()
   end
@@ -93,13 +93,14 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.QuestionValidator do
     data = fix(data)
 
     struct
-    |> cast(data, __schema__(:fields) -- [:anyOf, :oneOf, :attachment])
+    |> cast(data, __schema__(:fields) -- [:anyOf, :oneOf, :attachment, :tag])
     |> cast_embed(:attachment)
     |> cast_embed(:anyOf)
     |> cast_embed(:oneOf)
+    |> cast_embed(:tag)
   end
 
-  def validate_data(data_cng) do
+  defp validate_data(data_cng) do
     data_cng
     |> validate_inclusion(:type, ["Question"])
     |> validate_required([:id, :actor, :attributedTo, :type, :context, :context_id])
diff --git a/lib/pleroma/web/activity_pub/object_validators/tag_validator.ex b/lib/pleroma/web/activity_pub/object_validators/tag_validator.ex
new file mode 100644 (file)
index 0000000..7510215
--- /dev/null
@@ -0,0 +1,77 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.ObjectValidators.TagValidator do
+  use Ecto.Schema
+
+  alias Pleroma.EctoType.ActivityPub.ObjectValidators
+
+  import Ecto.Changeset
+
+  @primary_key false
+  embedded_schema do
+    # Common
+    field(:type, :string)
+    field(:name, :string)
+
+    # Mention, Hashtag
+    field(:href, ObjectValidators.Uri)
+
+    # Emoji
+    embeds_one :icon, IconObjectValidator, primary_key: false do
+      field(:type, :string)
+      field(:url, ObjectValidators.Uri)
+    end
+
+    field(:updated, ObjectValidators.DateTime)
+    field(:id, ObjectValidators.Uri)
+  end
+
+  def cast_and_validate(data) do
+    data
+    |> cast_data()
+  end
+
+  def cast_data(data) do
+    %__MODULE__{}
+    |> changeset(data)
+  end
+
+  def changeset(struct, %{"type" => "Mention"} = data) do
+    struct
+    |> cast(data, [:type, :name, :href])
+    |> validate_required([:type, :href])
+  end
+
+  def changeset(struct, %{"type" => "Hashtag", "name" => name} = data) do
+    name =
+      cond do
+        "#" <> name -> name
+        name -> name
+      end
+      |> String.downcase()
+
+    data = Map.put(data, "name", name)
+
+    struct
+    |> cast(data, [:type, :name, :href])
+    |> validate_required([:type, :name])
+  end
+
+  def changeset(struct, %{"type" => "Emoji"} = data) do
+    data = Map.put(data, "name", String.trim(data["name"], ":"))
+
+    struct
+    |> cast(data, [:type, :name, :updated, :id])
+    |> cast_embed(:icon, with: &icon_changeset/2)
+    |> validate_required([:type, :name, :icon])
+  end
+
+  def icon_changeset(struct, data) do
+    struct
+    |> cast(data, [:type, :url])
+    |> validate_inclusion(:type, ~w[Image])
+    |> validate_required([:type, :url])
+  end
+end
index 783a79ddb98a5618791c5cb80b5cc1ba2f6e2e72..e8af60ffa9667bc79ecb0f9514a0804635a1b988 100644 (file)
@@ -38,7 +38,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.UndoValidator do
     |> cast(data, __schema__(:fields))
   end
 
-  def validate_data(data_cng) do
+  defp validate_data(data_cng) do
     data_cng
     |> validate_inclusion(:type, ["Undo"])
     |> validate_required([:id, :type, :object, :actor, :to, :cc])
index a66d4140076925d9fc819989832faebe2b4960ae..6bb1dc7fa1f2c80d978296515d11446277b88634 100644 (file)
@@ -28,7 +28,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.UpdateValidator do
     |> cast(data, __schema__(:fields))
   end
 
-  def validate_data(cng) do
+  defp validate_data(cng) do
     cng
     |> validate_required([:id, :type, :actor, :to, :cc, :object])
     |> validate_inclusion(:type, ["Update"])
index 195596f94e3e4138cc286113b45c116c2032c5ac..0d6e8aad29630f5d8a5728bf25885739d21f5c80 100644 (file)
@@ -7,6 +7,7 @@ defmodule Pleroma.Web.ActivityPub.Pipeline do
   alias Pleroma.Config
   alias Pleroma.Object
   alias Pleroma.Repo
+  alias Pleroma.Utils
   alias Pleroma.Web.ActivityPub.ActivityPub
   alias Pleroma.Web.ActivityPub.MRF
   alias Pleroma.Web.ActivityPub.ObjectValidator
@@ -14,19 +15,19 @@ defmodule Pleroma.Web.ActivityPub.Pipeline do
   alias Pleroma.Web.ActivityPub.Visibility
   alias Pleroma.Web.Federator
 
-  @side_effects Config.get([:pipeline, :side_effects], SideEffects)
-  @federator Config.get([:pipeline, :federator], Federator)
-  @object_validator Config.get([:pipeline, :object_validator], ObjectValidator)
-  @mrf Config.get([:pipeline, :mrf], MRF)
-  @activity_pub Config.get([:pipeline, :activity_pub], ActivityPub)
-  @config Config.get([:pipeline, :config], Config)
+  defp side_effects, do: Config.get([:pipeline, :side_effects], SideEffects)
+  defp federator, do: Config.get([:pipeline, :federator], Federator)
+  defp object_validator, do: Config.get([:pipeline, :object_validator], ObjectValidator)
+  defp mrf, do: Config.get([:pipeline, :mrf], MRF)
+  defp activity_pub, do: Config.get([:pipeline, :activity_pub], ActivityPub)
+  defp config, do: Config.get([:pipeline, :config], Config)
 
   @spec common_pipeline(map(), keyword()) ::
           {:ok, Activity.t() | Object.t(), keyword()} | {:error, any()}
   def common_pipeline(object, meta) do
-    case Repo.transaction(fn -> do_common_pipeline(object, meta) end) do
+    case Repo.transaction(fn -> do_common_pipeline(object, meta) end, Utils.query_timeout()) do
       {:ok, {:ok, activity, meta}} ->
-        @side_effects.handle_after_transaction(meta)
+        side_effects().handle_after_transaction(meta)
         {:ok, activity, meta}
 
       {:ok, value} ->
@@ -40,19 +41,17 @@ defmodule Pleroma.Web.ActivityPub.Pipeline do
     end
   end
 
-  def do_common_pipeline(object, meta) do
-    with {_, {:ok, validated_object, meta}} <-
-           {:validate_object, @object_validator.validate(object, meta)},
-         {_, {:ok, mrfd_object, meta}} <-
-           {:mrf_object, @mrf.pipeline_filter(validated_object, meta)},
-         {_, {:ok, activity, meta}} <-
-           {:persist_object, @activity_pub.persist(mrfd_object, meta)},
-         {_, {:ok, activity, meta}} <-
-           {:execute_side_effects, @side_effects.handle(activity, meta)},
-         {_, {:ok, _}} <- {:federation, maybe_federate(activity, meta)} do
-      {:ok, activity, meta}
+  def do_common_pipeline(%{__struct__: _}, _meta), do: {:error, :is_struct}
+
+  def do_common_pipeline(message, meta) do
+    with {_, {:ok, message, meta}} <- {:validate, object_validator().validate(message, meta)},
+         {_, {:ok, message, meta}} <- {:mrf, mrf().pipeline_filter(message, meta)},
+         {_, {:ok, message, meta}} <- {:persist, activity_pub().persist(message, meta)},
+         {_, {:ok, message, meta}} <- {:side_effects, side_effects().handle(message, meta)},
+         {_, {:ok, _}} <- {:federation, maybe_federate(message, meta)} do
+      {:ok, message, meta}
     else
-      {:mrf_object, {:reject, message, _}} -> {:reject, message}
+      {:mrf, {:reject, message, _}} -> {:reject, message}
       e -> {:error, e}
     end
   end
@@ -61,7 +60,7 @@ defmodule Pleroma.Web.ActivityPub.Pipeline do
 
   defp maybe_federate(%Activity{} = activity, meta) do
     with {:ok, local} <- Keyword.fetch(meta, :local) do
-      do_not_federate = meta[:do_not_federate] || !@config.get([:instance, :federating])
+      do_not_federate = meta[:do_not_federate] || !config().get([:instance, :federating])
 
       if !do_not_federate and local and not Visibility.is_local_public?(activity) do
         activity =
@@ -71,7 +70,7 @@ defmodule Pleroma.Web.ActivityPub.Pipeline do
             activity
           end
 
-        @federator.publish(activity)
+        federator().publish(activity)
         {:ok, :federated}
       else
         {:ok, :not_federated}
index b12b2fc24a82e74a68747223eb93842c03c0d92c..590beef64a19cdbf89fe17c45a74f3a478cf7c3d 100644 (file)
@@ -272,7 +272,7 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
       },
       %{
         "rel" => "http://ostatus.org/schema/1.0/subscribe",
-        "template" => "#{Pleroma.Web.base_url()}/ostatus_subscribe?acct={uri}"
+        "template" => "#{Pleroma.Web.Endpoint.url()}/ostatus_subscribe?acct={uri}"
       }
     ]
   end
index 0b9a9f0c593793b2773909922b26983c85bce390..674356d9a8a09e2df08d38541fa59c5bff70d7e8 100644 (file)
@@ -203,6 +203,19 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
         Object.increase_replies_count(in_reply_to)
       end
 
+      reply_depth = (meta[:depth] || 0) + 1
+
+      # FIXME: Force inReplyTo to replies
+      if Pleroma.Web.Federator.allowed_thread_distance?(reply_depth) and
+           object.data["replies"] != nil do
+        for reply_id <- object.data["replies"] do
+          Pleroma.Workers.RemoteFetcherWorker.enqueue("fetch_remote", %{
+            "id" => reply_id,
+            "depth" => reply_depth
+          })
+        end
+      end
+
       ConcurrentLimiter.limit(Pleroma.Web.RichMedia.Helpers, fn ->
         Task.start(fn -> Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity) end)
       end)
@@ -276,10 +289,10 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
     result =
       case deleted_object do
         %Object{} ->
-          with {:ok, deleted_object, activity} <- Object.delete(deleted_object),
+          with {:ok, deleted_object, _activity} <- Object.delete(deleted_object),
                {_, actor} when is_binary(actor) <- {:actor, deleted_object.data["actor"]},
                %User{} = user <- User.get_cached_by_ap_id(actor) do
-            User.remove_pinnned_activity(user, activity)
+            User.remove_pinned_object_id(user, deleted_object.data["id"])
 
             {:ok, user} = ActivityPub.decrease_note_count_if_public(user, deleted_object)
 
@@ -312,6 +325,63 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
     end
   end
 
+  # Tasks this handles:
+  # - adds pin to user
+  # - removes expiration job for pinned activity, if was set for expiration
+  @impl true
+  def handle(%{data: %{"type" => "Add"} = data} = object, meta) do
+    with %User{} = user <- User.get_cached_by_ap_id(data["actor"]),
+         {:ok, _user} <- User.add_pinned_object_id(user, data["object"]) do
+      # if pinned activity was scheduled for deletion, we remove job
+      if expiration = Pleroma.Workers.PurgeExpiredActivity.get_expiration(meta[:activity_id]) do
+        Oban.cancel_job(expiration.id)
+      end
+
+      {:ok, object, meta}
+    else
+      nil ->
+        {:error, :user_not_found}
+
+      {:error, changeset} ->
+        if changeset.errors[:pinned_objects] do
+          {:error, :pinned_statuses_limit_reached}
+        else
+          changeset.errors
+        end
+    end
+  end
+
+  # Tasks this handles:
+  # - removes pin from user
+  # - removes corresponding Add activity
+  # - if activity had expiration, recreates activity expiration job
+  @impl true
+  def handle(%{data: %{"type" => "Remove"} = data} = object, meta) do
+    with %User{} = user <- User.get_cached_by_ap_id(data["actor"]),
+         {:ok, _user} <- User.remove_pinned_object_id(user, data["object"]) do
+      data["object"]
+      |> Activity.add_by_params_query(user.ap_id, user.featured_address)
+      |> Repo.delete_all()
+
+      # if pinned activity was scheduled for deletion, we reschedule it for deletion
+      if meta[:expires_at] do
+        # MRF.ActivityExpirationPolicy used UTC timestamps for expires_at in original implementation
+        {:ok, expires_at} =
+          Pleroma.EctoType.ActivityPub.ObjectValidators.DateTime.cast(meta[:expires_at])
+
+        Pleroma.Workers.PurgeExpiredActivity.enqueue(%{
+          activity_id: meta[:activity_id],
+          expires_at: expires_at
+        })
+      end
+
+      {:ok, object, meta}
+    else
+      nil -> {:error, :user_not_found}
+      error -> error
+    end
+  end
+
   # Nothing to do
   @impl true
   def handle(object, meta) do
@@ -366,7 +436,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
   end
 
   def handle_object_creation(%{"type" => objtype} = object, meta)
-      when objtype in ~w[Audio Video Question Event Article] do
+      when objtype in ~w[Audio Video Question Event Article Note] do
     with {:ok, object, meta} <- Pipeline.common_pipeline(object, meta) do
       {:ok, object, meta}
     end
index 8c7d6a7478c28ada25aeb7baf55b9fed5c9d2471..51c0cc86026a961b5045c611535831bd2af2dbc2 100644 (file)
@@ -43,7 +43,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
     |> fix_content_map()
     |> fix_addressing()
     |> fix_summary()
-    |> fix_type(options)
   end
 
   def fix_summary(%{"summary" => nil} = object) do
@@ -72,17 +71,21 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
     end
   end
 
-  def fix_explicit_addressing(
-        %{"to" => to, "cc" => cc} = object,
-        explicit_mentions,
-        follower_collection
-      ) do
-    explicit_to = Enum.filter(to, fn x -> x in explicit_mentions end)
+  # if directMessage flag is set to true, leave the addressing alone
+  def fix_explicit_addressing(%{"directMessage" => true} = object, _follower_collection),
+    do: object
+
+  def fix_explicit_addressing(%{"to" => to, "cc" => cc} = object, follower_collection) do
+    explicit_mentions =
+      Utils.determine_explicit_mentions(object) ++
+        [Pleroma.Constants.as_public(), follower_collection]
 
+    explicit_to = Enum.filter(to, fn x -> x in explicit_mentions end)
     explicit_cc = Enum.filter(to, fn x -> x not in explicit_mentions end)
 
     final_cc =
       (cc ++ explicit_cc)
+      |> Enum.filter(& &1)
       |> Enum.reject(fn x -> String.ends_with?(x, "/followers") and x != follower_collection end)
       |> Enum.uniq()
 
@@ -91,29 +94,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
     |> Map.put("cc", final_cc)
   end
 
-  def fix_explicit_addressing(object, _explicit_mentions, _followers_collection), do: object
-
-  # if directMessage flag is set to true, leave the addressing alone
-  def fix_explicit_addressing(%{"directMessage" => true} = object), do: object
-
-  def fix_explicit_addressing(object) do
-    explicit_mentions = Utils.determine_explicit_mentions(object)
-
-    %User{follower_address: follower_collection} =
-      object
-      |> Containment.get_actor()
-      |> User.get_cached_by_ap_id()
-
-    explicit_mentions =
-      explicit_mentions ++
-        [
-          Pleroma.Constants.as_public(),
-          follower_collection
-        ]
-
-    fix_explicit_addressing(object, explicit_mentions, follower_collection)
-  end
-
   # if as:Public is addressed, then make sure the followers collection is also addressed
   # so that the activities will be delivered to local users.
   def fix_implicit_addressing(%{"to" => to, "cc" => cc} = object, followers_collection) do
@@ -137,19 +117,19 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
     end
   end
 
-  def fix_implicit_addressing(object, _), do: object
-
   def fix_addressing(object) do
-    {:ok, %User{} = user} = User.get_or_fetch_by_ap_id(object["actor"])
-    followers_collection = User.ap_followers(user)
+    {:ok, %User{follower_address: follower_collection}} =
+      object
+      |> Containment.get_actor()
+      |> User.get_or_fetch_by_ap_id()
 
     object
     |> fix_addressing_list("to")
     |> fix_addressing_list("cc")
     |> fix_addressing_list("bto")
     |> fix_addressing_list("bcc")
-    |> fix_explicit_addressing()
-    |> fix_implicit_addressing(followers_collection)
+    |> fix_explicit_addressing(follower_collection)
+    |> fix_implicit_addressing(follower_collection)
   end
 
   def fix_actor(%{"attributedTo" => actor} = object) do
@@ -223,10 +203,17 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
 
         media_type =
           cond do
-            is_map(url) && MIME.valid?(url["mediaType"]) -> url["mediaType"]
-            MIME.valid?(data["mediaType"]) -> data["mediaType"]
-            MIME.valid?(data["mimeType"]) -> data["mimeType"]
-            true -> nil
+            is_map(url) && MIME.extensions(url["mediaType"]) != [] ->
+              url["mediaType"]
+
+            is_bitstring(data["mediaType"]) && MIME.extensions(data["mediaType"]) != [] ->
+              data["mediaType"]
+
+            is_bitstring(data["mimeType"]) && MIME.extensions(data["mimeType"]) != [] ->
+              data["mimeType"]
+
+            true ->
+              nil
           end
 
         href =
@@ -244,6 +231,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
               "type" => Map.get(url || %{}, "type", "Link")
             }
             |> Maps.put_if_present("mediaType", media_type)
+            |> Maps.put_if_present("width", (url || %{})["width"] || data["width"])
+            |> Maps.put_if_present("height", (url || %{})["height"] || data["height"])
 
           %{
             "url" => [attachment_url],
@@ -340,19 +329,18 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
 
   def fix_content_map(object), do: object
 
-  def fix_type(object, options \\ [])
+  defp fix_type(%{"type" => "Note", "inReplyTo" => reply_id, "name" => _} = object, options)
+       when is_binary(reply_id) do
+    options = Keyword.put(options, :fetch, true)
 
-  def fix_type(%{"inReplyTo" => reply_id, "name" => _} = object, options)
-      when is_binary(reply_id) do
-    with true <- Federator.allowed_thread_distance?(options[:depth]),
-         {:ok, %{data: %{"type" => "Question"} = _} = _} <- get_obj_helper(reply_id, options) do
+    with %Object{data: %{"type" => "Question"}} <- Object.normalize(reply_id, options) do
       Map.put(object, "type", "Answer")
     else
       _ -> object
     end
   end
 
-  def fix_type(object, _), do: object
+  defp fix_type(object, _options), do: object
 
   # Reduce the object list to find the reported user.
   defp get_reported(objects) do
@@ -423,10 +411,9 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
   # - tags
   # - emoji
   def handle_incoming(
-        %{"type" => "Create", "object" => %{"type" => objtype} = object} = data,
+        %{"type" => "Create", "object" => %{"type" => "Page"} = object} = data,
         options
-      )
-      when objtype in ~w{Note Page} do
+      ) do
     actor = Containment.get_actor(data)
 
     with nil <- Activity.get_create_by_object_ap_id(object["id"]),
@@ -518,14 +505,23 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
 
   def handle_incoming(
         %{"type" => "Create", "object" => %{"type" => objtype, "id" => obj_id}} = data,
-        _options
+        options
       )
-      when objtype in ~w{Question Answer ChatMessage Audio Video Event Article} do
-    data = Map.put(data, "object", strip_internal_fields(data["object"]))
+      when objtype in ~w{Question Answer ChatMessage Audio Video Event Article Note} do
+    fetch_options = Keyword.put(options, :depth, (options[:depth] || 0) + 1)
+
+    object =
+      data["object"]
+      |> strip_internal_fields()
+      |> fix_type(fetch_options)
+      |> fix_in_reply_to(fetch_options)
+
+    data = Map.put(data, "object", object)
+    options = Keyword.put(options, :local, false)
 
     with {:ok, %User{}} <- ObjectValidator.fetch_actor(data),
          nil <- Activity.get_create_by_object_ap_id(obj_id),
-         {:ok, activity, _} <- Pipeline.common_pipeline(data, local: false) do
+         {:ok, activity, _} <- Pipeline.common_pipeline(data, options) do
       {:ok, activity}
     else
       %Activity{} = activity -> {:ok, activity}
@@ -534,7 +530,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
   end
 
   def handle_incoming(%{"type" => type} = data, _options)
-      when type in ~w{Like EmojiReact Announce} do
+      when type in ~w{Like EmojiReact Announce Add Remove} do
     with :ok <- ObjectValidator.fetch_actor_and_object(data),
          {:ok, activity, _meta} <-
            Pipeline.common_pipeline(data, local: false) do
@@ -564,7 +560,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
            Pipeline.common_pipeline(data, local: false) do
       {:ok, activity}
     else
-      {:error, {:validate_object, _}} = e ->
+      {:error, {:validate, _}} = e ->
         # Check if we have a create activity for this
         with {:ok, object_id} <- ObjectValidators.ObjectID.cast(data["object"]),
              %Activity{data: %{"actor" => actor}} <-
@@ -949,7 +945,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
       object
       |> Map.get("attachment", [])
       |> Enum.map(fn data ->
-        [%{"mediaType" => media_type, "href" => href} | _] = data["url"]
+        [%{"mediaType" => media_type, "href" => href} = url | _] = data["url"]
 
         %{
           "url" => href,
@@ -957,6 +953,9 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
           "name" => data["name"],
           "type" => "Document"
         }
+        |> Maps.put_if_present("width", url["width"])
+        |> Maps.put_if_present("height", url["height"])
+        |> Maps.put_if_present("blurhash", data["blurhash"])
       end)
 
     Map.put(object, "attachment", attachments)
@@ -1000,6 +999,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
     with %User{local: false} = user <- User.get_cached_by_ap_id(ap_id),
          {:ok, data} <- ActivityPub.fetch_and_prepare_user_from_ap_id(ap_id),
          {:ok, user} <- update_user(user, data) do
+      {:ok, _pid} = Task.start(fn -> ActivityPub.pinned_fetch_task(user) end)
       TransmogrifierWorker.enqueue("user_upgrade", %{"user_id" => user.id})
       {:ok, user}
     else
index a4dc469dccc38c3b04ef805707eb3e6d7b2d2617..1df53f79ad75f20dc1f8b7ef193e7c52d1d8b9a9 100644 (file)
@@ -12,7 +12,6 @@ defmodule Pleroma.Web.ActivityPub.Utils do
   alias Pleroma.Object
   alias Pleroma.Repo
   alias Pleroma.User
-  alias Pleroma.Web
   alias Pleroma.Web.ActivityPub.ActivityPub
   alias Pleroma.Web.ActivityPub.Visibility
   alias Pleroma.Web.AdminAPI.AccountView
@@ -38,6 +37,8 @@ defmodule Pleroma.Web.ActivityPub.Utils do
   @supported_report_states ~w(open closed resolved)
   @valid_visibilities ~w(public unlisted private direct)
 
+  def as_local_public, do: Endpoint.url() <> "/#Public"
+
   # Some implementations send the actor URI as the actor field, others send the entire actor object,
   # so figure out what the actor's URI is based on what we have.
   def get_ap_id(%{"id" => id} = _), do: id
@@ -96,8 +97,11 @@ defmodule Pleroma.Web.ActivityPub.Utils do
         !label_in_collection?(ap_id, params["cc"])
 
     if need_splice? do
-      cc_list = extract_list(params["cc"])
-      Map.put(params, "cc", [ap_id | cc_list])
+      cc = [ap_id | extract_list(params["cc"])]
+
+      params
+      |> Map.put("cc", cc)
+      |> Maps.safe_put_in(["object", "cc"], cc)
     else
       params
     end
@@ -107,7 +111,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
     %{
       "@context" => [
         "https://www.w3.org/ns/activitystreams",
-        "#{Web.base_url()}/schemas/litepub-0.1.jsonld",
+        "#{Endpoint.url()}/schemas/litepub-0.1.jsonld",
         %{
           "@language" => "und"
         }
@@ -132,7 +136,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
   end
 
   def generate_id(type) do
-    "#{Web.base_url()}/#{type}/#{UUID.generate()}"
+    "#{Endpoint.url()}/#{type}/#{UUID.generate()}"
   end
 
   def get_notified_from_object(%{"type" => type} = object) when type in @supported_object_types do
index 8adc9878af3e3bc2b4f4889eed7e474275df8130..344da19d3a24f6b2c2ee887b45d8be612a7acbf2 100644 (file)
@@ -6,8 +6,10 @@ defmodule Pleroma.Web.ActivityPub.UserView do
   use Pleroma.Web, :view
 
   alias Pleroma.Keys
+  alias Pleroma.Object
   alias Pleroma.Repo
   alias Pleroma.User
+  alias Pleroma.Web.ActivityPub.ObjectView
   alias Pleroma.Web.ActivityPub.Transmogrifier
   alias Pleroma.Web.ActivityPub.Utils
   alias Pleroma.Web.Endpoint
@@ -97,6 +99,7 @@ defmodule Pleroma.Web.ActivityPub.UserView do
       "followers" => "#{user.ap_id}/followers",
       "inbox" => "#{user.ap_id}/inbox",
       "outbox" => "#{user.ap_id}/outbox",
+      "featured" => "#{user.ap_id}/collections/featured",
       "preferredUsername" => user.nickname,
       "name" => user.name,
       "summary" => user.bio,
@@ -245,6 +248,25 @@ defmodule Pleroma.Web.ActivityPub.UserView do
     |> Map.merge(pagination)
   end
 
+  def render("featured.json", %{
+        user: %{featured_address: featured_address, pinned_objects: pinned_objects}
+      }) do
+    objects =
+      pinned_objects
+      |> Enum.sort_by(fn {_, pinned_at} -> pinned_at end, &>=/2)
+      |> Enum.map(fn {id, _} ->
+        ObjectView.render("object.json", %{object: Object.get_cached_by_ap_id(id)})
+      end)
+
+    %{
+      "id" => featured_address,
+      "type" => "OrderedCollection",
+      "orderedItems" => objects,
+      "totalItems" => length(objects)
+    }
+    |> Map.merge(Utils.make_json_ld_header())
+  end
+
   defp maybe_put_total_items(map, false, _total), do: map
 
   defp maybe_put_total_items(map, true, total) do
index 00234c0b05c7eddf9d0cac3b86f4f8b4e516d73a..2be59144daeda148708949bd613d71736f72e3a3 100644 (file)
@@ -20,14 +20,14 @@ defmodule Pleroma.Web.ActivityPub.Visibility do
 
   def is_public?(data) do
     Utils.label_in_message?(Pleroma.Constants.as_public(), data) or
-      Utils.label_in_message?(Pleroma.Constants.as_local_public(), data)
+      Utils.label_in_message?(Utils.as_local_public(), data)
   end
 
   def is_local_public?(%Object{data: data}), do: is_local_public?(data)
   def is_local_public?(%Activity{data: data}), do: is_local_public?(data)
 
   def is_local_public?(data) do
-    Utils.label_in_message?(Pleroma.Constants.as_local_public(), data) and
+    Utils.label_in_message?(Utils.as_local_public(), data) and
       not Utils.label_in_message?(Pleroma.Constants.as_public(), data)
   end
 
@@ -127,7 +127,7 @@ defmodule Pleroma.Web.ActivityPub.Visibility do
       Pleroma.Constants.as_public() in cc ->
         "unlisted"
 
-      Pleroma.Constants.as_local_public() in to ->
+      Utils.as_local_public() in to ->
         "local"
 
       # this should use the sql for the object's activity
index 005fe67e2883a218127f5404a7b77f153c79ae5d..51b17d392862f502b873582e0619baa62b007dea 100644 (file)
@@ -13,7 +13,6 @@ defmodule Pleroma.Web.AdminAPI.OAuthAppController do
   require Logger
 
   plug(Pleroma.Web.ApiSpec.CastAndValidate)
-  plug(:put_view, Pleroma.Web.MastodonAPI.AppView)
 
   plug(
     OAuthScopesPlug,
diff --git a/lib/pleroma/web/admin_api/views/o_auth_app_view.ex b/lib/pleroma/web/admin_api/views/o_auth_app_view.ex
new file mode 100644 (file)
index 0000000..af046f3
--- /dev/null
@@ -0,0 +1,10 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.AdminAPI.OAuthAppView do
+  use Pleroma.Web, :view
+  alias Pleroma.Web.MastodonAPI
+
+  def render(view, opts), do: MastodonAPI.AppView.render(view, opts)
+end
index 85aa14869e524c7b22b3ccc0eac5f0babfed8f82..1e245b291b80885780b5ac62a558361b1d90ac70 100644 (file)
@@ -105,6 +105,7 @@ defmodule Pleroma.Web.ApiSpec.MediaOperation do
       responses: %{
         200 => Operation.response("Media", "application/json", Attachment),
         401 => Operation.response("Media", "application/json", ApiError),
+        403 => Operation.response("Media", "application/json", ApiError),
         422 => Operation.response("Media", "application/json", ApiError)
       }
     }
index 4bdb8e281ff8a7fa1c57b142593e38c4ba9b418a..802fbef3e90545a725d25e88a85afc7387b865e7 100644 (file)
@@ -182,7 +182,34 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do
       parameters: [id_param()],
       responses: %{
         200 => status_response(),
-        400 => Operation.response("Error", "application/json", ApiError)
+        400 =>
+          Operation.response("Bad Request", "application/json", %Schema{
+            allOf: [ApiError],
+            title: "Unprocessable Entity",
+            example: %{
+              "error" => "You have already pinned the maximum number of statuses"
+            }
+          }),
+        404 =>
+          Operation.response("Not found", "application/json", %Schema{
+            allOf: [ApiError],
+            title: "Unprocessable Entity",
+            example: %{
+              "error" => "Record not found"
+            }
+          }),
+        422 =>
+          Operation.response(
+            "Unprocessable Entity",
+            "application/json",
+            %Schema{
+              allOf: [ApiError],
+              title: "Unprocessable Entity",
+              example: %{
+                "error" => "Someone else's status cannot be pinned"
+              }
+            }
+          )
       }
     }
   end
@@ -197,7 +224,22 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do
       parameters: [id_param()],
       responses: %{
         200 => status_response(),
-        400 => Operation.response("Error", "application/json", ApiError)
+        400 =>
+          Operation.response("Bad Request", "application/json", %Schema{
+            allOf: [ApiError],
+            title: "Unprocessable Entity",
+            example: %{
+              "error" => "You have already pinned the maximum number of statuses"
+            }
+          }),
+        404 =>
+          Operation.response("Not found", "application/json", %Schema{
+            allOf: [ApiError],
+            title: "Unprocessable Entity",
+            example: %{
+              "error" => "Record not found"
+            }
+          })
       }
     }
   end
index cae18c75813c39841bee8107ab64f46f18f8b2c5..24d7929166f2ad2ef6993b7f7cb54aad9d0f8ad4 100644 (file)
@@ -115,7 +115,8 @@ defmodule Pleroma.Web.ApiSpec.TimelineOperation do
       ],
       operationId: "TimelineController.hashtag",
       responses: %{
-        200 => Operation.response("Array of Status", "application/json", array_of_statuses())
+        200 => Operation.response("Array of Status", "application/json", array_of_statuses()),
+        401 => Operation.response("Error", "application/json", ApiError)
       }
     }
   end
diff --git a/lib/pleroma/web/api_spec/operations/twitter_util_operation.ex b/lib/pleroma/web/api_spec/operations/twitter_util_operation.ex
new file mode 100644 (file)
index 0000000..0cafbc7
--- /dev/null
@@ -0,0 +1,219 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ApiSpec.TwitterUtilOperation do
+  alias OpenApiSpex.Operation
+  alias OpenApiSpex.Schema
+  alias Pleroma.Web.ApiSpec.Schemas.ApiError
+  alias Pleroma.Web.ApiSpec.Schemas.BooleanLike
+
+  def open_api_operation(action) do
+    operation = String.to_existing_atom("#{action}_operation")
+    apply(__MODULE__, operation, [])
+  end
+
+  def emoji_operation do
+    %Operation{
+      tags: ["Emojis"],
+      summary: "List all custom emojis",
+      operationId: "UtilController.emoji",
+      parameters: [],
+      responses: %{
+        200 =>
+          Operation.response("List", "application/json", %Schema{
+            type: :object,
+            additionalProperties: %Schema{
+              type: :object,
+              properties: %{
+                image_url: %Schema{type: :string},
+                tags: %Schema{type: :array, items: %Schema{type: :string}}
+              }
+            },
+            example: %{
+              "firefox" => %{
+                "image_url" => "/emoji/firefox.png",
+                "tag" => ["Fun"]
+              }
+            }
+          })
+      }
+    }
+  end
+
+  def frontend_configurations_operation do
+    %Operation{
+      tags: ["Configuration"],
+      summary: "Dump frontend configurations",
+      operationId: "UtilController.frontend_configurations",
+      parameters: [],
+      responses: %{
+        200 =>
+          Operation.response("List", "application/json", %Schema{
+            type: :object,
+            additionalProperties: %Schema{type: :object}
+          })
+      }
+    }
+  end
+
+  def change_password_operation do
+    %Operation{
+      tags: ["Account credentials"],
+      summary: "Change account password",
+      security: [%{"oAuth" => ["write:accounts"]}],
+      operationId: "UtilController.change_password",
+      parameters: [
+        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: %{
+        200 =>
+          Operation.response("Success", "application/json", %Schema{
+            type: :object,
+            properties: %{status: %Schema{type: :string, example: "success"}}
+          }),
+        400 => Operation.response("Error", "application/json", ApiError),
+        403 => Operation.response("Error", "application/json", ApiError)
+      }
+    }
+  end
+
+  def change_email_operation do
+    %Operation{
+      tags: ["Account credentials"],
+      summary: "Change account email",
+      security: [%{"oAuth" => ["write:accounts"]}],
+      operationId: "UtilController.change_email",
+      parameters: [
+        Operation.parameter(:password, :query, :string, "Current password", required: true),
+        Operation.parameter(:email, :query, :string, "New email", required: true)
+      ],
+      requestBody: nil,
+      responses: %{
+        200 =>
+          Operation.response("Success", "application/json", %Schema{
+            type: :object,
+            properties: %{status: %Schema{type: :string, example: "success"}}
+          }),
+        400 => Operation.response("Error", "application/json", ApiError),
+        403 => Operation.response("Error", "application/json", ApiError)
+      }
+    }
+  end
+
+  def update_notificaton_settings_operation do
+    %Operation{
+      tags: ["Accounts"],
+      summary: "Update Notification Settings",
+      security: [%{"oAuth" => ["write:accounts"]}],
+      operationId: "UtilController.update_notificaton_settings",
+      parameters: [
+        Operation.parameter(
+          :block_from_strangers,
+          :query,
+          BooleanLike,
+          "blocks notifications from accounts you do not follow"
+        ),
+        Operation.parameter(
+          :hide_notification_contents,
+          :query,
+          BooleanLike,
+          "removes the contents of a message from the push notification"
+        )
+      ],
+      requestBody: nil,
+      responses: %{
+        200 =>
+          Operation.response("Success", "application/json", %Schema{
+            type: :object,
+            properties: %{status: %Schema{type: :string, example: "success"}}
+          }),
+        400 => Operation.response("Error", "application/json", ApiError)
+      }
+    }
+  end
+
+  def disable_account_operation do
+    %Operation{
+      tags: ["Account credentials"],
+      summary: "Disable Account",
+      security: [%{"oAuth" => ["write:accounts"]}],
+      operationId: "UtilController.disable_account",
+      parameters: [
+        Operation.parameter(:password, :query, :string, "Password")
+      ],
+      responses: %{
+        200 =>
+          Operation.response("Success", "application/json", %Schema{
+            type: :object,
+            properties: %{status: %Schema{type: :string, example: "success"}}
+          }),
+        403 => Operation.response("Error", "application/json", ApiError)
+      }
+    }
+  end
+
+  def delete_account_operation do
+    %Operation{
+      tags: ["Account credentials"],
+      summary: "Delete Account",
+      security: [%{"oAuth" => ["write:accounts"]}],
+      operationId: "UtilController.delete_account",
+      parameters: [
+        Operation.parameter(:password, :query, :string, "Password")
+      ],
+      responses: %{
+        200 =>
+          Operation.response("Success", "application/json", %Schema{
+            type: :object,
+            properties: %{status: %Schema{type: :string, example: "success"}}
+          }),
+        403 => Operation.response("Error", "application/json", ApiError)
+      }
+    }
+  end
+
+  def captcha_operation do
+    %Operation{
+      summary: "Get a captcha",
+      operationId: "UtilController.captcha",
+      parameters: [],
+      responses: %{
+        200 => Operation.response("Success", "application/json", %Schema{type: :object})
+      }
+    }
+  end
+
+  def healthcheck_operation do
+    %Operation{
+      tags: ["Accounts"],
+      summary: "Quick status check on the instance",
+      security: [%{"oAuth" => ["write:accounts"]}],
+      operationId: "UtilController.healthcheck",
+      parameters: [],
+      responses: %{
+        200 => Operation.response("Healthy", "application/json", %Schema{type: :object}),
+        503 =>
+          Operation.response("Disabled or Unhealthy", "application/json", %Schema{type: :object})
+      }
+    }
+  end
+
+  def remote_subscribe_operation do
+    %Operation{
+      tags: ["Accounts"],
+      summary: "Remote Subscribe",
+      operationId: "UtilController.remote_subscribe",
+      parameters: [],
+      responses: %{200 => Operation.response("Web Page", "test/html", %Schema{type: :string})}
+    }
+  end
+end
index 6292e20043773cc1ccc0f918ba2c9dd74b525675..8df19f1fc0340d6c9f2b6d97dfc21221b36d59b2 100644 (file)
@@ -23,6 +23,7 @@ defmodule Pleroma.Web.ApiSpec.UserImportOperation do
       requestBody: request_body("Parameters", import_request(), required: true),
       responses: %{
         200 => ok_response(),
+        403 => Operation.response("Error", "application/json", ApiError),
         500 => Operation.response("Error", "application/json", ApiError)
       },
       security: [%{"oAuth" => ["write:follow"]}]
index 42fa987181946851bedc22c9546da5807faf8471..3d042dc19a225f2d4e268a97695241f9a1c2840d 100644 (file)
@@ -194,6 +194,13 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do
           parent_visible: %Schema{
             type: :boolean,
             description: "`true` if the parent post is visible to the user"
+          },
+          pinned_at: %Schema{
+            type: :string,
+            format: "date-time",
+            nullable: true,
+            description:
+              "A datetime (ISO 8601) that states when the post was pinned or `null` if the post is not pinned"
           }
         }
       },
index 1c09b67682fbaa49073d1ad035065e42d2e0f251..043206835221d71ab63509fa530ba8679a56db64 100644 (file)
@@ -8,7 +8,7 @@ defmodule Pleroma.Web.UserSocket do
 
   ## Channels
   # channel "room:*", Pleroma.Web.RoomChannel
-  channel("chat:*", Pleroma.Web.ChatChannel)
+  channel("chat:*", Pleroma.Web.ShoutChannel)
 
   # Socket params are passed from the client and can
   # be used to verify and authenticate a user. After
@@ -22,7 +22,7 @@ defmodule Pleroma.Web.UserSocket do
   # See `Phoenix.Token` documentation for examples in
   # performing token verification on connect.
   def connect(%{"token" => token}, socket) do
-    with true <- Pleroma.Config.get([:chat, :enabled]),
+    with true <- Pleroma.Config.get([:shout, :enabled]),
          {:ok, user_id} <- Phoenix.Token.verify(socket, "user socket", token, max_age: 84_600),
          %User{} = user <- Pleroma.User.get_cached_by_id(user_id) do
       {:ok, assign(socket, :user_name, user.nickname)}
index b003e30c7b6fd84b56ede42da927f60efa60c585..1b5f8491efa0ddff51ce3d7769f50ec208df3a5a 100644 (file)
@@ -228,17 +228,7 @@ defmodule Pleroma.Web.CommonAPI do
       {:find_object, _} ->
         {:error, :not_found}
 
-      {:common_pipeline,
-       {
-         :error,
-         {
-           :validate_object,
-           {
-             :error,
-             changeset
-           }
-         }
-       }} = e ->
+      {:common_pipeline, {:error, {:validate, {:error, changeset}}}} = e ->
         if {:object, {"already liked by this actor", []}} in changeset.errors do
           {:ok, :already_liked}
         else
@@ -411,29 +401,58 @@ defmodule Pleroma.Web.CommonAPI do
     end
   end
 
-  def pin(id, %{ap_id: user_ap_id} = user) do
-    with %Activity{
-           actor: ^user_ap_id,
-           data: %{"type" => "Create"},
-           object: %Object{data: %{"type" => object_type}}
-         } = activity <- Activity.get_by_id_with_object(id),
-         true <- object_type in ["Note", "Article", "Question"],
-         true <- Visibility.is_public?(activity),
-         {:ok, _user} <- User.add_pinnned_activity(user, activity) do
+  @spec pin(String.t(), User.t()) :: {:ok, Activity.t()} | {:error, term()}
+  def pin(id, %User{} = user) do
+    with %Activity{} = activity <- create_activity_by_id(id),
+         true <- activity_belongs_to_actor(activity, user.ap_id),
+         true <- object_type_is_allowed_for_pin(activity.object),
+         true <- activity_is_public(activity),
+         {:ok, pin_data, _} <- Builder.pin(user, activity.object),
+         {:ok, _pin, _} <-
+           Pipeline.common_pipeline(pin_data,
+             local: true,
+             activity_id: id
+           ) do
       {:ok, activity}
     else
-      {:error, %{errors: [pinned_activities: {err, _}]}} -> {:error, err}
-      _ -> {:error, dgettext("errors", "Could not pin")}
+      {:error, {:side_effects, error}} -> error
+      error -> error
     end
   end
 
+  defp create_activity_by_id(id) do
+    with nil <- Activity.create_by_id_with_object(id) do
+      {:error, :not_found}
+    end
+  end
+
+  defp activity_belongs_to_actor(%{actor: actor}, actor), do: true
+  defp activity_belongs_to_actor(_, _), do: {:error, :ownership_error}
+
+  defp object_type_is_allowed_for_pin(%{data: %{"type" => type}}) do
+    with false <- type in ["Note", "Article", "Question"] do
+      {:error, :not_allowed}
+    end
+  end
+
+  defp activity_is_public(activity) do
+    with false <- Visibility.is_public?(activity) do
+      {:error, :visibility_error}
+    end
+  end
+
+  @spec unpin(String.t(), User.t()) :: {:ok, User.t()} | {:error, term()}
   def unpin(id, user) do
-    with %Activity{data: %{"type" => "Create"}} = activity <- Activity.get_by_id(id),
-         {:ok, _user} <- User.remove_pinnned_activity(user, activity) do
+    with %Activity{} = activity <- create_activity_by_id(id),
+         {:ok, unpin_data, _} <- Builder.unpin(user, activity.object),
+         {:ok, _unpin, _} <-
+           Pipeline.common_pipeline(unpin_data,
+             local: true,
+             activity_id: activity.id,
+             expires_at: activity.data["expires_at"],
+             featured_address: user.featured_address
+           ) do
       {:ok, activity}
-    else
-      {:error, %{errors: [pinned_activities: {err, _}]}} -> {:error, err}
-      _ -> {:error, dgettext("errors", "Could not unpin")}
     end
   end
 
index 4e6a3feb04143ca7888029dafbf8f9b825ca4b17..4cc34002d11ae3c0a073abae3ddb6141a5983f24 100644 (file)
@@ -69,7 +69,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do
     to =
       case visibility do
         "public" -> [Pleroma.Constants.as_public() | draft.mentions]
-        "local" -> [Pleroma.Constants.as_local_public() | draft.mentions]
+        "local" -> [Utils.as_local_public() | draft.mentions]
       end
 
     cc = [draft.user.follower_address]
@@ -286,7 +286,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do
   def format_input(text, "text/markdown", options) do
     text
     |> Formatter.mentions_escape(options)
-    |> Earmark.as_html!(%Earmark.Options{renderer: Pleroma.EarmarkRenderer})
+    |> Formatter.markdown_to_html()
     |> Formatter.linkify(options)
     |> Formatter.html_escape("text/html")
   end
index f5ef76d32f0ae9a5324a325cc0fce8c91eeffb2d..69cfc2d52c8e5d2f76b11a732ce70fee6e6d723d 100644 (file)
@@ -96,6 +96,11 @@ defmodule Pleroma.Web.Federator do
         Logger.debug("Unhandled actor #{actor}, #{inspect(e)}")
         {:error, e}
 
+      {:error, {:validate_object, _}} = e ->
+        Logger.error("Incoming AP doc validation error: #{inspect(e)}")
+        Logger.debug(Jason.encode!(params, pretty: true))
+        e
+
       e ->
         # Just drop those for now
         Logger.debug(fn -> "Unhandled activity\n" <> Jason.encode!(params, pretty: true) end)
index 66940f3110a1abb7cbc46ccc9382851c1dd62da7..c0fb35e01ac89cb326c9427e4d05f3b1a242c5bc 100644 (file)
@@ -52,10 +52,10 @@ defmodule Pleroma.Web.Feed.FeedView do
   def feed_logo do
     case Pleroma.Config.get([:feed, :logo]) do
       nil ->
-        "#{Pleroma.Web.base_url()}/static/logo.svg"
+        "#{Pleroma.Web.Endpoint.url()}/static/logo.svg"
 
       logo ->
-        "#{Pleroma.Web.base_url()}#{logo}"
+        "#{Pleroma.Web.Endpoint.url()}#{logo}"
     end
     |> MediaProxy.url()
   end
index 58d35da1ed84b0b3cf3197356054897589603034..fa7879cafdb38f94507a9aa09e8191a4c04aa3ed 100644 (file)
@@ -28,7 +28,7 @@ defmodule Pleroma.Web.Feed.UserController do
 
   def feed_redirect(conn, %{"nickname" => nickname}) do
     with {_, %User{} = user} <- {:fetch_user, User.get_cached_by_nickname(nickname)} do
-      redirect(conn, external: "#{user_feed_url(conn, :feed, user.nickname)}.atom")
+      redirect(conn, external: "#{Routes.user_feed_url(conn, :feed, user.nickname)}.atom")
     end
   end
 
diff --git a/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex b/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex
new file mode 100644 (file)
index 0000000..4920d65
--- /dev/null
@@ -0,0 +1,108 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.MastodonAPI.AuthController do
+  use Pleroma.Web, :controller
+
+  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
+
+  action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
+
+  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"
+  def password_reset(conn, params) do
+    nickname_or_email = params["email"] || params["nickname"]
+
+    TwitterAPI.password_reset(nickname_or_email)
+
+    json_response(conn, :no_content, "")
+  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
index d25f848376b0ea03eb491b846b56778c82788ee9..84621500ee33bcca95bcd217382db016d635f30b 100644 (file)
@@ -30,6 +30,12 @@ defmodule Pleroma.Web.MastodonAPI.FallbackController do
     |> json(%{error: error_message})
   end
 
+  def call(conn, {:error, status, message}) do
+    conn
+    |> put_status(status)
+    |> json(%{error: message})
+  end
+
   def call(conn, _) do
     conn
     |> put_status(:internal_server_error)
index 63d0e2c35d5ac3559f35c76ed94c46a8a2d8ac55..d915298f11824c9ed51c7d4e660c87c5e61540a4 100644 (file)
@@ -9,7 +9,6 @@ defmodule Pleroma.Web.MastodonAPI.FollowRequestController do
   alias Pleroma.Web.CommonAPI
   alias Pleroma.Web.Plugs.OAuthScopesPlug
 
-  plug(:put_view, Pleroma.Web.MastodonAPI.AccountView)
   plug(Pleroma.Web.ApiSpec.CastAndValidate)
   plug(:assign_follower when action != :index)
 
index d6949ed8075c0d1cc1df27dfdcb31a3407cfc678..5918b288d49e1cceb6aa9ca39b06fba8b0676735 100644 (file)
@@ -13,7 +13,6 @@ defmodule Pleroma.Web.MastodonAPI.MediaController do
   action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
   plug(Majic.Plug, [pool: Pleroma.MajicPool] when action in [:create, :create2])
   plug(Pleroma.Web.ApiSpec.CastAndValidate)
-  plug(:put_view, Pleroma.Web.MastodonAPI.StatusView)
 
   plug(OAuthScopesPlug, %{scopes: ["read:media"]} when action == :show)
   plug(OAuthScopesPlug, %{scopes: ["write:media"]} when action != :show)
index af93e453df9579422a960f14755cad407238d801..64b177eb3b11439e143fb4f40e1170c6bfa0d6f8 100644 (file)
@@ -8,8 +8,8 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do
   alias Pleroma.Activity
   alias Pleroma.Repo
   alias Pleroma.User
-  alias Pleroma.Web
   alias Pleroma.Web.ControllerHelper
+  alias Pleroma.Web.Endpoint
   alias Pleroma.Web.MastodonAPI.AccountView
   alias Pleroma.Web.MastodonAPI.StatusView
   alias Pleroma.Web.Plugs.OAuthScopesPlug
@@ -108,7 +108,7 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do
   end
 
   defp resource_search(:v2, "hashtags", query, options) do
-    tags_path = Web.base_url() <> "/tag/"
+    tags_path = Endpoint.url() <> "/tag/"
 
     query
     |> prepare_tags(options)
index b051fca741f560f512a12f455bb6563d1b7924fa..724dc5c5d9e7cf720466f19ff8d420636aed5eea 100644 (file)
@@ -260,6 +260,18 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
   def pin(%{assigns: %{user: user}} = conn, %{id: ap_id_or_id}) do
     with {:ok, activity} <- CommonAPI.pin(ap_id_or_id, user) do
       try_render(conn, "show.json", activity: activity, for: user, as: :activity)
+    else
+      {:error, :pinned_statuses_limit_reached} ->
+        {:error, "You have already pinned the maximum number of statuses"}
+
+      {:error, :ownership_error} ->
+        {:error, :unprocessable_entity, "Someone else's status cannot be pinned"}
+
+      {:error, :visibility_error} ->
+        {:error, :unprocessable_entity, "Non-public status cannot be pinned"}
+
+      error ->
+        error
     end
   end
 
index c611958be09ac3f603e67ccd3cbcab2cf0f93747..845f546d4b7ec12a97d92ab1837a6753cb764bc9 100644 (file)
@@ -37,8 +37,6 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
     when action in [:public, :hashtag]
   )
 
-  plug(:put_view, Pleroma.Web.MastodonAPI.StatusView)
-
   defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.TimelineOperation
 
   # GET /api/v1/timelines/home
index ac25aefdd6653850c4843e585aada9898d2b87e5..9e9de33f6167c615158440da34d0f6b3dd877a98 100644 (file)
@@ -292,6 +292,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
     |> maybe_put_allow_following_move(user, opts[:for])
     |> maybe_put_unread_conversation_count(user, opts[:for])
     |> maybe_put_unread_notification_count(user, opts[:for])
+    |> maybe_put_email_address(user, opts[:for])
   end
 
   defp username_from_nickname(string) when is_binary(string) do
@@ -403,6 +404,16 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
 
   defp maybe_put_unread_notification_count(data, _, _), do: data
 
+  defp maybe_put_email_address(data, %User{id: user_id}, %User{id: user_id} = user) do
+    Kernel.put_in(
+      data,
+      [:pleroma, :email],
+      user.email
+    )
+  end
+
+  defp maybe_put_email_address(data, _, _), do: data
+
   defp image_url(%{"url" => [%{"href" => href} | _]}), do: href
   defp image_url(_), do: nil
 end
index 40e31416409ffdaf181ab803be8f49a306304767..7d2d605e9c5132bc74a034e9db36b74c48984288 100644 (file)
@@ -6,14 +6,14 @@ defmodule Pleroma.Web.MastodonAPI.CustomEmojiView do
   use Pleroma.Web, :view
 
   alias Pleroma.Emoji
-  alias Pleroma.Web
+  alias Pleroma.Web.Endpoint
 
   def render("index.json", %{custom_emojis: custom_emojis}) do
     render_many(custom_emojis, __MODULE__, "show.json")
   end
 
   def render("show.json", %{custom_emoji: {shortcode, %Emoji{file: relative_url, tags: tags}}}) do
-    url = Web.base_url() |> URI.merge(relative_url) |> to_string()
+    url = Endpoint.url() |> URI.merge(relative_url) |> to_string()
 
     %{
       "shortcode" => shortcode,
diff --git a/lib/pleroma/web/mastodon_api/views/follow_request_view.ex b/lib/pleroma/web/mastodon_api/views/follow_request_view.ex
new file mode 100644 (file)
index 0000000..4c7d9fc
--- /dev/null
@@ -0,0 +1,10 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.MastodonAPI.FollowRequestView do
+  use Pleroma.Web, :view
+  alias Pleroma.Web.MastodonAPI
+
+  def render(view, opts), do: MastodonAPI.AccountView.render(view, opts)
+end
index dac68d8e606899a8926bc42c46ce563e5dc8a215..3528185d50345e102edc18686db70bde420cd08f 100644 (file)
@@ -14,7 +14,7 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do
     instance = Config.get(:instance)
 
     %{
-      uri: Pleroma.Web.base_url(),
+      uri: Pleroma.Web.Endpoint.url(),
       title: Keyword.get(instance, :name),
       description: Keyword.get(instance, :description),
       version: "#{@mastodon_api_level} (compatible; #{Pleroma.Application.named_version()})",
@@ -24,7 +24,8 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do
       },
       stats: Pleroma.Stats.get_stats(),
       thumbnail:
-        URI.merge(Pleroma.Web.base_url(), Keyword.get(instance, :instance_thumbnail)) |> to_string,
+        URI.merge(Pleroma.Web.Endpoint.url(), Keyword.get(instance, :instance_thumbnail))
+        |> to_string,
       languages: ["en"],
       registrations: Keyword.get(instance, :registrations_open),
       approval_required: Keyword.get(instance, :account_approval_required),
@@ -35,8 +36,8 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do
       avatar_upload_limit: Keyword.get(instance, :avatar_upload_limit),
       background_upload_limit: Keyword.get(instance, :background_upload_limit),
       banner_upload_limit: Keyword.get(instance, :banner_upload_limit),
-      background_image: Pleroma.Web.base_url() <> Keyword.get(instance, :background_image),
-      chat_limit: Keyword.get(instance, :chat_limit),
+      background_image: Pleroma.Web.Endpoint.url() <> Keyword.get(instance, :background_image),
+      shout_limit: Config.get([:shout, :limit]),
       description_limit: Keyword.get(instance, :description_limit),
       pleroma: %{
         metadata: %{
@@ -68,9 +69,13 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do
       if Config.get([:gopher, :enabled]) do
         "gopher"
       end,
-      if Config.get([:chat, :enabled]) do
+      # backwards compat
+      if Config.get([:shout, :enabled]) do
         "chat"
       end,
+      if Config.get([:shout, :enabled]) do
+        "shout"
+      end,
       if Config.get([:instance, :allow_relay]) do
         "relay"
       end,
diff --git a/lib/pleroma/web/mastodon_api/views/media_view.ex b/lib/pleroma/web/mastodon_api/views/media_view.ex
new file mode 100644 (file)
index 0000000..cf52188
--- /dev/null
@@ -0,0 +1,10 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.MastodonAPI.MediaView do
+  use Pleroma.Web, :view
+  alias Pleroma.Web.MastodonAPI
+
+  def render(view, opts), do: MastodonAPI.StatusView.render(view, opts)
+end
index 3753588f2609a571fe084999f98ad9595269b86b..da44e0a74a1918124f7e58b20e03e932a5d3963f 100644 (file)
@@ -9,6 +9,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
 
   alias Pleroma.Activity
   alias Pleroma.HTML
+  alias Pleroma.Maps
   alias Pleroma.Object
   alias Pleroma.Repo
   alias Pleroma.User
@@ -152,6 +153,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
       |> Enum.filter(& &1)
       |> Enum.map(fn user -> AccountView.render("mention.json", %{user: user}) end)
 
+    {pinned?, pinned_at} = pin_data(object, user)
+
     %{
       id: to_string(activity.id),
       uri: object.data["id"],
@@ -173,7 +176,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
       favourited: present?(favorited),
       bookmarked: present?(bookmarked),
       muted: false,
-      pinned: pinned?(activity, user),
+      pinned: pinned?,
       sensitive: false,
       spoiler_text: "",
       visibility: get_visibility(activity),
@@ -184,7 +187,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
       language: nil,
       emojis: [],
       pleroma: %{
-        local: activity.local
+        local: activity.local,
+        pinned_at: pinned_at
       }
     }
   end
@@ -256,7 +260,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
 
     content_html =
       content
-      |> HTML.get_cached_scrubbed_html_for_activity(
+      |> Activity.HTML.get_cached_scrubbed_html_for_activity(
         User.html_filter_policy(opts[:for]),
         activity,
         "mastoapi:content"
@@ -264,7 +268,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
 
     content_plaintext =
       content
-      |> HTML.get_cached_stripped_html_for_activity(
+      |> Activity.HTML.get_cached_stripped_html_for_activity(
         activity,
         "mastoapi:content"
       )
@@ -316,6 +320,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
           fn for_user, user -> User.mutes?(for_user, user) end
         )
 
+    {pinned?, pinned_at} = pin_data(object, user)
+
     %{
       id: to_string(activity.id),
       uri: object.data["id"],
@@ -339,7 +345,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
       favourited: present?(favorited),
       bookmarked: present?(bookmarked),
       muted: muted,
-      pinned: pinned?(activity, user),
+      pinned: pinned?,
       sensitive: sensitive,
       spoiler_text: summary,
       visibility: get_visibility(object),
@@ -360,7 +366,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
         direct_conversation_id: direct_conversation_id,
         thread_muted: thread_muted?,
         emoji_reactions: emoji_reactions,
-        parent_visible: visible_for_user?(reply_to, opts[:for])
+        parent_visible: visible_for_user?(reply_to, opts[:for]),
+        pinned_at: pinned_at
       }
     }
   end
@@ -411,6 +418,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
     media_type = attachment_url["mediaType"] || attachment_url["mimeType"] || "image"
     href = attachment_url["href"] |> MediaProxy.url()
     href_preview = attachment_url["href"] |> MediaProxy.preview_url()
+    meta = render("attachment_meta.json", %{attachment: attachment})
 
     type =
       cond do
@@ -433,8 +441,24 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
       pleroma: %{mime_type: media_type},
       blurhash: attachment["blurhash"]
     }
+    |> Maps.put_if_present(:meta, meta)
+  end
+
+  def render("attachment_meta.json", %{
+        attachment: %{"url" => [%{"width" => width, "height" => height} | _]}
+      })
+      when is_integer(width) and is_integer(height) do
+    %{
+      original: %{
+        width: width,
+        height: height,
+        aspect: width / height
+      }
+    }
   end
 
+  def render("attachment_meta.json", _), do: nil
+
   def render("context.json", %{activity: activity, activities: activities, user: user}) do
     %{ancestors: ancestors, descendants: descendants} =
       activities
@@ -490,7 +514,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
   def build_tags(object_tags) when is_list(object_tags) do
     object_tags
     |> Enum.filter(&is_binary/1)
-    |> Enum.map(&%{name: &1, url: "#{Pleroma.Web.base_url()}/tag/#{URI.encode(&1)}"})
+    |> Enum.map(&%{name: &1, url: "#{Pleroma.Web.Endpoint.url()}/tag/#{URI.encode(&1)}"})
   end
 
   def build_tags(_), do: []
@@ -529,8 +553,13 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
   defp present?(false), do: false
   defp present?(_), do: true
 
-  defp pinned?(%Activity{id: id}, %User{pinned_activities: pinned_activities}),
-    do: id in pinned_activities
+  defp pin_data(%Object{data: %{"id" => object_id}}, %User{pinned_objects: pinned_objects}) do
+    if pinned_at = pinned_objects[object_id] do
+      {true, Utils.to_masto_date(pinned_at)}
+    else
+      {false, nil}
+    end
+  end
 
   defp build_emoji_map(emoji, users, current_user) do
     %{
diff --git a/lib/pleroma/web/mastodon_api/views/timeline_view.ex b/lib/pleroma/web/mastodon_api/views/timeline_view.ex
new file mode 100644 (file)
index 0000000..91226d7
--- /dev/null
@@ -0,0 +1,10 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.MastodonAPI.TimelineView do
+  use Pleroma.Web, :view
+  alias Pleroma.Web.MastodonAPI
+
+  def render(view, opts), do: MastodonAPI.StatusView.render(view, opts)
+end
index d0d4bb4b3d81a66cbbe02579faa1ba9f10f8fc2c..0b232f14b583854e2e13990ea094f8d5dfba6192 100644 (file)
@@ -6,7 +6,7 @@ defmodule Pleroma.Web.MediaProxy do
   alias Pleroma.Config
   alias Pleroma.Helpers.UriHelper
   alias Pleroma.Upload
-  alias Pleroma.Web
+  alias Pleroma.Web.Endpoint
   alias Pleroma.Web.MediaProxy.Invalidation
 
   @base64_opts [padding: false]
@@ -69,7 +69,7 @@ defmodule Pleroma.Web.MediaProxy do
   #   non-local non-whitelisted URLs through it and be sure that body size constraint is preserved.
   def preview_enabled?, do: enabled?() and !!Config.get([:media_preview_proxy, :enabled])
 
-  def local?(url), do: String.starts_with?(url, Web.base_url())
+  def local?(url), do: String.starts_with?(url, Endpoint.url())
 
   def whitelisted?(url) do
     %{host: domain} = URI.parse(url)
@@ -127,7 +127,7 @@ defmodule Pleroma.Web.MediaProxy do
   end
 
   defp signed_url(url) do
-    :crypto.hmac(:sha, Config.get([Web.Endpoint, :secret_key_base]), url)
+    :crypto.mac(:hmac, :sha, Config.get([Endpoint, :secret_key_base]), url)
   end
 
   def filename(url_or_path) do
@@ -135,7 +135,7 @@ defmodule Pleroma.Web.MediaProxy do
   end
 
   def base_url do
-    Config.get([:media_proxy, :base_url], Web.base_url())
+    Config.get([:media_proxy, :base_url], Endpoint.url())
   end
 
   defp proxy_url(path, sig_base64, url_base64, filename) do
index de719543549d94d7484e641232cbb085c5cef563..bc31d66b9a7162b79cbee3c0c121f9df9709bcf4 100644 (file)
@@ -3,6 +3,7 @@
 # SPDX-License-Identifier: AGPL-3.0-only
 
 defmodule Pleroma.Web.Metadata.Utils do
+  alias Pleroma.Activity
   alias Pleroma.Emoji
   alias Pleroma.Formatter
   alias Pleroma.HTML
@@ -13,7 +14,7 @@ defmodule Pleroma.Web.Metadata.Utils do
     # html content comes from DB already encoded, decode first and scrub after
     |> HtmlEntities.decode()
     |> String.replace(~r/<br\s?\/?>/, " ")
-    |> HTML.get_cached_stripped_html_for_activity(object, "metadata")
+    |> Activity.HTML.get_cached_stripped_html_for_activity(object, "metadata")
     |> Emoji.Formatter.demojify()
     |> HtmlEntities.decode()
     |> Formatter.truncate()
index bca94d23644a48de9354b39ad566c35d29004aa6..69ec27ba05f1979ebeab7b040aab23bde9b82a29 100644 (file)
@@ -5,7 +5,7 @@
 defmodule Pleroma.Web.Nodeinfo.NodeinfoController do
   use Pleroma.Web, :controller
 
-  alias Pleroma.Web
+  alias Pleroma.Web.Endpoint
   alias Pleroma.Web.Nodeinfo.Nodeinfo
 
   def schemas(conn, _params) do
@@ -13,11 +13,11 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do
       links: [
         %{
           rel: "http://nodeinfo.diaspora.software/ns/schema/2.0",
-          href: Web.base_url() <> "/nodeinfo/2.0.json"
+          href: Endpoint.url() <> "/nodeinfo/2.0.json"
         },
         %{
           rel: "http://nodeinfo.diaspora.software/ns/schema/2.1",
-          href: Web.base_url() <> "/nodeinfo/2.1.json"
+          href: Endpoint.url() <> "/nodeinfo/2.1.json"
         }
       ]
     }
index ae040c6eb94b7767d0b6d6bba4ce33a5bcacda7e..73d48549710fcf014e50cfc9008ef2738fa453fd 100644 (file)
@@ -427,7 +427,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
       |> Map.put("state", state)
 
     # Handing the request to Ueberauth
-    redirect(conn, to: o_auth_path(conn, :request, provider, params))
+    redirect(conn, to: Routes.o_auth_path(conn, :request, provider, params))
   end
 
   def request(%Plug.Conn{} = conn, params) do
@@ -600,6 +600,12 @@ defmodule Pleroma.Web.OAuth.OAuthController do
     end
   end
 
+<<<<<<< HEAD
+=======
+  # Special case: Local MastodonFE
+  defp redirect_uri(%Plug.Conn{} = conn, "."), do: Routes.auth_url(conn, :login)
+
+>>>>>>> 0c56f9de0d607b88fd107e0bd13ef286f0629346
   defp redirect_uri(%Plug.Conn{}, redirect_uri), do: redirect_uri
 
   defp get_session_registration_id(%Plug.Conn{} = conn), do: get_session(conn, :registration_id)
index 281bbcc3ce98a7626830d691bd8472c7d8647b87..1419c96a2f905bbecffeb70702b5bc0bf711ed08 100644 (file)
@@ -10,6 +10,7 @@ defmodule Pleroma.Web.OAuth.OAuthView do
 
   def render("token.json", %{token: token} = opts) do
     response = %{
+      id: token.id,
       token_type: "Bearer",
       access_token: token.token,
       refresh_token: token.refresh_token,
index 165afd3b42391029c212bfceb95bf360e19f0993..6e01c549799ac6c5ad4dff4ba91cdfc48c676302 100644 (file)
@@ -47,7 +47,6 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do
   plug(RateLimiter, [name: :account_confirmation_resend] when action == :confirmation_resend)
 
   plug(:assign_account_by_id when action in [:favourites, :subscribe, :unsubscribe])
-  plug(:put_view, Pleroma.Web.MastodonAPI.AccountView)
 
   defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaAccountOperation
 
index d285e490745ca879964106f1afc597e7eef6d935..be2f4617d5edc89daac513d3737289fcef5147b9 100644 (file)
@@ -13,7 +13,6 @@ defmodule Pleroma.Web.PleromaAPI.ConversationController do
   alias Pleroma.Web.Plugs.OAuthScopesPlug
 
   plug(Pleroma.Web.ApiSpec.CastAndValidate)
-  plug(:put_view, Pleroma.Web.MastodonAPI.ConversationView)
   plug(OAuthScopesPlug, %{scopes: ["read:statuses"]} when action in [:show, :statuses])
 
   plug(
index 257bcd55033403ed651f2ee5e703481d85346cb9..bcb3a9ae18febbf14c86e1a3c50373ab965c0131 100644 (file)
@@ -14,8 +14,6 @@ defmodule Pleroma.Web.PleromaAPI.NotificationController do
     %{scopes: ["write:notifications"]} when action == :mark_as_read
   )
 
-  plug(:put_view, Pleroma.Web.MastodonAPI.NotificationView)
-
   defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaNotificationOperation
 
   def mark_as_read(%{assigns: %{user: user}, body_params: %{id: notification_id}} = conn, _) do
diff --git a/lib/pleroma/web/pleroma_api/views/account_view.ex b/lib/pleroma/web/pleroma_api/views/account_view.ex
new file mode 100644 (file)
index 0000000..28941f4
--- /dev/null
@@ -0,0 +1,10 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.PleromaAPI.AccountView do
+  use Pleroma.Web, :view
+  alias Pleroma.Web.MastodonAPI
+
+  def render(view, opts), do: MastodonAPI.AccountView.render(view, opts)
+end
diff --git a/lib/pleroma/web/pleroma_api/views/conversation_view.ex b/lib/pleroma/web/pleroma_api/views/conversation_view.ex
new file mode 100644 (file)
index 0000000..1730063
--- /dev/null
@@ -0,0 +1,10 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.PleromaAPI.ConversationView do
+  use Pleroma.Web, :view
+  alias Pleroma.Web.MastodonAPI
+
+  def render(view, opts), do: MastodonAPI.ConversationView.render(view, opts)
+end
diff --git a/lib/pleroma/web/pleroma_api/views/notification_view.ex b/lib/pleroma/web/pleroma_api/views/notification_view.ex
new file mode 100644 (file)
index 0000000..36b2fdf
--- /dev/null
@@ -0,0 +1,10 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.PleromaAPI.NotificationView do
+  use Pleroma.Web, :view
+  alias Pleroma.Web.MastodonAPI
+
+  def render(view, opts), do: MastodonAPI.NotificationView.render(view, opts)
+end
index eb385e94dfc97020acd05ba0b3d988fa9e1007a9..ebe7eaf86bbdac1b55e58c36a9532bc8a2d30d0a 100644 (file)
@@ -10,8 +10,6 @@ defmodule Pleroma.Web.Plugs.FrontendStatic do
   """
   @behaviour Plug
 
-  @api_routes Pleroma.Web.get_api_routes()
-
   def file_path(path, frontend_type \\ :primary) do
     if configuration = Pleroma.Config.get([:frontends, frontend_type]) do
       instance_static_path = Pleroma.Config.get([:instance, :static_dir], "instance/static")
@@ -55,10 +53,13 @@ defmodule Pleroma.Web.Plugs.FrontendStatic do
   defp invalid_path?([h | t], match), do: String.contains?(h, match) or invalid_path?(t)
   defp invalid_path?([], _match), do: false
 
-  defp api_route?([h | _]) when h in @api_routes, do: true
-  defp api_route?([_ | t]), do: api_route?(t)
   defp api_route?([]), do: false
 
+  defp api_route?([h | t]) do
+    api_routes = Pleroma.Web.Router.get_api_routes()
+    if h in api_routes, do: true, else: api_route?(t)
+  end
+
   defp call_static(conn, opts, from) do
     opts = Map.put(opts, :from, from)
     Plug.Static.call(conn, opts)
index 0025b042a455e0c99e4965fe5997f09d6339cdd7..d1e6cc9d3f03f00c7f19dc475345714641711812 100644 (file)
@@ -48,7 +48,8 @@ defmodule Pleroma.Web.Plugs.HTTPSecurityPlug do
       {"x-content-type-options", "nosniff"},
       {"referrer-policy", referrer_policy},
       {"x-download-options", "noopen"},
-      {"content-security-policy", csp_string()}
+      {"content-security-policy", csp_string()},
+      {"permissions-policy", "interest-cohort=()"}
     ]
 
     headers =
index 936053ee2fcfc65ea9eccfed9ef098728e3f6a16..9695667b6428b354e86f279fcee528714b964b42 100644 (file)
@@ -134,6 +134,10 @@ defmodule Pleroma.Web.Router do
     plug(Pleroma.Web.Plugs.MappedSignatureToIdentityPlug)
   end
 
+  pipeline :static_fe do
+    plug(Pleroma.Web.Plugs.StaticFEPlug)
+  end
+
   scope "/api/v1/pleroma", Pleroma.Web.TwitterAPI do
     pipe_through(:pleroma_api)
 
@@ -607,18 +611,12 @@ defmodule Pleroma.Web.Router do
 
     get("/oauth_tokens", TwitterAPI.Controller, :oauth_tokens)
     delete("/oauth_tokens/:id", TwitterAPI.Controller, :revoke_token)
-
-    post(
-      "/qvitter/statuses/notifications/read",
-      TwitterAPI.Controller,
-      :mark_notifications_as_read
-    )
   end
 
   scope "/", Pleroma.Web do
     # Note: html format is supported only if static FE is enabled
     # Note: http signature is only considered for json requests (no auth for non-json requests)
-    pipe_through([:accepts_html_json, :http_signature, Pleroma.Web.Plugs.StaticFEPlug])
+    pipe_through([:accepts_html_json, :http_signature, :static_fe])
 
     get("/objects/:uuid", OStatus.OStatusController, :object)
     get("/activities/:uuid", OStatus.OStatusController, :activity)
@@ -632,7 +630,7 @@ defmodule Pleroma.Web.Router do
   scope "/", Pleroma.Web do
     # Note: html format is supported only if static FE is enabled
     # Note: http signature is only considered for json requests (no auth for non-json requests)
-    pipe_through([:accepts_html_xml_json, :http_signature, Pleroma.Web.Plugs.StaticFEPlug])
+    pipe_through([:accepts_html_xml_json, :http_signature, :static_fe])
 
     # Note: returns user _profile_ for json requests, redirects to user _feed_ for non-json ones
     get("/users/:nickname", Feed.UserController, :feed_redirect, as: :user_feed)
@@ -640,7 +638,7 @@ defmodule Pleroma.Web.Router do
 
   scope "/", Pleroma.Web do
     # Note: html format is supported only if static FE is enabled
-    pipe_through([:accepts_html_xml, Pleroma.Web.Plugs.StaticFEPlug])
+    pipe_through([:accepts_html_xml, :static_fe])
 
     get("/users/:nickname/feed", Feed.UserController, :feed, as: :user_feed)
   end
@@ -691,6 +689,7 @@ defmodule Pleroma.Web.Router do
     # The following two are S2S as well, see `ActivityPub.fetch_follow_information_for_user/1`:
     get("/users/:nickname/followers", ActivityPubController, :followers)
     get("/users/:nickname/following", ActivityPubController, :following)
+    get("/users/:nickname/collections/featured", ActivityPubController, :pinned)
   end
 
   scope "/", Pleroma.Web.ActivityPub do
@@ -732,11 +731,11 @@ defmodule Pleroma.Web.Router do
     get("/:version", Nodeinfo.NodeinfoController, :nodeinfo)
   end
 
-  scope "/proxy/", Pleroma.Web.MediaProxy do
-    get("/preview/:sig/:url", MediaProxyController, :preview)
-    get("/preview/:sig/:url/:filename", MediaProxyController, :preview)
-    get("/:sig/:url", MediaProxyController, :remote)
-    get("/:sig/:url/:filename", MediaProxyController, :remote)
+  scope "/proxy/", Pleroma.Web do
+    get("/preview/:sig/:url", MediaProxy.MediaProxyController, :preview)
+    get("/preview/:sig/:url/:filename", MediaProxy.MediaProxyController, :preview)
+    get("/:sig/:url", MediaProxy.MediaProxyController, :remote)
+    get("/:sig/:url/:filename", MediaProxy.MediaProxyController, :remote)
   end
 
   if Pleroma.Config.get(:env) == :dev do
@@ -789,4 +788,16 @@ defmodule Pleroma.Web.Router do
 
     options("/*path", RedirectController, :empty)
   end
+
+  # TODO: Change to Phoenix.Router.routes/1 for Phoenix 1.6.0+
+  def get_api_routes do
+    __MODULE__.__routes__()
+    |> Enum.reject(fn r -> r.plug == Pleroma.Web.Fallback.RedirectController end)
+    |> Enum.map(fn r ->
+      r.path
+      |> String.split("/", trim: true)
+      |> List.first()
+    end)
+    |> Enum.uniq()
+  end
 end
similarity index 78%
rename from lib/pleroma/web/chat_channel.ex
rename to lib/pleroma/web/shout_channel.ex
index 4008129e9754517b9ac734cae6c0eb08cf0b0bc6..17caecb1ad5e7f1f310e676f8a0d6152c12ffa93 100644 (file)
@@ -2,12 +2,12 @@
 # Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
 # SPDX-License-Identifier: AGPL-3.0-only
 
-defmodule Pleroma.Web.ChatChannel do
+defmodule Pleroma.Web.ShoutChannel do
   use Phoenix.Channel
 
   alias Pleroma.User
-  alias Pleroma.Web.ChatChannel.ChatChannelState
   alias Pleroma.Web.MastodonAPI.AccountView
+  alias Pleroma.Web.ShoutChannel.ShoutChannelState
 
   def join("chat:public", _message, socket) do
     send(self(), :after_join)
@@ -15,18 +15,18 @@ defmodule Pleroma.Web.ChatChannel do
   end
 
   def handle_info(:after_join, socket) do
-    push(socket, "messages", %{messages: ChatChannelState.messages()})
+    push(socket, "messages", %{messages: ShoutChannelState.messages()})
     {:noreply, socket}
   end
 
   def handle_in("new_msg", %{"text" => text}, %{assigns: %{user_name: user_name}} = socket) do
     text = String.trim(text)
 
-    if String.length(text) in 1..Pleroma.Config.get([:instance, :chat_limit]) do
+    if String.length(text) in 1..Pleroma.Config.get([:shout, :limit]) do
       author = User.get_cached_by_nickname(user_name)
       author_json = AccountView.render("show.json", user: author, skip_visibility_check: true)
 
-      message = ChatChannelState.add_message(%{text: text, author: author_json})
+      message = ShoutChannelState.add_message(%{text: text, author: author_json})
 
       broadcast!(socket, "new_msg", message)
     end
@@ -35,7 +35,7 @@ defmodule Pleroma.Web.ChatChannel do
   end
 end
 
-defmodule Pleroma.Web.ChatChannel.ChatChannelState do
+defmodule Pleroma.Web.ShoutChannel.ShoutChannelState do
   use Agent
 
   @max_messages 20
index fe485d10db892e64ca254ad781e8707258314012..50f0927a36c207863cb4a372856a8b1fa9245b51 100644 (file)
@@ -14,7 +14,6 @@ defmodule Pleroma.Web.StaticFE.StaticFEController do
   alias Pleroma.Web.Router.Helpers
 
   plug(:put_layout, :static_fe)
-  plug(:put_view, Pleroma.Web.StaticFE.StaticFEView)
   plug(:assign_id)
 
   @page_keys ["max_id", "min_id", "limit", "since_id", "order"]
index 6688830baa954d081ac736703fe870e514535259..57bd9246836434d0bd5dc1524ce589ebbb49360a 100644 (file)
@@ -38,7 +38,7 @@
     <%= if id == Pleroma.Constants.as_public() do %>
       <link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/>
     <% else %>
-      <%= unless Regex.match?(~r/^#{Pleroma.Web.base_url()}.+followers$/, id) do %>
+      <%= unless Regex.match?(~r/^#{Pleroma.Web.Endpoint.url()}.+followers$/, id) do %>
         <link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/person" href="<%= id %>"/>
       <% end %>
     <% end %>
index 592b9dcdc4331b86ab4ee5c2c2c52203c0745593..279f2171d811a491ed133edd9713c9df5bda54ed 100644 (file)
@@ -38,7 +38,7 @@
     <%= if id == Pleroma.Constants.as_public() do %>
       <link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection">http://activityschema.org/collection/public</link>
     <% else %>
-      <%= unless Regex.match?(~r/^#{Pleroma.Web.base_url()}.+followers$/, id) do %>
+      <%= unless Regex.match?(~r/^#{Pleroma.Web.Endpoint.url()}.+followers$/, id) do %>
         <link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/person"><%= id %></link>
       <% end %>
     <% end %>
index c2de28fe48888bea7d8f3fc8c71d6a576cc5a89b..aa3035bca514ef17432c48799716b72d7393e618 100644 (file)
@@ -33,7 +33,7 @@
           ostatus:object-type="http://activitystrea.ms/schema/1.0/collection"
           href="http://activityschema.org/collection/public"/>
       <% else %>
-        <%= unless Regex.match?(~r/^#{Pleroma.Web.base_url()}.+followers$/, id) do %>
+        <%= unless Regex.match?(~r/^#{Pleroma.Web.Endpoint.url()}.+followers$/, id) do %>
           <link rel="mentioned"
             ostatus:object-type="http://activitystrea.ms/schema/1.0/person"
             href="<%= id %>" />
index a288539ed6faa481b7f02949582681e3565a40c5..de07310856bb9b2b05b21c5f98a97e415baf5da7 100644 (file)
@@ -9,13 +9,13 @@
       xmlns:ostatus="http://ostatus.org/schema/1.0"
       xmlns:statusnet="http://status.net/schema/api/1/">
 
-    <id><%= '#{tag_feed_url(@conn, :feed, @tag)}.rss' %></id>
+    <id><%= '#{Routes.tag_feed_url(@conn, :feed, @tag)}.rss' %></id>
     <title>#<%= @tag %></title>
 
     <subtitle>These are public toots tagged with #<%= @tag %>. You can interact with them if you have an account anywhere in the fediverse.</subtitle>
     <logo><%= feed_logo() %></logo>
     <updated><%= most_recent_update(@activities) %></updated>
-    <link rel="self" href="<%= '#{tag_feed_url(@conn, :feed, @tag)}.atom'  %>" type="application/atom+xml"/>
+    <link rel="self" href="<%= '#{Routes.tag_feed_url(@conn, :feed, @tag)}.atom'  %>" type="application/atom+xml"/>
     <%= for activity <- @activities do %>
     <%= render @view_module, "_tag_activity.atom", Map.merge(assigns, prepare_activity(activity, actor: true)) %>
     <% end %>
index eeda01a0468bb60951402bec9637c7264ef24e46..9c3613febcc9e9ea9e9c7355cf70fe67a54739c2 100644 (file)
@@ -5,7 +5,7 @@
 
     <title>#<%= @tag %></title>
     <description>These are public toots tagged with #<%= @tag %>. You can interact with them if you have an account anywhere in the fediverse.</description>
-    <link><%= '#{tag_feed_url(@conn, :feed, @tag)}.rss' %></link>
+    <link><%= '#{Routes.tag_feed_url(@conn, :feed, @tag)}.rss' %></link>
     <webfeeds:logo><%= feed_logo() %></webfeeds:logo>
     <webfeeds:accentColor>2b90d9</webfeeds:accentColor>
     <%= for activity <- @activities do %>
index c6acd848f2efd07907170b6e8aa7e320fbb9e16a..5c1f0ecbc77559df2fcb26f2638274ec223d2029 100644 (file)
@@ -6,16 +6,16 @@
   xmlns:poco="http://portablecontacts.net/spec/1.0"
   xmlns:ostatus="http://ostatus.org/schema/1.0">
 
-  <id><%= user_feed_url(@conn, :feed, @user.nickname) <> ".atom" %></id>
+  <id><%= Routes.user_feed_url(@conn, :feed, @user.nickname) <> ".atom" %></id>
   <title><%= @user.nickname <> "'s timeline" %></title>
   <updated><%= most_recent_update(@activities, @user) %></updated>
   <logo><%= logo(@user) %></logo>
-  <link rel="self" href="<%= '#{user_feed_url(@conn, :feed, @user.nickname)}.atom' %>" type="application/atom+xml"/>
+  <link rel="self" href="<%= '#{Routes.user_feed_url(@conn, :feed, @user.nickname)}.atom' %>" type="application/atom+xml"/>
 
   <%= render @view_module, "_author.atom", assigns %>
 
   <%= if last_activity(@activities) do %>
-    <link rel="next" href="<%= '#{user_feed_url(@conn, :feed, @user.nickname)}.atom?max_id=#{last_activity(@activities).id}' %>" type="application/atom+xml"/>
+    <link rel="next" href="<%= '#{Routes.user_feed_url(@conn, :feed, @user.nickname)}.atom?max_id=#{last_activity(@activities).id}' %>" type="application/atom+xml"/>
   <% end %>
 
   <%= for activity <- @activities do %>
index d691204800b5df85a0a2c76c03ce882e90b40e19..6b842a0854a0fbef0ac53759159d3048c741aa6b 100644 (file)
@@ -1,16 +1,16 @@
 <?xml version="1.0" encoding="UTF-8" ?>
 <rss version="2.0">
   <channel>
-    <guid><%= user_feed_url(@conn, :feed, @user.nickname) <> ".rss" %></guid>
+    <guid><%= Routes.user_feed_url(@conn, :feed, @user.nickname) <> ".rss" %></guid>
     <title><%= @user.nickname <> "'s timeline" %></title>
     <updated><%= most_recent_update(@activities, @user) %></updated>
     <image><%= logo(@user) %></image>
-    <link><%= '#{user_feed_url(@conn, :feed, @user.nickname)}.rss' %></link>
+    <link><%= '#{Routes.user_feed_url(@conn, :feed, @user.nickname)}.rss' %></link>
 
     <%= render @view_module, "_author.rss", assigns %>
 
     <%= if last_activity(@activities) do %>
-      <link rel="next"><%= '#{user_feed_url(@conn, :feed, @user.nickname)}.rss?max_id=#{last_activity(@activities).id}' %></link>
+      <link rel="next"><%= '#{Routes.user_feed_url(@conn, :feed, @user.nickname)}.rss?max_id=#{last_activity(@activities).id}' %></link>
     <% end %>
 
     <%= for activity <- @activities do %>
diff --git a/lib/pleroma/web/templates/masto_fe/index.html.eex b/lib/pleroma/web/templates/masto_fe/index.html.eex
new file mode 100644 (file)
index 0000000..6f2b989
--- /dev/null
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html lang='en'>
+<head>
+<meta charset='utf-8'>
+<meta content='width=device-width, initial-scale=1' name='viewport'>
+<title>
+<%= Config.get([:instance, :name]) %>
+</title>
+<link rel="icon" type="image/png" href="/favicon.png"/>
+<link rel="manifest" type="applicaton/manifest+json" href="<%= Routes.masto_fe_path(Pleroma.Web.Endpoint, :manifest) %>" />
+
+<meta name="theme-color" content="<%= Config.get([:manifest, :theme_color]) %>" />
+
+<script crossorigin='anonymous' src="/packs/locales.js"></script>
+<script crossorigin='anonymous' src="/packs/locales/glitch/en.js"></script>
+
+<link rel='preload' as='script' crossorigin='anonymous' href='/packs/features/getting_started.js'>
+<link rel='preload' as='script' crossorigin='anonymous' href='/packs/features/compose.js'>
+<link rel='preload' as='script' crossorigin='anonymous' href='/packs/features/home_timeline.js'>
+<link rel='preload' as='script' crossorigin='anonymous' href='/packs/features/notifications.js'>
+<script id='initial-state' type='application/json'><%= initial_state(@token, @user, @custom_emojis) %></script>
+
+<script src="/packs/core/common.js"></script>
+<link rel="stylesheet" media="all" href="/packs/core/common.css" />
+
+<script src="/packs/flavours/glitch/common.js"></script>
+<link rel="stylesheet" media="all" href="/packs/flavours/glitch/common.css" />
+
+<script src="/packs/flavours/glitch/home.js"></script>
+</head>
+<body class='app-body no-reduce-motion system-font'>
+  <div class='app-holder' data-props='{&quot;locale&quot;:&quot;en&quot;}' id='mastodon'>
+  </div>
+</body>
+</html>
index 5ab59b57b1be9a1e963ea95402be5dce3a3dda07..b9daa8d8b52078b1d2a310382a04a33356550099 100644 (file)
@@ -7,7 +7,7 @@
 
 <h2>Two-factor recovery</h2>
 
-<%= form_for @conn, mfa_verify_path(@conn, :verify), [as: "mfa"], fn f -> %>
+<%= form_for @conn, Routes.mfa_verify_path(@conn, :verify), [as: "mfa"], fn f -> %>
 <div class="input">
   <%= label f, :code, "Recovery code" %>
   <%= text_input f, :code, [autocomplete: false, autocorrect: "off", autocapitalize: "off", autofocus: true, spellcheck: false] %>
@@ -19,6 +19,6 @@
 
 <%= submit "Verify" %>
 <% end %>
-<a href="<%= mfa_path(@conn, :show, %{challenge_type: "totp", mfa_token: @mfa_token, state: @state, redirect_uri: @redirect_uri}) %>">
+<a href="<%= Routes.mfa_path(@conn, :show, %{challenge_type: "totp", mfa_token: @mfa_token, state: @state, redirect_uri: @redirect_uri}) %>">
   Enter a two-factor code
 </a>
index af85777ebb6117f78768da25b6868833de81e8c7..29ea7c5fb6f2a3a0e3db74addb50109feb89da3e 100644 (file)
@@ -7,7 +7,7 @@
 
 <h2>Two-factor authentication</h2>
 
-<%= form_for @conn, mfa_verify_path(@conn, :verify), [as: "mfa"], fn f -> %>
+<%= form_for @conn, Routes.mfa_verify_path(@conn, :verify), [as: "mfa"], fn f -> %>
 <div class="input">
   <%= label f, :code, "Authentication code" %>
   <%= text_input f, :code, [autocomplete: false, autocorrect: "off", autocapitalize: "off", autofocus: true, pattern: "[0-9]*", spellcheck: false] %>
@@ -19,6 +19,6 @@
 
 <%= submit "Verify" %>
 <% end %>
-<a href="<%= mfa_path(@conn, :show, %{challenge_type: "recovery", mfa_token: @mfa_token, state: @state, redirect_uri: @redirect_uri}) %>">
+<a href="<%= Routes.mfa_path(@conn, :show, %{challenge_type: "recovery", mfa_token: @mfa_token, state: @state, redirect_uri: @redirect_uri}) %>">
   Enter a two-factor recovery code
 </a>
index 4a0718851a8dc21986c9e3df79e2f660891a399d..dc4521a62b41b7ee358e7c1908eafe09216d24fd 100644 (file)
@@ -1,6 +1,6 @@
 <h2>Sign in with external provider</h2>
 
-<%= form_for @conn, o_auth_path(@conn, :prepare_request), [as: "authorization", method: "get"], fn f -> %>
+<%= form_for @conn, Routes.o_auth_path(@conn, :prepare_request), [as: "authorization", method: "get"], fn f -> %>
   <div style="display: none">
     <%= render @view_module, "_scopes.html", Map.merge(assigns, %{form: f}) %>
   </div>
index facedc8db6082dd15c49318d75b58f65b8aca538..99f900fb7ccf59a9a64f1c9c22f78c11bd822605 100644 (file)
@@ -8,7 +8,7 @@
 <h2>Registration Details</h2>
 
 <p>If you'd like to register a new account, please provide the details below.</p>
-<%= form_for @conn, o_auth_path(@conn, :register), [as: "authorization"], fn f -> %>
+<%= form_for @conn, Routes.o_auth_path(@conn, :register), [as: "authorization"], fn f -> %>
 
 <div class="input">
   <%= label f, :nickname, "Nickname" %>
index 1a85818ecbad26e4f77371f86653b7b1a90291db..2846ec7e7d49d8a69a3b58674a6bf038d116b8fb 100644 (file)
@@ -5,7 +5,7 @@
 <p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p>
 <% end %>
 
-<%= form_for @conn, o_auth_path(@conn, :authorize), [as: "authorization"], fn f -> %>
+<%= form_for @conn, Routes.o_auth_path(@conn, :authorize), [as: "authorization"], fn f -> %>
 
 <%= if @user do %>
   <div class="account-header">
index 7d3ef6b0d41791d80b0e827ee1f06321d48c0987..fbcacdc14819867f7fc8599b0920f49e9c6eddc8 100644 (file)
@@ -1,5 +1,5 @@
 <h2>Password Reset for <%= @user.nickname %></h2>
-<%= form_for @conn, reset_password_path(@conn, :do_reset), [as: "data"], fn f -> %>
+<%= form_for @conn, Routes.reset_password_path(@conn, :do_reset), [as: "data"], fn f -> %>
   <div class="form-row">
     <%= label f, :password, "Password" %>
     <%= password_input f, :password %>
index df037c01ee7fd012c3d9b8fdb2a25bfd3afb9ba7..4ed4ac8bce314effb5bc9c09f79ea790faa3ac07 100644 (file)
@@ -1,2 +1,2 @@
 <h2>Password reset failed</h2>
-<h3><a href="<%= Pleroma.Web.base_url() %>">Homepage</a></h3>
+<h3><a href="<%= Pleroma.Web.Endpoint.url() %>">Homepage</a></h3>
index f30ba3274bd72a99e9d86924089c7deb90a89185..086d4e08b6ad0394f40ee9a59c942b8634377c15 100644 (file)
@@ -1,2 +1,2 @@
 <h2>Password changed!</h2>
-<h3><a href="<%= Pleroma.Web.base_url() %>">Homepage</a></h3>
+<h3><a href="<%= Pleroma.Web.Endpoint.url() %>">Homepage</a></h3>
index 5ba192cd703d73b0680204b0316e9a91e44c26ab..a7be530914e3d4f87a76d9de1757e3b2e8d41a65 100644 (file)
@@ -4,7 +4,7 @@
     <h2>Remote follow</h2>
     <img height="128" width="128" src="<%= avatar_url(@followee) %>">
     <p><%= @followee.nickname %></p>
-    <%= form_for @conn, remote_follow_path(@conn, :do_follow), [as: "user"], fn f -> %>
+    <%= form_for @conn, Routes.remote_follow_path(@conn, :do_follow), [as: "user"], fn f -> %>
     <%= hidden_input f, :id, value: @followee.id %>
     <%= submit "Authorize" %>
     <% end %>
index df44988ee209cbac156660306a09317e725ac913..a8026fa9d5f056e2a0c94f31c68a3397d81beaca 100644 (file)
@@ -4,7 +4,7 @@
 <h2>Log in to follow</h2>
 <p><%= @followee.nickname %></p>
 <img height="128" width="128" src="<%= avatar_url(@followee) %>">
-<%= form_for @conn, remote_follow_path(@conn, :do_follow), [as: "authorization"], fn f -> %>
+<%= form_for @conn, Routes.remote_follow_path(@conn, :do_follow), [as: "authorization"], fn f -> %>
 <%= text_input f, :name, placeholder: "Username", required: true %>
 <br>
 <%= password_input f, :password, placeholder: "Password", required: true %>
index adc3a3e3de12e5e15756da3fe5e25697d3cfd2df..a54ed83b593ae90df0030615f342a2006cb1fcf1 100644 (file)
@@ -4,7 +4,7 @@
 <h2>Two-factor authentication</h2>
 <p><%= @followee.nickname %></p>
 <img height="128" width="128" src="<%= avatar_url(@followee) %>">
-<%= form_for @conn, remote_follow_path(@conn, :do_follow), [as: "mfa"], fn f -> %>
+<%= form_for @conn, Routes.remote_follow_path(@conn, :do_follow), [as: "mfa"], fn f -> %>
 <%= text_input f, :code, placeholder: "Authentication code", required: true %>
 <br>
 <%= hidden_input f, :id, value: @followee.id %>
index f60accebfb1bb0108d816633737a960ba3d73667..a6b313d8aed26f4c1c24f9028372d0c5c2292b6e 100644 (file)
@@ -2,7 +2,7 @@
   <h2>Error: <%= @error %></h2>
 <% else %>
   <h2>Remotely follow <%= @nickname %></h2>
-  <%= form_for @conn, util_path(@conn, :remote_subscribe), [as: "user"], fn f -> %>
+  <%= form_for @conn, Routes.util_path(@conn, :remote_subscribe), [as: "user"], fn f -> %>
   <%= hidden_input f, :nickname, value: @nickname %>
   <%= text_input f, :profile, placeholder: "Your account ID, e.g. lain@quitter.se" %>
   <%= submit "Follow" %>
index 077bfa70d5661db507bcfd106abd5e9460540a8f..e32713311e6bb3b70231660226c10c2013bc8d47 100644 (file)
@@ -5,7 +5,6 @@
 defmodule Pleroma.Web.TwitterAPI.Controller do
   use Pleroma.Web, :controller
 
-  alias Pleroma.Notification
   alias Pleroma.User
   alias Pleroma.Web.OAuth.Token
   alias Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug
@@ -14,11 +13,6 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
 
   require Logger
 
-  plug(
-    OAuthScopesPlug,
-    %{scopes: ["write:notifications"]} when action == :mark_notifications_as_read
-  )
-
   plug(
     :skip_plug,
     [OAuthScopesPlug, EnsurePublicOrAuthenticatedPlug] when action == :confirm_email
@@ -67,31 +61,4 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
     |> put_resp_content_type("application/json")
     |> send_resp(status, json)
   end
-
-  def mark_notifications_as_read(
-        %{assigns: %{user: user}} = conn,
-        %{"latest_id" => latest_id} = params
-      ) do
-    Notification.set_read_up_to(user, latest_id)
-
-    notifications = Notification.for_user(user, params)
-
-    conn
-    # XXX: This is a hack because pleroma-fe still uses that API.
-    |> put_view(Pleroma.Web.MastodonAPI.NotificationView)
-    |> render("index.json", %{notifications: notifications, for: user})
-  end
-
-  def mark_notifications_as_read(%{assigns: %{user: _user}} = conn, _) do
-    bad_request_reply(conn, "You need to specify latest_id")
-  end
-
-  defp bad_request_reply(conn, error_message) do
-    json = error_json(conn, error_message)
-    json_reply(conn, 400, json)
-  end
-
-  defp error_json(conn, error_message) do
-    %{"error" => error_message, "request" => conn.request_path} |> Jason.encode!()
-  end
 end
index 6ca02fbd7ffa81e6197c951e7faedb0e4922ff0a..9843cc36271515c01c9883b1f21bfe9250ad7d74 100644 (file)
@@ -38,7 +38,7 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowController do
   defp follow_status(conn, _user, acct) do
     with {:ok, object} <- Fetcher.fetch_object_from_id(acct),
          %Activity{id: activity_id} <- Activity.get_create_by_object_ap_id(object.data["id"]) do
-      redirect(conn, to: o_status_path(conn, :notice, activity_id))
+      redirect(conn, to: Routes.o_status_path(conn, :notice, activity_id))
     else
       error ->
         handle_follow_error(conn, error)
index 940a645bb09e207597ee45beefb53611d13d7e10..a2e69666e46be00c27ce7d93a7a64984d13103d9 100644 (file)
@@ -10,12 +10,12 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
   alias Pleroma.Config
   alias Pleroma.Emoji
   alias Pleroma.Healthcheck
-  alias Pleroma.Notification
   alias Pleroma.User
   alias Pleroma.Web.CommonAPI
   alias Pleroma.Web.Plugs.OAuthScopesPlug
   alias Pleroma.Web.WebFinger
 
+  plug(Pleroma.Web.ApiSpec.CastAndValidate when action != :remote_subscribe)
   plug(Pleroma.Web.Plugs.FederatingPlug when action == :remote_subscribe)
 
   plug(
@@ -30,7 +30,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
          ]
   )
 
-  plug(OAuthScopesPlug, %{scopes: ["write:notifications"]} when action == :notifications_read)
+  defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.TwitterUtilOperation
 
   def remote_subscribe(conn, %{"nickname" => nick, "profile" => _}) do
     with %User{} = user <- User.get_cached_by_nickname(nick),
@@ -62,17 +62,6 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
     end
   end
 
-  def notifications_read(%{assigns: %{user: user}} = conn, %{"id" => notification_id}) do
-    with {:ok, _} <- Notification.read_one(user, notification_id) do
-      json(conn, %{status: "success"})
-    else
-      {:error, message} ->
-        conn
-        |> put_resp_content_type("application/json")
-        |> send_resp(403, Jason.encode!(%{"error" => message}))
-    end
-  end
-
   def frontend_configurations(conn, _params) do
     render(conn, "frontend_configurations.json")
   end
@@ -92,13 +81,17 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
     end
   end
 
-  def change_password(%{assigns: %{user: user}} = conn, params) do
-    case CommonAPI.Utils.confirm_current_password(user, params["password"]) do
+  def change_password(%{assigns: %{user: user}} = conn, %{
+        password: password,
+        new_password: new_password,
+        new_password_confirmation: new_password_confirmation
+      }) do
+    case CommonAPI.Utils.confirm_current_password(user, password) do
       {:ok, user} ->
         with {:ok, _user} <-
                User.reset_password(user, %{
-                 password: params["new_password"],
-                 password_confirmation: params["new_password_confirmation"]
+                 password: new_password,
+                 password_confirmation: new_password_confirmation
                }) do
           json(conn, %{status: "success"})
         else
@@ -115,10 +108,10 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
     end
   end
 
-  def change_email(%{assigns: %{user: user}} = conn, params) do
-    case CommonAPI.Utils.confirm_current_password(user, params["password"]) do
+  def change_email(%{assigns: %{user: user}} = conn, %{password: password, email: email}) do
+    case CommonAPI.Utils.confirm_current_password(user, password) do
       {:ok, user} ->
-        with {:ok, _user} <- User.change_email(user, params["email"]) do
+        with {:ok, _user} <- User.change_email(user, email) do
           json(conn, %{status: "success"})
         else
           {:error, changeset} ->
@@ -135,7 +128,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
   end
 
   def delete_account(%{assigns: %{user: user}} = conn, params) do
-    password = params["password"] || ""
+    password = params[:password] || ""
 
     case CommonAPI.Utils.confirm_current_password(user, password) do
       {:ok, user} ->
@@ -148,7 +141,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
   end
 
   def disable_account(%{assigns: %{user: user}} = conn, params) do
-    case CommonAPI.Utils.confirm_current_password(user, params["password"]) do
+    case CommonAPI.Utils.confirm_current_password(user, params[:password]) do
       {:ok, user} ->
         User.set_activation_async(user, false)
         json(conn, %{status: "success"})
index 9b13c09b302efd6388527fb1a97fb86b8ada1142..87cb79dd793742a4c2a60d4912caa96d37273f3c 100644 (file)
@@ -6,14 +6,14 @@ defmodule Pleroma.Web.TwitterAPI.UtilView do
   use Pleroma.Web, :view
   import Phoenix.HTML.Form
   alias Pleroma.Config
-  alias Pleroma.Web
+  alias Pleroma.Web.Endpoint
 
   def status_net_config(instance) do
     """
     <config>
     <site>
     <name>#{Keyword.get(instance, :name)}</name>
-    <site>#{Web.base_url()}</site>
+    <site>#{Endpoint.url()}</site>
     <textlimit>#{Keyword.get(instance, :limit)}</textlimit>
     <closed>#{!Keyword.get(instance, :registrations_open)}</closed>
     </site>
diff --git a/lib/pleroma/web/views/masto_fe_view.ex b/lib/pleroma/web/views/masto_fe_view.ex
new file mode 100644 (file)
index 0000000..63a9c81
--- /dev/null
@@ -0,0 +1,91 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.MastoFEView do
+  use Pleroma.Web, :view
+  alias Pleroma.Config
+  alias Pleroma.User
+  alias Pleroma.Web.MastodonAPI.AccountView
+  alias Pleroma.Web.MastodonAPI.CustomEmojiView
+
+  def initial_state(token, user, custom_emojis) do
+    limit = Config.get([:instance, :limit])
+
+    %{
+      meta: %{
+        streaming_api_base_url: Pleroma.Web.Endpoint.websocket_url(),
+        access_token: token,
+        locale: "en",
+        domain: Pleroma.Web.Endpoint.host(),
+        admin: "1",
+        me: "#{user.id}",
+        unfollow_modal: false,
+        boost_modal: false,
+        delete_modal: true,
+        auto_play_gif: false,
+        display_sensitive_media: false,
+        reduce_motion: false,
+        max_toot_chars: limit,
+        mascot: User.get_mascot(user)["url"]
+      },
+      poll_limits: Config.get([:instance, :poll_limits]),
+      rights: %{
+        delete_others_notice: present?(user.is_moderator),
+        admin: present?(user.is_admin)
+      },
+      compose: %{
+        me: "#{user.id}",
+        default_privacy: user.default_scope,
+        default_sensitive: false,
+        allow_content_types: Config.get([:instance, :allowed_post_formats])
+      },
+      media_attachments: %{
+        accept_content_types: [
+          ".jpg",
+          ".jpeg",
+          ".png",
+          ".gif",
+          ".webm",
+          ".mp4",
+          ".m4v",
+          "image\/jpeg",
+          "image\/png",
+          "image\/gif",
+          "video\/webm",
+          "video\/mp4"
+        ]
+      },
+      settings: user.mastofe_settings || %{},
+      push_subscription: nil,
+      accounts: %{user.id => render(AccountView, "show.json", user: user, for: user)},
+      custom_emojis: render(CustomEmojiView, "index.json", custom_emojis: custom_emojis),
+      char_limit: limit
+    }
+    |> Jason.encode!()
+    |> Phoenix.HTML.raw()
+  end
+
+  defp present?(nil), do: false
+  defp present?(false), do: false
+  defp present?(_), do: true
+
+  def render("manifest.json", _params) do
+    %{
+      name: Config.get([:instance, :name]),
+      description: Config.get([:instance, :description]),
+      icons: Config.get([:manifest, :icons]),
+      theme_color: Config.get([:manifest, :theme_color]),
+      background_color: Config.get([:manifest, :background_color]),
+      display: "standalone",
+      scope: Pleroma.Web.Endpoint.url(),
+      start_url: Routes.masto_fe_path(Pleroma.Web.Endpoint, :index, ["getting-started"]),
+      categories: [
+        "social"
+      ],
+      serviceworker: %{
+        src: "/sw.js"
+      }
+    }
+  end
+end
index 21b10e654a6692bc4046de0efb6c027a9dfc45f4..938fc09e36d623bdd9ef495c0007e5aa2ffe3387 100644 (file)
@@ -5,7 +5,7 @@
 defmodule Pleroma.Web.WebFinger do
   alias Pleroma.HTTP
   alias Pleroma.User
-  alias Pleroma.Web
+  alias Pleroma.Web.Endpoint
   alias Pleroma.Web.Federator.Publisher
   alias Pleroma.Web.XML
   alias Pleroma.XmlBuilder
@@ -13,7 +13,7 @@ defmodule Pleroma.Web.WebFinger do
   require Logger
 
   def host_meta do
-    base_url = Web.base_url()
+    base_url = Endpoint.url()
 
     {
       :XRD,
diff --git a/mix.exs b/mix.exs
index ae74f50a3ce3939284e60349d9c221e32a78abef..5d945bf5f69b5038a6bab08ccc6e54601f5495ea 100644 (file)
--- a/mix.exs
+++ b/mix.exs
@@ -38,7 +38,7 @@ defmodule Pleroma.Mixfile do
           include_executables_for: [:unix],
           applications: [ex_syslogger: :load, syslog: :load, eldap: :transient],
           steps: [:assemble, &put_otp_version/1, &copy_files/1, &copy_nginx_config/1],
-          config_providers: [{Pleroma.Config.ReleaseRuntimeProvider, nil}]
+          config_providers: [{Pleroma.Config.ReleaseRuntimeProvider, []}]
         ]
       ]
     ]
@@ -137,20 +137,20 @@ defmodule Pleroma.Mixfile do
       {:tesla, "~> 1.4.0", override: true},
       {:castore, "~> 0.1"},
       {:cowlib, "~> 2.9", override: true},
-      {:gun,
-       github: "ninenines/gun", ref: "921c47146b2d9567eac7e9a4d2ccc60fffd4f327", override: true},
+      {:gun, "~> 2.0.0-rc.1", override: true},
       {:jason, "~> 1.2"},
       {:mogrify, "~> 0.7.4"},
       {:ex_aws, "~> 2.1.6"},
       {:ex_aws_s3, "~> 2.0"},
       {:sweet_xml, "~> 0.6.6"},
-      {:earmark, "1.4.3"},
+      {:earmark, "1.4.15"},
       {:bbcode_pleroma, "~> 0.2.0"},
       {:crypt,
        git: "https://git.pleroma.social/pleroma/elixir-libraries/crypt.git",
        ref: "cf2aa3f11632e8b0634810a15b3e612c7526f6a3"},
       {:cors_plug, "~> 2.0"},
-      {:web_push_encryption, "~> 0.3"},
+      {:web_push_encryption,
+       git: "https://github.com/lanodan/elixir-web-push-encryption.git", branch: "bugfix/otp-24"},
       {:swoosh, "~> 1.0"},
       {:phoenix_swoosh, "~> 0.3"},
       {:gen_smtp, "~> 0.13"},
@@ -178,7 +178,7 @@ defmodule Pleroma.Mixfile do
       {:quack, "~> 0.1.1"},
       {:joken, "~> 2.0"},
       {:benchee, "~> 1.0"},
-      {:pot, "~> 0.11"},
+      {:pot, "~> 1.0"},
       {:esshd, "~> 0.1.0", runtime: Application.get_env(:esshd, :enabled, false)},
       {:ex_const, "~> 0.2"},
       {:plug_static_index_html, "~> 1.0.0"},
@@ -196,6 +196,9 @@ defmodule Pleroma.Mixfile do
       {:majic,
        git: "https://git.pleroma.social/pleroma/elixir-libraries/majic.git",
        ref: "289cda1b6d0d70ccb2ba508a2b0bd24638db2880"},
+      {:eblurhash,
+       git: "https://github.com/zotonic/eblurhash.git",
+       ref: "04a0b76eadf4de1be17726f39b6313b88708fd12"},
       {:open_api_spex, "~> 3.10"},
 
       ## dev & test
@@ -205,10 +208,7 @@ defmodule Pleroma.Mixfile do
       {:mock, "~> 0.3.5", only: :test},
       # temporary downgrade for excoveralls, hackney until hackney max_connections bug will be fixed
       {:excoveralls, "0.12.3", only: :test},
-      {:hackney,
-       git: "https://git.pleroma.social/pleroma/elixir-libraries/hackney.git",
-       ref: "7d7119f0651515d6d7669c78393fd90950a3ec6e",
-       override: true},
+      {:hackney, "~> 1.17.0", override: true},
       {:mox, "~> 1.0", only: :test},
       {:websocket_client, git: "https://github.com/jeremyong/websocket_client.git", only: :test}
     ] ++ oauth_deps()
index 19b90660bd2e0bcbd67720e74dd1262f8b583f89..1a0cae3eee480518bfe94e584dccc656335e5141 100644 (file)
--- a/mix.lock
+++ b/mix.lock
@@ -1,34 +1,35 @@
 %{
   "accept": {:hex, :accept, "0.3.5", "b33b127abca7cc948bbe6caa4c263369abf1347cfa9d8e699c6d214660f10cd1", [:rebar3], [], "hexpm", "11b18c220bcc2eab63b5470c038ef10eb6783bcb1fcdb11aa4137defa5ac1bb8"},
-  "base62": {:hex, :base62, "1.2.1", "4866763e08555a7b3917064e9eef9194c41667276c51b59de2bc42c6ea65f806", [:mix], [{:custom_base, "~> 0.2.1", [hex: :custom_base, repo: "hexpm", optional: false]}], "hexpm", "3b29948de2013d3f93aa898c884a9dff847e7aec75d9d6d8c1dc4c61c2716c42"},
+  "base62": {:hex, :base62, "1.2.2", "85c6627eb609317b70f555294045895ffaaeb1758666ab9ef9ca38865b11e629", [:mix], [{:custom_base, "~> 0.2.1", [hex: :custom_base, repo: "hexpm", optional: false]}], "hexpm", "d41336bda8eaa5be197f1e4592400513ee60518e5b9f4dcf38f4b4dae6f377bb"},
   "base64url": {:hex, :base64url, "0.0.1", "36a90125f5948e3afd7be97662a1504b934dd5dac78451ca6e9abf85a10286be", [:rebar], [], "hexpm"},
   "bbcode": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/bbcode.git", "f2d267675e9a7e1ad1ea9beb4cc23382762b66c2", [ref: "v0.2.0"]},
   "bbcode_pleroma": {:hex, :bbcode_pleroma, "0.2.0", "d36f5bca6e2f62261c45be30fa9b92725c0655ad45c99025cb1c3e28e25803ef", [:mix], [{:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "19851074419a5fedb4ef49e1f01b30df504bb5dbb6d6adfc135238063bebd1c3"},
-  "bcrypt_elixir": {:hex, :bcrypt_elixir, "2.2.0", "3df902b81ce7fa8867a2ae30d20a1da6877a2c056bfb116fd0bc8a5f0190cea4", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "762be3fcb779f08207531bc6612cca480a338e4b4357abb49f5ce00240a77d1e"},
+  "bcrypt_elixir": {:hex, :bcrypt_elixir, "2.3.0", "6cb662d5c1b0a8858801cf20997bd006e7016aa8c52959c9ef80e0f34fb60b7a", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "2c81d61d4f6ed0e5cf7bf27a9109b791ff216a1034b3d541327484f46dd43769"},
   "benchee": {:hex, :benchee, "1.0.1", "66b211f9bfd84bd97e6d1beaddf8fc2312aaabe192f776e8931cb0c16f53a521", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}], "hexpm", "3ad58ae787e9c7c94dd7ceda3b587ec2c64604563e049b2a0e8baafae832addb"},
   "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"},
-  "cachex": {:hex, :cachex, "3.2.0", "a596476c781b0646e6cb5cd9751af2e2974c3e0d5498a8cab71807618b74fe2f", [:mix], [{:eternal, "~> 1.2", [hex: :eternal, repo: "hexpm", optional: false]}, {:jumper, "~> 1.0", [hex: :jumper, repo: "hexpm", optional: false]}, {:sleeplocks, "~> 1.1", [hex: :sleeplocks, repo: "hexpm", optional: false]}, {:unsafe, "~> 1.0", [hex: :unsafe, repo: "hexpm", optional: false]}], "hexpm", "aef93694067a43697ae0531727e097754a9e992a1e7946296f5969d6dd9ac986"},
+  "cachex": {:hex, :cachex, "3.3.0", "6f2ebb8f27491fe39121bd207c78badc499214d76c695658b19d6079beeca5c2", [:mix], [{:eternal, "~> 1.2", [hex: :eternal, repo: "hexpm", optional: false]}, {:jumper, "~> 1.0", [hex: :jumper, repo: "hexpm", optional: false]}, {:sleeplocks, "~> 1.1", [hex: :sleeplocks, repo: "hexpm", optional: false]}, {:unsafe, "~> 1.0", [hex: :unsafe, repo: "hexpm", optional: false]}], "hexpm", "d90e5ee1dde14cef33f6b187af4335b88748b72b30c038969176cd4e6ccc31a1"},
   "calendar": {:hex, :calendar, "1.0.0", "f52073a708528482ec33d0a171954ca610fe2bd28f1e871f247dc7f1565fa807", [:mix], [{:tzdata, "~> 0.5.20 or ~> 0.1.201603 or ~> 1.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "990e9581920c82912a5ee50e62ff5ef96da6b15949a2ee4734f935fdef0f0a6f"},
   "captcha": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/elixir-captcha.git", "e0f16822d578866e186a0974d65ad58cddc1e2ab", [ref: "e0f16822d578866e186a0974d65ad58cddc1e2ab"]},
-  "castore": {:hex, :castore, "0.1.7", "1ca19eee705cde48c9e809e37fdd0730510752cc397745e550f6065a56a701e9", [:mix], [], "hexpm", "a2ae2c13d40e9c308387f1aceb14786dca019ebc2a11484fb2a9f797ea0aa0d8"},
-  "certifi": {:git, "https://github.com/certifi/erlang-certifi", "e08b12e8993502240c25b78563993776f87ecd2a", [tag: "2.5.1"]},
+  "castore": {:hex, :castore, "0.1.10", "b01a007416a0ae4188e70b3b306236021b16c11474038ead7aff79dd75538c23", [:mix], [], "hexpm", "a48314e0cb45682db2ea27b8ebfa11bd6fa0a6e21a65e5772ad83ca136ff2665"},
+  "certifi": {:hex, :certifi, "2.6.1", "dbab8e5e155a0763eea978c913ca280a6b544bfa115633fa20249c3d396d9493", [:rebar3], [], "hexpm", "524c97b4991b3849dd5c17a631223896272c6b0af446778ba4675a1dff53bb7e"},
   "combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"},
-  "comeonin": {:hex, :comeonin, "5.3.1", "7fe612b739c78c9c1a75186ef2d322ce4d25032d119823269d0aa1e2f1e20025", [:mix], [], "hexpm", "d6222483060c17f0977fad1b7401ef0c5863c985a64352755f366aee3799c245"},
+  "comeonin": {:hex, :comeonin, "5.3.2", "5c2f893d05c56ae3f5e24c1b983c2d5dfb88c6d979c9287a76a7feb1e1d8d646", [:mix], [], "hexpm", "d0993402844c49539aeadb3fe46a3c9bd190f1ecf86b6f9ebd71957534c95f04"},
   "concurrent_limiter": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/concurrent_limiter.git", "d81be41024569330f296fc472e24198d7499ba78", [ref: "d81be41024569330f296fc472e24198d7499ba78"]},
   "connection": {:hex, :connection, "1.1.0", "ff2a49c4b75b6fb3e674bfc5536451607270aac754ffd1bdfe175abe4a6d7a68", [:mix], [], "hexpm", "722c1eb0a418fbe91ba7bd59a47e28008a189d47e37e0e7bb85585a016b2869c"},
-  "cors_plug": {:hex, :cors_plug, "2.0.2", "2b46083af45e4bc79632bd951550509395935d3e7973275b2b743bd63cc942ce", [:mix], [{:plug, "~> 1.8", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "f0d0e13f71c51fd4ef8b2c7e051388e4dfb267522a83a22392c856de7e46465f"},
-  "cowboy": {:hex, :cowboy, "2.8.0", "f3dc62e35797ecd9ac1b50db74611193c29815401e53bac9a5c0577bd7bc667d", [:rebar3], [{:cowlib, "~> 2.9.1", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "4643e4fba74ac96d4d152c75803de6fad0b3fa5df354c71afdd6cbeeb15fac8a"},
-  "cowboy_telemetry": {:hex, :cowboy_telemetry, "0.3.0", "69fdb5cf92df6373e15675eb4018cf629f5d8e35e74841bb637d6596cb797bbc", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "42868c229d9a2900a1501c5d0355bfd46e24c862c322b0b4f5a6f14fe0216753"},
-  "cowlib": {:hex, :cowlib, "2.9.1", "61a6c7c50cf07fdd24b2f45b89500bb93b6686579b069a89f88cb211e1125c78", [:rebar3], [], "hexpm", "e4175dc240a70d996156160891e1c62238ede1729e45740bdd38064dad476170"},
-  "credo": {:hex, :credo, "1.4.1", "16392f1edd2cdb1de9fe4004f5ab0ae612c92e230433968eab00aafd976282fc", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "155f8a2989ad77504de5d8291fa0d41320fdcaa6a1030472e9967f285f8c7692"},
+  "cors_plug": {:hex, :cors_plug, "2.0.3", "316f806d10316e6d10f09473f19052d20ba0a0ce2a1d910ddf57d663dac402ae", [:mix], [{:plug, "~> 1.8", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "ee4ae1418e6ce117fc42c2ba3e6cbdca4e95ecd2fe59a05ec6884ca16d469aea"},
+  "cowboy": {:hex, :cowboy, "2.9.0", "865dd8b6607e14cf03282e10e934023a1bd8be6f6bacf921a7e2a96d800cd452", [:make, :rebar3], [{:cowlib, "2.11.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "2c729f934b4e1aa149aff882f57c6372c15399a20d54f65c8d67bef583021bde"},
+  "cowboy_telemetry": {:hex, :cowboy_telemetry, "0.3.1", "ebd1a1d7aff97f27c66654e78ece187abdc646992714164380d8a041eda16754", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3a6efd3366130eab84ca372cbd4a7d3c3a97bdfcfb4911233b035d117063f0af"},
+  "cowlib": {:hex, :cowlib, "2.11.0", "0b9ff9c346629256c42ebe1eeb769a83c6cb771a6ee5960bd110ab0b9b872063", [:make, :rebar3], [], "hexpm", "2b3e9da0b21c4565751a6d4901c20d1b4cc25cbb7fd50d91d2ab6dd287bc86a9"},
+  "credo": {:hex, :credo, "1.5.5", "e8f422026f553bc3bebb81c8e8bf1932f498ca03339856c7fec63d3faac8424b", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "dd8623ab7091956a855dc9f3062486add9c52d310dfd62748779c4315d8247de"},
   "crontab": {:hex, :crontab, "1.1.8", "2ce0e74777dfcadb28a1debbea707e58b879e6aa0ffbf9c9bb540887bce43617", [:mix], [{:ecto, "~> 1.0 or ~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm"},
   "crypt": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/crypt.git", "cf2aa3f11632e8b0634810a15b3e612c7526f6a3", [ref: "cf2aa3f11632e8b0634810a15b3e612c7526f6a3"]},
   "custom_base": {:hex, :custom_base, "0.2.1", "4a832a42ea0552299d81652aa0b1f775d462175293e99dfbe4d7dbaab785a706", [:mix], [], "hexpm", "8df019facc5ec9603e94f7270f1ac73ddf339f56ade76a721eaa57c1493ba463"},
-  "db_connection": {:hex, :db_connection, "2.3.1", "4c9f3ed1ef37471cbdd2762d6655be11e38193904d9c5c1c9389f1b891a3088e", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm", "abaab61780dde30301d840417890bd9f74131041afd02174cf4e10635b3a63f5"},
+  "db_connection": {:hex, :db_connection, "2.4.0", "d04b1b73795dae60cead94189f1b8a51cc9e1f911c234cc23074017c43c031e5", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ad416c21ad9f61b3103d254a71b63696ecadb6a917b36f563921e0de00d7d7c8"},
   "decimal": {:hex, :decimal, "2.0.0", "a78296e617b0f5dd4c6caf57c714431347912ffb1d0842e998e9792b5642d697", [:mix], [], "hexpm", "34666e9c55dea81013e77d9d87370fe6cb6291d1ef32f46a1600230b1d44f577"},
   "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"},
-  "earmark": {:hex, :earmark, "1.4.3", "364ca2e9710f6bff494117dbbd53880d84bebb692dafc3a78eb50aa3183f2bfd", [:mix], [], "hexpm", "8cf8a291ebf1c7b9539e3cddb19e9cef066c2441b1640f13c34c1d3cfc825fec"},
-  "earmark_parser": {:hex, :earmark_parser, "1.4.10", "6603d7a603b9c18d3d20db69921527f82ef09990885ed7525003c7fe7dc86c56", [:mix], [], "hexpm", "8e2d5370b732385db2c9b22215c3f59c84ac7dda7ed7e544d7c459496ae519c0"},
+  "earmark": {:hex, :earmark, "1.4.15", "2c7f924bf495ec1f65bd144b355d0949a05a254d0ec561740308a54946a67888", [:mix], [{:earmark_parser, ">= 1.4.13", [hex: :earmark_parser, repo: "hexpm", optional: false]}], "hexpm", "3b1209b85bc9f3586f370f7c363f6533788fb4e51db23aa79565875e7f9999ee"},
+  "earmark_parser": {:hex, :earmark_parser, "1.4.13", "0c98163e7d04a15feb62000e1a891489feb29f3d10cb57d4f845c405852bbef8", [:mix], [], "hexpm", "d602c26af3a0af43d2f2645613f65841657ad6efc9f0e361c3b6c06b578214ba"},
+  "eblurhash": {:git, "https://github.com/zotonic/eblurhash.git", "04a0b76eadf4de1be17726f39b6313b88708fd12", [ref: "04a0b76eadf4de1be17726f39b6313b88708fd12"]},
   "ecto": {:hex, :ecto, "3.4.6", "08f7afad3257d6eb8613309af31037e16c36808dfda5a3cd0cb4e9738db030e4", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "6f13a9e2a62e75c2dcfc7207bfc65645ab387af8360db4c89fee8b5a4bf3f70b"},
   "ecto_enum": {:hex, :ecto_enum, "1.4.0", "d14b00e04b974afc69c251632d1e49594d899067ee2b376277efd8233027aec8", [:mix], [{:ecto, ">= 3.0.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "> 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:mariaex, ">= 0.0.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "8fb55c087181c2b15eee406519dc22578fa60dd82c088be376d0010172764ee4"},
   "ecto_explain": {:hex, :ecto_explain, "0.1.2", "a9d504cbd4adc809911f796d5ef7ebb17a576a6d32286c3d464c015bd39d5541", [:mix], [], "hexpm", "1d0e7798ae30ecf4ce34e912e5354a0c1c832b7ebceba39298270b9a9f316330"},
   "eimp": {:hex, :eimp, "1.0.14", "fc297f0c7e2700457a95a60c7010a5f1dcb768a083b6d53f49cd94ab95a28f22", [:rebar3], [{:p1_utils, "1.0.18", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "501133f3112079b92d9e22da8b88bf4f0e13d4d67ae9c15c42c30bd25ceb83b6"},
   "elixir_make": {:hex, :elixir_make, "0.6.2", "7dffacd77dec4c37b39af867cedaabb0b59f6a871f89722c25b28fcd4bd70530", [:mix], [], "hexpm", "03e49eadda22526a7e5279d53321d1cced6552f344ba4e03e619063de75348d9"},
   "esshd": {:hex, :esshd, "0.1.1", "d4dd4c46698093a40a56afecce8a46e246eb35463c457c246dacba2e056f31b5", [:mix], [], "hexpm", "d73e341e3009d390aa36387dc8862860bf9f874c94d9fd92ade2926376f49981"},
-  "eternal": {:hex, :eternal, "1.2.1", "d5b6b2499ba876c57be2581b5b999ee9bdf861c647401066d3eeed111d096bc4", [:mix], [], "hexpm", "b14f1dc204321429479c569cfbe8fb287541184ed040956c8862cb7a677b8406"},
+  "eternal": {:hex, :eternal, "1.2.2", "d1641c86368de99375b98d183042dd6c2b234262b8d08dfd72b9eeaafc2a1abd", [:mix], [], "hexpm", "2c9fe32b9c3726703ba5e1d43a1d255a4f3f2d8f8f9bc19f094c7cb1a7a9e782"},
   "ex2ms": {:hex, :ex2ms, "1.5.0", "19e27f9212be9a96093fed8cdfbef0a2b56c21237196d26760f11dfcfae58e97", [:mix], [], "hexpm"},
-  "ex_aws": {:hex, :ex_aws, "2.1.6", "41ab8b4caa48035c96d07faa035d2d9de6df480e7e084c054e662ac888dcd4d4", [:mix], [{:configparser_ex, "~> 4.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8", [hex: :jsx, repo: "hexpm", optional: true]}, {:sweet_xml, "~> 0.6", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "a541bd042c1ee26412bb1e749ddf2a1c327e4fb7e382b1cd227e1b00eed3d469"},
-  "ex_aws_s3": {:hex, :ex_aws_s3, "2.0.2", "c0258bbdfea55de4f98f0b2f0ca61fe402cc696f573815134beb1866e778f47b", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:sweet_xml, ">= 0.0.0", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "0569f5b211b1a3b12b705fe2a9d0e237eb1360b9d76298028df2346cad13097a"},
+  "ex_aws": {:hex, :ex_aws, "2.1.9", "dc4865ecc20a05190a34a0ac5213e3e5e2b0a75a0c2835e923ae7bfeac5e3c31", [:mix], [{:configparser_ex, "~> 4.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:jsx, "~> 3.0", [hex: :jsx, repo: "hexpm", optional: true]}, {:sweet_xml, "~> 0.6", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "3e6c776703c9076001fbe1f7c049535f042cb2afa0d2cbd3b47cbc4e92ac0d10"},
+  "ex_aws_s3": {:hex, :ex_aws_s3, "2.2.0", "07a09de557070320e264893c0acc8a1d2e7ddf80155736e0aed966486d1988e6", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:sweet_xml, ">= 0.0.0", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "15175c613371e29e1f88b78ec8a4327389ca1ec5b34489744b175727496b21bd"},
   "ex_const": {:hex, :ex_const, "0.2.4", "d06e540c9d834865b012a17407761455efa71d0ce91e5831e86881b9c9d82448", [:mix], [], "hexpm", "96fd346610cc992b8f896ed26a98be82ac4efb065a0578f334a32d60a3ba9767"},
-  "ex_doc": {:hex, :ex_doc, "0.22.2", "03a2a58bdd2ba0d83d004507c4ee113b9c521956938298eba16e55cc4aba4a6c", [:mix], [{:earmark_parser, "~> 1.4.0", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm", "cf60e1b3e2efe317095b6bb79651f83a2c1b3edcb4d319c421d7fcda8b3aff26"},
-  "ex_machina": {:hex, :ex_machina, "2.4.0", "09a34c5d371bfb5f78399029194a8ff67aff340ebe8ba19040181af35315eabb", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm", "a20bc9ddc721b33ea913b93666c5d0bdca5cbad7a67540784ae277228832d72c"},
+  "ex_doc": {:hex, :ex_doc, "0.24.2", "e4c26603830c1a2286dae45f4412a4d1980e1e89dc779fcd0181ed1d5a05c8d9", [:mix], [{:earmark_parser, "~> 1.4.0", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "e134e1d9e821b8d9e4244687fb2ace58d479b67b282de5158333b0d57c6fb7da"},
+  "ex_machina": {:hex, :ex_machina, "2.7.0", "b792cc3127fd0680fecdb6299235b4727a4944a09ff0fa904cc639272cd92dc7", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm", "419aa7a39bde11894c87a615c4ecaa52d8f107bbdd81d810465186f783245bf8"},
   "ex_syslogger": {:hex, :ex_syslogger, "1.5.2", "72b6aa2d47a236e999171f2e1ec18698740f40af0bd02c8c650bf5f1fd1bac79", [:mix], [{:poison, ">= 1.5.0", [hex: :poison, repo: "hexpm", optional: true]}, {:syslog, "~> 1.1.0", [hex: :syslog, repo: "hexpm", optional: false]}], "hexpm", "ab9fab4136dbc62651ec6f16fa4842f10cf02ab4433fa3d0976c01be99398399"},
   "excoveralls": {:hex, :excoveralls, "0.12.3", "2142be7cb978a3ae78385487edda6d1aff0e482ffc6123877bb7270a8ffbcfe0", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "568a3e616c264283f5dea5b020783ae40eef3f7ee2163f7a67cbd7b35bcadada"},
   "fast_html": {:hex, :fast_html, "2.0.4", "4910ee49f2f6b19692e3bf30bf97f1b6b7dac489cd6b0f34cd0fe3042c56ba30", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.1.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}], "hexpm", "3bb49d541dfc02ad5e425904f53376d758c09f89e521afc7d2b174b3227761ea"},
   "fast_sanitize": {:hex, :fast_sanitize, "0.2.2", "3cbbaebaea6043865dfb5b4ecb0f1af066ad410a51470e353714b10c42007b81", [:mix], [{:fast_html, "~> 2.0", [hex: :fast_html, repo: "hexpm", optional: false]}, {:plug, "~> 1.8", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "69f204db9250afa94a0d559d9110139850f57de2b081719fbafa1e9a89e94466"},
+  "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"},
   "flake_id": {:hex, :flake_id, "0.1.0", "7716b086d2e405d09b647121a166498a0d93d1a623bead243e1f74216079ccb3", [:mix], [{:base62, "~> 1.2", [hex: :base62, repo: "hexpm", optional: false]}, {:ecto, ">= 2.0.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm", "31fc8090fde1acd267c07c36ea7365b8604055f897d3a53dd967658c691bd827"},
-  "floki": {:hex, :floki, "0.27.0", "6b29a14283f1e2e8fad824bc930eaa9477c462022075df6bea8f0ad811c13599", [:mix], [{:html_entities, "~> 0.5.0", [hex: :html_entities, repo: "hexpm", optional: false]}], "hexpm", "583b8c13697c37179f1f82443bcc7ad2f76fbc0bf4c186606eebd658f7f2631b"},
+  "floki": {:hex, :floki, "0.30.1", "75d35526d3a1459920b6e87fdbc2e0b8a3670f965dd0903708d2b267e0904c55", [:mix], [{:html_entities, "~> 0.5.0", [hex: :html_entities, repo: "hexpm", optional: false]}], "hexpm", "e9c03524447d1c4cbfccd672d739b8c18453eee377846b119d4fd71b1a176bb8"},
   "gen_smtp": {:hex, :gen_smtp, "0.15.0", "9f51960c17769b26833b50df0b96123605a8024738b62db747fece14eb2fbfcc", [:rebar3], [], "hexpm", "29bd14a88030980849c7ed2447b8db6d6c9278a28b11a44cafe41b791205440f"},
   "gen_stage": {:hex, :gen_stage, "0.14.3", "d0c66f1c87faa301c1a85a809a3ee9097a4264b2edf7644bf5c123237ef732bf", [:mix], [], "hexpm"},
   "gen_state_machine": {:hex, :gen_state_machine, "2.0.5", "9ac15ec6e66acac994cc442dcc2c6f9796cf380ec4b08267223014be1c728a95", [:mix], [], "hexpm"},
   "gettext": {:hex, :gettext, "0.18.2", "7df3ea191bb56c0309c00a783334b288d08a879f53a7014341284635850a6e55", [:mix], [], "hexpm", "f9f537b13d4fdd30f3039d33cb80144c3aa1f8d9698e47d7bcbcc8df93b1f5c5"},
-  "gun": {:git, "https://github.com/ninenines/gun.git", "921c47146b2d9567eac7e9a4d2ccc60fffd4f327", [ref: "921c47146b2d9567eac7e9a4d2ccc60fffd4f327"]},
-  "hackney": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/hackney.git", "7d7119f0651515d6d7669c78393fd90950a3ec6e", [ref: "7d7119f0651515d6d7669c78393fd90950a3ec6e"]},
-  "html_entities": {:hex, :html_entities, "0.5.1", "1c9715058b42c35a2ab65edc5b36d0ea66dd083767bef6e3edb57870ef556549", [:mix], [], "hexpm", "30efab070904eb897ff05cd52fa61c1025d7f8ef3a9ca250bc4e6513d16c32de"},
+  "gun": {:hex, :gun, "2.0.0-rc.2", "7c489a32dedccb77b6e82d1f3c5a7dadfbfa004ec14e322cdb5e579c438632d2", [:make, :rebar3], [{:cowlib, "2.11.0", [hex: :cowlib, repo: "hexpm", optional: false]}], "hexpm", "6b9d1eae146410d727140dbf8b404b9631302ecc2066d1d12f22097ad7d254fc"},
+  "hackney": {:hex, :hackney, "1.17.4", "99da4674592504d3fb0cfef0db84c3ba02b4508bae2dff8c0108baa0d6e0977c", [:rebar3], [{:certifi, "~>2.6.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~>6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~>1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~>1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "de16ff4996556c8548d512f4dbe22dd58a587bf3332e7fd362430a7ef3986b16"},
+  "html_entities": {:hex, :html_entities, "0.5.2", "9e47e70598da7de2a9ff6af8758399251db6dbb7eebe2b013f2bbd2515895c3c", [:mix], [], "hexpm", "c53ba390403485615623b9531e97696f076ed415e8d8058b1dbaa28181f4fdcc"},
   "html_sanitize_ex": {:hex, :html_sanitize_ex, "1.3.0", "f005ad692b717691203f940c686208aa3d8ffd9dd4bb3699240096a51fa9564e", [:mix], [{:mochiweb, "~> 2.15", [hex: :mochiweb, repo: "hexpm", optional: false]}], "hexpm"},
   "http_signatures": {:hex, :http_signatures, "0.1.0", "4e4b501a936dbf4cb5222597038a89ea10781776770d2e185849fa829686b34c", [:mix], [], "hexpm", "f8a7b3731e3fd17d38fa6e343fcad7b03d6874a3b0a108c8568a71ed9c2cf824"},
-  "httpoison": {:hex, :httpoison, "1.6.2", "ace7c8d3a361cebccbed19c283c349b3d26991eff73a1eaaa8abae2e3c8089b6", [:mix], [{:hackney, "~> 1.15 and >= 1.15.2", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "aa2c74bd271af34239a3948779612f87df2422c2fdcfdbcec28d9c105f0773fe"},
-  "idna": {:git, "https://github.com/benoitc/erlang-idna", "6cff72747821110169ecfac871b0c69e5064afff", [tag: "6.0.0"]},
+  "httpoison": {:hex, :httpoison, "1.8.0", "6b85dea15820b7804ef607ff78406ab449dd78bed923a49c7160e1886e987a3d", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "28089eaa98cf90c66265b6b5ad87c59a3729bea2e74e9d08f9b51eb9729b3c3a"},
+  "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"},
   "inet_cidr": {:hex, :inet_cidr, "1.0.4", "a05744ab7c221ca8e395c926c3919a821eb512e8f36547c062f62c4ca0cf3d6e", [:mix], [], "hexpm", "64a2d30189704ae41ca7dbdd587f5291db5d1dda1414e0774c29ffc81088c1bc"},
   "jason": {:hex, :jason, "1.2.2", "ba43e3f2709fd1aa1dce90aaabfd039d000469c05c56f0b8e31978e03fa39052", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "18a228f5f0058ee183f29f9eae0805c6e59d61c3b006760668d8d18ff0d12179"},
-  "joken": {:hex, :joken, "2.2.0", "2daa1b12be05184aff7b5ace1d43ca1f81345962285fff3f88db74927c954d3a", [:mix], [{:jose, "~> 1.9", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "b4f92e30388206f869dd25d1af628a1d99d7586e5cf0672f64d4df84c4d2f5e9"},
-  "jose": {:hex, :jose, "1.10.1", "16d8e460dae7203c6d1efa3f277e25b5af8b659febfc2f2eb4bacf87f128b80a", [:mix, :rebar3], [], "hexpm", "3c7ddc8a9394b92891db7c2771da94bf819834a1a4c92e30857b7d582e2f8257"},
+  "joken": {:hex, :joken, "2.3.0", "62a979c46f2c81dcb8ddc9150453b60d3757d1ac393c72bb20fc50a7b0827dc6", [:mix], [{:jose, "~> 1.10", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "57b263a79c0ec5d536ac02d569c01e6b4de91bd1cb825625fe90eab4feb7bc1e"},
+  "jose": {:hex, :jose, "1.11.1", "59da64010c69aad6cde2f5b9248b896b84472e99bd18f246085b7b9fe435dcdb", [:mix, :rebar3], [], "hexpm", "078f6c9fb3cd2f4cfafc972c814261a7d1e8d2b3685c0a76eb87e158efff1ac5"},
   "jumper": {:hex, :jumper, "1.0.1", "3c00542ef1a83532b72269fab9f0f0c82bf23a35e27d278bfd9ed0865cecabff", [:mix], [], "hexpm", "318c59078ac220e966d27af3646026db9b5a5e6703cb2aa3e26bcfaba65b7433"},
   "libring": {:hex, :libring, "1.4.0", "41246ba2f3fbc76b3971f6bce83119dfec1eee17e977a48d8a9cfaaf58c2a8d6", [:mix], [], "hexpm"},
   "linkify": {:hex, :linkify, "0.5.0", "e0ea8de73ff44742d6a889721221f4c4eccaad5284957ee9832ffeb347602d54", [:mix], [], "hexpm", "4ccd958350aee7c51c89e21f05b15d30596ebbba707e051d21766be1809df2d7"},
   "majic": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/majic.git", "289cda1b6d0d70ccb2ba508a2b0bd24638db2880", [ref: "289cda1b6d0d70ccb2ba508a2b0bd24638db2880"]},
-  "makeup": {:hex, :makeup, "1.0.3", "e339e2f766d12e7260e6672dd4047405963c5ec99661abdc432e6ec67d29ef95", [:mix], [{:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "2e9b4996d11832947731f7608fed7ad2f9443011b3b479ae288011265cdd3dad"},
+  "makeup": {:hex, :makeup, "1.0.5", "d5a830bc42c9800ce07dd97fa94669dfb93d3bf5fcf6ea7a0c67b2e0e4a7f26c", [:mix], [{:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cfa158c02d3f5c0c665d0af11512fed3fba0144cf1aadee0f2ce17747fba2ca9"},
   "makeup_elixir": {:hex, :makeup_elixir, "0.14.1", "4f0e96847c63c17841d42c08107405a005a2680eb9c7ccadfd757bd31dabccfb", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f2438b1a80eaec9ede832b5c41cd4f373b38fd7aa33e3b22d9db79e640cbde11"},
-  "meck": {:hex, :meck, "0.8.13", "ffedb39f99b0b99703b8601c6f17c7f76313ee12de6b646e671e3188401f7866", [:rebar3], [], "hexpm", "d34f013c156db51ad57cc556891b9720e6a1c1df5fe2e15af999c84d6cebeb1a"},
-  "metrics": {:git, "https://github.com/benoitc/erlang-metrics", "c6eb4dcf29f9e907539915e2ab996f40c2ec7e8e", [tag: "1.0.1"]},
-  "mime": {:hex, :mime, "1.4.0", "5066f14944b470286146047d2f73518cf5cca82f8e4815cf35d196b58cf07c47", [:mix], [], "hexpm", "75fa42c4228ea9a23f70f123c74ba7cece6a03b1fd474fe13f6a7a85c6ea4ff6"},
-  "mimerl": {:git, "https://github.com/benoitc/mimerl", "5a1b22a8fada5b3b40438da00a6923cb87a42bbc", [tag: "1.2.0"]},
+  "makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"},
+  "meck": {:hex, :meck, "0.9.2", "85ccbab053f1db86c7ca240e9fc718170ee5bda03810a6292b5306bf31bae5f5", [:rebar3], [], "hexpm", "81344f561357dc40a8344afa53767c32669153355b626ea9fcbc8da6b3045826"},
+  "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
+  "mime": {:hex, :mime, "1.6.0", "dabde576a497cef4bbdd60aceee8160e02a6c89250d6c0b29e56c0dfb00db3d2", [:mix], [], "hexpm", "31a1a8613f8321143dde1dafc36006a17d28d02bdfecb9e95a880fa7aabd19a7"},
+  "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"},
   "mochiweb": {:hex, :mochiweb, "2.18.0", "eb55f1db3e6e960fac4e6db4e2db9ec3602cc9f30b86cd1481d56545c3145d2e", [:rebar3], [], "hexpm"},
-  "mock": {:hex, :mock, "0.3.5", "feb81f52b8dcf0a0d65001d2fec459f6b6a8c22562d94a965862f6cc066b5431", [:mix], [{:meck, "~> 0.8.13", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "6fae404799408300f863550392635d8f7e3da6b71abdd5c393faf41b131c8728"},
+  "mock": {:hex, :mock, "0.3.7", "75b3bbf1466d7e486ea2052a73c6e062c6256fb429d6797999ab02fa32f29e03", [:mix], [{:meck, "~> 0.9.2", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "4da49a4609e41fd99b7836945c26f373623ea968cfb6282742bcb94440cf7e5c"},
   "mogrify": {:hex, :mogrify, "0.7.4", "9b2496dde44b1ce12676f85d7dc531900939e6367bc537c7243a1b089435b32d", [:mix], [], "hexpm", "50d79e337fba6bc95bfbef918058c90f50b17eed9537771e61d4619488f099c3"},
   "mox": {:hex, :mox, "1.0.0", "4b3c7005173f47ff30641ba044eb0fe67287743eec9bd9545e37f3002b0a9f8b", [:mix], [], "hexpm", "201b0a20b7abdaaab083e9cf97884950f8a30a1350a1da403b3145e213c6f4df"},
   "myhtmlex": {:git, "https://git.pleroma.social/pleroma/myhtmlex.git", "ad0097e2f61d4953bfef20fb6abddf23b87111e6", [ref: "ad0097e2f61d4953bfef20fb6abddf23b87111e6", submodules: true]},
-  "nimble_parsec": {:hex, :nimble_parsec, "0.6.0", "32111b3bf39137144abd7ba1cce0914533b2d16ef35e8abc5ec8be6122944263", [:mix], [], "hexpm", "27eac315a94909d4dc68bc07a4a83e06c8379237c5ea528a9acff4ca1c873c52"},
+  "nimble_parsec": {:hex, :nimble_parsec, "0.5.0", "90e2eca3d0266e5c53f8fbe0079694740b9c91b6747f2b7e3c5d21966bba8300", [:mix], [], "hexpm", "5c040b8469c1ff1b10093d3186e2e10dbe483cd73d79ec017993fb3985b8a9b3"},
   "nimble_pool": {:hex, :nimble_pool, "0.1.0", "ffa9d5be27eee2b00b0c634eb649aa27f97b39186fec3c493716c2a33e784ec6", [:mix], [], "hexpm", "343a1eaa620ddcf3430a83f39f2af499fe2370390d4f785cd475b4df5acaf3f9"},
   "nodex": {:git, "https://git.pleroma.social/pleroma/nodex", "cb6730f943cfc6aad674c92161be23a8411f15d1", [ref: "cb6730f943cfc6aad674c92161be23a8411f15d1"]},
   "oban": {:hex, :oban, "2.3.4", "ec7509b9af2524d55f529cb7aee93d36131ae0bf0f37706f65d2fe707f4d9fd8", [:mix], [{:ecto_sql, ">= 3.4.3", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.14", [hex: :postgrex, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c70ca0434758fd1805422ea4446af5e910ddc697c0c861549c8f0eb0cfbd2fdf"},
   "open_api_spex": {:hex, :open_api_spex, "3.10.0", "94e9521ad525b3fcf6dc77da7c45f87fdac24756d4de588cb0816b413e7c1844", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 3.1", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm", "2dbb2bde3d2b821f06936e8dfaf3284331186556291946d84eeba3750ac28765"},
   "p1_utils": {:hex, :p1_utils, "1.0.18", "3fe224de5b2e190d730a3c5da9d6e8540c96484cf4b4692921d1e28f0c32b01c", [:rebar3], [], "hexpm", "1fc8773a71a15553b179c986b22fbeead19b28fe486c332d4929700ffeb71f88"},
-  "parse_trans": {:git, "https://github.com/uwiger/parse_trans.git", "76abb347c3c1d00fb0ccf9e4b43e22b3d2288484", [tag: "3.3.0"]},
+  "parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"},
   "pbkdf2_elixir": {:hex, :pbkdf2_elixir, "1.2.1", "9cbe354b58121075bd20eb83076900a3832324b7dd171a6895fab57b6bb2752c", [:mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}], "hexpm", "d3b40a4a4630f0b442f19eca891fcfeeee4c40871936fed2f68e1c4faa30481f"},
-  "phoenix": {:hex, :phoenix, "1.5.6", "8298cdb4e0f943242ba8410780a6a69cbbe972fef199b341a36898dd751bdd66", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 2.13", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.1.2 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "0dc4d39af1306b6aa5122729b0a95ca779e42c708c6fe7abbb3d336d5379e956"},
+  "phoenix": {:hex, :phoenix, "1.5.9", "a6368d36cfd59d917b37c44386e01315bc89f7609a10a45a22f47c007edf2597", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 2.13 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.1.2 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7e4bce20a67c012f1fbb0af90e5da49fa7bf0d34e3a067795703b74aef75427d"},
   "phoenix_ecto": {:hex, :phoenix_ecto, "4.2.1", "13f124cf0a3ce0f1948cf24654c7b9f2347169ff75c1123f44674afee6af3b03", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 2.15", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "478a1bae899cac0a6e02be1deec7e2944b7754c04e7d4107fc5a517f877743c0"},
-  "phoenix_html": {:hex, :phoenix_html, "2.14.2", "b8a3899a72050f3f48a36430da507dd99caf0ac2d06c77529b1646964f3d563e", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "58061c8dfd25da5df1ea0ca47c972f161beb6c875cd293917045b92ffe1bf617"},
+  "phoenix_html": {:hex, :phoenix_html, "2.14.3", "51f720d0d543e4e157ff06b65de38e13303d5778a7919bcc696599e5934271b8", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "efd697a7fff35a13eeeb6b43db884705cba353a1a41d127d118fda5f90c8e80f"},
   "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.0.0", "a1ae76717bb168cdeb10ec9d92d1480fec99e3080f011402c0a2d68d47395ffb", [:mix], [], "hexpm", "c52d948c4f261577b9c6fa804be91884b381a7f8f18450c5045975435350f771"},
-  "phoenix_swoosh": {:hex, :phoenix_swoosh, "0.3.2", "43d3518349a22b8b1910ea28b4dd5119926d5017b3187db3fbd1a1e05769a851", [:mix], [{:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:swoosh, "~> 1.0", [hex: :swoosh, repo: "hexpm", optional: false]}], "hexpm", "3e2ac4e883db7af0702d75ba00c19901760e8342b91f8f66e13941de552e777f"},
-  "plug": {:hex, :plug, "1.10.4", "41eba7d1a2d671faaf531fa867645bd5a3dce0957d8e2a3f398ccff7d2ef017f", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ad1e233fe73d2eec56616568d260777b67f53148a999dc2d048f4eb9778fe4a0"},
-  "plug_cowboy": {:hex, :plug_cowboy, "2.4.0", "e936ef151751f386804c51f87f7300f5aaae6893cdad726559c3930c6c032948", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e25ddcfc06b1b76e55af79d078b03cbc86bbcb99ce4e5e0a5e4a8114ee039be6"},
-  "plug_crypto": {:hex, :plug_crypto, "1.2.0", "1cb20793aa63a6c619dd18bb33d7a3aa94818e5fd39ad357051a67f26dfa2df6", [:mix], [], "hexpm", "a48b538ae8bf381ffac344520755f3007cc10bd8e90b240af98ea29b69683fc2"},
+  "phoenix_swoosh": {:hex, :phoenix_swoosh, "0.3.3", "039435dd975f7e55953525b88f1d596f26c6141412584c16f4db109708a8ee68", [:mix], [{:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:swoosh, "~> 1.0", [hex: :swoosh, repo: "hexpm", optional: false]}], "hexpm", "4a540cea32e05356541737033d666ee7fea7700eb2101bf76783adbfe06601cd"},
+  "plug": {:hex, :plug, "1.11.1", "f2992bac66fdae679453c9e86134a4201f6f43a687d8ff1cd1b2862d53c80259", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "23524e4fefbb587c11f0833b3910bfb414bf2e2534d61928e920f54e3a1b881f"},
+  "plug_cowboy": {:hex, :plug_cowboy, "2.5.0", "51c998f788c4e68fc9f947a5eba8c215fbb1d63a520f7604134cab0270ea6513", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "5b2c8925a5e2587446f33810a58c01e66b3c345652eeec809b76ba007acde71a"},
+  "plug_crypto": {:hex, :plug_crypto, "1.2.2", "05654514ac717ff3a1843204b424477d9e60c143406aa94daf2274fdd280794d", [:mix], [], "hexpm", "87631c7ad914a5a445f0a3809f99b079113ae4ed4b867348dd9eec288cecb6db"},
   "plug_static_index_html": {:hex, :plug_static_index_html, "1.0.0", "840123d4d3975585133485ea86af73cb2600afd7f2a976f9f5fd8b3808e636a0", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "79fd4fcf34d110605c26560cbae8f23c603ec4158c08298bd4360fdea90bb5cf"},
   "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm", "fec8660eb7733ee4117b85f55799fd3833eb769a6df71ccf8903e8dc5447cfce"},
   "poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm", "dad79704ce5440f3d5a3681c8590b9dc25d1a561e8f5a9c995281012860901e3"},
-  "postgrex": {:hex, :postgrex, "0.15.7", "724410acd48abac529d0faa6c2a379fb8ae2088e31247687b16cacc0e0883372", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "88310c010ff047cecd73d5ceca1d99205e4b1ab1b9abfdab7e00f5c9d20ef8f9"},
-  "pot": {:hex, :pot, "0.11.0", "61bad869a94534739dd4614a25a619bc5c47b9970e9a0ea5bef4628036fc7a16", [:rebar3], [], "hexpm", "57ee6ee6bdeb639661ffafb9acefe3c8f966e45394de6a766813bb9e1be4e54b"},
-  "prometheus": {:hex, :prometheus, "4.6.0", "20510f381db1ccab818b4cf2fac5fa6ab5cc91bc364a154399901c001465f46f", [:mix, :rebar3], [], "hexpm", "4905fd2992f8038eccd7aa0cd22f40637ed618c0bed1f75c05aacec15b7545de"},
+  "postgrex": {:hex, :postgrex, "0.15.9", "46f8fe6f25711aeb861c4d0ae09780facfdf3adbd2fb5594ead61504dd489bda", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "610719103e4cb2223d4ab78f9f0f3e720320eeca6011415ab4137ddef730adee"},
+  "pot": {:hex, :pot, "1.0.1", "81b511b1fa7c3123171c265cb7065a1528cebd7277b0cbc94257c50a8b2e4c17", [:rebar3], [], "hexpm", "ed87f5976531d91528452faa1138a5328db7f9f20d8feaae15f5051f79bcfb6d"},
+  "prometheus": {:hex, :prometheus, "4.8.0", "1ce1e1002b173c336d61f186b56263346536e76814edd9a142e12aeb2d6c1ad2", [:mix, :rebar3], [], "hexpm", "0fc2e17103073edb3758a46a5d44b006191bf25b73cbaa2b779109de396afcb5"},
   "prometheus_ecto": {:hex, :prometheus_ecto, "1.4.3", "3dd4da1812b8e0dbee81ea58bb3b62ed7588f2eae0c9e97e434c46807ff82311", [:mix], [{:ecto, "~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.1 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}], "hexpm", "8d66289f77f913b37eda81fd287340c17e61a447549deb28efc254532b2bed82"},
   "prometheus_ex": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/prometheus.ex.git", "a4e9beb3c1c479d14b352fd9d6dd7b1f6d7deee5", [ref: "a4e9beb3c1c479d14b352fd9d6dd7b1f6d7deee5"]},
   "prometheus_phoenix": {:hex, :prometheus_phoenix, "1.3.0", "c4b527e0b3a9ef1af26bdcfbfad3998f37795b9185d475ca610fe4388fdd3bb5", [:mix], [{:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.3 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}], "hexpm", "c4d1404ac4e9d3d963da601db2a7d8ea31194f0017057fabf0cfb9bf5a6c8c75"},
   "prometheus_phx": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/prometheus-phx.git", "9cd8f248c9381ffedc799905050abce194a97514", [branch: "no-logging"]},
   "prometheus_plugs": {:hex, :prometheus_plugs, "1.1.5", "25933d48f8af3a5941dd7b621c889749894d8a1082a6ff7c67cc99dec26377c5", [:mix], [{:accept, "~> 0.1", [hex: :accept, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.1 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}, {:prometheus_process_collector, "~> 1.1", [hex: :prometheus_process_collector, repo: "hexpm", optional: true]}], "hexpm", "0273a6483ccb936d79ca19b0ab629aef0dba958697c94782bb728b920dfc6a79"},
   "quack": {:hex, :quack, "0.1.1", "cca7b4da1a233757fdb44b3334fce80c94785b3ad5a602053b7a002b5a8967bf", [:mix], [{:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: false]}, {:tesla, "~> 1.2.0", [hex: :tesla, repo: "hexpm", optional: false]}], "hexpm", "d736bfa7444112eb840027bb887832a0e403a4a3437f48028c3b29a2dbbd2543"},
-  "ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm", "451d8527787df716d99dc36162fca05934915db0b6141bbdac2ea8d3c7afc7d7"},
+  "ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"},
   "recon": {:hex, :recon, "2.5.1", "430ffa60685ac1efdfb1fe4c97b8767c92d0d92e6e7c3e8621559ba77598678a", [:mix, :rebar3], [], "hexpm", "5721c6b6d50122d8f68cccac712caa1231f97894bab779eff5ff0f886cb44648"},
   "remote_ip": {:git, "https://git.pleroma.social/pleroma/remote_ip.git", "b647d0deecaa3acb140854fe4bda5b7e1dc6d1c8", [ref: "b647d0deecaa3acb140854fe4bda5b7e1dc6d1c8"]},
   "sleeplocks": {:hex, :sleeplocks, "1.1.1", "3d462a0639a6ef36cc75d6038b7393ae537ab394641beb59830a1b8271faeed3", [:rebar3], [], "hexpm", "84ee37aeff4d0d92b290fff986d6a95ac5eedf9b383fadfd1d88e9b84a1c02e1"},
-  "ssl_verify_fun": {:git, "https://github.com/deadtrickster/ssl_verify_fun.erl", "c5718226b0b9f3d1a38ef6ca3c3b4c75f53dda92", [tag: "1.1.4"]},
+  "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"},
   "sweet_xml": {:hex, :sweet_xml, "0.6.6", "fc3e91ec5dd7c787b6195757fbcf0abc670cee1e4172687b45183032221b66b8", [:mix], [], "hexpm", "2e1ec458f892ffa81f9f8386e3f35a1af6db7a7a37748a64478f13163a1f3573"},
-  "swoosh": {:hex, :swoosh, "1.0.6", "6765e334c67dacabe721f0d701c7e5a6f06e4595c90df6f91e73ebd54d555833", [:mix], [{:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}], "hexpm", "7c50ef78e4acfd1cbd4907dc1fa87b5540675a6be9dc979d04890f49d7ec1830"},
+  "swoosh": {:hex, :swoosh, "1.3.11", "34f79c57f19892b43bd2168de9ff5de478a721a26328ef59567aad4243e7a77b", [:mix], [{:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}], "hexpm", "f1e2a048db454f9982b9cf840f75e7399dd48be31ecc2a7dc10012a803b913af"},
   "syslog": {:hex, :syslog, "1.1.0", "6419a232bea84f07b56dc575225007ffe34d9fdc91abe6f1b2f254fd71d8efc2", [:rebar3], [], "hexpm", "4c6a41373c7e20587be33ef841d3de6f3beba08519809329ecc4d27b15b659e1"},
-  "telemetry": {:hex, :telemetry, "0.4.2", "2808c992455e08d6177322f14d3bdb6b625fbcfd233a73505870d8738a2f4599", [:rebar3], [], "hexpm", "2d1419bd9dda6a206d7b5852179511722e2b18812310d304620c7bd92a13fcef"},
-  "tesla": {:hex, :tesla, "1.4.0", "1081bef0124b8bdec1c3d330bbe91956648fb008cf0d3950a369cda466a31a87", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.3", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, "~> 1.3", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "~> 4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "bf1374a5569f5fca8e641363b63f7347d680d91388880979a33bc12a6eb3e0aa"},
-  "timex": {:hex, :timex, "3.7.3", "df8a2ea814749d700d6878ab9eacac9fdb498ecee2f507cb0002ec172bc24d0f", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "8691c1d86ca3a7bc14a156e2199dc8927be95d1a8f0e3b69e4bb2d6262c53ac6"},
+  "telemetry": {:hex, :telemetry, "0.4.3", "a06428a514bdbc63293cd9a6263aad00ddeb66f608163bdec7c8995784080818", [:rebar3], [], "hexpm", "eb72b8365ffda5bed68a620d1da88525e326cb82a75ee61354fc24b844768041"},
+  "tesla": {:hex, :tesla, "1.4.1", "ff855f1cac121e0d16281b49e8f066c4a0d89965f98864515713878cca849ac8", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.3", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, "~> 1.3", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "~> 4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "95f5de35922c8c4b3945bee7406f66eb680b0955232f78f5fb7e853aa1ce201a"},
+  "timex": {:hex, :timex, "3.7.5", "3eca56e23bfa4e0848f0b0a29a92fa20af251a975116c6d504966e8a90516dfd", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "a15608dca680f2ef663d71c95842c67f0af08a0f3b1d00e17bbd22872e2874e4"},
   "trailing_format_plug": {:hex, :trailing_format_plug, "0.0.7", "64b877f912cf7273bed03379936df39894149e35137ac9509117e59866e10e45", [:mix], [{:plug, "> 0.12.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "bd4fde4c15f3e993a999e019d64347489b91b7a9096af68b2bdadd192afa693f"},
   "tzdata": {:hex, :tzdata, "1.0.5", "69f1ee029a49afa04ad77801febaf69385f3d3e3d1e4b56b9469025677b89a28", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "55519aa2a99e5d2095c1e61cc74c9be69688f8ab75c27da724eb8279ff402a5a"},
   "ueberauth": {:hex, :ueberauth, "0.6.3", "d42ace28b870e8072cf30e32e385579c57b9cc96ec74fa1f30f30da9c14f3cc0", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "afc293d8a1140d6591b53e3eaf415ca92842cb1d32fad3c450c6f045f7f91b60"},
-  "unicode_util_compat": {:git, "https://github.com/benoitc/unicode_util_compat.git", "38d7bc105f51159e8ea3279c40121db9db1e652f", [tag: "0.3.1"]},
+  "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"},
   "unsafe": {:hex, :unsafe, "1.0.1", "a27e1874f72ee49312e0a9ec2e0b27924214a05e3ddac90e91727bc76f8613d8", [:mix], [], "hexpm", "6c7729a2d214806450d29766abc2afaa7a2cbecf415be64f36a6691afebb50e5"},
-  "web_push_encryption": {:hex, :web_push_encryption, "0.3.0", "598b5135e696fd1404dc8d0d7c0fa2c027244a4e5d5e5a98ba267f14fdeaabc8", [:mix], [{:httpoison, "~> 1.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jose, "~> 1.8", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "f10bdd1afe527ede694749fb77a2f22f146a51b054c7fa541c9fd920fba7c875"},
+  "web_push_encryption": {:git, "https://github.com/lanodan/elixir-web-push-encryption.git", "026a043037a89db4da8f07560bc8f9c68bcf0cc0", [branch: "bugfix/otp-24"]},
   "websocket_client": {:git, "https://github.com/jeremyong/websocket_client.git", "9a6f65d05ebf2725d62fb19262b21f1805a59fbf", []},
 }
diff --git a/priv/gettext/zh_Hant/LC_MESSAGES/errors.po b/priv/gettext/zh_Hant/LC_MESSAGES/errors.po
new file mode 100644 (file)
index 0000000..9678ca2
--- /dev/null
@@ -0,0 +1,580 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2021-03-15 15:10+0000\n"
+"PO-Revision-Date: 2021-05-12 01:41+0000\n"
+"Last-Translator: Snow <build-a-website@protonmail.com>\n"
+"Language-Team: Chinese (Traditional) <https://translate.pleroma.social/"
+"projects/pleroma/pleroma/zh_Hant/>\n"
+"Language: zh_Hant\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Generator: Weblate 4.0.4\n"
+
+## This file is a PO Template file.
+##
+## `msgid`s here are often extracted from source code.
+## Add new translations manually only if they're dynamic
+## translations that can't be statically extracted.
+##
+## Run `mix gettext.extract` to bring this file up to
+## date. Leave `msgstr`s empty as changing them here as no
+## effect: edit them in PO (`.po`) files instead.
+## From Ecto.Changeset.cast/4
+msgid "can't be blank"
+msgstr "不能為空"
+
+## From Ecto.Changeset.unique_constraint/3
+msgid "has already been taken"
+msgstr "已被占用"
+
+## From Ecto.Changeset.put_change/3
+msgid "is invalid"
+msgstr ""
+
+## From Ecto.Changeset.validate_format/3
+msgid "has invalid format"
+msgstr ""
+
+## From Ecto.Changeset.validate_subset/3
+msgid "has an invalid entry"
+msgstr ""
+
+## From Ecto.Changeset.validate_exclusion/3
+msgid "is reserved"
+msgstr "是被保留的"
+
+## From Ecto.Changeset.validate_confirmation/3
+msgid "does not match confirmation"
+msgstr ""
+
+## From Ecto.Changeset.no_assoc_constraint/3
+msgid "is still associated with this entry"
+msgstr ""
+
+msgid "are still associated with this entry"
+msgstr ""
+
+## From Ecto.Changeset.validate_length/3
+msgid "should be %{count} character(s)"
+msgid_plural "should be %{count} character(s)"
+msgstr[0] ""
+
+msgid "should have %{count} item(s)"
+msgid_plural "should have %{count} item(s)"
+msgstr[0] ""
+
+msgid "should be at least %{count} character(s)"
+msgid_plural "should be at least %{count} character(s)"
+msgstr[0] ""
+
+msgid "should have at least %{count} item(s)"
+msgid_plural "should have at least %{count} item(s)"
+msgstr[0] ""
+
+msgid "should be at most %{count} character(s)"
+msgid_plural "should be at most %{count} character(s)"
+msgstr[0] ""
+
+msgid "should have at most %{count} item(s)"
+msgid_plural "should have at most %{count} item(s)"
+msgstr[0] ""
+
+## From Ecto.Changeset.validate_number/3
+msgid "must be less than %{number}"
+msgstr "必須小於{number}%"
+
+msgid "must be greater than %{number}"
+msgstr "must be greater than {number}%"
+
+msgid "must be less than or equal to %{number}"
+msgstr ""
+
+msgid "must be greater than or equal to %{number}"
+msgstr ""
+
+msgid "must be equal to %{number}"
+msgstr ""
+
+#: lib/pleroma/web/common_api/common_api.ex:505
+#, elixir-format
+msgid "Account not found"
+msgstr ""
+
+#: lib/pleroma/web/common_api/common_api.ex:339
+#, elixir-format
+msgid "Already voted"
+msgstr ""
+
+#: lib/pleroma/web/oauth/oauth_controller.ex:359
+#, elixir-format
+msgid "Bad request"
+msgstr ""
+
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:426
+#, elixir-format
+msgid "Can't delete object"
+msgstr ""
+
+#: lib/pleroma/web/controller_helper.ex:105
+#: lib/pleroma/web/controller_helper.ex:111
+#, elixir-format
+msgid "Can't display this activity"
+msgstr ""
+
+#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:285
+#, elixir-format
+msgid "Can't find user"
+msgstr ""
+
+#: lib/pleroma/web/pleroma_api/controllers/account_controller.ex:61
+#, elixir-format
+msgid "Can't get favorites"
+msgstr ""
+
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:438
+#, elixir-format
+msgid "Can't like object"
+msgstr ""
+
+#: lib/pleroma/web/common_api/utils.ex:563
+#, elixir-format
+msgid "Cannot post an empty status without attachments"
+msgstr ""
+
+#: lib/pleroma/web/common_api/utils.ex:511
+#, elixir-format
+msgid "Comment must be up to %{max_size} characters"
+msgstr ""
+
+#: lib/pleroma/config/config_db.ex:191
+#, elixir-format
+msgid "Config with params %{params} not found"
+msgstr ""
+
+#: lib/pleroma/web/common_api/common_api.ex:181
+#: lib/pleroma/web/common_api/common_api.ex:185
+#, elixir-format
+msgid "Could not delete"
+msgstr ""
+
+#: lib/pleroma/web/common_api/common_api.ex:231
+#, elixir-format
+msgid "Could not favorite"
+msgstr ""
+
+#: lib/pleroma/web/common_api/common_api.ex:453
+#, elixir-format
+msgid "Could not pin"
+msgstr ""
+
+#: lib/pleroma/web/common_api/common_api.ex:278
+#, elixir-format
+msgid "Could not unfavorite"
+msgstr ""
+
+#: lib/pleroma/web/common_api/common_api.ex:463
+#, elixir-format
+msgid "Could not unpin"
+msgstr ""
+
+#: lib/pleroma/web/common_api/common_api.ex:216
+#, elixir-format
+msgid "Could not unrepeat"
+msgstr ""
+
+#: lib/pleroma/web/common_api/common_api.ex:512
+#: lib/pleroma/web/common_api/common_api.ex:521
+#, elixir-format
+msgid "Could not update state"
+msgstr ""
+
+#: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:207
+#, elixir-format
+msgid "Error."
+msgstr ""
+
+#: lib/pleroma/web/twitter_api/twitter_api.ex:106
+#, elixir-format
+msgid "Invalid CAPTCHA"
+msgstr "無效的驗證碼"
+
+#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:116
+#: lib/pleroma/web/oauth/oauth_controller.ex:568
+#, elixir-format
+msgid "Invalid credentials"
+msgstr ""
+
+#: lib/pleroma/plugs/ensure_authenticated_plug.ex:38
+#, elixir-format
+msgid "Invalid credentials."
+msgstr ""
+
+#: lib/pleroma/web/common_api/common_api.ex:355
+#, elixir-format
+msgid "Invalid indices"
+msgstr ""
+
+#: lib/pleroma/web/admin_api/controllers/fallback_controller.ex:29
+#, elixir-format
+msgid "Invalid parameters"
+msgstr ""
+
+#: lib/pleroma/web/common_api/utils.ex:414
+#, elixir-format
+msgid "Invalid password."
+msgstr ""
+
+#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:220
+#, elixir-format
+msgid "Invalid request"
+msgstr ""
+
+#: lib/pleroma/web/twitter_api/twitter_api.ex:109
+#, elixir-format
+msgid "Kocaptcha service unavailable"
+msgstr ""
+
+#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:112
+#, elixir-format
+msgid "Missing parameters"
+msgstr ""
+
+#: lib/pleroma/web/common_api/utils.ex:547
+#, elixir-format
+msgid "No such conversation"
+msgstr ""
+
+#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:388
+#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:414 lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:456
+#, elixir-format
+msgid "No such permission_group"
+msgstr ""
+
+#: lib/pleroma/plugs/uploaded_media.ex:84
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:486 lib/pleroma/web/admin_api/controllers/fallback_controller.ex:11
+#: lib/pleroma/web/feed/user_controller.ex:71 lib/pleroma/web/ostatus/ostatus_controller.ex:143
+#, elixir-format
+msgid "Not found"
+msgstr ""
+
+#: lib/pleroma/web/common_api/common_api.ex:331
+#, elixir-format
+msgid "Poll's author can't vote"
+msgstr ""
+
+#: lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:20
+#: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:37 lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:49
+#: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:50 lib/pleroma/web/mastodon_api/controllers/status_controller.ex:306
+#: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:71
+#, elixir-format
+msgid "Record not found"
+msgstr ""
+
+#: lib/pleroma/web/admin_api/controllers/fallback_controller.ex:35
+#: lib/pleroma/web/feed/user_controller.ex:77 lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:36
+#: lib/pleroma/web/ostatus/ostatus_controller.ex:149
+#, elixir-format
+msgid "Something went wrong"
+msgstr ""
+
+#: lib/pleroma/web/common_api/activity_draft.ex:107
+#, elixir-format
+msgid "The message visibility must be direct"
+msgstr ""
+
+#: lib/pleroma/web/common_api/utils.ex:573
+#, elixir-format
+msgid "The status is over the character limit"
+msgstr ""
+
+#: lib/pleroma/plugs/ensure_public_or_authenticated_plug.ex:31
+#, elixir-format
+msgid "This resource requires authentication."
+msgstr ""
+
+#: lib/pleroma/plugs/rate_limiter/rate_limiter.ex:206
+#, elixir-format
+msgid "Throttled"
+msgstr ""
+
+#: lib/pleroma/web/common_api/common_api.ex:356
+#, elixir-format
+msgid "Too many choices"
+msgstr ""
+
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:443
+#, elixir-format
+msgid "Unhandled activity type"
+msgstr ""
+
+#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:485
+#, elixir-format
+msgid "You can't revoke your own admin status."
+msgstr ""
+
+#: lib/pleroma/web/oauth/oauth_controller.ex:221
+#: lib/pleroma/web/oauth/oauth_controller.ex:308
+#, elixir-format
+msgid "Your account is currently disabled"
+msgstr ""
+
+#: lib/pleroma/web/oauth/oauth_controller.ex:183
+#: lib/pleroma/web/oauth/oauth_controller.ex:331
+#, elixir-format
+msgid "Your login is missing a confirmed e-mail address"
+msgstr ""
+
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:390
+#, elixir-format
+msgid "can't read inbox of %{nickname} as %{as_nickname}"
+msgstr ""
+
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:473
+#, elixir-format
+msgid "can't update outbox of %{nickname} as %{as_nickname}"
+msgstr ""
+
+#: lib/pleroma/web/common_api/common_api.ex:471
+#, elixir-format
+msgid "conversation is already muted"
+msgstr ""
+
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:314
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:492
+#, elixir-format
+msgid "error"
+msgstr ""
+
+#: lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex:32
+#, elixir-format
+msgid "mascots can only be images"
+msgstr ""
+
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:62
+#, elixir-format
+msgid "not found"
+msgstr ""
+
+#: lib/pleroma/web/oauth/oauth_controller.ex:394
+#, elixir-format
+msgid "Bad OAuth request."
+msgstr ""
+
+#: lib/pleroma/web/twitter_api/twitter_api.ex:115
+#, elixir-format
+msgid "CAPTCHA already used"
+msgstr ""
+
+#: lib/pleroma/web/twitter_api/twitter_api.ex:112
+#, elixir-format
+msgid "CAPTCHA expired"
+msgstr ""
+
+#: lib/pleroma/plugs/uploaded_media.ex:57
+#, elixir-format
+msgid "Failed"
+msgstr ""
+
+#: lib/pleroma/web/oauth/oauth_controller.ex:410
+#, elixir-format
+msgid "Failed to authenticate: %{message}."
+msgstr ""
+
+#: lib/pleroma/web/oauth/oauth_controller.ex:441
+#, elixir-format
+msgid "Failed to set up user account."
+msgstr ""
+
+#: lib/pleroma/plugs/oauth_scopes_plug.ex:38
+#, elixir-format
+msgid "Insufficient permissions: %{permissions}."
+msgstr ""
+
+#: lib/pleroma/plugs/uploaded_media.ex:104
+#, elixir-format
+msgid "Internal Error"
+msgstr ""
+
+#: lib/pleroma/web/oauth/fallback_controller.ex:22
+#: lib/pleroma/web/oauth/fallback_controller.ex:29
+#, elixir-format
+msgid "Invalid Username/Password"
+msgstr ""
+
+#: lib/pleroma/web/twitter_api/twitter_api.ex:118
+#, elixir-format
+msgid "Invalid answer data"
+msgstr ""
+
+#: lib/pleroma/web/nodeinfo/nodeinfo_controller.ex:33
+#, elixir-format
+msgid "Nodeinfo schema version not handled"
+msgstr ""
+
+#: lib/pleroma/web/oauth/oauth_controller.ex:172
+#, elixir-format
+msgid "This action is outside the authorized scopes"
+msgstr ""
+
+#: lib/pleroma/web/oauth/fallback_controller.ex:14
+#, elixir-format
+msgid "Unknown error, please check the details and try again."
+msgstr ""
+
+#: lib/pleroma/web/oauth/oauth_controller.ex:119
+#: lib/pleroma/web/oauth/oauth_controller.ex:158
+#, elixir-format
+msgid "Unlisted redirect_uri."
+msgstr ""
+
+#: lib/pleroma/web/oauth/oauth_controller.ex:390
+#, elixir-format
+msgid "Unsupported OAuth provider: %{provider}."
+msgstr ""
+
+#: lib/pleroma/uploaders/uploader.ex:72
+#, elixir-format
+msgid "Uploader callback timeout"
+msgstr ""
+
+#: lib/pleroma/web/uploader_controller.ex:23
+#, elixir-format
+msgid "bad request"
+msgstr ""
+
+#: lib/pleroma/web/twitter_api/twitter_api.ex:103
+#, elixir-format
+msgid "CAPTCHA Error"
+msgstr ""
+
+#: lib/pleroma/web/common_api/common_api.ex:290
+#, elixir-format
+msgid "Could not add reaction emoji"
+msgstr ""
+
+#: lib/pleroma/web/common_api/common_api.ex:301
+#, elixir-format
+msgid "Could not remove reaction emoji"
+msgstr ""
+
+#: lib/pleroma/web/twitter_api/twitter_api.ex:129
+#, elixir-format
+msgid "Invalid CAPTCHA (Missing parameter: %{name})"
+msgstr ""
+
+#: lib/pleroma/web/mastodon_api/controllers/list_controller.ex:92
+#, elixir-format
+msgid "List not found"
+msgstr ""
+
+#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:123
+#, elixir-format
+msgid "Missing parameter: %{name}"
+msgstr ""
+
+#: lib/pleroma/web/oauth/oauth_controller.ex:210
+#: lib/pleroma/web/oauth/oauth_controller.ex:321
+#, elixir-format
+msgid "Password reset is required"
+msgstr ""
+
+#: lib/pleroma/tests/auth_test_controller.ex:9
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:6 lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:6
+#: lib/pleroma/web/admin_api/controllers/config_controller.ex:6 lib/pleroma/web/admin_api/controllers/fallback_controller.ex:6
+#: lib/pleroma/web/admin_api/controllers/invite_controller.ex:6 lib/pleroma/web/admin_api/controllers/media_proxy_cache_controller.ex:6
+#: lib/pleroma/web/admin_api/controllers/oauth_app_controller.ex:6 lib/pleroma/web/admin_api/controllers/relay_controller.ex:6
+#: lib/pleroma/web/admin_api/controllers/report_controller.ex:6 lib/pleroma/web/admin_api/controllers/status_controller.ex:6
+#: lib/pleroma/web/controller_helper.ex:6 lib/pleroma/web/embed_controller.ex:6
+#: lib/pleroma/web/fallback_redirect_controller.ex:6 lib/pleroma/web/feed/tag_controller.ex:6
+#: lib/pleroma/web/feed/user_controller.ex:6 lib/pleroma/web/mailer/subscription_controller.ex:2
+#: lib/pleroma/web/masto_fe_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/account_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/app_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/auth_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/conversation_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/custom_emoji_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/filter_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/instance_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/list_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/marker_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex:14
+#: lib/pleroma/web/mastodon_api/controllers/media_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/notification_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/report_controller.ex:8
+#: lib/pleroma/web/mastodon_api/controllers/scheduled_activity_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/search_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/status_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:7
+#: lib/pleroma/web/mastodon_api/controllers/suggestion_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:6
+#: lib/pleroma/web/media_proxy/media_proxy_controller.ex:6 lib/pleroma/web/mongooseim/mongoose_im_controller.ex:6
+#: lib/pleroma/web/nodeinfo/nodeinfo_controller.ex:6 lib/pleroma/web/oauth/fallback_controller.ex:6
+#: lib/pleroma/web/oauth/mfa_controller.ex:10 lib/pleroma/web/oauth/oauth_controller.ex:6
+#: lib/pleroma/web/ostatus/ostatus_controller.ex:6 lib/pleroma/web/pleroma_api/controllers/account_controller.ex:6
+#: lib/pleroma/web/pleroma_api/controllers/chat_controller.ex:5 lib/pleroma/web/pleroma_api/controllers/conversation_controller.ex:6
+#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:2 lib/pleroma/web/pleroma_api/controllers/emoji_reaction_controller.ex:6
+#: lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex:6 lib/pleroma/web/pleroma_api/controllers/notification_controller.ex:6
+#: lib/pleroma/web/pleroma_api/controllers/scrobble_controller.ex:6
+#: lib/pleroma/web/pleroma_api/controllers/two_factor_authentication_controller.ex:7 lib/pleroma/web/static_fe/static_fe_controller.ex:6
+#: lib/pleroma/web/twitter_api/controllers/password_controller.ex:10 lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex:6
+#: lib/pleroma/web/twitter_api/controllers/util_controller.ex:6 lib/pleroma/web/twitter_api/twitter_api_controller.ex:6
+#: lib/pleroma/web/uploader_controller.ex:6 lib/pleroma/web/web_finger/web_finger_controller.ex:6
+#, elixir-format
+msgid "Security violation: OAuth scopes check was neither handled nor explicitly skipped."
+msgstr ""
+
+#: lib/pleroma/plugs/ensure_authenticated_plug.ex:28
+#, elixir-format
+msgid "Two-factor authentication enabled, you must use a access token."
+msgstr ""
+
+#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:210
+#, elixir-format
+msgid "Unexpected error occurred while adding file to pack."
+msgstr ""
+
+#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:138
+#, elixir-format
+msgid "Unexpected error occurred while creating pack."
+msgstr ""
+
+#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:278
+#, elixir-format
+msgid "Unexpected error occurred while removing file from pack."
+msgstr ""
+
+#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:250
+#, elixir-format
+msgid "Unexpected error occurred while updating file in pack."
+msgstr ""
+
+#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:179
+#, elixir-format
+msgid "Unexpected error occurred while updating pack metadata."
+msgstr ""
+
+#: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:61
+#, elixir-format
+msgid "Web push subscription is disabled on this Pleroma instance"
+msgstr ""
+
+#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:451
+#, elixir-format
+msgid "You can't revoke your own admin/moderator status."
+msgstr ""
+
+#: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:126
+#, elixir-format
+msgid "authorization required for timeline view"
+msgstr ""
+
+#: lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:24
+#, elixir-format
+msgid "Access denied"
+msgstr ""
+
+#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:282
+#, elixir-format
+msgid "This API requires an authenticated user"
+msgstr ""
+
+#: lib/pleroma/plugs/user_is_admin_plug.ex:21
+#, elixir-format
+msgid "User is not an admin."
+msgstr ""
diff --git a/priv/repo/migrations/20200806175913_rename_instance_chat.exs b/priv/repo/migrations/20200806175913_rename_instance_chat.exs
new file mode 100644 (file)
index 0000000..31585ef
--- /dev/null
@@ -0,0 +1,77 @@
+defmodule Pleroma.Repo.Migrations.RenameInstanceChat do
+  use Ecto.Migration
+
+  alias Pleroma.ConfigDB
+
+  @instance_params %{group: :pleroma, key: :instance}
+  @shout_params %{group: :pleroma, key: :shout}
+  @chat_params %{group: :pleroma, key: :chat}
+
+  def up do
+    instance_updated? = maybe_update_instance_key(:up) != :noop
+    chat_updated? = maybe_update_chat_key(:up) != :noop
+
+    case Enum.any?([instance_updated?, chat_updated?]) do
+      true -> :ok
+      false -> :noop
+    end
+  end
+
+  def down do
+    instance_updated? = maybe_update_instance_key(:down) != :noop
+    chat_updated? = maybe_update_chat_key(:down) != :noop
+
+    case Enum.any?([instance_updated?, chat_updated?]) do
+      true -> :ok
+      false -> :noop
+    end
+  end
+
+  # pleroma.instance.chat_limit -> pleroma.shout.limit
+  defp maybe_update_instance_key(:up) do
+    with %ConfigDB{value: values} <- ConfigDB.get_by_params(@instance_params),
+         limit when is_integer(limit) <- values[:chat_limit] do
+      @shout_params |> Map.put(:value, limit: limit) |> ConfigDB.update_or_create()
+      @instance_params |> Map.put(:subkeys, [":chat_limit"]) |> ConfigDB.delete()
+    else
+      _ ->
+        :noop
+    end
+  end
+
+  # pleroma.shout.limit -> pleroma.instance.chat_limit
+  defp maybe_update_instance_key(:down) do
+    with %ConfigDB{value: values} <- ConfigDB.get_by_params(@shout_params),
+         limit when is_integer(limit) <- values[:limit] do
+      @instance_params |> Map.put(:value, chat_limit: limit) |> ConfigDB.update_or_create()
+      @shout_params |> Map.put(:subkeys, [":limit"]) |> ConfigDB.delete()
+    else
+      _ ->
+        :noop
+    end
+  end
+
+  # pleroma.chat.enabled -> pleroma.shout.enabled
+  defp maybe_update_chat_key(:up) do
+    with %ConfigDB{value: values} <- ConfigDB.get_by_params(@chat_params),
+         enabled? when is_boolean(enabled?) <- values[:enabled] do
+      @shout_params |> Map.put(:value, enabled: enabled?) |> ConfigDB.update_or_create()
+      @chat_params |> Map.put(:subkeys, [":enabled"]) |> ConfigDB.delete()
+    else
+      _ ->
+        :noop
+    end
+  end
+
+  # pleroma.shout.enabled -> pleroma.chat.enabled
+  defp maybe_update_chat_key(:down) do
+    with %ConfigDB{value: values} <- ConfigDB.get_by_params(@shout_params),
+         enabled? when is_boolean(enabled?) <- values[:enabled] do
+      @chat_params |> Map.put(:value, enabled: enabled?) |> ConfigDB.update_or_create()
+      @shout_params |> Map.put(:subkeys, [":enabled"]) |> ConfigDB.delete()
+    else
+      _ ->
+        :noop
+    end
+  end
+end
diff --git a/priv/repo/migrations/20210202110641_add_pinned_objects_to_users.exs b/priv/repo/migrations/20210202110641_add_pinned_objects_to_users.exs
new file mode 100644 (file)
index 0000000..6445272
--- /dev/null
@@ -0,0 +1,9 @@
+defmodule Pleroma.Repo.Migrations.AddPinnedObjectsToUsers do
+  use Ecto.Migration
+
+  def change do
+    alter table(:users) do
+      add(:pinned_objects, :map)
+    end
+  end
+end
diff --git a/priv/repo/migrations/20210203141144_add_featured_address_to_users.exs b/priv/repo/migrations/20210203141144_add_featured_address_to_users.exs
new file mode 100644 (file)
index 0000000..0f6a216
--- /dev/null
@@ -0,0 +1,23 @@
+defmodule Pleroma.Repo.Migrations.AddFeaturedAddressToUsers do
+  use Ecto.Migration
+
+  def up do
+    alter table(:users) do
+      add(:featured_address, :string)
+    end
+
+    create(index(:users, [:featured_address]))
+
+    execute("""
+
+    update users set featured_address = concat(ap_id, '/collections/featured') where local = true and featured_address is null;
+
+    """)
+  end
+
+  def down do
+    alter table(:users) do
+      remove(:featured_address)
+    end
+  end
+end
diff --git a/priv/repo/migrations/20210205145000_move_pinned_activities_into_pinned_objects.exs b/priv/repo/migrations/20210205145000_move_pinned_activities_into_pinned_objects.exs
new file mode 100644 (file)
index 0000000..9aee545
--- /dev/null
@@ -0,0 +1,28 @@
+defmodule Pleroma.Repo.Migrations.MovePinnedActivitiesIntoPinnedObjects do
+  use Ecto.Migration
+
+  import Ecto.Query
+
+  alias Pleroma.Repo
+  alias Pleroma.User
+
+  def up do
+    from(u in User)
+    |> select([u], {u.id, fragment("?.pinned_activities", u)})
+    |> Repo.stream()
+    |> Stream.each(fn {user_id, pinned_activities_ids} ->
+      pinned_activities = Pleroma.Activity.all_by_ids_with_object(pinned_activities_ids)
+
+      pins =
+        Map.new(pinned_activities, fn %{object: %{data: %{"id" => object_id}}} ->
+          {object_id, NaiveDateTime.utc_now()}
+        end)
+
+      from(u in User, where: u.id == ^user_id)
+      |> Repo.update_all(set: [pinned_objects: pins])
+    end)
+    |> Stream.run()
+  end
+
+  def down, do: :noop
+end
diff --git a/priv/repo/migrations/20210206045221_remove_pinned_activities_from_users.exs b/priv/repo/migrations/20210206045221_remove_pinned_activities_from_users.exs
new file mode 100644 (file)
index 0000000..a3ee93f
--- /dev/null
@@ -0,0 +1,15 @@
+defmodule Pleroma.Repo.Migrations.RemovePinnedActivitiesFromUsers do
+  use Ecto.Migration
+
+  def up do
+    alter table(:users) do
+      remove(:pinned_activities)
+    end
+  end
+
+  def down do
+    alter table(:users) do
+      add(:pinned_activities, {:array, :string}, default: [])
+    end
+  end
+end
index 7b06994de1dce831d5a3d9584cf53d84463b001b..4694a92a53b657f501c6574f7e33fc0f20064daa 100644 (file)
@@ -39,6 +39,7 @@ defmodule Pleroma.HTML.Scrubber.Default do
   Meta.allow_tag_with_these_attributes(:code, [])
   Meta.allow_tag_with_these_attributes(:del, [])
   Meta.allow_tag_with_these_attributes(:em, [])
+  Meta.allow_tag_with_these_attributes(:hr, [])
   Meta.allow_tag_with_these_attributes(:i, [])
   Meta.allow_tag_with_these_attributes(:li, [])
   Meta.allow_tag_with_these_attributes(:ol, [])
@@ -58,6 +59,8 @@ defmodule Pleroma.HTML.Scrubber.Default do
   Meta.allow_tag_with_this_attribute_values(:span, "class", ["h-card"])
   Meta.allow_tag_with_these_attributes(:span, [])
 
+  Meta.allow_tag_with_this_attribute_values(:code, "class", ["inline"])
+
   @allow_inline_images Pleroma.Config.get([:markup, :allow_inline_images])
 
   if @allow_inline_images do
index c985e072b1f37083f717cf181b0444d8e956dc0c..e592081bc62bae459b56b53fbcaeeee0c9a02d29 100644 (file)
@@ -3,6 +3,7 @@
   "type": "Create",
   "object": {
     "type": "Note",
+    "to": ["https://www.w3.org/ns/activitystreams#Public"],
     "content": "It's a note"
   },
   "to": ["https://www.w3.org/ns/activitystreams#Public"]
diff --git a/test/fixtures/config/temp.exported_from_db.secret.exs b/test/fixtures/config/temp.exported_from_db.secret.exs
new file mode 100644 (file)
index 0000000..dda5d0f
--- /dev/null
@@ -0,0 +1,5 @@
+import Config
+
+config :pleroma, exported_config_merged: true
+
+config :pleroma, :first_setting, key: "new value"
index 4b3af39ecec6bcca251df26a488a616017245b55..9c5c88d98890f2745d0b9afd975979b51b21333d 100644 (file)
@@ -2,7 +2,7 @@
 # Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
 # SPDX-License-Identifier: AGPL-3.0-only
 
-use Mix.Config
+import Config
 
 config :pleroma, :first_setting, key: "value", key2: [Pleroma.Repo]
 
diff --git a/test/fixtures/mastodon/collections/featured.json b/test/fixtures/mastodon/collections/featured.json
new file mode 100644 (file)
index 0000000..56f8f56
--- /dev/null
@@ -0,0 +1,39 @@
+{
+  "@context": [
+    "https://www.w3.org/ns/activitystreams",
+    "https://{{domain}}/schemas/litepub-0.1.jsonld",
+    {
+      "@language": "und"
+    }
+  ],
+  "id": "https://{{domain}}/users/{{nickname}}/collections/featured",
+  "orderedItems": [
+    {
+      "@context": [
+        "https://www.w3.org/ns/activitystreams",
+        "https://{{domain}}/schemas/litepub-0.1.jsonld",
+        {
+          "@language": "und"
+        }
+      ],
+      "actor": "https://{{domain}}/users/{{nickname}}",
+      "attachment": [],
+      "attributedTo": "https://{{domain}}/users/{{nickname}}",
+      "cc": [
+        "https://{{domain}}/users/{{nickname}}/followers"
+      ],
+      "content": "",
+      "id": "https://{{domain}}/objects/{{object_id}}",
+      "published": "2021-02-12T15:13:43.915429Z",
+      "sensitive": false,
+      "source": "",
+      "summary": "",
+      "tag": [],
+      "to": [
+        "https://www.w3.org/ns/activitystreams#Public"
+      ],
+      "type": "Note"
+    }
+  ],
+  "type": "OrderedCollection"
+}
diff --git a/test/fixtures/statuses/masto-note.json b/test/fixtures/statuses/masto-note.json
new file mode 100644 (file)
index 0000000..6b96de4
--- /dev/null
@@ -0,0 +1,47 @@
+{
+  "@context": [
+    "https://www.w3.org/ns/activitystreams",
+    {
+      "ostatus": "http://ostatus.org#",
+      "atomUri": "ostatus:atomUri",
+      "inReplyToAtomUri": "ostatus:inReplyToAtomUri",
+      "conversation": "ostatus:conversation",
+      "sensitive": "as:sensitive",
+      "toot": "http://joinmastodon.org/ns#",
+      "votersCount": "toot:votersCount"
+    }
+  ],
+  "id": "https://example.com/users/{{nickname}}/statuses/{{status_id}}",
+  "type": "Note",
+  "summary": null,
+  "inReplyTo": null,
+  "published": "2021-02-24T12:40:49Z",
+  "url": "https://example.com/@{{nickname}}/{{status_id}}",
+  "attributedTo": "https://example.com/users/{{nickname}}",
+  "to": [
+    "https://www.w3.org/ns/activitystreams#Public"
+  ],
+  "cc": [
+    "https://example.com/users/{{nickname}}/followers"
+  ],
+  "sensitive": false,
+  "atomUri": "https://example.com/users/{{nickname}}/statuses/{{status_id}}",
+  "inReplyToAtomUri": null,
+  "conversation": "tag:example.com,2021-02-24:objectId=15:objectType=Conversation",
+  "content": "<p></p>",
+  "contentMap": {
+    "en": "<p></p>"
+  },
+  "attachment": [],
+  "tag": [],
+  "replies": {
+    "id": "https://example.com/users/{{nickname}}/statuses/{{status_id}}/replies",
+    "type": "Collection",
+    "first": {
+      "type": "CollectionPage",
+      "next": "https://example.com/users/{{nickname}}/statuses/{{status_id}}/replies?only_other_accounts=true&page=true",
+      "partOf": "https://example.com/users/{{nickname}}/statuses/{{status_id}}/replies",
+      "items": []
+    }
+  }
+}
diff --git a/test/fixtures/statuses/note.json b/test/fixtures/statuses/note.json
new file mode 100644 (file)
index 0000000..41735cb
--- /dev/null
@@ -0,0 +1,27 @@
+{
+  "@context": [
+    "https://www.w3.org/ns/activitystreams",
+    "https://example.com/schemas/litepub-0.1.jsonld",
+    {
+      "@language": "und"
+    }
+  ],
+  "actor": "https://example.com/users/{{nickname}}",
+  "attachment": [],
+  "attributedTo": "https://example.com/users/{{nickname}}",
+  "cc": [
+    "https://example.com/users/{{nickname}}/followers"
+  ],
+  "content": "Content",
+  "context": "https://example.com/contexts/e4b180e1-7403-477f-aeb4-de57e7a3fe7f",
+  "conversation": "https://example.com/contexts/e4b180e1-7403-477f-aeb4-de57e7a3fe7f",
+  "id": "https://example.com/objects/{{object_id}}",
+  "published": "2019-12-15T22:00:05.279583Z",
+  "sensitive": false,
+  "summary": "",
+  "tag": [],
+  "to": [
+    "https://www.w3.org/ns/activitystreams#Public"
+  ],
+  "type": "Note"
+}
diff --git a/test/fixtures/users_mock/masto_featured.json b/test/fixtures/users_mock/masto_featured.json
new file mode 100644 (file)
index 0000000..646a343
--- /dev/null
@@ -0,0 +1,18 @@
+{
+  "@context": [
+    "https://www.w3.org/ns/activitystreams",
+    {
+      "ostatus": "http://ostatus.org#",
+      "atomUri": "ostatus:atomUri",
+      "inReplyToAtomUri": "ostatus:inReplyToAtomUri",
+      "conversation": "ostatus:conversation",
+      "sensitive": "as:sensitive",
+      "toot": "http://joinmastodon.org/ns#",
+      "votersCount": "toot:votersCount"
+    }
+  ],
+  "id": "https://{{domain}}/users/{{nickname}}/collections/featured",
+  "type": "OrderedCollection",
+  "totalItems": 0,
+  "orderedItems": []
+}
diff --git a/test/fixtures/users_mock/user.json b/test/fixtures/users_mock/user.json
new file mode 100644 (file)
index 0000000..c722a11
--- /dev/null
@@ -0,0 +1,42 @@
+{
+  "@context": [
+    "https://www.w3.org/ns/activitystreams",
+    "https://example.com/schemas/litepub-0.1.jsonld",
+    {
+      "@language": "und"
+    }
+  ],
+  "attachment": [],
+  "endpoints": {
+    "oauthAuthorizationEndpoint": "https://example.com/oauth/authorize",
+    "oauthRegistrationEndpoint": "https://example.com/api/v1/apps",
+    "oauthTokenEndpoint": "https://example.com/oauth/token",
+    "sharedInbox": "https://example.com/inbox"
+  },
+  "followers": "https://example.com/users/{{nickname}}/followers",
+  "following": "https://example.com/users/{{nickname}}/following",
+  "icon": {
+    "type": "Image",
+    "url": "https://example.com/media/4e914f5b84e4a259a3f6c2d2edc9ab642f2ab05f3e3d9c52c81fc2d984b3d51e.jpg"
+  },
+  "id": "https://example.com/users/{{nickname}}",
+  "image": {
+    "type": "Image",
+    "url": "https://example.com/media/f739efddefeee49c6e67e947c4811fdc911785c16ae43da4c3684051fbf8da6a.jpg?name=f739efddefeee49c6e67e947c4811fdc911785c16ae43da4c3684051fbf8da6a.jpg"
+  },
+  "inbox": "https://example.com/users/{{nickname}}/inbox",
+  "manuallyApprovesFollowers": false,
+  "name": "{{nickname}}",
+  "outbox": "https://example.com/users/{{nickname}}/outbox",
+  "preferredUsername": "{{nickname}}",
+  "publicKey": {
+    "id": "https://example.com/users/{{nickname}}#main-key",
+    "owner": "https://example.com/users/{{nickname}}",
+    "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5DLtwGXNZElJyxFGfcVc\nXANhaMadj/iYYQwZjOJTV9QsbtiNBeIK54PJrYuU0/0YIdrvS1iqheX5IwXRhcwa\nhm3ZyLz7XeN9st7FBni4BmZMBtMpxAuYuu5p/jbWy13qAiYOhPreCx0wrWgm/lBD\n9mkgaxIxPooBE0S4ZWEJIDIV1Vft3AWcRUyWW1vIBK0uZzs6GYshbQZB952S0yo4\nFzI1hABGHncH8UvuFauh4EZ8tY7/X5I0pGRnDOcRN1dAht5w5yTA+6r5kebiFQjP\nIzN/eCO/a9Flrj9YGW7HDNtjSOH0A31PLRGlJtJO3yK57dnf5ppyCZGfL4emShQo\ncQIDAQAB\n-----END PUBLIC KEY-----\n\n"
+  },
+  "featured": "https://example.com/users/{{nickname}}/collections/featured",
+  "summary": "your friendly neighborhood pleroma developer<br>I like cute things and distributed systems, and really hate delete and redrafts",
+  "tag": [],
+  "type": "Person",
+  "url": "https://example.com/users/{{nickname}}"
+}
index 3ed1e94b8ad4f0f9b27b10bd7ab259ee04a28875..2b8252db7e926cedc0b41e54fbf7144bcb59871e 100644 (file)
@@ -188,15 +188,8 @@ defmodule Mix.Tasks.Pleroma.ConfigTest do
       assert File.exists?(temp_file)
       {:ok, file} = File.read(temp_file)
 
-      header =
-        if Code.ensure_loaded?(Config.Reader) do
-          "import Config"
-        else
-          "use Mix.Config"
-        end
-
       assert file ==
-               "#{header}\n\nconfig :pleroma, :instance,\n  name: \"Pleroma\",\n  email: \"example@example.com\",\n  notify_email: \"noreply@example.com\",\n  description: \"A Pleroma instance, an alternative fediverse server\",\n  limit: 5000,\n  chat_limit: 5000,\n  remote_limit: 100_000,\n  upload_limit: 16_000_000,\n  avatar_upload_limit: 2_000_000,\n  background_upload_limit: 4_000_000,\n  banner_upload_limit: 4_000_000,\n  poll_limits: %{\n    max_expiration: 31_536_000,\n    max_option_chars: 200,\n    max_options: 20,\n    min_expiration: 0\n  },\n  registrations_open: true,\n  federating: true,\n  federation_incoming_replies_max_depth: 100,\n  federation_reachability_timeout_days: 7,\n  federation_publisher_modules: [Pleroma.Web.ActivityPub.Publisher],\n  allow_relay: true,\n  public: true,\n  quarantined_instances: [],\n  managed_config: true,\n  static_dir: \"instance/static/\",\n  allowed_post_formats: [\"text/plain\", \"text/html\", \"text/markdown\", \"text/bbcode\"],\n  autofollowed_nicknames: [],\n  max_pinned_statuses: 1,\n  attachment_links: false,\n  max_report_comment_size: 1000,\n  safe_dm_mentions: false,\n  healthcheck: false,\n  remote_post_retention_days: 90,\n  skip_thread_containment: true,\n  limit_to_local_content: :unauthenticated,\n  user_bio_length: 5000,\n  user_name_length: 100,\n  max_account_fields: 10,\n  max_remote_account_fields: 20,\n  account_field_name_length: 512,\n  account_field_value_length: 2048,\n  external_user_synchronization: true,\n  extended_nickname_format: true,\n  multi_factor_authentication: [\n    totp: [digits: 6, period: 30],\n    backup_codes: [number: 2, length: 6]\n  ]\n"
+               "import Config\n\nconfig :pleroma, :instance,\n  name: \"Pleroma\",\n  email: \"example@example.com\",\n  notify_email: \"noreply@example.com\",\n  description: \"A Pleroma instance, an alternative fediverse server\",\n  limit: 5000,\n  chat_limit: 5000,\n  remote_limit: 100_000,\n  upload_limit: 16_000_000,\n  avatar_upload_limit: 2_000_000,\n  background_upload_limit: 4_000_000,\n  banner_upload_limit: 4_000_000,\n  poll_limits: %{\n    max_expiration: 31_536_000,\n    max_option_chars: 200,\n    max_options: 20,\n    min_expiration: 0\n  },\n  registrations_open: true,\n  federating: true,\n  federation_incoming_replies_max_depth: 100,\n  federation_reachability_timeout_days: 7,\n  federation_publisher_modules: [Pleroma.Web.ActivityPub.Publisher],\n  allow_relay: true,\n  public: true,\n  quarantined_instances: [],\n  managed_config: true,\n  static_dir: \"instance/static/\",\n  allowed_post_formats: [\"text/plain\", \"text/html\", \"text/markdown\", \"text/bbcode\"],\n  autofollowed_nicknames: [],\n  max_pinned_statuses: 1,\n  attachment_links: false,\n  max_report_comment_size: 1000,\n  safe_dm_mentions: false,\n  healthcheck: false,\n  remote_post_retention_days: 90,\n  skip_thread_containment: true,\n  limit_to_local_content: :unauthenticated,\n  user_bio_length: 5000,\n  user_name_length: 100,\n  max_account_fields: 10,\n  max_remote_account_fields: 20,\n  account_field_name_length: 512,\n  account_field_value_length: 2048,\n  external_user_synchronization: true,\n  extended_nickname_format: true,\n  multi_factor_authentication: [\n    totp: [digits: 6, period: 30],\n    backup_codes: [number: 2, length: 6]\n  ]\n"
     end
   end
 
index 390a063447cadb040bbf47095c7f42983798e3ef..4f9144f91bb0d468a383c7f3f287e748475bb15f 100644 (file)
@@ -123,7 +123,8 @@ defmodule Pleroma.ActivityTest do
           "type" => "Note",
           "content" => "find me!",
           "id" => "http://mastodon.example.org/users/admin/objects/1",
-          "attributedTo" => "http://mastodon.example.org/users/admin"
+          "attributedTo" => "http://mastodon.example.org/users/admin",
+          "to" => ["https://www.w3.org/ns/activitystreams#Public"]
         },
         "to" => ["https://www.w3.org/ns/activitystreams#Public"]
       }
@@ -132,6 +133,7 @@ defmodule Pleroma.ActivityTest do
       {:ok, japanese_activity} = Pleroma.Web.CommonAPI.post(user, %{status: "更新情報"})
       {:ok, job} = Pleroma.Web.Federator.incoming_ap_doc(params)
       {:ok, remote_activity} = ObanHelpers.perform(job)
+      remote_activity = Activity.get_by_id_with_object(remote_activity.id)
 
       %{
         japanese_activity: japanese_activity,
@@ -254,4 +256,26 @@ defmodule Pleroma.ActivityTest do
 
     assert %{id: ^id} = Activity.get_by_object_ap_id_with_object(obj_id)
   end
+
+  test "add_by_params_query/3" do
+    user = insert(:user)
+
+    note = insert(:note_activity, user: user)
+
+    insert(:add_activity, user: user, note: note)
+    insert(:add_activity, user: user, note: note)
+    insert(:add_activity, user: user)
+
+    assert Repo.aggregate(Activity, :count, :id) == 4
+
+    add_query =
+      Activity.add_by_params_query(note.data["object"], user.ap_id, user.featured_address)
+
+    assert Repo.aggregate(add_query, :count, :id) == 2
+
+    Repo.delete_all(add_query)
+    assert Repo.aggregate(add_query, :count, :id) == 0
+
+    assert Repo.aggregate(Activity, :count, :id) == 2
+  end
 end
index 683ac8c96c175642c94de85b8a3f691d592e6094..a54c379681d4b9c9763342142cd4974feb0b89c0 100644 (file)
@@ -35,13 +35,13 @@ defmodule Pleroma.ApplicationRequirementsTest do
     setup do: clear_config([:welcome])
     setup do: clear_config([Pleroma.Emails.Mailer])
 
-    test "raises if welcome email enabled but mail disabled" do
+    test "warns if welcome email enabled but mail disabled" do
       clear_config([:welcome, :email, :enabled], true)
       clear_config([Pleroma.Emails.Mailer, :enabled], false)
 
-      assert_raise Pleroma.ApplicationRequirements.VerifyError, "The mail disabled.", fn ->
-        capture_log(&Pleroma.ApplicationRequirements.verify!/0)
-      end
+      assert capture_log(fn ->
+               assert Pleroma.ApplicationRequirements.verify!() == :ok
+             end) =~ "Welcome emails will NOT be sent"
     end
   end
 
@@ -57,15 +57,13 @@ defmodule Pleroma.ApplicationRequirementsTest do
 
     setup do: clear_config([:instance, :account_activation_required])
 
-    test "raises if account confirmation is required but mailer isn't enable" do
+    test "warns if account confirmation is required but mailer isn't enabled" do
       clear_config([:instance, :account_activation_required], true)
       clear_config([Pleroma.Emails.Mailer, :enabled], false)
 
-      assert_raise Pleroma.ApplicationRequirements.VerifyError,
-                   "Account activation enabled, but Mailer is disabled. Cannot send confirmation emails.",
-                   fn ->
-                     capture_log(&Pleroma.ApplicationRequirements.verify!/0)
-                   end
+      assert capture_log(fn ->
+               assert Pleroma.ApplicationRequirements.verify!() == :ok
+             end) =~ "Users will NOT be able to confirm their accounts"
     end
 
     test "doesn't do anything if account confirmation is disabled" do
index 15f4982ea6dc04f873c5179ddbd3dda498fd0fc9..ccf86634f0b593f5de77d1be6420f54afcb6f66d 100644 (file)
@@ -146,4 +146,14 @@ defmodule Pleroma.Config.DeprecationWarningsTest do
                "Your config is using old setting name `timeout` instead of `recv_timeout` in pool settings"
     end
   end
+
+  test "check_old_chat_shoutbox/0" do
+    clear_config([:instance, :chat_limit], 1_000)
+    clear_config([:chat, :enabled], true)
+
+    assert capture_log(fn ->
+             DeprecationWarnings.check_old_chat_shoutbox()
+           end) =~
+             "Your config is using the old namespace for the Shoutbox configuration."
+  end
 end
diff --git a/test/pleroma/config/release_runtime_provider_test.exs b/test/pleroma/config/release_runtime_provider_test.exs
new file mode 100644 (file)
index 0000000..6578d32
--- /dev/null
@@ -0,0 +1,45 @@
+defmodule Pleroma.Config.ReleaseRuntimeProviderTest do
+  use ExUnit.Case, async: true
+
+  alias Pleroma.Config.ReleaseRuntimeProvider
+
+  describe "load/2" do
+    test "loads release defaults config and warns about non-existent runtime config" do
+      ExUnit.CaptureIO.capture_io(fn ->
+        merged = ReleaseRuntimeProvider.load([], [])
+        assert merged == Pleroma.Config.Holder.release_defaults()
+      end) =~
+        "!!! Config path is not declared! Please ensure it exists and that PLEROMA_CONFIG_PATH is unset or points to an existing file"
+    end
+
+    test "merged runtime config" do
+      merged =
+        ReleaseRuntimeProvider.load([], config_path: "test/fixtures/config/temp.secret.exs")
+
+      assert merged[:pleroma][:first_setting] == [key: "value", key2: [Pleroma.Repo]]
+      assert merged[:pleroma][:second_setting] == [key: "value2", key2: ["Activity"]]
+    end
+
+    test "merged exported config" do
+      ExUnit.CaptureIO.capture_io(fn ->
+        merged =
+          ReleaseRuntimeProvider.load([],
+            exported_config_path: "test/fixtures/config/temp.exported_from_db.secret.exs"
+          )
+
+        assert merged[:pleroma][:exported_config_merged]
+      end) =~
+        "!!! Config path is not declared! Please ensure it exists and that PLEROMA_CONFIG_PATH is unset or points to an existing file"
+    end
+
+    test "runtime config is merged with exported config" do
+      merged =
+        ReleaseRuntimeProvider.load([],
+          config_path: "test/fixtures/config/temp.secret.exs",
+          exported_config_path: "test/fixtures/config/temp.exported_from_db.secret.exs"
+        )
+
+      assert merged[:pleroma][:first_setting] == [key2: [Pleroma.Repo], key: "new value"]
+    end
+  end
+end
index 8ae5d3b8142e5cb3a6cbb2461b1412c2012c854d..7d51fd84ce50fa4a3f0f6cac0a4480d9b96ed753 100644 (file)
@@ -93,8 +93,8 @@ defmodule Pleroma.Config.TransferTaskTest do
     end
 
     test "on reboot time key" do
-      clear_config(:chat)
-      insert(:config, key: :chat, value: [enabled: false])
+      clear_config(:shout)
+      insert(:config, key: :shout, value: [enabled: false])
       assert capture_log(fn -> TransferTask.start_link([]) end) =~ "pleroma restarted"
     end
 
@@ -105,10 +105,10 @@ defmodule Pleroma.Config.TransferTaskTest do
     end
 
     test "don't restart pleroma on reboot time key and subkey if there is false flag" do
-      clear_config(:chat)
+      clear_config(:shout)
       clear_config(Pleroma.Captcha)
 
-      insert(:config, key: :chat, value: [enabled: false])
+      insert(:config, key: :shout, value: [enabled: false])
       insert(:config, key: Pleroma.Captcha, value: [seconds_valid: 60])
 
       refute String.contains?(
diff --git a/test/pleroma/earmark_renderer_test.exs b/test/pleroma/earmark_renderer_test.exs
deleted file mode 100644 (file)
index 776bc49..0000000
+++ /dev/null
@@ -1,79 +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.EarmarkRendererTest do
-  use Pleroma.DataCase, async: true
-
-  test "Paragraph" do
-    code = ~s[Hello\n\nWorld!]
-    result = Earmark.as_html!(code, %Earmark.Options{renderer: Pleroma.EarmarkRenderer})
-    assert result == "<p>Hello</p><p>World!</p>"
-  end
-
-  test "raw HTML" do
-    code = ~s[<a href="http://example.org/">OwO</a><!-- what's this?-->]
-    result = Earmark.as_html!(code, %Earmark.Options{renderer: Pleroma.EarmarkRenderer})
-    assert result == "<p>#{code}</p>"
-  end
-
-  test "rulers" do
-    code = ~s[before\n\n-----\n\nafter]
-    result = Earmark.as_html!(code, %Earmark.Options{renderer: Pleroma.EarmarkRenderer})
-    assert result == "<p>before</p><hr /><p>after</p>"
-  end
-
-  test "headings" do
-    code = ~s[# h1\n## h2\n### h3\n]
-    result = Earmark.as_html!(code, %Earmark.Options{renderer: Pleroma.EarmarkRenderer})
-    assert result == ~s[<h1>h1</h1><h2>h2</h2><h3>h3</h3>]
-  end
-
-  test "blockquote" do
-    code = ~s[> whoms't are you quoting?]
-    result = Earmark.as_html!(code, %Earmark.Options{renderer: Pleroma.EarmarkRenderer})
-    assert result == "<blockquote><p>whoms’t are you quoting?</p></blockquote>"
-  end
-
-  test "code" do
-    code = ~s[`mix`]
-    result = Earmark.as_html!(code, %Earmark.Options{renderer: Pleroma.EarmarkRenderer})
-    assert result == ~s[<p><code class="inline">mix</code></p>]
-
-    code = ~s[``mix``]
-    result = Earmark.as_html!(code, %Earmark.Options{renderer: Pleroma.EarmarkRenderer})
-    assert result == ~s[<p><code class="inline">mix</code></p>]
-
-    code = ~s[```\nputs "Hello World"\n```]
-    result = Earmark.as_html!(code, %Earmark.Options{renderer: Pleroma.EarmarkRenderer})
-    assert result == ~s[<pre><code class="">puts &quot;Hello World&quot;</code></pre>]
-  end
-
-  test "lists" do
-    code = ~s[- one\n- two\n- three\n- four]
-    result = Earmark.as_html!(code, %Earmark.Options{renderer: Pleroma.EarmarkRenderer})
-    assert result == "<ul><li>one</li><li>two</li><li>three</li><li>four</li></ul>"
-
-    code = ~s[1. one\n2. two\n3. three\n4. four\n]
-    result = Earmark.as_html!(code, %Earmark.Options{renderer: Pleroma.EarmarkRenderer})
-    assert result == "<ol><li>one</li><li>two</li><li>three</li><li>four</li></ol>"
-  end
-
-  test "delegated renderers" do
-    code = ~s[a<br/>b]
-    result = Earmark.as_html!(code, %Earmark.Options{renderer: Pleroma.EarmarkRenderer})
-    assert result == "<p>#{code}</p>"
-
-    code = ~s[*aaaa~*]
-    result = Earmark.as_html!(code, %Earmark.Options{renderer: Pleroma.EarmarkRenderer})
-    assert result == ~s[<p><em>aaaa~</em></p>]
-
-    code = ~s[**aaaa~**]
-    result = Earmark.as_html!(code, %Earmark.Options{renderer: Pleroma.EarmarkRenderer})
-    assert result == ~s[<p><strong>aaaa~</strong></p>]
-
-    # strikethrought
-    code = ~s[<del>aaaa~</del>]
-    result = Earmark.as_html!(code, %Earmark.Options{renderer: Pleroma.EarmarkRenderer})
-    assert result == ~s[<p><del>aaaa~</del></p>]
-  end
-end
index d3a2fd13f9f81843522ff52d17377325033bd133..4cdafa8982fe73e6480c8377079c09ce92bc6bd5 100644 (file)
@@ -6,10 +6,10 @@ defmodule Pleroma.EctoType.ActivityPub.ObjectValidators.RecipientsTest do
   alias Pleroma.EctoType.ActivityPub.ObjectValidators.Recipients
   use Pleroma.DataCase, async: true
 
-  test "it asserts that all elements of the list are object ids" do
+  test "it only keeps elements that are valid object ids" do
     list = ["https://lain.com/users/lain", "invalid"]
 
-    assert :error == Recipients.cast(list)
+    assert {:ok, ["https://lain.com/users/lain"]} == Recipients.cast(list)
   end
 
   test "it works with a list" do
index e9b0c4a8a25c4c004cc293a05efe0238330e7bd1..433beaac113f3e7c3fd87eba42f4931ec1cfa9b3 100644 (file)
@@ -34,24 +34,32 @@ defmodule Pleroma.HTTP.RequestBuilderTest do
 
   describe "add_param/4" do
     test "add file parameter" do
-      %Request{
-        body: %Tesla.Multipart{
-          boundary: _,
-          content_type_params: [],
-          parts: [
-            %Tesla.Multipart.Part{
-              body: %File.Stream{
-                line_or_bytes: 2048,
-                modes: [:raw, :read_ahead, :read, :binary],
-                path: "some-path/filename.png",
-                raw: true
-              },
-              dispositions: [name: "filename.png", filename: "filename.png"],
-              headers: []
-            }
-          ]
-        }
-      } = RequestBuilder.add_param(%Request{}, :file, "filename.png", "some-path/filename.png")
+      assert match?(
+               %Request{
+                 body: %Tesla.Multipart{
+                   boundary: _,
+                   content_type_params: [],
+                   parts: [
+                     %Tesla.Multipart.Part{
+                       body: %File.Stream{
+                         line_or_bytes: 2048,
+                         modes: [:raw, :read_ahead, :binary],
+                         path: "some-path/filename.png",
+                         raw: true
+                       },
+                       dispositions: [name: "filename.png", filename: "filename.png"],
+                       headers: []
+                     }
+                   ]
+                 }
+               },
+               RequestBuilder.add_param(
+                 %Request{},
+                 :file,
+                 "filename.png",
+                 "some-path/filename.png"
+               )
+             )
     end
 
     test "add key to body" do
index abf1b04109255ba42a70031cdb6a9167e93f61c8..85f895f0fab7fa8973822d3429c51522c59063ce 100644 (file)
@@ -624,6 +624,8 @@ defmodule Pleroma.NotificationTest do
         "actor" => user.ap_id,
         "object" => %{
           "type" => "Note",
+          "id" => Pleroma.Web.ActivityPub.Utils.generate_object_id(),
+          "to" => ["https://www.w3.org/ns/activitystreams#Public"],
           "content" => "message with a Mention tag, but no explicit tagging",
           "tag" => [
             %{
@@ -655,6 +657,9 @@ defmodule Pleroma.NotificationTest do
         "actor" => user.ap_id,
         "object" => %{
           "type" => "Note",
+          "id" => Pleroma.Web.ActivityPub.Utils.generate_object_id(),
+          "to" => ["https://www.w3.org/ns/activitystreams#Public"],
+          "cc" => [other_user.ap_id],
           "content" => "hi everyone",
           "attributedTo" => user.ap_id
         }
@@ -951,6 +956,7 @@ defmodule Pleroma.NotificationTest do
         "cc" => [],
         "object" => %{
           "type" => "Note",
+          "id" => remote_user.ap_id <> "/objects/test",
           "content" => "Hello!",
           "tag" => [
             %{
index a7ac9034864306f90c9be702a2ac6b43179ff583..bd0a6e497617bad3933654326b0017116d3edac7 100644 (file)
@@ -66,6 +66,14 @@ defmodule Pleroma.Object.FetcherTest do
           %Tesla.Env{
             status: 500
           }
+
+        %{
+          method: :get,
+          url: "https://stereophonic.space/objects/02997b83-3ea7-4b63-94af-ef3aa2d4ed17"
+        } ->
+          %Tesla.Env{
+            status: 500
+          }
       end)
 
       :ok
@@ -124,8 +132,7 @@ defmodule Pleroma.Object.FetcherTest do
       {:ok, object} =
         Fetcher.fetch_object_from_id("http://mastodon.example.org/@admin/99541947525187367")
 
-      assert activity = Activity.get_create_by_object_ap_id(object.data["id"])
-      assert activity.data["id"]
+      assert _activity = Activity.get_create_by_object_ap_id(object.data["id"])
 
       {:ok, object_again} =
         Fetcher.fetch_object_from_id("http://mastodon.example.org/@admin/99541947525187367")
diff --git a/test/pleroma/repo/migrations/rename_instance_chat_test.exs b/test/pleroma/repo/migrations/rename_instance_chat_test.exs
new file mode 100644 (file)
index 0000000..acd4560
--- /dev/null
@@ -0,0 +1,52 @@
+defmodule Pleroma.Repo.Migrations.RenameInstanceChatTest do
+  use Pleroma.DataCase
+  import Pleroma.Factory
+  import Pleroma.Tests.Helpers
+  alias Pleroma.ConfigDB
+
+  setup do: clear_config([:instance])
+  setup do: clear_config([:chat])
+  setup_all do: require_migration("20200806175913_rename_instance_chat")
+
+  describe "up/0" do
+    test "migrates chat settings to shout", %{migration: migration} do
+      insert(:config, group: :pleroma, key: :instance, value: [chat_limit: 6000])
+      insert(:config, group: :pleroma, key: :chat, value: [enabled: true])
+
+      assert migration.up() == :ok
+
+      assert ConfigDB.get_by_params(%{group: :pleroma, key: :chat}) == nil
+      assert ConfigDB.get_by_params(%{group: :pleroma, key: :instance}) == nil
+
+      assert ConfigDB.get_by_params(%{group: :pleroma, key: :shout}).value == [
+               limit: 6000,
+               enabled: true
+             ]
+    end
+
+    test "does nothing when chat settings are not set", %{migration: migration} do
+      assert migration.up() == :noop
+      assert ConfigDB.get_by_params(%{group: :pleroma, key: :chat}) == nil
+      assert ConfigDB.get_by_params(%{group: :pleroma, key: :shout}) == nil
+    end
+  end
+
+  describe "down/0" do
+    test "migrates shout settings back to instance and chat", %{migration: migration} do
+      insert(:config, group: :pleroma, key: :shout, value: [limit: 42, enabled: true])
+
+      assert migration.down() == :ok
+
+      assert ConfigDB.get_by_params(%{group: :pleroma, key: :chat}).value == [enabled: true]
+      assert ConfigDB.get_by_params(%{group: :pleroma, key: :instance}).value == [chat_limit: 42]
+      assert ConfigDB.get_by_params(%{group: :pleroma, key: :shout}) == nil
+    end
+
+    test "does nothing when shout settings are not set", %{migration: migration} do
+      assert migration.down() == :noop
+      assert ConfigDB.get_by_params(%{group: :pleroma, key: :chat}) == nil
+      assert ConfigDB.get_by_params(%{group: :pleroma, key: :instance}) == nil
+      assert ConfigDB.get_by_params(%{group: :pleroma, key: :shout}) == nil
+    end
+  end
+end
diff --git a/test/pleroma/upload/filter/analyze_metadata_test.exs b/test/pleroma/upload/filter/analyze_metadata_test.exs
new file mode 100644 (file)
index 0000000..6f0e432
--- /dev/null
@@ -0,0 +1,19 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Upload.Filter.AnalyzeMetadataTest do
+  use Pleroma.DataCase, async: true
+  alias Pleroma.Upload.Filter.AnalyzeMetadata
+
+  test "adds the image dimensions" do
+    upload = %Pleroma.Upload{
+      name: "an… image.jpg",
+      content_type: "image/jpeg",
+      path: Path.absname("test/fixtures/image.jpg"),
+      tempfile: Path.absname("test/fixtures/image.jpg")
+    }
+
+    assert {:ok, :filtered, %{width: 1024, height: 768}} = AnalyzeMetadata.filter(upload)
+  end
+end
index faaf86fcb88db6d78026367f4b29e2f8a23e6fb7..f8777f0f751057ed3295afd0c1f8b8711e7a642c 100644 (file)
@@ -151,7 +151,7 @@ defmodule Pleroma.UserTest do
   test "ap_id returns the activity pub id for the user" do
     user = UserBuilder.build()
 
-    expected_ap_id = "#{Pleroma.Web.base_url()}/users/#{user.nickname}"
+    expected_ap_id = "#{Pleroma.Web.Endpoint.url()}/users/#{user.nickname}"
 
     assert expected_ap_id == User.ap_id(user)
   end
@@ -572,6 +572,24 @@ defmodule Pleroma.UserTest do
       )
     end
 
+    test "it fails gracefully with invalid email config" do
+      cng = User.register_changeset(%User{}, @full_user_data)
+
+      # Disable the mailer but enable all the things that want to send emails
+      clear_config([Pleroma.Emails.Mailer, :enabled], false)
+      clear_config([:instance, :account_activation_required], true)
+      clear_config([:instance, :account_approval_required], true)
+      clear_config([:welcome, :email, :enabled], true)
+      clear_config([:welcome, :email, :sender], "lain@lain.com")
+
+      # The user is still created
+      assert {:ok, %User{nickname: "nick"}} = User.register(cng)
+
+      # No emails are sent
+      ObanHelpers.perform_all()
+      refute_email_sent()
+    end
+
     test "it requires an email, name, nickname and password, bio is optional when account_activation_required is enabled" do
       clear_config([:instance, :account_activation_required], true)
 
@@ -2336,4 +2354,49 @@ defmodule Pleroma.UserTest do
     assert User.active_user_count(6) == 3
     assert User.active_user_count(1) == 1
   end
+
+  describe "pins" do
+    setup do
+      user = insert(:user)
+
+      [user: user, object_id: object_id_from_created_activity(user)]
+    end
+
+    test "unique pins", %{user: user, object_id: object_id} do
+      assert {:ok, %{pinned_objects: %{^object_id => pinned_at1} = pins} = updated_user} =
+               User.add_pinned_object_id(user, object_id)
+
+      assert Enum.count(pins) == 1
+
+      assert {:ok, %{pinned_objects: %{^object_id => pinned_at2} = pins}} =
+               User.add_pinned_object_id(updated_user, object_id)
+
+      assert pinned_at1 == pinned_at2
+
+      assert Enum.count(pins) == 1
+    end
+
+    test "respects max_pinned_statuses limit", %{user: user, object_id: object_id} do
+      clear_config([:instance, :max_pinned_statuses], 1)
+      {:ok, updated} = User.add_pinned_object_id(user, object_id)
+
+      object_id2 = object_id_from_created_activity(user)
+
+      {:error, %{errors: errors}} = User.add_pinned_object_id(updated, object_id2)
+      assert Keyword.has_key?(errors, :pinned_objects)
+    end
+
+    test "remove_pinned_object_id/2", %{user: user, object_id: object_id} do
+      assert {:ok, updated} = User.add_pinned_object_id(user, object_id)
+
+      {:ok, after_remove} = User.remove_pinned_object_id(updated, object_id)
+      assert after_remove.pinned_objects == %{}
+    end
+  end
+
+  defp object_id_from_created_activity(user) do
+    %{id: id} = insert(:note_activity, user: user)
+    %{object: %{data: %{"id" => object_id}}} = Activity.get_by_id_with_object(id)
+    object_id
+  end
 end
index 19e04d4726d5574edce20b226884912f66fab5b1..c7039d1f850b66301beac2d5ca2f271f52a6ce3d 100644 (file)
@@ -539,7 +539,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
         File.read!("test/fixtures/mastodon-post-activity.json")
         |> Jason.decode!()
         |> Map.put("actor", user.ap_id)
-        |> put_in(["object", "attridbutedTo"], user.ap_id)
+        |> put_in(["object", "attributedTo"], user.ap_id)
 
       conn =
         conn
@@ -636,6 +636,186 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
       |> post("/inbox", non_create_data)
       |> json_response(400)
     end
+
+    test "accepts Add/Remove activities", %{conn: conn} do
+      object_id = "c61d6733-e256-4fe1-ab13-1e369789423f"
+
+      status =
+        File.read!("test/fixtures/statuses/note.json")
+        |> String.replace("{{nickname}}", "lain")
+        |> String.replace("{{object_id}}", object_id)
+
+      object_url = "https://example.com/objects/#{object_id}"
+
+      user =
+        File.read!("test/fixtures/users_mock/user.json")
+        |> String.replace("{{nickname}}", "lain")
+
+      actor = "https://example.com/users/lain"
+
+      Tesla.Mock.mock(fn
+        %{
+          method: :get,
+          url: ^object_url
+        } ->
+          %Tesla.Env{
+            status: 200,
+            body: status,
+            headers: [{"content-type", "application/activity+json"}]
+          }
+
+        %{
+          method: :get,
+          url: ^actor
+        } ->
+          %Tesla.Env{
+            status: 200,
+            body: user,
+            headers: [{"content-type", "application/activity+json"}]
+          }
+
+        %{method: :get, url: "https://example.com/users/lain/collections/featured"} ->
+          %Tesla.Env{
+            status: 200,
+            body:
+              "test/fixtures/users_mock/masto_featured.json"
+              |> File.read!()
+              |> String.replace("{{domain}}", "example.com")
+              |> String.replace("{{nickname}}", "lain"),
+            headers: [{"content-type", "application/activity+json"}]
+          }
+      end)
+
+      data = %{
+        "id" => "https://example.com/objects/d61d6733-e256-4fe1-ab13-1e369789423f",
+        "actor" => actor,
+        "object" => object_url,
+        "target" => "https://example.com/users/lain/collections/featured",
+        "type" => "Add",
+        "to" => [Pleroma.Constants.as_public()]
+      }
+
+      assert "ok" ==
+               conn
+               |> assign(:valid_signature, true)
+               |> put_req_header("content-type", "application/activity+json")
+               |> post("/inbox", data)
+               |> json_response(200)
+
+      ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
+      assert Activity.get_by_ap_id(data["id"])
+      user = User.get_cached_by_ap_id(data["actor"])
+      assert user.pinned_objects[data["object"]]
+
+      data = %{
+        "id" => "https://example.com/objects/d61d6733-e256-4fe1-ab13-1e369789423d",
+        "actor" => actor,
+        "object" => object_url,
+        "target" => "https://example.com/users/lain/collections/featured",
+        "type" => "Remove",
+        "to" => [Pleroma.Constants.as_public()]
+      }
+
+      assert "ok" ==
+               conn
+               |> assign(:valid_signature, true)
+               |> put_req_header("content-type", "application/activity+json")
+               |> post("/inbox", data)
+               |> json_response(200)
+
+      ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
+      user = refresh_record(user)
+      refute user.pinned_objects[data["object"]]
+    end
+
+    test "mastodon pin/unpin", %{conn: conn} do
+      status_id = "105786274556060421"
+
+      status =
+        File.read!("test/fixtures/statuses/masto-note.json")
+        |> String.replace("{{nickname}}", "lain")
+        |> String.replace("{{status_id}}", status_id)
+
+      status_url = "https://example.com/users/lain/statuses/#{status_id}"
+
+      user =
+        File.read!("test/fixtures/users_mock/user.json")
+        |> String.replace("{{nickname}}", "lain")
+
+      actor = "https://example.com/users/lain"
+
+      Tesla.Mock.mock(fn
+        %{
+          method: :get,
+          url: ^status_url
+        } ->
+          %Tesla.Env{
+            status: 200,
+            body: status,
+            headers: [{"content-type", "application/activity+json"}]
+          }
+
+        %{
+          method: :get,
+          url: ^actor
+        } ->
+          %Tesla.Env{
+            status: 200,
+            body: user,
+            headers: [{"content-type", "application/activity+json"}]
+          }
+
+        %{method: :get, url: "https://example.com/users/lain/collections/featured"} ->
+          %Tesla.Env{
+            status: 200,
+            body:
+              "test/fixtures/users_mock/masto_featured.json"
+              |> File.read!()
+              |> String.replace("{{domain}}", "example.com")
+              |> String.replace("{{nickname}}", "lain"),
+            headers: [{"content-type", "application/activity+json"}]
+          }
+      end)
+
+      data = %{
+        "@context" => "https://www.w3.org/ns/activitystreams",
+        "actor" => actor,
+        "object" => status_url,
+        "target" => "https://example.com/users/lain/collections/featured",
+        "type" => "Add"
+      }
+
+      assert "ok" ==
+               conn
+               |> assign(:valid_signature, true)
+               |> put_req_header("content-type", "application/activity+json")
+               |> post("/inbox", data)
+               |> json_response(200)
+
+      ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
+      assert Activity.get_by_object_ap_id_with_object(data["object"])
+      user = User.get_cached_by_ap_id(data["actor"])
+      assert user.pinned_objects[data["object"]]
+
+      data = %{
+        "actor" => actor,
+        "object" => status_url,
+        "target" => "https://example.com/users/lain/collections/featured",
+        "type" => "Remove"
+      }
+
+      assert "ok" ==
+               conn
+               |> assign(:valid_signature, true)
+               |> put_req_header("content-type", "application/activity+json")
+               |> post("/inbox", data)
+               |> json_response(200)
+
+      ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
+      assert Activity.get_by_object_ap_id_with_object(data["object"])
+      user = refresh_record(user)
+      refute user.pinned_objects[data["object"]]
+    end
   end
 
   describe "/users/:nickname/inbox" do
@@ -649,7 +829,11 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
 
     test "it inserts an incoming activity into the database", %{conn: conn, data: data} do
       user = insert(:user)
-      data = Map.put(data, "bcc", [user.ap_id])
+
+      data =
+        data
+        |> Map.put("bcc", [user.ap_id])
+        |> Kernel.put_in(["object", "bcc"], [user.ap_id])
 
       conn =
         conn
@@ -666,8 +850,11 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
       user = insert(:user)
 
       data =
-        Map.put(data, "to", user.ap_id)
-        |> Map.delete("cc")
+        data
+        |> Map.put("to", user.ap_id)
+        |> Map.put("cc", [])
+        |> Kernel.put_in(["object", "to"], user.ap_id)
+        |> Kernel.put_in(["object", "cc"], [])
 
       conn =
         conn
@@ -684,8 +871,11 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
       user = insert(:user)
 
       data =
-        Map.put(data, "cc", user.ap_id)
-        |> Map.delete("to")
+        data
+        |> Map.put("to", [])
+        |> Map.put("cc", user.ap_id)
+        |> Kernel.put_in(["object", "to"], [])
+        |> Kernel.put_in(["object", "cc"], user.ap_id)
 
       conn =
         conn
@@ -703,9 +893,13 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
       user = insert(:user)
 
       data =
-        Map.put(data, "bcc", user.ap_id)
-        |> Map.delete("to")
-        |> Map.delete("cc")
+        data
+        |> Map.put("to", [])
+        |> Map.put("cc", [])
+        |> Map.put("bcc", user.ap_id)
+        |> Kernel.put_in(["object", "to"], [])
+        |> Kernel.put_in(["object", "cc"], [])
+        |> Kernel.put_in(["object", "bcc"], user.ap_id)
 
       conn =
         conn
@@ -820,29 +1014,34 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
       assert Instances.reachable?(sender_host)
     end
 
+    @tag capture_log: true
     test "it removes all follower collections but actor's", %{conn: conn} do
       [actor, recipient] = insert_pair(:user)
 
-      data =
-        File.read!("test/fixtures/activitypub-client-post-activity.json")
-        |> Jason.decode!()
+      to = [
+        recipient.ap_id,
+        recipient.follower_address,
+        "https://www.w3.org/ns/activitystreams#Public"
+      ]
 
-      object = Map.put(data["object"], "attributedTo", actor.ap_id)
+      cc = [recipient.follower_address, actor.follower_address]
 
-      data =
-        data
-        |> Map.put("id", Utils.generate_object_id())
-        |> Map.put("actor", actor.ap_id)
-        |> Map.put("object", object)
-        |> Map.put("cc", [
-          recipient.follower_address,
-          actor.follower_address
-        ])
-        |> Map.put("to", [
-          recipient.ap_id,
-          recipient.follower_address,
-          "https://www.w3.org/ns/activitystreams#Public"
-        ])
+      data = %{
+        "@context" => ["https://www.w3.org/ns/activitystreams"],
+        "type" => "Create",
+        "id" => Utils.generate_activity_id(),
+        "to" => to,
+        "cc" => cc,
+        "actor" => actor.ap_id,
+        "object" => %{
+          "type" => "Note",
+          "to" => to,
+          "cc" => cc,
+          "content" => "It's a note",
+          "attributedTo" => actor.ap_id,
+          "id" => Utils.generate_object_id()
+        }
+      }
 
       conn
       |> assign(:valid_signature, true)
@@ -852,7 +1051,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
 
       ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
 
-      activity = Activity.get_by_ap_id(data["id"])
+      assert activity = Activity.get_by_ap_id(data["id"])
 
       assert activity.id
       assert actor.follower_address in activity.recipients
@@ -984,7 +1183,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
         "actor" => remote_actor,
         "content" => "test report",
         "id" => "https://#{remote_domain}/e3b12fd1-948c-446e-b93b-a5e67edbe1d8",
-        "nickname" => reported_user.nickname,
         "object" => [
           reported_user.ap_id,
           note.data["object"]
@@ -1772,4 +1970,29 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
       |> json_response(403)
     end
   end
+
+  test "pinned collection", %{conn: conn} do
+    clear_config([:instance, :max_pinned_statuses], 2)
+    user = insert(:user)
+    objects = insert_list(2, :note, user: user)
+
+    Enum.reduce(objects, user, fn %{data: %{"id" => object_id}}, user ->
+      {:ok, updated} = User.add_pinned_object_id(user, object_id)
+      updated
+    end)
+
+    %{nickname: nickname, featured_address: featured_address, pinned_objects: pinned_objects} =
+      refresh_record(user)
+
+    %{"id" => ^featured_address, "orderedItems" => items, "totalItems" => 2} =
+      conn
+      |> get("/users/#{nickname}/collections/featured")
+      |> json_response(200)
+
+    object_ids = Enum.map(items, & &1["id"])
+
+    assert Enum.all?(pinned_objects, fn {obj_id, _} ->
+             obj_id in object_ids
+           end)
+  end
 end
index c7fa452f7370c3adc405bad8d07b89a353e4a3c1..64e12066e58620ced13a96d2376e9b6fc5ef25a5 100644 (file)
@@ -235,6 +235,83 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
                "url" => [%{"href" => "https://jk.nipponalba.scot/images/profile.jpg"}]
              }
     end
+
+    test "fetches user featured collection" do
+      ap_id = "https://example.com/users/lain"
+
+      featured_url = "https://example.com/users/lain/collections/featured"
+
+      user_data =
+        "test/fixtures/users_mock/user.json"
+        |> File.read!()
+        |> String.replace("{{nickname}}", "lain")
+        |> Jason.decode!()
+        |> Map.put("featured", featured_url)
+        |> Jason.encode!()
+
+      object_id = Ecto.UUID.generate()
+
+      featured_data =
+        "test/fixtures/mastodon/collections/featured.json"
+        |> File.read!()
+        |> String.replace("{{domain}}", "example.com")
+        |> String.replace("{{nickname}}", "lain")
+        |> String.replace("{{object_id}}", object_id)
+
+      object_url = "https://example.com/objects/#{object_id}"
+
+      object_data =
+        "test/fixtures/statuses/note.json"
+        |> File.read!()
+        |> String.replace("{{object_id}}", object_id)
+        |> String.replace("{{nickname}}", "lain")
+
+      Tesla.Mock.mock(fn
+        %{
+          method: :get,
+          url: ^ap_id
+        } ->
+          %Tesla.Env{
+            status: 200,
+            body: user_data,
+            headers: [{"content-type", "application/activity+json"}]
+          }
+
+        %{
+          method: :get,
+          url: ^featured_url
+        } ->
+          %Tesla.Env{
+            status: 200,
+            body: featured_data,
+            headers: [{"content-type", "application/activity+json"}]
+          }
+      end)
+
+      Tesla.Mock.mock_global(fn
+        %{
+          method: :get,
+          url: ^object_url
+        } ->
+          %Tesla.Env{
+            status: 200,
+            body: object_data,
+            headers: [{"content-type", "application/activity+json"}]
+          }
+      end)
+
+      {:ok, user} = ActivityPub.make_user_from_ap_id(ap_id)
+      Process.sleep(50)
+
+      assert user.featured_address == featured_url
+      assert Map.has_key?(user.pinned_objects, object_url)
+
+      in_db = Pleroma.User.get_by_ap_id(ap_id)
+      assert in_db.featured_address == featured_url
+      assert Map.has_key?(user.pinned_objects, object_url)
+
+      assert %{data: %{"id" => ^object_url}} = Object.get_by_ap_id(object_url)
+    end
   end
 
   test "it fetches the appropriate tag-restricted posts" do
index 5c0aff26eb3bb6f1f714613cda67f8ea4b87d337..0b0143d09435386e792bf6a2cedf1990923b1579 100644 (file)
@@ -254,6 +254,30 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do
 
       assert {:reject, _} = SimplePolicy.filter(remote_user)
     end
+
+    test "reject Announce when object would be rejected" do
+      clear_config([:mrf_simple, :reject], ["blocked.tld"])
+
+      announce = %{
+        "type" => "Announce",
+        "actor" => "https://okay.tld/users/alice",
+        "object" => %{"type" => "Note", "actor" => "https://blocked.tld/users/bob"}
+      }
+
+      assert {:reject, _} = SimplePolicy.filter(announce)
+    end
+
+    test "reject by URI object" do
+      clear_config([:mrf_simple, :reject], ["blocked.tld"])
+
+      announce = %{
+        "type" => "Announce",
+        "actor" => "https://okay.tld/users/alice",
+        "object" => "https://blocked.tld/activities/1"
+      }
+
+      assert {:reject, _} = SimplePolicy.filter(announce)
+    end
   end
 
   describe "when :followers_only" do
@@ -498,7 +522,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do
 
   defp build_local_message do
     %{
-      "actor" => "#{Pleroma.Web.base_url()}/users/alice",
+      "actor" => "#{Pleroma.Web.Endpoint.url()}/users/alice",
       "to" => [],
       "cc" => []
     }
index b775515e0f85945b947b4b232b2e0b5d8020ad33..0e49fda992219e5ca88e4dc3f3aef7e26e2aed00 100644 (file)
@@ -72,5 +72,38 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidatorTest do
 
       assert attachment.mediaType == "image/jpeg"
     end
+
+    test "it handles image dimensions" do
+      attachment = %{
+        "url" => [
+          %{
+            "type" => "Link",
+            "mediaType" => "image/jpeg",
+            "href" => "https://example.com/images/1.jpg",
+            "width" => 200,
+            "height" => 100
+          }
+        ],
+        "type" => "Document",
+        "name" => nil,
+        "mediaType" => "image/jpeg"
+      }
+
+      {:ok, attachment} =
+        AttachmentValidator.cast_and_validate(attachment)
+        |> Ecto.Changeset.apply_action(:insert)
+
+      assert [
+               %{
+                 href: "https://example.com/images/1.jpg",
+                 type: "Link",
+                 mediaType: "image/jpeg",
+                 width: 200,
+                 height: 100
+               }
+             ] = attachment.url
+
+      assert attachment.mediaType == "image/jpeg"
+    end
   end
 end
index 52fa933ee2d91b2fdde5811f3abf820846ce80b8..e606fa3d11fbc3353ab797b29203f2e41d50b0aa 100644 (file)
@@ -25,9 +25,6 @@ defmodule Pleroma.Web.ActivityPub.PipelineTest do
       MRFMock
       |> expect(:pipeline_filter, fn o, m -> {:ok, o, m} end)
 
-      ActivityPubMock
-      |> expect(:persist, fn o, m -> {:ok, o, m} end)
-
       SideEffectsMock
       |> expect(:handle, fn o, m -> {:ok, o, m} end)
       |> expect(:handle_after_transaction, fn m -> m end)
@@ -42,6 +39,9 @@ defmodule Pleroma.Web.ActivityPub.PipelineTest do
 
       activity_with_object = %{activity | data: Map.put(activity.data, "object", object)}
 
+      ActivityPubMock
+      |> expect(:persist, fn _, m -> {:ok, activity, m} end)
+
       FederatorMock
       |> expect(:publish, fn ^activity_with_object -> :ok end)
 
@@ -50,7 +50,7 @@ defmodule Pleroma.Web.ActivityPub.PipelineTest do
 
       assert {:ok, ^activity, ^meta} =
                Pleroma.Web.ActivityPub.Pipeline.common_pipeline(
-                 activity,
+                 activity.data,
                  meta
                )
     end
@@ -59,6 +59,9 @@ defmodule Pleroma.Web.ActivityPub.PipelineTest do
       activity = insert(:note_activity)
       meta = [local: true]
 
+      ActivityPubMock
+      |> expect(:persist, fn _, m -> {:ok, activity, m} end)
+
       FederatorMock
       |> expect(:publish, fn ^activity -> :ok end)
 
@@ -66,29 +69,35 @@ defmodule Pleroma.Web.ActivityPub.PipelineTest do
       |> expect(:get, fn [:instance, :federating] -> true end)
 
       assert {:ok, ^activity, ^meta} =
-               Pleroma.Web.ActivityPub.Pipeline.common_pipeline(activity, meta)
+               Pleroma.Web.ActivityPub.Pipeline.common_pipeline(activity.data, meta)
     end
 
     test "it goes through validation, filtering, persisting, side effects without federation for remote activities" do
       activity = insert(:note_activity)
       meta = [local: false]
 
+      ActivityPubMock
+      |> expect(:persist, fn _, m -> {:ok, activity, m} end)
+
       ConfigMock
       |> expect(:get, fn [:instance, :federating] -> true end)
 
       assert {:ok, ^activity, ^meta} =
-               Pleroma.Web.ActivityPub.Pipeline.common_pipeline(activity, meta)
+               Pleroma.Web.ActivityPub.Pipeline.common_pipeline(activity.data, meta)
     end
 
     test "it goes through validation, filtering, persisting, side effects without federation for local activities if federation is deactivated" do
       activity = insert(:note_activity)
       meta = [local: true]
 
+      ActivityPubMock
+      |> expect(:persist, fn _, m -> {:ok, activity, m} end)
+
       ConfigMock
       |> expect(:get, fn [:instance, :federating] -> false end)
 
       assert {:ok, ^activity, ^meta} =
-               Pleroma.Web.ActivityPub.Pipeline.common_pipeline(activity, meta)
+               Pleroma.Web.ActivityPub.Pipeline.common_pipeline(activity.data, meta)
     end
   end
 end
index f0ce3d7f2398a44fdc58a6f6643aa84f933a0196..89f3ad411d1b5f7a10d3c0575991be6c3a55762c 100644 (file)
@@ -38,7 +38,7 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do
         },
         %{
           "rel" => "http://ostatus.org/schema/1.0/subscribe",
-          "template" => "#{Pleroma.Web.base_url()}/ostatus_subscribe?acct={uri}"
+          "template" => "#{Pleroma.Web.Endpoint.url()}/ostatus_subscribe?acct={uri}"
         }
       ]
 
diff --git a/test/pleroma/web/activity_pub/transmogrifier/add_remove_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/add_remove_handling_test.exs
new file mode 100644 (file)
index 0000000..fc77571
--- /dev/null
@@ -0,0 +1,172 @@
+defmodule Pleroma.Web.ActivityPub.Transmogrifier.AddRemoveHandlingTest do
+  use Oban.Testing, repo: Pleroma.Repo
+  use Pleroma.DataCase, async: true
+
+  require Pleroma.Constants
+
+  import Pleroma.Factory
+
+  alias Pleroma.User
+  alias Pleroma.Web.ActivityPub.Transmogrifier
+
+  test "it accepts Add/Remove activities" do
+    user =
+      "test/fixtures/users_mock/user.json"
+      |> File.read!()
+      |> String.replace("{{nickname}}", "lain")
+
+    object_id = "c61d6733-e256-4fe1-ab13-1e369789423f"
+
+    object =
+      "test/fixtures/statuses/note.json"
+      |> File.read!()
+      |> String.replace("{{nickname}}", "lain")
+      |> String.replace("{{object_id}}", object_id)
+
+    object_url = "https://example.com/objects/#{object_id}"
+
+    actor = "https://example.com/users/lain"
+
+    Tesla.Mock.mock(fn
+      %{
+        method: :get,
+        url: ^actor
+      } ->
+        %Tesla.Env{
+          status: 200,
+          body: user,
+          headers: [{"content-type", "application/activity+json"}]
+        }
+
+      %{
+        method: :get,
+        url: ^object_url
+      } ->
+        %Tesla.Env{
+          status: 200,
+          body: object,
+          headers: [{"content-type", "application/activity+json"}]
+        }
+
+      %{method: :get, url: "https://example.com/users/lain/collections/featured"} ->
+        %Tesla.Env{
+          status: 200,
+          body:
+            "test/fixtures/users_mock/masto_featured.json"
+            |> File.read!()
+            |> String.replace("{{domain}}", "example.com")
+            |> String.replace("{{nickname}}", "lain"),
+          headers: [{"content-type", "application/activity+json"}]
+        }
+    end)
+
+    message = %{
+      "id" => "https://example.com/objects/d61d6733-e256-4fe1-ab13-1e369789423f",
+      "actor" => actor,
+      "object" => object_url,
+      "target" => "https://example.com/users/lain/collections/featured",
+      "type" => "Add",
+      "to" => [Pleroma.Constants.as_public()],
+      "cc" => ["https://example.com/users/lain/followers"]
+    }
+
+    assert {:ok, activity} = Transmogrifier.handle_incoming(message)
+    assert activity.data == message
+    user = User.get_cached_by_ap_id(actor)
+    assert user.pinned_objects[object_url]
+
+    remove = %{
+      "id" => "http://localhost:400/objects/d61d6733-e256-4fe1-ab13-1e369789423d",
+      "actor" => actor,
+      "object" => object_url,
+      "target" => "https://example.com/users/lain/collections/featured",
+      "type" => "Remove",
+      "to" => [Pleroma.Constants.as_public()],
+      "cc" => ["https://example.com/users/lain/followers"]
+    }
+
+    assert {:ok, activity} = Transmogrifier.handle_incoming(remove)
+    assert activity.data == remove
+
+    user = refresh_record(user)
+    refute user.pinned_objects[object_url]
+  end
+
+  test "Add/Remove activities for remote users without featured address" do
+    user = insert(:user, local: false, domain: "example.com")
+
+    user =
+      user
+      |> Ecto.Changeset.change(featured_address: nil)
+      |> Repo.update!()
+
+    %{host: host} = URI.parse(user.ap_id)
+
+    user_data =
+      "test/fixtures/users_mock/user.json"
+      |> File.read!()
+      |> String.replace("{{nickname}}", user.nickname)
+
+    object_id = "c61d6733-e256-4fe1-ab13-1e369789423f"
+
+    object =
+      "test/fixtures/statuses/note.json"
+      |> File.read!()
+      |> String.replace("{{nickname}}", user.nickname)
+      |> String.replace("{{object_id}}", object_id)
+
+    object_url = "https://#{host}/objects/#{object_id}"
+
+    actor = "https://#{host}/users/#{user.nickname}"
+
+    featured = "https://#{host}/users/#{user.nickname}/collections/featured"
+
+    Tesla.Mock.mock(fn
+      %{
+        method: :get,
+        url: ^actor
+      } ->
+        %Tesla.Env{
+          status: 200,
+          body: user_data,
+          headers: [{"content-type", "application/activity+json"}]
+        }
+
+      %{
+        method: :get,
+        url: ^object_url
+      } ->
+        %Tesla.Env{
+          status: 200,
+          body: object,
+          headers: [{"content-type", "application/activity+json"}]
+        }
+
+      %{method: :get, url: ^featured} ->
+        %Tesla.Env{
+          status: 200,
+          body:
+            "test/fixtures/users_mock/masto_featured.json"
+            |> File.read!()
+            |> String.replace("{{domain}}", "#{host}")
+            |> String.replace("{{nickname}}", user.nickname),
+          headers: [{"content-type", "application/activity+json"}]
+        }
+    end)
+
+    message = %{
+      "id" => "https://#{host}/objects/d61d6733-e256-4fe1-ab13-1e369789423f",
+      "actor" => actor,
+      "object" => object_url,
+      "target" => "https://#{host}/users/#{user.nickname}/collections/featured",
+      "type" => "Add",
+      "to" => [Pleroma.Constants.as_public()],
+      "cc" => ["https://#{host}/users/#{user.nickname}/followers"]
+    }
+
+    assert {:ok, activity} = Transmogrifier.handle_incoming(message)
+    assert activity.data == message
+    user = User.get_cached_by_ap_id(actor)
+    assert user.pinned_objects[object_url]
+  end
+end
index e733f167d0ec486fd46732d0da2e0ed198a50b49..a929f828db62f81b1f965ea8c2c3f7986e2ff2a7 100644 (file)
@@ -24,6 +24,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.AudioHandlingTest do
       "actor" => "http://mastodon.example.org/users/admin",
       "object" => %{
         "type" => "Audio",
+        "to" => ["https://www.w3.org/ns/activitystreams#Public"],
+        "cc" => [],
         "id" => "http://mastodon.example.org/users/admin/listens/1234",
         "attributedTo" => "http://mastodon.example.org/users/admin",
         "title" => "lain radio episode 1",
@@ -61,7 +63,9 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.AudioHandlingTest do
 
     assert object.data["to"] == ["https://www.w3.org/ns/activitystreams#Public"]
 
-    assert object.data["cc"] == []
+    assert object.data["cc"] == [
+             "https://channels.tests.funkwhale.audio/federation/actors/compositions/followers"
+           ]
 
     assert object.data["url"] == "https://channels.tests.funkwhale.audio/library/tracks/74"
 
@@ -76,7 +80,9 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.AudioHandlingTest do
                    "href" =>
                      "https://channels.tests.funkwhale.audio/api/v1/listen/3901e5d8-0445-49d5-9711-e096cf32e515/?upload=42342395-0208-4fee-a38d-259a6dae0871&download=false",
                    "mediaType" => "audio/ogg",
-                   "type" => "Link"
+                   "type" => "Link",
+                   "width" => nil,
+                   "height" => nil
                  }
                ]
              }
index c4879fda1b09665678bc537d364c47f9cc87d08e..14f5f704ad274235c2827b62bf1a2d9df8aa6abd 100644 (file)
@@ -31,7 +31,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.EventHandlingTest do
              )
 
     assert object.data["to"] == ["https://www.w3.org/ns/activitystreams#Public"]
-    assert object.data["cc"] == []
+    assert object.data["cc"] == ["https://mobilizon.org/@tcit/followers"]
 
     assert object.data["url"] ==
              "https://mobilizon.org/events/252d5816-00a3-4a89-a66f-15bf65c33e39"
index deb956410f3fa5544e02d1080303908eecc9d5cb..1846b229178fc428fb2972c9e62489a061a2c54a 100644 (file)
@@ -10,11 +10,11 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.NoteHandlingTest do
   alias Pleroma.Object
   alias Pleroma.User
   alias Pleroma.Web.ActivityPub.Transmogrifier
+  alias Pleroma.Web.ActivityPub.Utils
   alias Pleroma.Web.CommonAPI
 
   import Mock
   import Pleroma.Factory
-  import ExUnit.CaptureLog
 
   setup_all do
     Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
@@ -43,36 +43,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.NoteHandlingTest do
       assert Object.hashtags(object) == ["test"]
     end
 
-    test "it cleans up incoming notices which are not really DMs" do
-      user = insert(:user)
-      other_user = insert(:user)
-
-      to = [user.ap_id, other_user.ap_id]
-
-      data =
-        File.read!("test/fixtures/mastodon-post-activity.json")
-        |> Jason.decode!()
-        |> Map.put("to", to)
-        |> Map.put("cc", [])
-
-      object =
-        data["object"]
-        |> Map.put("to", to)
-        |> Map.put("cc", [])
-
-      data = Map.put(data, "object", object)
-
-      {:ok, %Activity{data: data, local: false} = activity} = Transmogrifier.handle_incoming(data)
-
-      assert data["to"] == []
-      assert data["cc"] == to
-
-      object_data = Object.normalize(activity, fetch: false).data
-
-      assert object_data["to"] == []
-      assert object_data["cc"] == to
-    end
-
     test "it ignores an incoming notice if we already have it" do
       activity = insert(:note_activity)
 
@@ -147,9 +117,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.NoteHandlingTest do
         data
         |> Map.put("object", object)
 
-      assert capture_log(fn ->
-               {:ok, _returned_activity} = Transmogrifier.handle_incoming(data)
-             end) =~ "[warn] Couldn't fetch \"https://404.site/whatever\", error: nil"
+      assert {:ok, _returned_activity} = Transmogrifier.handle_incoming(data)
     end
 
     test "it does not work for deactivated users" do
@@ -174,8 +142,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.NoteHandlingTest do
       assert data["to"] == ["https://www.w3.org/ns/activitystreams#Public"]
 
       assert data["cc"] == [
-               "http://mastodon.example.org/users/admin/followers",
-               "http://localtesting.pleroma.lol/users/lain"
+               "http://localtesting.pleroma.lol/users/lain",
+               "http://mastodon.example.org/users/admin/followers"
              ]
 
       assert data["actor"] == "http://mastodon.example.org/users/admin"
@@ -188,8 +156,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.NoteHandlingTest do
       assert object_data["to"] == ["https://www.w3.org/ns/activitystreams#Public"]
 
       assert object_data["cc"] == [
-               "http://mastodon.example.org/users/admin/followers",
-               "http://localtesting.pleroma.lol/users/lain"
+               "http://localtesting.pleroma.lol/users/lain",
+               "http://mastodon.example.org/users/admin/followers"
              ]
 
       assert object_data["actor"] == "http://mastodon.example.org/users/admin"
@@ -221,8 +189,25 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.NoteHandlingTest do
       {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
       object = Object.normalize(data["object"], fetch: false)
 
-      assert Enum.at(Object.tags(object), 2) == "moo"
-      assert Object.hashtags(object) == ["moo"]
+      assert match?(
+               %{
+                 "href" => "http://localtesting.pleroma.lol/users/lain",
+                 "name" => "@lain@localtesting.pleroma.lol",
+                 "type" => "Mention"
+               },
+               Enum.at(object.data["tag"], 0)
+             )
+
+      assert match?(
+               %{
+                 "href" => "http://mastodon.example.org/tags/moo",
+                 "name" => "#moo",
+                 "type" => "Hashtag"
+               },
+               Enum.at(object.data["tag"], 1)
+             )
+
+      assert "moo" == Enum.at(object.data["tag"], 2)
     end
 
     test "it works for incoming notices with contentMap" do
@@ -276,13 +261,11 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.NoteHandlingTest do
         File.read!("test/fixtures/mastodon-post-activity.json")
         |> Jason.decode!()
         |> Map.put("actor", user.ap_id)
-        |> Map.put("to", nil)
         |> Map.put("cc", nil)
 
       object =
         data["object"]
         |> Map.put("attributedTo", user.ap_id)
-        |> Map.put("to", nil)
         |> Map.put("cc", nil)
         |> Map.put("id", user.ap_id <> "/activities/12345678")
 
@@ -290,8 +273,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.NoteHandlingTest do
 
       {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
 
-      assert !is_nil(data["to"])
-      assert !is_nil(data["cc"])
+      refute is_nil(data["cc"])
     end
 
     test "it strips internal likes" do
@@ -310,9 +292,11 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.NoteHandlingTest do
       object = Map.put(data["object"], "likes", likes)
       data = Map.put(data, "object", object)
 
-      {:ok, %Activity{object: object}} = Transmogrifier.handle_incoming(data)
+      {:ok, %Activity{} = activity} = Transmogrifier.handle_incoming(data)
+
+      object = Object.normalize(activity)
 
-      refute Map.has_key?(object.data, "likes")
+      assert object.data["likes"] == []
     end
 
     test "it strips internal reactions" do
@@ -330,70 +314,46 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.NoteHandlingTest do
     end
 
     test "it correctly processes messages with non-array to field" do
-      user = insert(:user)
+      data =
+        File.read!("test/fixtures/mastodon-post-activity.json")
+        |> Poison.decode!()
+        |> Map.put("to", "https://www.w3.org/ns/activitystreams#Public")
+        |> put_in(["object", "to"], "https://www.w3.org/ns/activitystreams#Public")
 
-      message = %{
-        "@context" => "https://www.w3.org/ns/activitystreams",
-        "to" => "https://www.w3.org/ns/activitystreams#Public",
-        "type" => "Create",
-        "object" => %{
-          "content" => "blah blah blah",
-          "type" => "Note",
-          "attributedTo" => user.ap_id,
-          "inReplyTo" => nil
-        },
-        "actor" => user.ap_id
-      }
+      assert {:ok, activity} = Transmogrifier.handle_incoming(data)
 
-      assert {:ok, activity} = Transmogrifier.handle_incoming(message)
+      assert [
+               "http://localtesting.pleroma.lol/users/lain",
+               "http://mastodon.example.org/users/admin/followers"
+             ] == activity.data["cc"]
 
       assert ["https://www.w3.org/ns/activitystreams#Public"] == activity.data["to"]
     end
 
     test "it correctly processes messages with non-array cc field" do
-      user = insert(:user)
-
-      message = %{
-        "@context" => "https://www.w3.org/ns/activitystreams",
-        "to" => user.follower_address,
-        "cc" => "https://www.w3.org/ns/activitystreams#Public",
-        "type" => "Create",
-        "object" => %{
-          "content" => "blah blah blah",
-          "type" => "Note",
-          "attributedTo" => user.ap_id,
-          "inReplyTo" => nil
-        },
-        "actor" => user.ap_id
-      }
+      data =
+        File.read!("test/fixtures/mastodon-post-activity.json")
+        |> Poison.decode!()
+        |> Map.put("cc", "http://mastodon.example.org/users/admin/followers")
+        |> put_in(["object", "cc"], "http://mastodon.example.org/users/admin/followers")
 
-      assert {:ok, activity} = Transmogrifier.handle_incoming(message)
+      assert {:ok, activity} = Transmogrifier.handle_incoming(data)
 
-      assert ["https://www.w3.org/ns/activitystreams#Public"] == activity.data["cc"]
-      assert [user.follower_address] == activity.data["to"]
+      assert ["http://mastodon.example.org/users/admin/followers"] == activity.data["cc"]
+      assert ["https://www.w3.org/ns/activitystreams#Public"] == activity.data["to"]
     end
 
     test "it correctly processes messages with weirdness in address fields" do
-      user = insert(:user)
-
-      message = %{
-        "@context" => "https://www.w3.org/ns/activitystreams",
-        "to" => [nil, user.follower_address],
-        "cc" => ["https://www.w3.org/ns/activitystreams#Public", ["¿"]],
-        "type" => "Create",
-        "object" => %{
-          "content" => "…",
-          "type" => "Note",
-          "attributedTo" => user.ap_id,
-          "inReplyTo" => nil
-        },
-        "actor" => user.ap_id
-      }
+      data =
+        File.read!("test/fixtures/mastodon-post-activity.json")
+        |> Poison.decode!()
+        |> Map.put("cc", ["http://mastodon.example.org/users/admin/followers", ["¿"]])
+        |> put_in(["object", "cc"], ["http://mastodon.example.org/users/admin/followers", ["¿"]])
 
-      assert {:ok, activity} = Transmogrifier.handle_incoming(message)
+      assert {:ok, activity} = Transmogrifier.handle_incoming(data)
 
-      assert ["https://www.w3.org/ns/activitystreams#Public"] == activity.data["cc"]
-      assert [user.follower_address] == activity.data["to"]
+      assert ["http://mastodon.example.org/users/admin/followers"] == activity.data["cc"]
+      assert ["https://www.w3.org/ns/activitystreams#Public"] == activity.data["to"]
     end
   end
 
@@ -419,7 +379,11 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.NoteHandlingTest do
     } do
       clear_config([:instance, :federation_incoming_replies_max_depth], 10)
 
-      {:ok, _activity} = Transmogrifier.handle_incoming(data)
+      {:ok, activity} = Transmogrifier.handle_incoming(data)
+
+      object = Object.normalize(activity.data["object"])
+
+      assert object.data["replies"] == items
 
       for id <- items do
         job_args = %{"op" => "fetch_remote", "id" => id, "depth" => 1}
@@ -442,45 +406,38 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.NoteHandlingTest do
     setup do: clear_config([:instance, :federation_incoming_replies_max_depth])
 
     setup do
-      user = insert(:user)
-
-      {:ok, activity} = CommonAPI.post(user, %{status: "post1"})
-
-      {:ok, reply1} =
-        CommonAPI.post(user, %{status: "reply1", in_reply_to_status_id: activity.id})
-
-      {:ok, reply2} =
-        CommonAPI.post(user, %{status: "reply2", in_reply_to_status_id: activity.id})
-
-      replies_uris = Enum.map([reply1, reply2], fn a -> a.object.data["id"] end)
-
-      {:ok, federation_output} = Transmogrifier.prepare_outgoing(activity.data)
+      replies = %{
+        "type" => "Collection",
+        "items" => [Utils.generate_object_id(), Utils.generate_object_id()]
+      }
 
-      Repo.delete(activity.object)
-      Repo.delete(activity)
+      activity =
+        File.read!("test/fixtures/mastodon-post-activity.json")
+        |> Poison.decode!()
+        |> Kernel.put_in(["object", "replies"], replies)
 
-      %{federation_output: federation_output, replies_uris: replies_uris}
+      %{activity: activity}
     end
 
     test "schedules background fetching of `replies` items if max thread depth limit allows", %{
-      federation_output: federation_output,
-      replies_uris: replies_uris
+      activity: activity
     } do
       clear_config([:instance, :federation_incoming_replies_max_depth], 1)
 
-      {:ok, _activity} = Transmogrifier.handle_incoming(federation_output)
+      assert {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(activity)
+      object = Object.normalize(data["object"])
 
-      for id <- replies_uris do
+      for id <- object.data["replies"] do
         job_args = %{"op" => "fetch_remote", "id" => id, "depth" => 1}
         assert_enqueued(worker: Pleroma.Workers.RemoteFetcherWorker, args: job_args)
       end
     end
 
     test "does NOT schedule background fetching of `replies` beyond max thread depth limit allows",
-         %{federation_output: federation_output} do
+         %{activity: activity} do
       clear_config([:instance, :federation_incoming_replies_max_depth], 0)
 
-      {:ok, _activity} = Transmogrifier.handle_incoming(federation_output)
+      {:ok, _activity} = Transmogrifier.handle_incoming(activity)
 
       assert all_enqueued(worker: Pleroma.Workers.RemoteFetcherWorker) == []
     end
@@ -498,6 +455,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.NoteHandlingTest do
         "object" => %{
           "to" => ["https://www.w3.org/ns/activitystreams#Public"],
           "cc" => [],
+          "id" => Utils.generate_object_id(),
           "type" => "Note",
           "content" => "Hi",
           "inReplyTo" => nil,
@@ -522,6 +480,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.NoteHandlingTest do
         "object" => %{
           "to" => ["https://www.w3.org/ns/activitystreams#Public"],
           "cc" => [],
+          "id" => Utils.generate_object_id(),
           "type" => "Note",
           "content" => "Hi",
           "inReplyTo" => nil,
index 6ddf7c1720b6b512e06a143577c31d13508d29aa..62b4a2cb37ba2d6f9a1274472897e551e41100da 100644 (file)
@@ -60,7 +60,9 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.VideoHandlingTest do
                    "href" =>
                      "https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4",
                    "mediaType" => "video/mp4",
-                   "type" => "Link"
+                   "type" => "Link",
+                   "width" => nil,
+                   "height" => nil
                  }
                ]
              }
@@ -83,7 +85,9 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.VideoHandlingTest do
                    "href" =>
                      "https://framatube.org/static/webseed/6050732a-8a7a-43d4-a6cd-809525a1d206-1080.mp4",
                    "mediaType" => "video/mp4",
-                   "type" => "Link"
+                   "type" => "Link",
+                   "width" => nil,
+                   "height" => nil
                  }
                ]
              }
@@ -113,7 +117,9 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.VideoHandlingTest do
                    "href" =>
                      "https://peertube.stream/static/streaming-playlists/hls/abece3c3-b9c6-47f4-8040-f3eed8c602e6/abece3c3-b9c6-47f4-8040-f3eed8c602e6-1080-fragmented.mp4",
                    "mediaType" => "video/mp4",
-                   "type" => "Link"
+                   "type" => "Link",
+                   "width" => nil,
+                   "height" => nil
                  }
                ]
              }
index 4c3fcb44a295e99f801d8b666600ee411464e2a8..5a3b57acb84d1923ac2d950f1c022105a93d2ffb 100644 (file)
@@ -11,6 +11,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
   alias Pleroma.Tests.ObanHelpers
   alias Pleroma.User
   alias Pleroma.Web.ActivityPub.Transmogrifier
+  alias Pleroma.Web.ActivityPub.Utils
   alias Pleroma.Web.AdminAPI.AccountView
   alias Pleroma.Web.CommonAPI
 
@@ -159,8 +160,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
       {:ok, activity} = CommonAPI.post(user, %{status: "hey"})
       {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
 
-      assert modified["@context"] ==
-               Pleroma.Web.ActivityPub.Utils.make_json_ld_header()["@context"]
+      assert modified["@context"] == Utils.make_json_ld_header()["@context"]
 
       assert modified["object"]["conversation"] == modified["context"]
     end
@@ -446,7 +446,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
           end)
       }
 
-      fixed_object = Transmogrifier.fix_explicit_addressing(object)
+      fixed_object = Transmogrifier.fix_explicit_addressing(object, user.follower_address)
       assert Enum.all?(explicitly_mentioned_actors, &(&1 in fixed_object["to"]))
       refute "https://social.beepboop.ga/users/dirb" in fixed_object["to"]
       assert "https://social.beepboop.ga/users/dirb" in fixed_object["cc"]
@@ -459,7 +459,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
         "cc" => []
       }
 
-      fixed_object = Transmogrifier.fix_explicit_addressing(object)
+      fixed_object = Transmogrifier.fix_explicit_addressing(object, user.follower_address)
       assert user.follower_address in fixed_object["to"]
       refute user.follower_address in fixed_object["cc"]
     end
@@ -473,7 +473,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
         "cc" => [user.follower_address, recipient.follower_address]
       }
 
-      fixed_object = Transmogrifier.fix_explicit_addressing(object)
+      fixed_object = Transmogrifier.fix_explicit_addressing(object, user.follower_address)
 
       assert user.follower_address in fixed_object["cc"]
       refute recipient.follower_address in fixed_object["cc"]
index c39c1b1e19ed4fa62278c6135a97f246cdbd03e9..d8ca07cd37a8a030a4c939226dfe09fc7bd66015 100644 (file)
@@ -409,7 +409,7 @@ defmodule Pleroma.Web.AdminAPI.ConfigControllerTest do
     end
 
     test "saving config which need pleroma reboot", %{conn: conn} do
-      clear_config([:chat, :enabled], true)
+      clear_config([:shout, :enabled], true)
 
       assert conn
              |> put_req_header("content-type", "application/json")
@@ -417,7 +417,7 @@ defmodule Pleroma.Web.AdminAPI.ConfigControllerTest do
                "/api/pleroma/admin/config",
                %{
                  configs: [
-                   %{group: ":pleroma", key: ":chat", value: [%{"tuple" => [":enabled", true]}]}
+                   %{group: ":pleroma", key: ":shout", value: [%{"tuple" => [":enabled", true]}]}
                  ]
                }
              )
@@ -426,7 +426,7 @@ defmodule Pleroma.Web.AdminAPI.ConfigControllerTest do
                  %{
                    "db" => [":enabled"],
                    "group" => ":pleroma",
-                   "key" => ":chat",
+                   "key" => ":shout",
                    "value" => [%{"tuple" => [":enabled", true]}]
                  }
                ],
@@ -454,7 +454,7 @@ defmodule Pleroma.Web.AdminAPI.ConfigControllerTest do
     end
 
     test "update setting which need reboot, don't change reboot flag until reboot", %{conn: conn} do
-      clear_config([:chat, :enabled], true)
+      clear_config([:shout, :enabled], true)
 
       assert conn
              |> put_req_header("content-type", "application/json")
@@ -462,7 +462,7 @@ defmodule Pleroma.Web.AdminAPI.ConfigControllerTest do
                "/api/pleroma/admin/config",
                %{
                  configs: [
-                   %{group: ":pleroma", key: ":chat", value: [%{"tuple" => [":enabled", true]}]}
+                   %{group: ":pleroma", key: ":shout", value: [%{"tuple" => [":enabled", true]}]}
                  ]
                }
              )
@@ -471,7 +471,7 @@ defmodule Pleroma.Web.AdminAPI.ConfigControllerTest do
                  %{
                    "db" => [":enabled"],
                    "group" => ":pleroma",
-                   "key" => ":chat",
+                   "key" => ":shout",
                    "value" => [%{"tuple" => [":enabled", true]}]
                  }
                ],
index 8c7b63f3404f6d2f349c5df46aa61bc7780f6948..d9b25719a0bd1c56afaefe38533b19702f434004 100644 (file)
@@ -8,7 +8,7 @@ defmodule Pleroma.Web.AdminAPI.OAuthAppControllerTest do
 
   import Pleroma.Factory
 
-  alias Pleroma.Web
+  alias Pleroma.Web.Endpoint
 
   setup do
     admin = insert(:user, is_admin: true)
@@ -36,7 +36,7 @@ defmodule Pleroma.Web.AdminAPI.OAuthAppControllerTest do
     end
 
     test "success", %{conn: conn} do
-      base_url = Web.base_url()
+      base_url = Endpoint.url()
       app_name = "Trusted app"
 
       response =
@@ -58,7 +58,7 @@ defmodule Pleroma.Web.AdminAPI.OAuthAppControllerTest do
     end
 
     test "with trusted", %{conn: conn} do
-      base_url = Web.base_url()
+      base_url = Endpoint.url()
       app_name = "Trusted app"
 
       response =
index 31319b5e5480bdf097aded9a50a692969d436462..1a3aa439b687a08898d2c6ae5332ee748e84df6c 100644 (file)
@@ -14,9 +14,9 @@ defmodule Pleroma.Web.AdminAPI.UserControllerTest do
   alias Pleroma.Repo
   alias Pleroma.Tests.ObanHelpers
   alias Pleroma.User
-  alias Pleroma.Web
   alias Pleroma.Web.ActivityPub.Relay
   alias Pleroma.Web.CommonAPI
+  alias Pleroma.Web.Endpoint
   alias Pleroma.Web.MediaProxy
 
   setup_all do
@@ -411,7 +411,7 @@ defmodule Pleroma.Web.AdminAPI.UserControllerTest do
     end
 
     test "pagination works correctly with service users", %{conn: conn} do
-      service1 = User.get_or_create_service_actor_by_ap_id(Web.base_url() <> "/meido", "meido")
+      service1 = User.get_or_create_service_actor_by_ap_id(Endpoint.url() <> "/meido", "meido")
 
       insert_list(25, :user)
 
index f2043e15220e447ca327e347d9913f9f0e053cf7..b0e567ff0b250acf99905623d62847414cc50758 100644 (file)
@@ -168,6 +168,123 @@ defmodule Pleroma.Web.CommonAPI.UtilsTest do
     end
   end
 
+  describe "format_input/3 with markdown" do
+    test "Paragraph" do
+      code = ~s[Hello\n\nWorld!]
+      {result, [], []} = Utils.format_input(code, "text/markdown")
+      assert result == "<p>Hello</p><p>World!</p>"
+    end
+
+    test "links" do
+      code = "https://en.wikipedia.org/wiki/Animal_Crossing_(video_game)"
+      {result, [], []} = Utils.format_input(code, "text/markdown")
+      assert result == ~s[<p><a href="#{code}">#{code}</a></p>]
+
+      code = "https://github.com/pragdave/earmark/"
+      {result, [], []} = Utils.format_input(code, "text/markdown")
+      assert result == ~s[<p><a href="#{code}">#{code}</a></p>]
+    end
+
+    test "link with local mention" do
+      insert(:user, %{nickname: "lain"})
+
+      code = "https://example.com/@lain"
+      {result, [], []} = Utils.format_input(code, "text/markdown")
+      assert result == ~s[<p><a href="#{code}">#{code}</a></p>]
+    end
+
+    test "local mentions" do
+      mario = insert(:user, %{nickname: "mario"})
+      luigi = insert(:user, %{nickname: "luigi"})
+
+      code = "@mario @luigi yo what's up?"
+      {result, _, []} = Utils.format_input(code, "text/markdown")
+
+      assert result ==
+               ~s[<p><span class="h-card"><a class="u-url mention" data-user="#{mario.id}" href="#{
+                 mario.ap_id
+               }" rel="ugc">@<span>mario</span></a></span> <span class="h-card"><a class="u-url mention" data-user="#{
+                 luigi.id
+               }" href="#{luigi.ap_id}" rel="ugc">@<span>luigi</span></a></span> yo what’s up?</p>]
+    end
+
+    test "remote mentions" do
+      mario = insert(:user, %{nickname: "mario@mushroom.world", local: false})
+      luigi = insert(:user, %{nickname: "luigi@mushroom.world", local: false})
+
+      code = "@mario@mushroom.world @luigi@mushroom.world yo what's up?"
+      {result, _, []} = Utils.format_input(code, "text/markdown")
+
+      assert result ==
+               ~s[<p><span class="h-card"><a class="u-url mention" data-user="#{mario.id}" href="#{
+                 mario.ap_id
+               }" rel="ugc">@<span>mario</span></a></span> <span class="h-card"><a class="u-url mention" data-user="#{
+                 luigi.id
+               }" href="#{luigi.ap_id}" rel="ugc">@<span>luigi</span></a></span> yo what’s up?</p>]
+    end
+
+    test "raw HTML" do
+      code = ~s[<a href="http://example.org/">OwO</a><!-- what's this?-->]
+      {result, [], []} = Utils.format_input(code, "text/markdown")
+      assert result == ~s[<a href="http://example.org/">OwO</a>]
+    end
+
+    test "rulers" do
+      code = ~s[before\n\n-----\n\nafter]
+      {result, [], []} = Utils.format_input(code, "text/markdown")
+      assert result == "<p>before</p><hr/><p>after</p>"
+    end
+
+    test "blockquote" do
+      code = ~s[> whoms't are you quoting?]
+      {result, [], []} = Utils.format_input(code, "text/markdown")
+      assert result == "<blockquote><p>whoms’t are you quoting?</p></blockquote>"
+    end
+
+    test "code" do
+      code = ~s[`mix`]
+      {result, [], []} = Utils.format_input(code, "text/markdown")
+      assert result == ~s[<p><code class="inline">mix</code></p>]
+
+      code = ~s[``mix``]
+      {result, [], []} = Utils.format_input(code, "text/markdown")
+      assert result == ~s[<p><code class="inline">mix</code></p>]
+
+      code = ~s[```\nputs "Hello World"\n```]
+      {result, [], []} = Utils.format_input(code, "text/markdown")
+      assert result == ~s[<pre><code>puts &quot;Hello World&quot;</code></pre>]
+
+      code = ~s[    <div>\n    </div>]
+      {result, [], []} = Utils.format_input(code, "text/markdown")
+      assert result == ~s[<pre><code>&lt;div&gt;\n&lt;/div&gt;</code></pre>]
+    end
+
+    test "lists" do
+      code = ~s[- one\n- two\n- three\n- four]
+      {result, [], []} = Utils.format_input(code, "text/markdown")
+      assert result == "<ul><li>one</li><li>two</li><li>three</li><li>four</li></ul>"
+
+      code = ~s[1. one\n2. two\n3. three\n4. four\n]
+      {result, [], []} = Utils.format_input(code, "text/markdown")
+      assert result == "<ol><li>one</li><li>two</li><li>three</li><li>four</li></ol>"
+    end
+
+    test "delegated renderers" do
+      code = ~s[*aaaa~*]
+      {result, [], []} = Utils.format_input(code, "text/markdown")
+      assert result == ~s[<p><em>aaaa~</em></p>]
+
+      code = ~s[**aaaa~**]
+      {result, [], []} = Utils.format_input(code, "text/markdown")
+      assert result == ~s[<p><strong>aaaa~</strong></p>]
+
+      # strikethrough
+      code = ~s[~~aaaa~~~]
+      {result, [], []} = Utils.format_input(code, "text/markdown")
+      assert result == ~s[<p><del>aaaa</del>~</p>]
+    end
+  end
+
   describe "context_to_conversation_id" do
     test "creates a mapping object" do
       conversation_id = Utils.context_to_conversation_id("random context")
index 6619f8fc8c7b0fb900d2d86deca01bb5df59d0a3..a5dfd39342e0776954aff9b44233518d7ff55fd6 100644 (file)
@@ -519,7 +519,7 @@ defmodule Pleroma.Web.CommonAPITest do
       {:ok, activity} = CommonAPI.post(user, %{status: "hey :blank:"})
 
       assert %{"blank" => url} = Object.normalize(activity).data["emoji"]
-      assert url == "#{Pleroma.Web.base_url()}/emoji/blank.png"
+      assert url == "#{Pleroma.Web.Endpoint.url()}/emoji/blank.png"
     end
 
     test "it copies emoji from the subject of the parent post" do
@@ -539,8 +539,8 @@ defmodule Pleroma.Web.CommonAPITest do
           spoiler_text: ":joker_smile:"
         })
 
-      assert Object.normalize(reply_activity).data["emoji"][":joker_smile:"]
-      refute Object.normalize(reply_activity).data["emoji"][":joker_disapprove:"]
+      assert Object.normalize(reply_activity).data["emoji"]["joker_smile"]
+      refute Object.normalize(reply_activity).data["emoji"]["joker_disapprove"]
     end
 
     test "deactivated users can't post" do
@@ -597,7 +597,7 @@ defmodule Pleroma.Web.CommonAPITest do
 
       object = Object.normalize(activity, fetch: false)
 
-      assert object.data["content"] == "<p><b>2hu</b></p>alert(&#39;xss&#39;)"
+      assert object.data["content"] == "<p><b>2hu</b></p>"
       assert object.data["source"] == post
     end
 
@@ -827,13 +827,17 @@ defmodule Pleroma.Web.CommonAPITest do
       [user: user, activity: activity]
     end
 
+    test "activity not found error", %{user: user} do
+      assert {:error, :not_found} = CommonAPI.pin("id", user)
+    end
+
     test "pin status", %{user: user, activity: activity} do
       assert {:ok, ^activity} = CommonAPI.pin(activity.id, user)
 
-      id = activity.id
+      %{data: %{"id" => object_id}} = Object.normalize(activity)
       user = refresh_record(user)
 
-      assert %User{pinned_activities: [^id]} = user
+      assert user.pinned_objects |> Map.keys() == [object_id]
     end
 
     test "pin poll", %{user: user} do
@@ -845,10 +849,11 @@ defmodule Pleroma.Web.CommonAPITest do
 
       assert {:ok, ^activity} = CommonAPI.pin(activity.id, user)
 
-      id = activity.id
+      %{data: %{"id" => object_id}} = Object.normalize(activity)
+
       user = refresh_record(user)
 
-      assert %User{pinned_activities: [^id]} = user
+      assert user.pinned_objects |> Map.keys() == [object_id]
     end
 
     test "unlisted statuses can be pinned", %{user: user} do
@@ -859,7 +864,7 @@ defmodule Pleroma.Web.CommonAPITest do
     test "only self-authored can be pinned", %{activity: activity} do
       user = insert(:user)
 
-      assert {:error, "Could not pin"} = CommonAPI.pin(activity.id, user)
+      assert {:error, :ownership_error} = CommonAPI.pin(activity.id, user)
     end
 
     test "max pinned statuses", %{user: user, activity: activity_one} do
@@ -869,8 +874,12 @@ defmodule Pleroma.Web.CommonAPITest do
 
       user = refresh_record(user)
 
-      assert {:error, "You have already pinned the maximum number of statuses"} =
-               CommonAPI.pin(activity_two.id, user)
+      assert {:error, :pinned_statuses_limit_reached} = CommonAPI.pin(activity_two.id, user)
+    end
+
+    test "only public can be pinned", %{user: user} do
+      {:ok, activity} = CommonAPI.post(user, %{status: "private status", visibility: "private"})
+      {:error, :visibility_error} = CommonAPI.pin(activity.id, user)
     end
 
     test "unpin status", %{user: user, activity: activity} do
@@ -884,7 +893,7 @@ defmodule Pleroma.Web.CommonAPITest do
 
       user = refresh_record(user)
 
-      assert %User{pinned_activities: []} = user
+      assert user.pinned_objects == %{}
     end
 
     test "should unpin when deleting a status", %{user: user, activity: activity} do
@@ -896,7 +905,40 @@ defmodule Pleroma.Web.CommonAPITest do
 
       user = refresh_record(user)
 
-      assert %User{pinned_activities: []} = user
+      assert user.pinned_objects == %{}
+    end
+
+    test "ephemeral activity won't be deleted if was pinned", %{user: user} do
+      {:ok, activity} = CommonAPI.post(user, %{status: "Hello!", expires_in: 601})
+
+      assert Pleroma.Workers.PurgeExpiredActivity.get_expiration(activity.id)
+
+      {:ok, _activity} = CommonAPI.pin(activity.id, user)
+      refute Pleroma.Workers.PurgeExpiredActivity.get_expiration(activity.id)
+
+      user = refresh_record(user)
+      {:ok, _} = CommonAPI.unpin(activity.id, user)
+
+      # recreates expiration job on unpin
+      assert Pleroma.Workers.PurgeExpiredActivity.get_expiration(activity.id)
+    end
+
+    test "ephemeral activity deletion job won't be deleted on pinning error", %{
+      user: user,
+      activity: activity
+    } do
+      clear_config([:instance, :max_pinned_statuses], 1)
+
+      {:ok, _activity} = CommonAPI.pin(activity.id, user)
+
+      {:ok, activity2} = CommonAPI.post(user, %{status: "another status", expires_in: 601})
+
+      assert Pleroma.Workers.PurgeExpiredActivity.get_expiration(activity2.id)
+
+      user = refresh_record(user)
+      {:error, :pinned_statuses_limit_reached} = CommonAPI.pin(activity2.id, user)
+
+      assert Pleroma.Workers.PurgeExpiredActivity.get_expiration(activity2.id)
     end
   end
 
index 532ee6d306c84c435e33f7ccc406537e7d9344d7..372b6a73acd736d799c0a1262917ad1fdc04eb66 100644 (file)
@@ -123,7 +123,8 @@ defmodule Pleroma.Web.FederatorTest do
           "type" => "Note",
           "content" => "hi world!",
           "id" => "http://mastodon.example.org/users/admin/objects/1",
-          "attributedTo" => "http://mastodon.example.org/users/admin"
+          "attributedTo" => "http://mastodon.example.org/users/admin",
+          "to" => ["https://www.w3.org/ns/activitystreams#Public"]
         },
         "to" => ["https://www.w3.org/ns/activitystreams#Public"]
       }
@@ -145,7 +146,8 @@ defmodule Pleroma.Web.FederatorTest do
           "type" => "Note",
           "content" => "hi world!",
           "id" => "http://mastodon.example.org/users/admin/objects/1",
-          "attributedTo" => "http://mastodon.example.org/users/admin"
+          "attributedTo" => "http://mastodon.example.org/users/admin",
+          "to" => ["https://www.w3.org/ns/activitystreams#Public"]
         },
         "to" => ["https://www.w3.org/ns/activitystreams#Public"]
       }
index 5c9201de1e3de82c15cce7666fa258215f8e471f..140cdb8bf5f5c05b4dcb566d410ab70f7c1ee41f 100644 (file)
@@ -127,10 +127,10 @@ defmodule Pleroma.Web.Feed.TagControllerTest do
              "These are public toots tagged with #pleromaart. You can interact with them if you have an account anywhere in the fediverse."
 
     assert xpath(xml, ~x"//channel/link/text()") ==
-             '#{Pleroma.Web.base_url()}/tags/pleromaart.rss'
+             '#{Pleroma.Web.Endpoint.url()}/tags/pleromaart.rss'
 
     assert xpath(xml, ~x"//channel/webfeeds:logo/text()") ==
-             '#{Pleroma.Web.base_url()}/static/logo.svg'
+             '#{Pleroma.Web.Endpoint.url()}/static/logo.svg'
 
     assert xpath(xml, ~x"//channel/item/title/text()"l) == [
              '42 This is :moominmamm...',
index 408653d92db42165867584b555a50611b408ca33..6f6ff433f193332cd4c177c6f7f2fb9b41dce06a 100644 (file)
@@ -217,7 +217,9 @@ defmodule Pleroma.Web.Feed.UserControllerTest do
         |> get("/users/#{user.nickname}")
 
       assert conn.status == 302
-      assert redirected_to(conn) == "#{Pleroma.Web.base_url()}/users/#{user.nickname}/feed.atom"
+
+      assert redirected_to(conn) ==
+               "#{Pleroma.Web.Endpoint.url()}/users/#{user.nickname}/feed.atom"
     end
 
     test "with non-html / non-json format, it returns error when user is not found", %{conn: conn} do
index a327c0d1df62a04599ce4535ea1846d42dcfe1de..3036e25b3d633bce7f4283dfd3fec35f1a3d5a32 100644 (file)
@@ -514,11 +514,11 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
       {:ok, post_2} = CommonAPI.post(user, %{status: "second post"})
 
       response_1 = get(conn, "/api/v1/accounts/#{user.id}/statuses?limit=1")
-      assert [res] = json_response(response_1, 200)
+      assert [res] = json_response_and_validate_schema(response_1, 200)
       assert res["id"] == post_2.id
 
       response_2 = get(conn, "/api/v1/accounts/#{user.id}/statuses?limit=1&max_id=#{res["id"]}")
-      assert [res] = json_response(response_2, 200)
+      assert [res] = json_response_and_validate_schema(response_2, 200)
       assert res["id"] == post_1.id
 
       refute response_1 == response_2
@@ -881,7 +881,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
       assert [] ==
                conn
                |> get("/api/v1/timelines/home")
-               |> json_response(200)
+               |> json_response_and_validate_schema(200)
 
       assert %{"showing_reblogs" => true} =
                conn
@@ -892,7 +892,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
       assert [%{"id" => ^reblog_id}] =
                conn
                |> get("/api/v1/timelines/home")
-               |> json_response(200)
+               |> json_response_and_validate_schema(200)
     end
 
     test "following with reblogs" do
@@ -910,7 +910,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
       assert [%{"id" => ^reblog_id}] =
                conn
                |> get("/api/v1/timelines/home")
-               |> json_response(200)
+               |> json_response_and_validate_schema(200)
 
       assert %{"showing_reblogs" => false} =
                conn
@@ -921,7 +921,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
       assert [] ==
                conn
                |> get("/api/v1/timelines/home")
-               |> json_response(200)
+               |> json_response_and_validate_schema(200)
     end
 
     test "following / unfollowing errors", %{user: user, conn: conn} do
index 3176f12968069aac77bbdb8d15e321589dbabc52..00797a9ea6810aaa457de4411be52f4b2a624cc6 100644 (file)
@@ -214,7 +214,8 @@ defmodule Pleroma.Web.MastodonAPI.ConversationControllerTest do
 
     res_conn = get(conn, "/api/v1/statuses/#{direct.id}/context")
 
-    assert %{"ancestors" => [], "descendants" => []} == json_response(res_conn, 200)
+    assert %{"ancestors" => [], "descendants" => []} ==
+             json_response_and_validate_schema(res_conn, 200)
   end
 
   test "Removes a conversation", %{user: user_one, conn: conn} do
index b998566591512c873f763051a5cb73a93b85a6a9..e76cbc75b348a45f61c97b5f4366ef44e79951d4 100644 (file)
@@ -14,8 +14,8 @@ defmodule Pleroma.Web.MastodonAPI.InstanceControllerTest do
     assert result = json_response_and_validate_schema(conn, 200)
 
     email = Pleroma.Config.get([:instance, :email])
-    thumbnail = Pleroma.Web.base_url() <> Pleroma.Config.get([:instance, :instance_thumbnail])
-    background = Pleroma.Web.base_url() <> Pleroma.Config.get([:instance, :background_image])
+    thumbnail = Pleroma.Web.Endpoint.url() <> Pleroma.Config.get([:instance, :instance_thumbnail])
+    background = Pleroma.Web.Endpoint.url() <> Pleroma.Config.get([:instance, :background_image])
 
     # Note: not checking for "max_toot_chars" since it's optional
     assert %{
@@ -38,7 +38,7 @@ defmodule Pleroma.Web.MastodonAPI.InstanceControllerTest do
              "background_upload_limit" => _,
              "banner_upload_limit" => _,
              "background_image" => from_config_background,
-             "chat_limit" => _,
+             "shout_limit" => _,
              "description_limit" => _
            } = result
 
index 6c8f984d50ea9009364fe4a7ffd6400fbaa8ade2..39d7f99f6596c01d3510a6d6689b994fc80bec67 100644 (file)
@@ -140,7 +140,7 @@ defmodule Pleroma.Web.MastodonAPI.MediaControllerTest do
 
       conn
       |> get("/api/v1/media/#{object.id}")
-      |> json_response(403)
+      |> json_response_and_validate_schema(403)
     end
   end
 end
index 1dd0fa3b8392c6518947ab83060c53b68d7e01cc..7b0bbd8bd6779ba640fe6ede11fbf145a9bfb726 100644 (file)
@@ -6,8 +6,8 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do
   use Pleroma.Web.ConnCase
 
   alias Pleroma.Object
-  alias Pleroma.Web
   alias Pleroma.Web.CommonAPI
+  alias Pleroma.Web.Endpoint
   import Pleroma.Factory
   import ExUnit.CaptureLog
   import Tesla.Mock
@@ -61,7 +61,7 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do
       assert account["id"] == to_string(user_three.id)
 
       assert results["hashtags"] == [
-               %{"name" => "private", "url" => "#{Web.base_url()}/tag/private"}
+               %{"name" => "private", "url" => "#{Endpoint.url()}/tag/private"}
              ]
 
       [status] = results["statuses"]
@@ -72,7 +72,7 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do
         |> json_response_and_validate_schema(200)
 
       assert results["hashtags"] == [
-               %{"name" => "天子", "url" => "#{Web.base_url()}/tag/天子"}
+               %{"name" => "天子", "url" => "#{Endpoint.url()}/tag/天子"}
              ]
 
       [status] = results["statuses"]
@@ -87,8 +87,8 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do
         |> json_response_and_validate_schema(200)
 
       assert results["hashtags"] == [
-               %{"name" => "explicit", "url" => "#{Web.base_url()}/tag/explicit"},
-               %{"name" => "hashtags", "url" => "#{Web.base_url()}/tag/hashtags"}
+               %{"name" => "explicit", "url" => "#{Endpoint.url()}/tag/explicit"},
+               %{"name" => "hashtags", "url" => "#{Endpoint.url()}/tag/hashtags"}
              ]
 
       results =
@@ -97,9 +97,9 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do
         |> json_response_and_validate_schema(200)
 
       assert results["hashtags"] == [
-               %{"name" => "john", "url" => "#{Web.base_url()}/tag/john"},
-               %{"name" => "doe", "url" => "#{Web.base_url()}/tag/doe"},
-               %{"name" => "JohnDoe", "url" => "#{Web.base_url()}/tag/JohnDoe"}
+               %{"name" => "john", "url" => "#{Endpoint.url()}/tag/john"},
+               %{"name" => "doe", "url" => "#{Endpoint.url()}/tag/doe"},
+               %{"name" => "JohnDoe", "url" => "#{Endpoint.url()}/tag/JohnDoe"}
              ]
 
       results =
@@ -108,9 +108,9 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do
         |> json_response_and_validate_schema(200)
 
       assert results["hashtags"] == [
-               %{"name" => "accident", "url" => "#{Web.base_url()}/tag/accident"},
-               %{"name" => "prone", "url" => "#{Web.base_url()}/tag/prone"},
-               %{"name" => "AccidentProne", "url" => "#{Web.base_url()}/tag/AccidentProne"}
+               %{"name" => "accident", "url" => "#{Endpoint.url()}/tag/accident"},
+               %{"name" => "prone", "url" => "#{Endpoint.url()}/tag/prone"},
+               %{"name" => "AccidentProne", "url" => "#{Endpoint.url()}/tag/AccidentProne"}
              ]
 
       results =
@@ -119,7 +119,7 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do
         |> json_response_and_validate_schema(200)
 
       assert results["hashtags"] == [
-               %{"name" => "shpuld", "url" => "#{Web.base_url()}/tag/shpuld"}
+               %{"name" => "shpuld", "url" => "#{Endpoint.url()}/tag/shpuld"}
              ]
 
       results =
@@ -136,18 +136,18 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do
         |> json_response_and_validate_schema(200)
 
       assert results["hashtags"] == [
-               %{"name" => "nascar", "url" => "#{Web.base_url()}/tag/nascar"},
-               %{"name" => "ban", "url" => "#{Web.base_url()}/tag/ban"},
-               %{"name" => "display", "url" => "#{Web.base_url()}/tag/display"},
-               %{"name" => "confederate", "url" => "#{Web.base_url()}/tag/confederate"},
-               %{"name" => "flag", "url" => "#{Web.base_url()}/tag/flag"},
-               %{"name" => "all", "url" => "#{Web.base_url()}/tag/all"},
-               %{"name" => "events", "url" => "#{Web.base_url()}/tag/events"},
-               %{"name" => "properties", "url" => "#{Web.base_url()}/tag/properties"},
+               %{"name" => "nascar", "url" => "#{Endpoint.url()}/tag/nascar"},
+               %{"name" => "ban", "url" => "#{Endpoint.url()}/tag/ban"},
+               %{"name" => "display", "url" => "#{Endpoint.url()}/tag/display"},
+               %{"name" => "confederate", "url" => "#{Endpoint.url()}/tag/confederate"},
+               %{"name" => "flag", "url" => "#{Endpoint.url()}/tag/flag"},
+               %{"name" => "all", "url" => "#{Endpoint.url()}/tag/all"},
+               %{"name" => "events", "url" => "#{Endpoint.url()}/tag/events"},
+               %{"name" => "properties", "url" => "#{Endpoint.url()}/tag/properties"},
                %{
                  "name" => "NascarBanDisplayConfederateFlagAllEventsProperties",
                  "url" =>
-                   "#{Web.base_url()}/tag/NascarBanDisplayConfederateFlagAllEventsProperties"
+                   "#{Endpoint.url()}/tag/NascarBanDisplayConfederateFlagAllEventsProperties"
                }
              ]
     end
@@ -163,8 +163,8 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do
         |> json_response_and_validate_schema(200)
 
       assert results["hashtags"] == [
-               %{"name" => "text", "url" => "#{Web.base_url()}/tag/text"},
-               %{"name" => "with", "url" => "#{Web.base_url()}/tag/with"}
+               %{"name" => "text", "url" => "#{Endpoint.url()}/tag/text"},
+               %{"name" => "with", "url" => "#{Endpoint.url()}/tag/with"}
              ]
     end
 
index f616f405e39d4a029defa8f6de4ec7e06ba9557c..d478a81ee7dfbb3a4bc2a0e580bc23a9f3bf548a 100644 (file)
@@ -14,6 +14,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
   alias Pleroma.Tests.ObanHelpers
   alias Pleroma.User
   alias Pleroma.Web.ActivityPub.ActivityPub
+  alias Pleroma.Web.ActivityPub.Utils
   alias Pleroma.Web.CommonAPI
 
   import Pleroma.Factory
@@ -81,6 +82,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
           "sensitive" => 0
         })
 
+      # Idempotency plug response means detection fail
       assert %{"id" => second_id} = json_response(conn_two, 200)
       assert id == second_id
 
@@ -1209,20 +1211,27 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
     setup do: clear_config([:instance, :max_pinned_statuses], 1)
 
     test "pin status", %{conn: conn, user: user, activity: activity} do
-      id_str = to_string(activity.id)
+      id = activity.id
 
-      assert %{"id" => ^id_str, "pinned" => true} =
+      assert %{"id" => ^id, "pinned" => true} =
                conn
                |> put_req_header("content-type", "application/json")
                |> post("/api/v1/statuses/#{activity.id}/pin")
                |> json_response_and_validate_schema(200)
 
-      assert [%{"id" => ^id_str, "pinned" => true}] =
+      assert [%{"id" => ^id, "pinned" => true}] =
                conn
                |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
                |> json_response_and_validate_schema(200)
     end
 
+    test "non authenticated user", %{activity: activity} do
+      assert build_conn()
+             |> put_req_header("content-type", "application/json")
+             |> post("/api/v1/statuses/#{activity.id}/pin")
+             |> json_response(403) == %{"error" => "Invalid credentials."}
+    end
+
     test "/pin: returns 400 error when activity is not public", %{conn: conn, user: user} do
       {:ok, dm} = CommonAPI.post(user, %{status: "test", visibility: "direct"})
 
@@ -1231,7 +1240,18 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
         |> put_req_header("content-type", "application/json")
         |> post("/api/v1/statuses/#{dm.id}/pin")
 
-      assert json_response_and_validate_schema(conn, 400) == %{"error" => "Could not pin"}
+      assert json_response_and_validate_schema(conn, 422) == %{
+               "error" => "Non-public status cannot be pinned"
+             }
+    end
+
+    test "pin by another user", %{activity: activity} do
+      %{conn: conn} = oauth_access(["write:accounts"])
+
+      assert conn
+             |> put_req_header("content-type", "application/json")
+             |> post("/api/v1/statuses/#{activity.id}/pin")
+             |> json_response(422) == %{"error" => "Someone else's status cannot be pinned"}
     end
 
     test "unpin status", %{conn: conn, user: user, activity: activity} do
@@ -1252,13 +1272,11 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
                |> json_response_and_validate_schema(200)
     end
 
-    test "/unpin: returns 400 error when activity is not exist", %{conn: conn} do
-      conn =
-        conn
-        |> put_req_header("content-type", "application/json")
-        |> post("/api/v1/statuses/1/unpin")
-
-      assert json_response_and_validate_schema(conn, 400) == %{"error" => "Could not unpin"}
+    test "/unpin: returns 404 error when activity doesn't exist", %{conn: conn} do
+      assert conn
+             |> put_req_header("content-type", "application/json")
+             |> post("/api/v1/statuses/1/unpin")
+             |> json_response_and_validate_schema(404) == %{"error" => "Record not found"}
     end
 
     test "max pinned statuses", %{conn: conn, user: user, activity: activity_one} do
@@ -1542,7 +1560,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
       |> assign(:token, insert(:oauth_token, user: user3, scopes: ["read:statuses"]))
       |> get("api/v1/timelines/home")
 
-    [reblogged_activity] = json_response(conn3, 200)
+    [reblogged_activity] = json_response_and_validate_schema(conn3, 200)
 
     assert reblogged_activity["reblog"]["in_reply_to_id"] == replied_to.id
 
@@ -1893,10 +1911,10 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
         "visibility" => "local"
       })
 
-    local = Pleroma.Constants.as_local_public()
+    local = Utils.as_local_public()
 
     assert %{"content" => "cofe", "id" => id, "visibility" => "local"} =
-             json_response(conn_one, 200)
+             json_response_and_validate_schema(conn_one, 200)
 
     assert %Activity{id: ^id, data: %{"to" => [^local]}} = Activity.get_by_id(id)
   end
index cc409451c1a4c398a0db6980017811c9038ea7fb..ed1286675cdb0419719998048e5bfc07bf74f86a 100644 (file)
@@ -905,10 +905,10 @@ defmodule Pleroma.Web.MastodonAPI.TimelineControllerTest do
       %{conn: auth_conn} = oauth_access(["read:statuses"])
 
       res_conn = get(auth_conn, "#{base_uri}?local=true")
-      assert length(json_response(res_conn, 200)) == 1
+      assert length(json_response_and_validate_schema(res_conn, 200)) == 1
 
       res_conn = get(auth_conn, "#{base_uri}?local=false")
-      assert length(json_response(res_conn, 200)) == 2
+      assert length(json_response_and_validate_schema(res_conn, 200)) == 2
     end
 
     test "with default settings on private instances, returns 403 for unauthenticated users", %{
@@ -922,7 +922,7 @@ defmodule Pleroma.Web.MastodonAPI.TimelineControllerTest do
       for local <- [true, false] do
         res_conn = get(conn, "#{base_uri}?local=#{local}")
 
-        assert json_response(res_conn, :unauthorized) == error_response
+        assert json_response_and_validate_schema(res_conn, :unauthorized) == error_response
       end
 
       ensure_authenticated_access(base_uri)
@@ -939,7 +939,7 @@ defmodule Pleroma.Web.MastodonAPI.TimelineControllerTest do
       for local <- [true, false] do
         res_conn = get(conn, "#{base_uri}?local=#{local}")
 
-        assert json_response(res_conn, :unauthorized) == error_response
+        assert json_response_and_validate_schema(res_conn, :unauthorized) == error_response
       end
 
       ensure_authenticated_access(base_uri)
@@ -951,10 +951,10 @@ defmodule Pleroma.Web.MastodonAPI.TimelineControllerTest do
       clear_config([:restrict_unauthenticated, :timelines, :federated], true)
 
       res_conn = get(conn, "#{base_uri}?local=true")
-      assert length(json_response(res_conn, 200)) == 1
+      assert length(json_response_and_validate_schema(res_conn, 200)) == 1
 
       res_conn = get(conn, "#{base_uri}?local=false")
-      assert json_response(res_conn, :unauthorized) == error_response
+      assert json_response_and_validate_schema(res_conn, :unauthorized) == error_response
 
       ensure_authenticated_access(base_uri)
     end
@@ -966,11 +966,11 @@ defmodule Pleroma.Web.MastodonAPI.TimelineControllerTest do
       clear_config([:restrict_unauthenticated, :timelines, :federated], false)
 
       res_conn = get(conn, "#{base_uri}?local=true")
-      assert json_response(res_conn, :unauthorized) == error_response
+      assert json_response_and_validate_schema(res_conn, :unauthorized) == error_response
 
       # Note: local activities get delivered as part of federated timeline
       res_conn = get(conn, "#{base_uri}?local=false")
-      assert length(json_response(res_conn, 200)) == 2
+      assert length(json_response_and_validate_schema(res_conn, 200)) == 2
 
       ensure_authenticated_access(base_uri)
     end
diff --git a/test/pleroma/web/mastodon_api/masto_fe_controller_test.exs b/test/pleroma/web/mastodon_api/masto_fe_controller_test.exs
new file mode 100644 (file)
index 0000000..e679d78
--- /dev/null
@@ -0,0 +1,85 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.MastodonAPI.MastoFEControllerTest do
+  use Pleroma.Web.ConnCase
+
+  alias Pleroma.User
+
+  import Pleroma.Factory
+
+  setup do: clear_config([:instance, :public])
+
+  test "put settings", %{conn: conn} do
+    user = insert(:user)
+
+    conn =
+      conn
+      |> assign(:user, user)
+      |> assign(:token, insert(:oauth_token, user: user, scopes: ["write:accounts"]))
+      |> put("/api/web/settings", %{"data" => %{"programming" => "socks"}})
+
+    assert %{} = json_response(conn, 200)
+
+    user = User.get_cached_by_ap_id(user.ap_id)
+    assert user.mastofe_settings == %{"programming" => "socks"}
+  end
+
+  describe "index/2 redirections" do
+    setup %{conn: conn} do
+      session_opts = [
+        store: :cookie,
+        key: "_test",
+        signing_salt: "cooldude"
+      ]
+
+      conn =
+        conn
+        |> Plug.Session.call(Plug.Session.init(session_opts))
+        |> fetch_session()
+
+      test_path = "/web/statuses/test"
+      %{conn: conn, path: test_path}
+    end
+
+    test "redirects not logged-in users to the login page", %{conn: conn, path: path} do
+      conn = get(conn, path)
+
+      assert conn.status == 302
+      assert redirected_to(conn) == "/web/login"
+    end
+
+    test "redirects not logged-in users to the login page on private instances", %{
+      conn: conn,
+      path: path
+    } do
+      clear_config([:instance, :public], false)
+
+      conn = get(conn, path)
+
+      assert conn.status == 302
+      assert redirected_to(conn) == "/web/login"
+    end
+
+    test "does not redirect logged in users to the login page", %{conn: conn, path: path} do
+      {:ok, app} = Pleroma.Web.MastodonAPI.AuthController.local_mastofe_app()
+      token = insert(:oauth_token, app: app, scopes: ["read"])
+
+      conn =
+        conn
+        |> assign(:user, token.user)
+        |> assign(:token, token)
+        |> get(path)
+
+      assert conn.status == 200
+    end
+
+    test "saves referer path to session", %{conn: conn, path: path} do
+      conn = get(conn, path)
+      return_to = Plug.Conn.get_session(conn, :return_to)
+
+      assert return_to == path
+    end
+  end
+end
index 5373a17c38f84a0db34a20ef213e0812daadd7b3..60881756d9b402a3d624f44b17d6239c66d10994 100644 (file)
@@ -468,6 +468,23 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
                %{user: user, for: user}
              )[:pleroma][:unread_notifications_count] == 7
     end
+
+    test "shows email only to the account owner" do
+      user = insert(:user)
+      other_user = insert(:user)
+
+      user = User.get_cached_by_ap_id(user.ap_id)
+
+      assert AccountView.render(
+               "show.json",
+               %{user: user, for: other_user}
+             )[:pleroma][:email] == nil
+
+      assert AccountView.render(
+               "show.json",
+               %{user: user, for: user}
+             )[:pleroma][:email] == user.email
+    end
   end
 
   describe "follow requests counter" do
@@ -562,12 +579,12 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
       AccountView.render("show.json", %{user: user, skip_visibility_check: true})
       |> Enum.all?(fn
         {key, url} when key in [:avatar, :avatar_static, :header, :header_static] ->
-          String.starts_with?(url, Pleroma.Web.base_url())
+          String.starts_with?(url, Pleroma.Web.Endpoint.url())
 
         {:emojis, emojis} ->
           Enum.all?(emojis, fn %{url: url, static_url: static_url} ->
-            String.starts_with?(url, Pleroma.Web.base_url()) &&
-              String.starts_with?(static_url, Pleroma.Web.base_url())
+            String.starts_with?(url, Pleroma.Web.Endpoint.url()) &&
+              String.starts_with?(static_url, Pleroma.Web.Endpoint.url())
           end)
 
         _ ->
index 4172cc2945ecb280720f3d06f30763ba99f8190e..9dfdf8bf064da99035d867dc7f2a022cb84fc0f9 100644 (file)
@@ -286,7 +286,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do
         direct_conversation_id: nil,
         thread_muted: false,
         emoji_reactions: [],
-        parent_visible: false
+        parent_visible: false,
+        pinned_at: nil
       }
     }
 
@@ -458,7 +459,9 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do
       "url" => [
         %{
           "mediaType" => "image/png",
-          "href" => "someurl"
+          "href" => "someurl",
+          "width" => 200,
+          "height" => 100
         }
       ],
       "blurhash" => "UJJ8X[xYW,%Jtq%NNFbXB5j]IVM|9GV=WHRn",
@@ -474,6 +477,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do
       text_url: "someurl",
       description: nil,
       pleroma: %{mime_type: "image/png"},
+      meta: %{original: %{width: 200, height: 100, aspect: 2}},
       blurhash: "UJJ8X[xYW,%Jtq%NNFbXB5j]IVM|9GV=WHRn"
     }
 
index b5ee6328d7aaa25e7fc746ed87d9642f8ae70aff..d97874f3ae2c43727533a943bb491e76ff91d262 100644 (file)
@@ -41,7 +41,7 @@ defmodule Pleroma.Web.MediaProxyTest do
 
       assert String.starts_with?(
                encoded,
-               Config.get([:media_proxy, :base_url], Pleroma.Web.base_url())
+               Config.get([:media_proxy, :base_url], Pleroma.Web.Endpoint.url())
              )
 
       assert String.ends_with?(encoded, "/logo.png")
index 312500febf243a04ca96526f9de790513f324a1f..0fdd5b8e9fb853e6cd2f4a84ef6eb1ad39030292 100644 (file)
@@ -805,10 +805,12 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
           "client_secret" => app.client_secret
         })
 
-      assert %{"access_token" => token} = json_response(conn, 200)
+      assert %{"id" => id, "access_token" => access_token} = json_response(conn, 200)
 
-      token = Repo.get_by(Token, token: token)
+      token = Repo.get_by(Token, token: access_token)
       assert token
+      assert token.id == id
+      assert token.token == access_token
       assert token.scopes == app.scopes
     end
 
index 2038f4ddd12bd654062a9484695892f59ba381b9..81d66983763b1f879775d0ab1aaeafaaddc5f68f 100644 (file)
@@ -182,7 +182,7 @@ defmodule Pleroma.Web.OStatus.OStatusControllerTest do
         |> response(200)
 
       assert resp =~
-               "<meta content=\"#{Pleroma.Web.base_url()}/notice/#{note_activity.id}\" property=\"og:url\">"
+               "<meta content=\"#{Pleroma.Web.Endpoint.url()}/notice/#{note_activity.id}\" property=\"og:url\">"
 
       user = insert(:user)
 
index 25a7f8374b38c6c1dfd0fec9aa6e8a2840628d95..d977bc3a22903a4104b75cb15a40770f6bed9bc6 100644 (file)
@@ -83,7 +83,7 @@ defmodule Pleroma.Web.PleromaAPI.UserImportControllerTest do
           assert %{"error" => "Insufficient permissions: follow | write:follows."} ==
                    json_response(conn, 403)
         else
-          assert json_response(conn, 200)
+          assert json_response_and_validate_schema(conn, 200)
         end
       end
     end
index 9b6821ff9fa6de971effc6b4b8877f2f2085883e..a9342e6f02306906a19ce36fa1365aea9a9fd408 100644 (file)
@@ -100,6 +100,6 @@ defmodule Pleroma.Web.Plugs.FrontendStaticPlugTest do
       "check_password"
     ]
 
-    assert expected_routes == Pleroma.Web.get_api_routes()
+    assert expected_routes == Pleroma.Web.Router.get_api_routes()
   end
 end
similarity index 81%
rename from test/pleroma/web/chat_channel_test.exs
rename to test/pleroma/web/shout_channel_test.ex
index 29999701c52f315a7a69ab38bd780e4629c81e6a..a266543d26ddbc3a9f92d4e11ba530550da548b8 100644 (file)
@@ -2,9 +2,9 @@
 # Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
 # SPDX-License-Identifier: AGPL-3.0-only
 
-defmodule Pleroma.Web.ChatChannelTest do
+defmodule Pleroma.Web.ShoutChannelTest do
   use Pleroma.Web.ChannelCase
-  alias Pleroma.Web.ChatChannel
+  alias Pleroma.Web.ShoutChannel
   alias Pleroma.Web.UserSocket
 
   import Pleroma.Factory
@@ -14,7 +14,7 @@ defmodule Pleroma.Web.ChatChannelTest do
 
     {:ok, _, socket} =
       socket(UserSocket, "", %{user_name: user.nickname})
-      |> subscribe_and_join(ChatChannel, "chat:public")
+      |> subscribe_and_join(ShoutChannel, "shout:public")
 
     {:ok, socket: socket}
   end
@@ -25,7 +25,7 @@ defmodule Pleroma.Web.ChatChannelTest do
   end
 
   describe "message lengths" do
-    setup do: clear_config([:instance, :chat_limit])
+    setup do: clear_config([:shout, :limit])
 
     test "it ignores messages of length zero", %{socket: socket} do
       push(socket, "new_msg", %{"text" => ""})
@@ -33,7 +33,7 @@ defmodule Pleroma.Web.ChatChannelTest do
     end
 
     test "it ignores messages above a certain length", %{socket: socket} do
-      clear_config([:instance, :chat_limit], 2)
+      clear_config([:shout, :limit], 2)
       push(socket, "new_msg", %{"text" => "123"})
       refute_broadcast("new_msg", %{text: "123"})
     end
index 2af14dfebcbe1b7c4ef5288a0a905c19fe3d978e..5752cffda5f18b1f5c8fce7acbb787ed6eea234e 100644 (file)
@@ -7,6 +7,7 @@ defmodule Pleroma.Web.StaticFE.StaticFEControllerTest do
 
   alias Pleroma.Activity
   alias Pleroma.Web.ActivityPub.Transmogrifier
+  alias Pleroma.Web.ActivityPub.Utils
   alias Pleroma.Web.CommonAPI
 
   import Pleroma.Factory
@@ -185,16 +186,16 @@ defmodule Pleroma.Web.StaticFE.StaticFEControllerTest do
     test "302 for remote cached status", %{conn: conn, user: user} do
       message = %{
         "@context" => "https://www.w3.org/ns/activitystreams",
-        "to" => user.follower_address,
-        "cc" => "https://www.w3.org/ns/activitystreams#Public",
         "type" => "Create",
+        "actor" => user.ap_id,
         "object" => %{
+          "to" => user.follower_address,
+          "cc" => "https://www.w3.org/ns/activitystreams#Public",
+          "id" => Utils.generate_object_id(),
           "content" => "blah blah blah",
           "type" => "Note",
-          "attributedTo" => user.ap_id,
-          "inReplyTo" => nil
-        },
-        "actor" => user.ap_id
+          "attributedTo" => user.ap_id
+        }
       }
 
       assert {:ok, activity} = Transmogrifier.handle_incoming(message)
index 583c904b2e18ac1fb059dd4c2db4a24dff0fb197..bca9e2dad6f7ae7b139fc8d77cc57ddcfc1768e0 100644 (file)
@@ -7,59 +7,10 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do
 
   alias Pleroma.Repo
   alias Pleroma.User
-  alias Pleroma.Web.CommonAPI
   alias Pleroma.Web.OAuth.Token
 
   import Pleroma.Factory
 
-  describe "POST /api/qvitter/statuses/notifications/read" do
-    test "without valid credentials", %{conn: conn} do
-      conn = post(conn, "/api/qvitter/statuses/notifications/read", %{"latest_id" => 1_234_567})
-      assert json_response(conn, 403) == %{"error" => "Invalid credentials."}
-    end
-
-    test "with credentials, without any params" do
-      %{conn: conn} = oauth_access(["write:notifications"])
-
-      conn = post(conn, "/api/qvitter/statuses/notifications/read")
-
-      assert json_response(conn, 400) == %{
-               "error" => "You need to specify latest_id",
-               "request" => "/api/qvitter/statuses/notifications/read"
-             }
-    end
-
-    test "with credentials, with params" do
-      %{user: current_user, conn: conn} =
-        oauth_access(["read:notifications", "write:notifications"])
-
-      other_user = insert(:user)
-
-      {:ok, _activity} =
-        CommonAPI.post(other_user, %{
-          status: "Hey @#{current_user.nickname}"
-        })
-
-      response_conn =
-        conn
-        |> get("/api/v1/notifications")
-
-      [notification] = json_response(response_conn, 200)
-
-      assert notification["pleroma"]["is_seen"] == false
-
-      response_conn =
-        conn
-        |> post("/api/qvitter/statuses/notifications/read", %{"latest_id" => notification["id"]})
-
-      [notification] = response = json_response(response_conn, 200)
-
-      assert length(response) == 1
-
-      assert notification["pleroma"]["is_seen"] == true
-    end
-  end
-
   describe "GET /api/account/confirm_email/:id/:token" do
     setup do
       {:ok, user} =
index f389c272bc0aad4faa4e418a700cf31e84f9c9df..fa3b290063ab2a5fceaefdd89ff74a28453f9404 100644 (file)
@@ -27,6 +27,16 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowControllerTest do
             body: File.read!("test/fixtures/tesla_mock/status.emelie.json")
           }
 
+        %{method: :get, url: "https://mastodon.social/users/emelie/collections/featured"} ->
+          %Tesla.Env{
+            status: 200,
+            headers: [{"content-type", "application/activity+json"}],
+            body:
+              File.read!("test/fixtures/users_mock/masto_featured.json")
+              |> String.replace("{{domain}}", "mastodon.social")
+              |> String.replace("{{nickname}}", "emelie")
+          }
+
         %{method: :get, url: "https://mastodon.social/users/emelie"} ->
           %Tesla.Env{
             status: 200,
@@ -52,6 +62,16 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowControllerTest do
             headers: [{"content-type", "application/activity+json"}],
             body: File.read!("test/fixtures/tesla_mock/emelie.json")
           }
+
+        %{method: :get, url: "https://mastodon.social/users/emelie/collections/featured"} ->
+          %Tesla.Env{
+            status: 200,
+            headers: [{"content-type", "application/activity+json"}],
+            body:
+              File.read!("test/fixtures/users_mock/masto_featured.json")
+              |> String.replace("{{domain}}", "mastodon.social")
+              |> String.replace("{{nickname}}", "emelie")
+          }
       end)
 
       response =
@@ -70,6 +90,16 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowControllerTest do
             headers: [{"content-type", "application/activity+json"}],
             body: File.read!("test/fixtures/tesla_mock/emelie.json")
           }
+
+        %{method: :get, url: "https://mastodon.social/users/emelie/collections/featured"} ->
+          %Tesla.Env{
+            status: 200,
+            headers: [{"content-type", "application/activity+json"}],
+            body:
+              File.read!("test/fixtures/users_mock/masto_featured.json")
+              |> String.replace("{{domain}}", "mastodon.social")
+              |> String.replace("{{nickname}}", "emelie")
+          }
       end)
 
       user = insert(:user)
index bdbc478c367fb0661e3417c3bf07c34b407c5f86..cc17940b5c078dfc3bf6f05a7de8ddfa47d02228 100644 (file)
@@ -25,11 +25,14 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
 
     test "it updates notification settings", %{user: user, conn: conn} do
       conn
-      |> put("/api/pleroma/notification_settings", %{
-        "block_from_strangers" => true,
-        "bar" => 1
-      })
-      |> json_response(:ok)
+      |> put(
+        "/api/pleroma/notification_settings?#{
+          URI.encode_query(%{
+            block_from_strangers: true
+          })
+        }"
+      )
+      |> json_response_and_validate_schema(:ok)
 
       user = refresh_record(user)
 
@@ -41,8 +44,14 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
 
     test "it updates notification settings to enable hiding contents", %{user: user, conn: conn} do
       conn
-      |> put("/api/pleroma/notification_settings", %{"hide_notification_contents" => "1"})
-      |> json_response(:ok)
+      |> put(
+        "/api/pleroma/notification_settings?#{
+          URI.encode_query(%{
+            hide_notification_contents: 1
+          })
+        }"
+      )
+      |> json_response_and_validate_schema(:ok)
 
       user = refresh_record(user)
 
@@ -70,7 +79,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
       response =
         conn
         |> get("/api/pleroma/frontend_configurations")
-        |> json_response(:ok)
+        |> json_response_and_validate_schema(:ok)
 
       assert response == Jason.encode!(config |> Enum.into(%{})) |> Jason.decode!()
     end
@@ -81,7 +90,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
       emoji =
         conn
         |> get("/api/pleroma/emoji")
-        |> json_response(200)
+        |> json_response_and_validate_schema(200)
 
       assert Enum.all?(emoji, fn
                {_key,
@@ -103,7 +112,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
       response =
         conn
         |> get("/api/pleroma/healthcheck")
-        |> json_response(503)
+        |> json_response_and_validate_schema(503)
 
       assert response == %{}
     end
@@ -116,7 +125,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
         response =
           conn
           |> get("/api/pleroma/healthcheck")
-          |> json_response(200)
+          |> json_response_and_validate_schema(200)
 
         assert %{
                  "active" => _,
@@ -136,7 +145,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
         response =
           conn
           |> get("/api/pleroma/healthcheck")
-          |> json_response(503)
+          |> json_response_and_validate_schema(503)
 
         assert %{
                  "active" => _,
@@ -155,8 +164,8 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
     test "with valid permissions and password, it disables the account", %{conn: conn, user: user} do
       response =
         conn
-        |> post("/api/pleroma/disable_account", %{"password" => "test"})
-        |> json_response(:ok)
+        |> post("/api/pleroma/disable_account?password=test")
+        |> json_response_and_validate_schema(:ok)
 
       assert response == %{"status" => "success"}
       ObanHelpers.perform_all()
@@ -171,8 +180,8 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
 
       response =
         conn
-        |> post("/api/pleroma/disable_account", %{"password" => "test1"})
-        |> json_response(:ok)
+        |> post("/api/pleroma/disable_account?password=test1")
+        |> json_response_and_validate_schema(:ok)
 
       assert response == %{"error" => "Invalid password."}
       user = User.get_cached_by_id(user.id)
@@ -252,54 +261,61 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
       conn =
         conn
         |> assign(:token, nil)
-        |> post("/api/pleroma/change_email")
-
-      assert json_response(conn, 403) == %{"error" => "Insufficient permissions: write:accounts."}
+        |> post(
+          "/api/pleroma/change_email?#{
+            URI.encode_query(%{password: "hi", email: "test@test.com"})
+          }"
+        )
+
+      assert json_response_and_validate_schema(conn, 403) == %{
+               "error" => "Insufficient permissions: write:accounts."
+             }
     end
 
     test "with proper permissions and invalid password", %{conn: conn} do
       conn =
-        post(conn, "/api/pleroma/change_email", %{
-          "password" => "hi",
-          "email" => "test@test.com"
-        })
-
-      assert json_response(conn, 200) == %{"error" => "Invalid password."}
+        post(
+          conn,
+          "/api/pleroma/change_email?#{
+            URI.encode_query(%{password: "hi", email: "test@test.com"})
+          }"
+        )
+
+      assert json_response_and_validate_schema(conn, 200) == %{"error" => "Invalid password."}
     end
 
     test "with proper permissions, valid password and invalid email", %{
       conn: conn
     } do
       conn =
-        post(conn, "/api/pleroma/change_email", %{
-          "password" => "test",
-          "email" => "foobar"
-        })
+        post(
+          conn,
+          "/api/pleroma/change_email?#{URI.encode_query(%{password: "test", email: "foobar"})}"
+        )
 
-      assert json_response(conn, 200) == %{"error" => "Email has invalid format."}
+      assert json_response_and_validate_schema(conn, 200) == %{
+               "error" => "Email has invalid format."
+             }
     end
 
     test "with proper permissions, valid password and no email", %{
       conn: conn
     } do
-      conn =
-        post(conn, "/api/pleroma/change_email", %{
-          "password" => "test"
-        })
+      conn = post(conn, "/api/pleroma/change_email?#{URI.encode_query(%{password: "test"})}")
 
-      assert json_response(conn, 200) == %{"error" => "Email can't be blank."}
+      assert %{"error" => "Missing field: email."} = json_response_and_validate_schema(conn, 400)
     end
 
     test "with proper permissions, valid password and blank email", %{
       conn: conn
     } do
       conn =
-        post(conn, "/api/pleroma/change_email", %{
-          "password" => "test",
-          "email" => ""
-        })
+        post(
+          conn,
+          "/api/pleroma/change_email?#{URI.encode_query(%{password: "test", email: ""})}"
+        )
 
-      assert json_response(conn, 200) == %{"error" => "Email can't be blank."}
+      assert json_response_and_validate_schema(conn, 200) == %{"error" => "Email can't be blank."}
     end
 
     test "with proper permissions, valid password and non unique email", %{
@@ -308,24 +324,28 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
       user = insert(:user)
 
       conn =
-        post(conn, "/api/pleroma/change_email", %{
-          "password" => "test",
-          "email" => user.email
-        })
+        post(
+          conn,
+          "/api/pleroma/change_email?#{URI.encode_query(%{password: "test", email: user.email})}"
+        )
 
-      assert json_response(conn, 200) == %{"error" => "Email has already been taken."}
+      assert json_response_and_validate_schema(conn, 200) == %{
+               "error" => "Email has already been taken."
+             }
     end
 
     test "with proper permissions, valid password and valid email", %{
       conn: conn
     } do
       conn =
-        post(conn, "/api/pleroma/change_email", %{
-          "password" => "test",
-          "email" => "cofe@foobar.com"
-        })
-
-      assert json_response(conn, 200) == %{"status" => "success"}
+        post(
+          conn,
+          "/api/pleroma/change_email?#{
+            URI.encode_query(%{password: "test", email: "cofe@foobar.com"})
+          }"
+        )
+
+      assert json_response_and_validate_schema(conn, 200) == %{"status" => "success"}
     end
   end
 
@@ -336,20 +356,35 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
       conn =
         conn
         |> assign(:token, nil)
-        |> post("/api/pleroma/change_password")
-
-      assert json_response(conn, 403) == %{"error" => "Insufficient permissions: write:accounts."}
+        |> post(
+          "/api/pleroma/change_password?#{
+            URI.encode_query(%{
+              password: "hi",
+              new_password: "newpass",
+              new_password_confirmation: "newpass"
+            })
+          }"
+        )
+
+      assert json_response_and_validate_schema(conn, 403) == %{
+               "error" => "Insufficient permissions: write:accounts."
+             }
     end
 
     test "with proper permissions and invalid password", %{conn: conn} do
       conn =
-        post(conn, "/api/pleroma/change_password", %{
-          "password" => "hi",
-          "new_password" => "newpass",
-          "new_password_confirmation" => "newpass"
-        })
-
-      assert json_response(conn, 200) == %{"error" => "Invalid password."}
+        post(
+          conn,
+          "/api/pleroma/change_password?#{
+            URI.encode_query(%{
+              password: "hi",
+              new_password: "newpass",
+              new_password_confirmation: "newpass"
+            })
+          }"
+        )
+
+      assert json_response_and_validate_schema(conn, 200) == %{"error" => "Invalid password."}
     end
 
     test "with proper permissions, valid password and new password and confirmation not matching",
@@ -357,13 +392,18 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
            conn: conn
          } do
       conn =
-        post(conn, "/api/pleroma/change_password", %{
-          "password" => "test",
-          "new_password" => "newpass",
-          "new_password_confirmation" => "notnewpass"
-        })
-
-      assert json_response(conn, 200) == %{
+        post(
+          conn,
+          "/api/pleroma/change_password?#{
+            URI.encode_query(%{
+              password: "test",
+              new_password: "newpass",
+              new_password_confirmation: "notnewpass"
+            })
+          }"
+        )
+
+      assert json_response_and_validate_schema(conn, 200) == %{
                "error" => "New password does not match confirmation."
              }
     end
@@ -372,13 +412,14 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
       conn: conn
     } do
       conn =
-        post(conn, "/api/pleroma/change_password", %{
-          "password" => "test",
-          "new_password" => "",
-          "new_password_confirmation" => ""
-        })
-
-      assert json_response(conn, 200) == %{
+        post(
+          conn,
+          "/api/pleroma/change_password?#{
+            URI.encode_query(%{password: "test", new_password: "", new_password_confirmation: ""})
+          }"
+        )
+
+      assert json_response_and_validate_schema(conn, 200) == %{
                "error" => "New password can't be blank."
              }
     end
@@ -388,13 +429,18 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
       user: user
     } do
       conn =
-        post(conn, "/api/pleroma/change_password", %{
-          "password" => "test",
-          "new_password" => "newpass",
-          "new_password_confirmation" => "newpass"
-        })
-
-      assert json_response(conn, 200) == %{"status" => "success"}
+        post(
+          conn,
+          "/api/pleroma/change_password?#{
+            URI.encode_query(%{
+              password: "test",
+              new_password: "newpass",
+              new_password_confirmation: "newpass"
+            })
+          }"
+        )
+
+      assert json_response_and_validate_schema(conn, 200) == %{"status" => "success"}
       fetched_user = User.get_cached_by_id(user.id)
       assert Pleroma.Password.Pbkdf2.verify_pass("newpass", fetched_user.password_hash) == true
     end
@@ -409,7 +455,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
         |> assign(:token, nil)
         |> post("/api/pleroma/delete_account")
 
-      assert json_response(conn, 403) ==
+      assert json_response_and_validate_schema(conn, 403) ==
                %{"error" => "Insufficient permissions: write:accounts."}
     end
 
@@ -417,14 +463,16 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
       for params <- [%{"password" => "hi"}, %{}] do
         ret_conn = post(conn, "/api/pleroma/delete_account", params)
 
-        assert json_response(ret_conn, 200) == %{"error" => "Invalid password."}
+        assert json_response_and_validate_schema(ret_conn, 200) == %{
+                 "error" => "Invalid password."
+               }
       end
     end
 
     test "with proper permissions and valid password", %{conn: conn, user: user} do
-      conn = post(conn, "/api/pleroma/delete_account", %{"password" => "test"})
+      conn = post(conn, "/api/pleroma/delete_account?password=test")
       ObanHelpers.perform_all()
-      assert json_response(conn, 200) == %{"status" => "success"}
+      assert json_response_and_validate_schema(conn, 200) == %{"status" => "success"}
 
       user = User.get_by_id(user.id)
       refute user.is_active
index 7059850bd795689a247d298b4c08d5d5eda9d214..2421c5800a82487db8d3120e5ea8707da2e0524a 100644 (file)
@@ -25,7 +25,7 @@ defmodule Pleroma.Web.WebFinger.WebFingerControllerTest do
 
     assert response.resp_body ==
              ~s(<?xml version="1.0" encoding="UTF-8"?><XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0"><Link rel="lrdd" template="#{
-               Pleroma.Web.base_url()
+               Pleroma.Web.Endpoint.url()
              }/.well-known/webfinger?resource={uri}" type="application/xrd+xml" /></XRD>)
   end
 
index 2d7b4a40bd48843928ba9df5efa9e269dd2cf3ea..0a36d57e6af96d781b1a7d5e3f8391c07d2f0eb8 100644 (file)
@@ -17,7 +17,7 @@ defmodule Pleroma.Web.WebFingerTest do
     test "returns a link to the xml lrdd" do
       host_info = WebFinger.host_meta()
 
-      assert String.contains?(host_info, Pleroma.Web.base_url())
+      assert String.contains?(host_info, Pleroma.Web.Endpoint.url())
     end
   end
 
index af4fff45b947c771e5d622b1ff7af73cf9108388..c267dba4ef41140f404ae2902662197c086b37aa 100644 (file)
@@ -4,6 +4,9 @@
 
 defmodule Pleroma.Factory do
   use ExMachina.Ecto, repo: Pleroma.Repo
+
+  require Pleroma.Constants
+
   alias Pleroma.Object
   alias Pleroma.User
 
@@ -41,23 +44,27 @@ defmodule Pleroma.Factory do
 
     urls =
       if attrs[:local] == false do
-        base_domain = Enum.random(["domain1.com", "domain2.com", "domain3.com"])
+        base_domain = attrs[:domain] || Enum.random(["domain1.com", "domain2.com", "domain3.com"])
 
         ap_id = "https://#{base_domain}/users/#{user.nickname}"
 
         %{
           ap_id: ap_id,
           follower_address: ap_id <> "/followers",
-          following_address: ap_id <> "/following"
+          following_address: ap_id <> "/following",
+          featured_address: ap_id <> "/collections/featured"
         }
       else
         %{
           ap_id: User.ap_id(user),
           follower_address: User.ap_followers(user),
-          following_address: User.ap_following(user)
+          following_address: User.ap_following(user),
+          featured_address: User.ap_featured_collection(user)
         }
       end
 
+    attrs = Map.delete(attrs, :domain)
+
     user
     |> Map.put(:raw_bio, user.bio)
     |> Map.merge(urls)
@@ -184,8 +191,8 @@ defmodule Pleroma.Factory do
   end
 
   def article_factory do
-    note_factory()
-    |> Map.put("type", "Article")
+    %Pleroma.Object{data: data} = note_factory()
+    %Pleroma.Object{data: Map.merge(data, %{"type" => "Article"})}
   end
 
   def tombstone_factory do
@@ -221,6 +228,45 @@ defmodule Pleroma.Factory do
     }
   end
 
+  def add_activity_factory(attrs \\ %{}) do
+    featured_collection_activity(attrs, "Add")
+  end
+
+  def remove_activity_factor(attrs \\ %{}) do
+    featured_collection_activity(attrs, "Remove")
+  end
+
+  defp featured_collection_activity(attrs, type) do
+    user = attrs[:user] || insert(:user)
+    note = attrs[:note] || insert(:note, user: user)
+
+    data_attrs =
+      attrs
+      |> Map.get(:data_attrs, %{})
+      |> Map.put(:type, type)
+
+    attrs = Map.drop(attrs, [:user, :note, :data_attrs])
+
+    data =
+      %{
+        "id" => Pleroma.Web.ActivityPub.Utils.generate_activity_id(),
+        "target" => user.featured_address,
+        "object" => note.data["object"],
+        "actor" => note.data["actor"],
+        "type" => "Add",
+        "to" => [Pleroma.Constants.as_public()],
+        "cc" => [user.follower_address]
+      }
+      |> Map.merge(data_attrs)
+
+    %Pleroma.Activity{
+      data: data,
+      actor: data["actor"],
+      recipients: data["to"]
+    }
+    |> Map.merge(attrs)
+  end
+
   def note_activity_factory(attrs \\ %{}) do
     user = attrs[:user] || insert(:user)
     note = attrs[:note] || insert(:note, user: user)
index eb692fab5d1366e707578496b819bb305e3e1c8e..8807c2d14679551e778c3cfe2661cefb6a54aec4 100644 (file)
@@ -89,6 +89,18 @@ defmodule HttpRequestMock do
      }}
   end
 
+  def get("https://mastodon.sdf.org/users/rinpatch/collections/featured", _, _, _) do
+    {:ok,
+     %Tesla.Env{
+       status: 200,
+       body:
+         File.read!("test/fixtures/users_mock/masto_featured.json")
+         |> String.replace("{{domain}}", "mastodon.sdf.org")
+         |> String.replace("{{nickname}}", "rinpatch"),
+       headers: [{"content-type", "application/activity+json"}]
+     }}
+  end
+
   def get("https://patch.cx/objects/tesla_mock/poll_attachment", _, _, _) do
     {:ok,
      %Tesla.Env{
@@ -905,6 +917,18 @@ defmodule HttpRequestMock do
      }}
   end
 
+  def get("https://mastodon.social/users/lambadalambda/collections/featured", _, _, _) do
+    {:ok,
+     %Tesla.Env{
+       status: 200,
+       body:
+         File.read!("test/fixtures/users_mock/masto_featured.json")
+         |> String.replace("{{domain}}", "mastodon.social")
+         |> String.replace("{{nickname}}", "lambadalambda"),
+       headers: activitypub_object_headers()
+     }}
+  end
+
   def get("https://apfed.club/channel/indio", _, _, _) do
     {:ok,
      %Tesla.Env{