summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/mix/pleroma.ex5
-rw-r--r--lib/mix/tasks/pleroma/app.ex2
-rw-r--r--lib/mix/tasks/pleroma/benchmark.ex2
-rw-r--r--lib/mix/tasks/pleroma/config.ex58
-rw-r--r--lib/mix/tasks/pleroma/count_statuses.ex2
-rw-r--r--lib/mix/tasks/pleroma/database.ex96
-rw-r--r--lib/mix/tasks/pleroma/digest.ex2
-rw-r--r--lib/mix/tasks/pleroma/docs.ex2
-rw-r--r--lib/mix/tasks/pleroma/ecto.ex4
-rw-r--r--lib/mix/tasks/pleroma/ecto/migrate.ex4
-rw-r--r--lib/mix/tasks/pleroma/ecto/rollback.ex9
-rw-r--r--lib/mix/tasks/pleroma/email.ex8
-rw-r--r--lib/mix/tasks/pleroma/emoji.ex2
-rw-r--r--lib/mix/tasks/pleroma/frontend.ex2
-rw-r--r--lib/mix/tasks/pleroma/instance.ex59
-rw-r--r--lib/mix/tasks/pleroma/notification_settings.ex2
-rw-r--r--lib/mix/tasks/pleroma/openapi_spec.ex12
-rw-r--r--lib/mix/tasks/pleroma/refresh_counter_cache.ex2
-rw-r--r--lib/mix/tasks/pleroma/relay.ex2
-rw-r--r--lib/mix/tasks/pleroma/robots_txt.ex2
-rw-r--r--lib/mix/tasks/pleroma/uploads.ex2
-rw-r--r--lib/mix/tasks/pleroma/user.ex123
-rw-r--r--lib/phoenix/transports/web_socket/raw.ex2
-rw-r--r--lib/pleroma/activity.ex126
-rw-r--r--lib/pleroma/activity/html.ex81
-rw-r--r--lib/pleroma/activity/ir/topics.ex45
-rw-r--r--lib/pleroma/activity/queries.ex13
-rw-r--r--lib/pleroma/activity/search.ex68
-rw-r--r--lib/pleroma/announcement.ex160
-rw-r--r--lib/pleroma/announcement_read_relationship.ex55
-rw-r--r--lib/pleroma/application.ex73
-rw-r--r--lib/pleroma/application_requirements.ex50
-rw-r--r--lib/pleroma/bbs/authenticator.ex2
-rw-r--r--lib/pleroma/bbs/handler.ex111
-rw-r--r--lib/pleroma/bookmark.ex2
-rw-r--r--lib/pleroma/caching.ex2
-rw-r--r--lib/pleroma/captcha.ex2
-rw-r--r--lib/pleroma/captcha/kocaptcha.ex2
-rw-r--r--lib/pleroma/captcha/native.ex2
-rw-r--r--lib/pleroma/captcha/service.ex2
-rw-r--r--lib/pleroma/chat.ex2
-rw-r--r--lib/pleroma/chat/message_reference.ex2
-rw-r--r--lib/pleroma/clippy.ex2
-rw-r--r--lib/pleroma/config.ex14
-rw-r--r--lib/pleroma/config/deprecation_warnings.ex250
-rw-r--r--lib/pleroma/config/getting.ex2
-rw-r--r--lib/pleroma/config/helpers.ex2
-rw-r--r--lib/pleroma/config/holder.ex2
-rw-r--r--lib/pleroma/config/loader.ex23
-rw-r--r--lib/pleroma/config/oban.ex6
-rw-r--r--lib/pleroma/config/release_runtime_provider.ex22
-rw-r--r--lib/pleroma/config/transfer_task.ex53
-rw-r--r--lib/pleroma/config_db.ex7
-rw-r--r--lib/pleroma/constants.ex47
-rw-r--r--lib/pleroma/conversation.ex10
-rw-r--r--lib/pleroma/conversation/participation.ex6
-rw-r--r--lib/pleroma/conversation/participation/recipient_ship.ex2
-rw-r--r--lib/pleroma/counter_cache.ex2
-rw-r--r--lib/pleroma/data_migration.ex46
-rw-r--r--lib/pleroma/delivery.ex3
-rw-r--r--lib/pleroma/docs/generator.ex2
-rw-r--r--lib/pleroma/docs/json.ex2
-rw-r--r--lib/pleroma/docs/markdown.ex2
-rw-r--r--lib/pleroma/docs/translator.ex10
-rw-r--r--lib/pleroma/docs/translator/compiler.ex119
-rw-r--r--lib/pleroma/earmark_renderer.ex256
-rw-r--r--lib/pleroma/ecto_enums.ex14
-rw-r--r--lib/pleroma/ecto_type/activity_pub/object_validators/date_time.ex2
-rw-r--r--lib/pleroma/ecto_type/activity_pub/object_validators/emoji.ex2
-rw-r--r--lib/pleroma/ecto_type/activity_pub/object_validators/mime.ex25
-rw-r--r--lib/pleroma/ecto_type/activity_pub/object_validators/object_id.ex2
-rw-r--r--lib/pleroma/ecto_type/activity_pub/object_validators/recipients.ex38
-rw-r--r--lib/pleroma/ecto_type/activity_pub/object_validators/safe_text.ex2
-rw-r--r--lib/pleroma/ecto_type/activity_pub/object_validators/uri.ex2
-rw-r--r--lib/pleroma/ecto_type/config/atom.ex2
-rw-r--r--lib/pleroma/ecto_type/config/binary_value.ex2
-rw-r--r--lib/pleroma/emails/admin_email.ex6
-rw-r--r--lib/pleroma/emails/mailer.ex2
-rw-r--r--lib/pleroma/emails/new_users_digest_email.ex2
-rw-r--r--lib/pleroma/emails/user_email.ex464
-rw-r--r--lib/pleroma/emoji-test.txt233
-rw-r--r--lib/pleroma/emoji.ex16
-rw-r--r--lib/pleroma/emoji/combinations.ex45
-rw-r--r--lib/pleroma/emoji/formatter.ex5
-rw-r--r--lib/pleroma/emoji/loader.ex25
-rw-r--r--lib/pleroma/emoji/pack.ex2
-rw-r--r--lib/pleroma/filter.ex126
-rw-r--r--lib/pleroma/following_relationship.ex7
-rw-r--r--lib/pleroma/formatter.ex50
-rw-r--r--lib/pleroma/frontend.ex2
-rw-r--r--lib/pleroma/gopher/server.ex4
-rw-r--r--lib/pleroma/gun.ex6
-rw-r--r--lib/pleroma/gun/api.ex2
-rw-r--r--lib/pleroma/gun/conn.ex10
-rw-r--r--lib/pleroma/gun/connection_pool.ex2
-rw-r--r--lib/pleroma/gun/connection_pool/reclaimer.ex8
-rw-r--r--lib/pleroma/gun/connection_pool/worker.ex12
-rw-r--r--lib/pleroma/gun/connection_pool/worker_supervisor.ex2
-rw-r--r--lib/pleroma/hashtag.ex106
-rw-r--r--lib/pleroma/healthcheck.ex2
-rw-r--r--lib/pleroma/helpers/auth_helper.ex2
-rw-r--r--lib/pleroma/helpers/inet_helper.ex2
-rw-r--r--lib/pleroma/helpers/media_helper.ex2
-rw-r--r--lib/pleroma/helpers/qt_fast_start.ex2
-rw-r--r--lib/pleroma/helpers/uri_helper.ex2
-rw-r--r--lib/pleroma/html.ex37
-rw-r--r--lib/pleroma/http.ex11
-rw-r--r--lib/pleroma/http/adapter_helper.ex2
-rw-r--r--lib/pleroma/http/adapter_helper/default.ex2
-rw-r--r--lib/pleroma/http/adapter_helper/gun.ex6
-rw-r--r--lib/pleroma/http/adapter_helper/hackney.ex6
-rw-r--r--lib/pleroma/http/ex_aws.ex2
-rw-r--r--lib/pleroma/http/request.ex2
-rw-r--r--lib/pleroma/http/request_builder.ex2
-rw-r--r--lib/pleroma/http/tzdata.ex2
-rw-r--r--lib/pleroma/http/web_push.ex6
-rw-r--r--lib/pleroma/instances.ex19
-rw-r--r--lib/pleroma/instances/instance.ex24
-rw-r--r--lib/pleroma/job_queue_monitor.ex2
-rw-r--r--lib/pleroma/jwt.ex2
-rw-r--r--lib/pleroma/keys.ex2
-rw-r--r--lib/pleroma/list.ex10
-rw-r--r--lib/pleroma/logging.ex2
-rw-r--r--lib/pleroma/maintenance.ex6
-rw-r--r--lib/pleroma/maps.ex8
-rw-r--r--lib/pleroma/marker.ex2
-rw-r--r--lib/pleroma/mfa.ex4
-rw-r--r--lib/pleroma/mfa/backup_codes.ex2
-rw-r--r--lib/pleroma/mfa/changeset.ex2
-rw-r--r--lib/pleroma/mfa/settings.ex2
-rw-r--r--lib/pleroma/mfa/token.ex2
-rw-r--r--lib/pleroma/mfa/totp.ex2
-rw-r--r--lib/pleroma/migration_helper/notification_backfill.ex2
-rw-r--r--lib/pleroma/migrators/context_objects_deletion_migrator.ex139
-rw-r--r--lib/pleroma/migrators/hashtags_table_migrator.ex208
-rw-r--r--lib/pleroma/migrators/support/base_migrator.ex210
-rw-r--r--lib/pleroma/migrators/support/base_migrator_state.ex117
-rw-r--r--lib/pleroma/moderation_log.ex30
-rw-r--r--lib/pleroma/notification.ex165
-rw-r--r--lib/pleroma/object.ex133
-rw-r--r--lib/pleroma/object/containment.ex10
-rw-r--r--lib/pleroma/object/fetcher.ex55
-rw-r--r--lib/pleroma/object/updater.ex240
-rw-r--r--lib/pleroma/object_tombstone.ex2
-rw-r--r--lib/pleroma/otp_version.ex2
-rw-r--r--lib/pleroma/pagination.ex5
-rw-r--r--lib/pleroma/password/pbkdf2.ex55
-rw-r--r--lib/pleroma/password_reset_token.ex2
-rw-r--r--lib/pleroma/registration.ex2
-rw-r--r--lib/pleroma/release_tasks.ex2
-rw-r--r--lib/pleroma/repo.ex8
-rw-r--r--lib/pleroma/report_note.ex2
-rw-r--r--lib/pleroma/reverse_proxy.ex30
-rw-r--r--lib/pleroma/reverse_proxy/client.ex20
-rw-r--r--lib/pleroma/reverse_proxy/client/hackney.ex3
-rw-r--r--lib/pleroma/reverse_proxy/client/tesla.ex2
-rw-r--r--lib/pleroma/reverse_proxy/client/wrapper.ex30
-rw-r--r--lib/pleroma/scheduled_activity.ex2
-rw-r--r--lib/pleroma/signature.ex30
-rw-r--r--lib/pleroma/stats.ex10
-rw-r--r--lib/pleroma/telemetry/logger.ex10
-rw-r--r--lib/pleroma/tesla/middleware/connection_pool.ex2
-rw-r--r--lib/pleroma/tests/auth_test_controller.ex14
-rw-r--r--lib/pleroma/thread_mute.ex2
-rw-r--r--lib/pleroma/upload.ex97
-rw-r--r--lib/pleroma/upload/filter.ex8
-rw-r--r--lib/pleroma/upload/filter/analyze_metadata.ex83
-rw-r--r--lib/pleroma/upload/filter/anonymize_filename.ex2
-rw-r--r--lib/pleroma/upload/filter/dedupe.ex2
-rw-r--r--lib/pleroma/upload/filter/exiftool/read_description.ex49
-rw-r--r--lib/pleroma/upload/filter/exiftool/strip_location.ex (renamed from lib/pleroma/upload/filter/exiftool.ex)11
-rw-r--r--lib/pleroma/upload/filter/mogrifun.ex6
-rw-r--r--lib/pleroma/upload/filter/mogrify.ex6
-rw-r--r--lib/pleroma/uploaders/local.ex2
-rw-r--r--lib/pleroma/uploaders/s3.ex20
-rw-r--r--lib/pleroma/uploaders/uploader.ex10
-rw-r--r--lib/pleroma/user.ex640
-rw-r--r--lib/pleroma/user/backup.ex20
-rw-r--r--lib/pleroma/user/import.ex2
-rw-r--r--lib/pleroma/user/notification_setting.ex2
-rw-r--r--lib/pleroma/user/query.ex47
-rw-r--r--lib/pleroma/user/search.ex7
-rw-r--r--lib/pleroma/user/welcome_chat_message.ex2
-rw-r--r--lib/pleroma/user/welcome_email.ex2
-rw-r--r--lib/pleroma/user/welcome_message.ex2
-rw-r--r--lib/pleroma/user_invite_token.ex2
-rw-r--r--lib/pleroma/user_note.ex52
-rw-r--r--lib/pleroma/user_relationship.ex49
-rw-r--r--lib/pleroma/utils.ex26
-rw-r--r--lib/pleroma/web.ex23
-rw-r--r--lib/pleroma/web/activity_pub/activity_pub.ex570
-rw-r--r--lib/pleroma/web/activity_pub/activity_pub/persisting.ex4
-rw-r--r--lib/pleroma/web/activity_pub/activity_pub/streaming.ex10
-rw-r--r--lib/pleroma/web/activity_pub/activity_pub_controller.ex240
-rw-r--r--lib/pleroma/web/activity_pub/builder.ex83
-rw-r--r--lib/pleroma/web/activity_pub/internal_fetch_actor.ex2
-rw-r--r--lib/pleroma/web/activity_pub/mrf.ex79
-rw-r--r--lib/pleroma/web/activity_pub/mrf/activity_expiration_policy.ex4
-rw-r--r--lib/pleroma/web/activity_pub/mrf/anti_followbot_policy.ex25
-rw-r--r--lib/pleroma/web/activity_pub/mrf/anti_link_spam_policy.ex7
-rw-r--r--lib/pleroma/web/activity_pub/mrf/drop_policy.ex4
-rw-r--r--lib/pleroma/web/activity_pub/mrf/ensure_re_prepended.ex12
-rw-r--r--lib/pleroma/web/activity_pub/mrf/follow_bot_policy.ex63
-rw-r--r--lib/pleroma/web/activity_pub/mrf/force_bot_unlisted_policy.ex6
-rw-r--r--lib/pleroma/web/activity_pub/mrf/force_mentions_in_content.ex135
-rw-r--r--lib/pleroma/web/activity_pub/mrf/hashtag_policy.ex143
-rw-r--r--lib/pleroma/web/activity_pub/mrf/hellthread_policy.ex4
-rw-r--r--lib/pleroma/web/activity_pub/mrf/keyword_policy.ex63
-rw-r--r--lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex15
-rw-r--r--lib/pleroma/web/activity_pub/mrf/mention_policy.ex4
-rw-r--r--lib/pleroma/web/activity_pub/mrf/no_empty_policy.ex70
-rw-r--r--lib/pleroma/web/activity_pub/mrf/no_op_policy.ex4
-rw-r--r--lib/pleroma/web/activity_pub/mrf/no_placeholder_text_policy.ex11
-rw-r--r--lib/pleroma/web/activity_pub/mrf/normalize_markup.ex10
-rw-r--r--lib/pleroma/web/activity_pub/mrf/object_age_policy.ex12
-rw-r--r--lib/pleroma/web/activity_pub/mrf/pipeline_filtering.ex2
-rw-r--r--lib/pleroma/web/activity_pub/mrf/policy.ex17
-rw-r--r--lib/pleroma/web/activity_pub/mrf/reject_non_public.ex6
-rw-r--r--lib/pleroma/web/activity_pub/mrf/simple_policy.ex231
-rw-r--r--lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex147
-rw-r--r--lib/pleroma/web/activity_pub/mrf/subchain_policy.ex8
-rw-r--r--lib/pleroma/web/activity_pub/mrf/tag_policy.ex27
-rw-r--r--lib/pleroma/web/activity_pub/mrf/user_allow_list_policy.ex6
-rw-r--r--lib/pleroma/web/activity_pub/mrf/vocabulary_policy.ex6
-rw-r--r--lib/pleroma/web/activity_pub/object_validator.ex281
-rw-r--r--lib/pleroma/web/activity_pub/object_validator/validating.ex2
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/accept_reject_validator.ex18
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/add_remove_validator.ex78
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/announce_validator.ex47
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/answer_validator.ex24
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/article_note_page_validator.ex109
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/article_note_validator.ex106
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex40
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/audio_video_validator.ex92
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/block_validator.ex25
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/chat_message_validator.ex4
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/common_fields.ex67
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/common_fixes.ex68
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/common_validations.ex13
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/create_chat_message_validator.ex13
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/create_generic_validator.ex83
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/create_note_validator.ex29
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/delete_validator.ex30
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/emoji_react_validator.ex66
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/event_validator.ex56
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/follow_validator.ex20
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/like_validator.ex69
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/question_options_validator.ex2
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/question_validator.ex52
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/tag_validator.ex77
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/undo_validator.ex30
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/update_validator.ex19
-rw-r--r--lib/pleroma/web/activity_pub/pipeline.ex45
-rw-r--r--lib/pleroma/web/activity_pub/publisher.ex30
-rw-r--r--lib/pleroma/web/activity_pub/relay.ex2
-rw-r--r--lib/pleroma/web/activity_pub/side_effects.ex214
-rw-r--r--lib/pleroma/web/activity_pub/side_effects/handling.ex2
-rw-r--r--lib/pleroma/web/activity_pub/transmogrifier.ex253
-rw-r--r--lib/pleroma/web/activity_pub/utils.ex116
-rw-r--r--lib/pleroma/web/activity_pub/views/object_view.ex8
-rw-r--r--lib/pleroma/web/activity_pub/views/user_view.ex34
-rw-r--r--lib/pleroma/web/activity_pub/visibility.ex31
-rw-r--r--lib/pleroma/web/admin_api/controllers/admin_api_controller.ex42
-rw-r--r--lib/pleroma/web/admin_api/controllers/announcement_controller.ex83
-rw-r--r--lib/pleroma/web/admin_api/controllers/chat_controller.ex13
-rw-r--r--lib/pleroma/web/admin_api/controllers/config_controller.ex58
-rw-r--r--lib/pleroma/web/admin_api/controllers/fallback_controller.ex2
-rw-r--r--lib/pleroma/web/admin_api/controllers/frontend_controller.ex14
-rw-r--r--lib/pleroma/web/admin_api/controllers/instance_controller.ex63
-rw-r--r--lib/pleroma/web/admin_api/controllers/instance_document_controller.ex6
-rw-r--r--lib/pleroma/web/admin_api/controllers/invite_controller.ex6
-rw-r--r--lib/pleroma/web/admin_api/controllers/media_proxy_cache_controller.ex6
-rw-r--r--lib/pleroma/web/admin_api/controllers/o_auth_app_controller.ex5
-rw-r--r--lib/pleroma/web/admin_api/controllers/relay_controller.ex6
-rw-r--r--lib/pleroma/web/admin_api/controllers/report_controller.ex6
-rw-r--r--lib/pleroma/web/admin_api/controllers/status_controller.ex12
-rw-r--r--lib/pleroma/web/admin_api/controllers/user_controller.ex172
-rw-r--r--lib/pleroma/web/admin_api/report.ex45
-rw-r--r--lib/pleroma/web/admin_api/search.ex10
-rw-r--r--lib/pleroma/web/admin_api/views/account_view.ex33
-rw-r--r--lib/pleroma/web/admin_api/views/announcement_view.ex15
-rw-r--r--lib/pleroma/web/admin_api/views/chat_view.ex2
-rw-r--r--lib/pleroma/web/admin_api/views/config_view.ex2
-rw-r--r--lib/pleroma/web/admin_api/views/frontend_view.ex2
-rw-r--r--lib/pleroma/web/admin_api/views/invite_view.ex2
-rw-r--r--lib/pleroma/web/admin_api/views/media_proxy_cache_view.ex2
-rw-r--r--lib/pleroma/web/admin_api/views/moderation_log_view.ex2
-rw-r--r--lib/pleroma/web/admin_api/views/o_auth_app_view.ex10
-rw-r--r--lib/pleroma/web/admin_api/views/report_view.ex2
-rw-r--r--lib/pleroma/web/admin_api/views/status_view.ex6
-rw-r--r--lib/pleroma/web/admin_api/views/user_view.ex10
-rw-r--r--lib/pleroma/web/api_spec.ex96
-rw-r--r--lib/pleroma/web/api_spec/cast_and_validate.ex33
-rw-r--r--lib/pleroma/web/api_spec/helpers.ex4
-rw-r--r--lib/pleroma/web/api_spec/operations/account_operation.ex251
-rw-r--r--lib/pleroma/web/api_spec/operations/admin/announcement_operation.ex165
-rw-r--r--lib/pleroma/web/api_spec/operations/admin/chat_operation.ex16
-rw-r--r--lib/pleroma/web/api_spec/operations/admin/config_operation.ex20
-rw-r--r--lib/pleroma/web/api_spec/operations/admin/frontend_operation.ex12
-rw-r--r--lib/pleroma/web/api_spec/operations/admin/instance_document_operation.ex20
-rw-r--r--lib/pleroma/web/api_spec/operations/admin/invite_operation.ex18
-rw-r--r--lib/pleroma/web/api_spec/operations/admin/media_proxy_cache_operation.ex22
-rw-r--r--lib/pleroma/web/api_spec/operations/admin/o_auth_app_operation.ex26
-rw-r--r--lib/pleroma/web/api_spec/operations/admin/relay_operation.ex20
-rw-r--r--lib/pleroma/web/api_spec/operations/admin/report_operation.ex40
-rw-r--r--lib/pleroma/web/api_spec/operations/admin/status_operation.ex29
-rw-r--r--lib/pleroma/web/api_spec/operations/admin/user_operation.ex453
-rw-r--r--lib/pleroma/web/api_spec/operations/announcement_operation.ex57
-rw-r--r--lib/pleroma/web/api_spec/operations/app_operation.ex37
-rw-r--r--lib/pleroma/web/api_spec/operations/chat_operation.ex52
-rw-r--r--lib/pleroma/web/api_spec/operations/conversation_operation.ex33
-rw-r--r--lib/pleroma/web/api_spec/operations/custom_emoji_operation.ex6
-rw-r--r--lib/pleroma/web/api_spec/operations/directory_operation.ex41
-rw-r--r--lib/pleroma/web/api_spec/operations/domain_block_operation.ex11
-rw-r--r--lib/pleroma/web/api_spec/operations/emoji_reaction_operation.ex10
-rw-r--r--lib/pleroma/web/api_spec/operations/filter_operation.ex49
-rw-r--r--lib/pleroma/web/api_spec/operations/follow_request_operation.ex14
-rw-r--r--lib/pleroma/web/api_spec/operations/instance_operation.ex6
-rw-r--r--lib/pleroma/web/api_spec/operations/list_operation.ex10
-rw-r--r--lib/pleroma/web/api_spec/operations/marker_operation.ex2
-rw-r--r--lib/pleroma/web/api_spec/operations/media_operation.ex19
-rw-r--r--lib/pleroma/web/api_spec/operations/notification_operation.ex17
-rw-r--r--lib/pleroma/web/api_spec/operations/pleroma_account_operation.ex72
-rw-r--r--lib/pleroma/web/api_spec/operations/pleroma_app_operation.ex31
-rw-r--r--lib/pleroma/web/api_spec/operations/pleroma_backup_operation.ex6
-rw-r--r--lib/pleroma/web/api_spec/operations/pleroma_conversation_operation.ex11
-rw-r--r--lib/pleroma/web/api_spec/operations/pleroma_emoji_file_operation.ex14
-rw-r--r--lib/pleroma/web/api_spec/operations/pleroma_emoji_pack_operation.ex32
-rw-r--r--lib/pleroma/web/api_spec/operations/pleroma_instances_operation.ex6
-rw-r--r--lib/pleroma/web/api_spec/operations/pleroma_mascot_operation.ex6
-rw-r--r--lib/pleroma/web/api_spec/operations/pleroma_notification_operation.ex7
-rw-r--r--lib/pleroma/web/api_spec/operations/pleroma_report_operation.ex97
-rw-r--r--lib/pleroma/web/api_spec/operations/pleroma_scrobble_operation.ex2
-rw-r--r--lib/pleroma/web/api_spec/operations/pleroma_settings_operation.ex72
-rw-r--r--lib/pleroma/web/api_spec/operations/poll_operation.ex2
-rw-r--r--lib/pleroma/web/api_spec/operations/report_operation.ex4
-rw-r--r--lib/pleroma/web/api_spec/operations/scheduled_activity_operation.ex10
-rw-r--r--lib/pleroma/web/api_spec/operations/search_operation.ex2
-rw-r--r--lib/pleroma/web/api_spec/operations/status_operation.ex360
-rw-r--r--lib/pleroma/web/api_spec/operations/subscription_operation.ex16
-rw-r--r--lib/pleroma/web/api_spec/operations/timeline_operation.ex24
-rw-r--r--lib/pleroma/web/api_spec/operations/twitter_util_operation.ex435
-rw-r--r--lib/pleroma/web/api_spec/operations/user_import_operation.ex15
-rw-r--r--lib/pleroma/web/api_spec/render_error.ex2
-rw-r--r--lib/pleroma/web/api_spec/schemas/account.ex18
-rw-r--r--lib/pleroma/web/api_spec/schemas/account_field.ex2
-rw-r--r--lib/pleroma/web/api_spec/schemas/account_relationship.ex12
-rw-r--r--lib/pleroma/web/api_spec/schemas/actor_type.ex2
-rw-r--r--lib/pleroma/web/api_spec/schemas/announcement.ex45
-rw-r--r--lib/pleroma/web/api_spec/schemas/api_error.ex2
-rw-r--r--lib/pleroma/web/api_spec/schemas/app.ex33
-rw-r--r--lib/pleroma/web/api_spec/schemas/attachment.ex2
-rw-r--r--lib/pleroma/web/api_spec/schemas/boolean_like.ex12
-rw-r--r--lib/pleroma/web/api_spec/schemas/chat.ex4
-rw-r--r--lib/pleroma/web/api_spec/schemas/chat_message.ex8
-rw-r--r--lib/pleroma/web/api_spec/schemas/conversation.ex2
-rw-r--r--lib/pleroma/web/api_spec/schemas/emoji.ex2
-rw-r--r--lib/pleroma/web/api_spec/schemas/flake_id.ex2
-rw-r--r--lib/pleroma/web/api_spec/schemas/list.ex2
-rw-r--r--lib/pleroma/web/api_spec/schemas/poll.ex2
-rw-r--r--lib/pleroma/web/api_spec/schemas/push_subscription.ex2
-rw-r--r--lib/pleroma/web/api_spec/schemas/scheduled_status.ex12
-rw-r--r--lib/pleroma/web/api_spec/schemas/status.ex35
-rw-r--r--lib/pleroma/web/api_spec/schemas/tag.ex2
-rw-r--r--lib/pleroma/web/api_spec/schemas/visibility_scope.ex2
-rw-r--r--lib/pleroma/web/auth/authenticator.ex65
-rw-r--r--lib/pleroma/web/auth/helpers.ex33
-rw-r--r--lib/pleroma/web/auth/ldap_authenticator.ex5
-rw-r--r--lib/pleroma/web/auth/pleroma_authenticator.ex7
-rw-r--r--lib/pleroma/web/auth/totp_authenticator.ex2
-rw-r--r--lib/pleroma/web/auth/wrapper_authenticator.ex42
-rw-r--r--lib/pleroma/web/channels/user_socket.ex6
-rw-r--r--lib/pleroma/web/common_api.ex145
-rw-r--r--lib/pleroma/web/common_api/activity_draft.ex51
-rw-r--r--lib/pleroma/web/common_api/utils.ex120
-rw-r--r--lib/pleroma/web/controller_helper.ex18
-rw-r--r--lib/pleroma/web/embed_controller.ex4
-rw-r--r--lib/pleroma/web/endpoint.ex15
-rw-r--r--lib/pleroma/web/fallback/legacy_pleroma_api_rerouter_plug.ex26
-rw-r--r--lib/pleroma/web/fallback/redirect_controller.ex2
-rw-r--r--lib/pleroma/web/federator.ex22
-rw-r--r--lib/pleroma/web/federator/publisher.ex2
-rw-r--r--lib/pleroma/web/federator/publishing.ex2
-rw-r--r--lib/pleroma/web/feed/feed_view.ex115
-rw-r--r--lib/pleroma/web/feed/tag_controller.ex2
-rw-r--r--lib/pleroma/web/feed/user_controller.ex6
-rw-r--r--lib/pleroma/web/gettext.ex194
-rw-r--r--lib/pleroma/web/instance_document.ex2
-rw-r--r--lib/pleroma/web/mailer/subscription_controller.ex2
-rw-r--r--lib/pleroma/web/manifest_controller.ex14
-rw-r--r--lib/pleroma/web/masto_fe_controller.ex65
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/account_controller.ex138
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/announcement_controller.ex60
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/app_controller.ex38
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/auth_controller.ex86
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/conversation_controller.ex11
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/custom_emoji_controller.ex8
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/directory_controller.ex82
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex2
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex8
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/filter_controller.ex63
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex3
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/instance_controller.ex10
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/list_controller.ex2
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/marker_controller.ex2
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex8
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/media_controller.ex3
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/notification_controller.ex6
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/poll_controller.ex2
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/report_controller.ex2
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/scheduled_activity_controller.ex2
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/search_controller.ex10
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/status_controller.ex103
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex2
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/suggestion_controller.ex92
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex59
-rw-r--r--lib/pleroma/web/mastodon_api/mastodon_api.ex22
-rw-r--r--lib/pleroma/web/mastodon_api/views/account_view.ex84
-rw-r--r--lib/pleroma/web/mastodon_api/views/announcement_view.ex15
-rw-r--r--lib/pleroma/web/mastodon_api/views/app_view.ex6
-rw-r--r--lib/pleroma/web/mastodon_api/views/conversation_view.ex2
-rw-r--r--lib/pleroma/web/mastodon_api/views/custom_emoji_view.ex6
-rw-r--r--lib/pleroma/web/mastodon_api/views/filter_view.ex2
-rw-r--r--lib/pleroma/web/mastodon_api/views/follow_request_view.ex10
-rw-r--r--lib/pleroma/web/mastodon_api/views/instance_view.ex55
-rw-r--r--lib/pleroma/web/mastodon_api/views/list_view.ex2
-rw-r--r--lib/pleroma/web/mastodon_api/views/marker_view.ex2
-rw-r--r--lib/pleroma/web/mastodon_api/views/media_view.ex10
-rw-r--r--lib/pleroma/web/mastodon_api/views/notification_view.ex22
-rw-r--r--lib/pleroma/web/mastodon_api/views/poll_view.ex40
-rw-r--r--lib/pleroma/web/mastodon_api/views/report_view.ex2
-rw-r--r--lib/pleroma/web/mastodon_api/views/scheduled_activity_view.ex5
-rw-r--r--lib/pleroma/web/mastodon_api/views/status_view.ex310
-rw-r--r--lib/pleroma/web/mastodon_api/views/subscription_view.ex2
-rw-r--r--lib/pleroma/web/mastodon_api/views/suggestion_view.ex28
-rw-r--r--lib/pleroma/web/mastodon_api/views/timeline_view.ex10
-rw-r--r--lib/pleroma/web/mastodon_api/websocket_handler.ex19
-rw-r--r--lib/pleroma/web/media_proxy.ex26
-rw-r--r--lib/pleroma/web/media_proxy/invalidation.ex2
-rw-r--r--lib/pleroma/web/media_proxy/invalidation/http.ex2
-rw-r--r--lib/pleroma/web/media_proxy/invalidation/script.ex21
-rw-r--r--lib/pleroma/web/media_proxy/media_proxy_controller.ex4
-rw-r--r--lib/pleroma/web/metadata.ex2
-rw-r--r--lib/pleroma/web/metadata/player_view.ex2
-rw-r--r--lib/pleroma/web/metadata/providers/feed.ex2
-rw-r--r--lib/pleroma/web/metadata/providers/open_graph.ex83
-rw-r--r--lib/pleroma/web/metadata/providers/provider.ex2
-rw-r--r--lib/pleroma/web/metadata/providers/rel_me.ex2
-rw-r--r--lib/pleroma/web/metadata/providers/restrict_indexing.ex2
-rw-r--r--lib/pleroma/web/metadata/providers/twitter_card.ex77
-rw-r--r--lib/pleroma/web/metadata/utils.ex25
-rw-r--r--lib/pleroma/web/mongoose_im/mongoose_im_controller.ex6
-rw-r--r--lib/pleroma/web/nodeinfo/nodeinfo.ex9
-rw-r--r--lib/pleroma/web/nodeinfo/nodeinfo_controller.ex8
-rw-r--r--lib/pleroma/web/o_auth.ex2
-rw-r--r--lib/pleroma/web/o_auth/app.ex13
-rw-r--r--lib/pleroma/web/o_auth/authorization.ex2
-rw-r--r--lib/pleroma/web/o_auth/fallback_controller.ex2
-rw-r--r--lib/pleroma/web/o_auth/mfa_controller.ex2
-rw-r--r--lib/pleroma/web/o_auth/mfa_view.ex3
-rw-r--r--lib/pleroma/web/o_auth/o_auth_controller.ex18
-rw-r--r--lib/pleroma/web/o_auth/o_auth_view.ex5
-rw-r--r--lib/pleroma/web/o_auth/scopes.ex2
-rw-r--r--lib/pleroma/web/o_auth/token.ex2
-rw-r--r--lib/pleroma/web/o_auth/token/query.ex2
-rw-r--r--lib/pleroma/web/o_auth/token/strategy/refresh_token.ex2
-rw-r--r--lib/pleroma/web/o_auth/token/strategy/revoke.ex16
-rw-r--r--lib/pleroma/web/o_auth/token/utils.ex2
-rw-r--r--lib/pleroma/web/o_status/o_status_controller.ex14
-rw-r--r--lib/pleroma/web/pleroma_api/controllers/account_controller.ex64
-rw-r--r--lib/pleroma/web/pleroma_api/controllers/app_controller.ex23
-rw-r--r--lib/pleroma/web/pleroma_api/controllers/backup_controller.ex6
-rw-r--r--lib/pleroma/web/pleroma_api/controllers/chat_controller.ex38
-rw-r--r--lib/pleroma/web/pleroma_api/controllers/conversation_controller.ex3
-rw-r--r--lib/pleroma/web/pleroma_api/controllers/emoji_file_controller.ex4
-rw-r--r--lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex10
-rw-r--r--lib/pleroma/web/pleroma_api/controllers/emoji_reaction_controller.ex4
-rw-r--r--lib/pleroma/web/pleroma_api/controllers/instances_controller.ex2
-rw-r--r--lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex2
-rw-r--r--lib/pleroma/web/pleroma_api/controllers/notification_controller.ex4
-rw-r--r--lib/pleroma/web/pleroma_api/controllers/report_controller.ex46
-rw-r--r--lib/pleroma/web/pleroma_api/controllers/scrobble_controller.ex2
-rw-r--r--lib/pleroma/web/pleroma_api/controllers/settings_controller.ex79
-rw-r--r--lib/pleroma/web/pleroma_api/controllers/two_factor_authentication_controller.ex2
-rw-r--r--lib/pleroma/web/pleroma_api/controllers/user_import_controller.ex4
-rw-r--r--lib/pleroma/web/pleroma_api/views/account_view.ex10
-rw-r--r--lib/pleroma/web/pleroma_api/views/app_view.ex11
-rw-r--r--lib/pleroma/web/pleroma_api/views/backup_view.ex5
-rw-r--r--lib/pleroma/web/pleroma_api/views/chat/message_reference_view.ex2
-rw-r--r--lib/pleroma/web/pleroma_api/views/chat_view.ex2
-rw-r--r--lib/pleroma/web/pleroma_api/views/conversation_view.ex10
-rw-r--r--lib/pleroma/web/pleroma_api/views/emoji_reaction_view.ex4
-rw-r--r--lib/pleroma/web/pleroma_api/views/notification_view.ex10
-rw-r--r--lib/pleroma/web/pleroma_api/views/report_view.ex55
-rw-r--r--lib/pleroma/web/pleroma_api/views/scrobble_view.ex4
-rw-r--r--lib/pleroma/web/plug.ex2
-rw-r--r--lib/pleroma/web/plugs/admin_secret_authentication_plug.ex2
-rw-r--r--lib/pleroma/web/plugs/authentication_plug.ex4
-rw-r--r--lib/pleroma/web/plugs/basic_auth_decoder_plug.ex2
-rw-r--r--lib/pleroma/web/plugs/cache.ex14
-rw-r--r--lib/pleroma/web/plugs/digest_plug.ex2
-rw-r--r--lib/pleroma/web/plugs/ensure_authenticated_plug.ex6
-rw-r--r--lib/pleroma/web/plugs/ensure_public_or_authenticated_plug.ex7
-rw-r--r--lib/pleroma/web/plugs/ensure_staff_privileged_plug.ex36
-rw-r--r--lib/pleroma/web/plugs/ensure_user_token_assigns_plug.ex7
-rw-r--r--lib/pleroma/web/plugs/expect_authenticated_check_plug.ex2
-rw-r--r--lib/pleroma/web/plugs/expect_public_or_authenticated_check_plug.ex2
-rw-r--r--lib/pleroma/web/plugs/federating_plug.ex2
-rw-r--r--lib/pleroma/web/plugs/frontend_static.ex12
-rw-r--r--lib/pleroma/web/plugs/http_security_plug.ex35
-rw-r--r--lib/pleroma/web/plugs/http_signature_plug.ex55
-rw-r--r--lib/pleroma/web/plugs/idempotency_plug.ex2
-rw-r--r--lib/pleroma/web/plugs/instance_static.ex2
-rw-r--r--lib/pleroma/web/plugs/mapped_signature_to_identity_plug.ex2
-rw-r--r--lib/pleroma/web/plugs/o_auth_plug.ex14
-rw-r--r--lib/pleroma/web/plugs/o_auth_scopes_plug.ex13
-rw-r--r--lib/pleroma/web/plugs/plug_helper.ex2
-rw-r--r--lib/pleroma/web/plugs/rate_limiter.ex2
-rw-r--r--lib/pleroma/web/plugs/rate_limiter/limiter_supervisor.ex2
-rw-r--r--lib/pleroma/web/plugs/rate_limiter/supervisor.ex2
-rw-r--r--lib/pleroma/web/plugs/remote_ip.ex2
-rw-r--r--lib/pleroma/web/plugs/set_format_plug.ex2
-rw-r--r--lib/pleroma/web/plugs/set_locale_plug.ex64
-rw-r--r--lib/pleroma/web/plugs/set_user_session_id_plug.ex2
-rw-r--r--lib/pleroma/web/plugs/static_fe_plug.ex2
-rw-r--r--lib/pleroma/web/plugs/trailing_format_plug.ex2
-rw-r--r--lib/pleroma/web/plugs/uploaded_media.ex13
-rw-r--r--lib/pleroma/web/plugs/user_enabled_plug.ex2
-rw-r--r--lib/pleroma/web/plugs/user_fetcher_plug.ex2
-rw-r--r--lib/pleroma/web/plugs/user_is_admin_plug.ex2
-rw-r--r--lib/pleroma/web/plugs/user_is_staff_plug.ex23
-rw-r--r--lib/pleroma/web/plugs/user_tracking_plug.ex30
-rw-r--r--lib/pleroma/web/preload.ex3
-rw-r--r--lib/pleroma/web/preload/providers/instance.ex2
-rw-r--r--lib/pleroma/web/preload/providers/provider.ex2
-rw-r--r--lib/pleroma/web/preload/providers/timelines.ex2
-rw-r--r--lib/pleroma/web/preload/providers/user.ex2
-rw-r--r--lib/pleroma/web/push.ex2
-rw-r--r--lib/pleroma/web/push/impl.ex20
-rw-r--r--lib/pleroma/web/push/subscription.ex4
-rw-r--r--lib/pleroma/web/rel_me.ex2
-rw-r--r--lib/pleroma/web/rich_media/helpers.ex4
-rw-r--r--lib/pleroma/web/rich_media/parser.ex2
-rw-r--r--lib/pleroma/web/rich_media/parser/ttl.ex2
-rw-r--r--lib/pleroma/web/rich_media/parser/ttl/aws_signed_url.ex2
-rw-r--r--lib/pleroma/web/rich_media/parsers/meta_tags_parser.ex2
-rw-r--r--lib/pleroma/web/rich_media/parsers/o_embed.ex2
-rw-r--r--lib/pleroma/web/rich_media/parsers/ogp.ex2
-rw-r--r--lib/pleroma/web/rich_media/parsers/twitter_card.ex2
-rw-r--r--lib/pleroma/web/router.ex283
-rw-r--r--lib/pleroma/web/shout_channel.ex (renamed from lib/pleroma/web/chat_channel.ex)14
-rw-r--r--lib/pleroma/web/static_fe/static_fe_controller.ex5
-rw-r--r--lib/pleroma/web/static_fe/static_fe_view.ex2
-rw-r--r--lib/pleroma/web/streamer.ex46
-rw-r--r--lib/pleroma/web/templates/email/digest.html.eex10
-rw-r--r--lib/pleroma/web/templates/embed/show.html.eex2
-rw-r--r--lib/pleroma/web/templates/feed/feed/_activity.atom.eex12
-rw-r--r--lib/pleroma/web/templates/feed/feed/_activity.rss.eex18
-rw-r--r--lib/pleroma/web/templates/feed/feed/_author.atom.eex19
-rw-r--r--lib/pleroma/web/templates/feed/feed/_author.rss.eex27
-rw-r--r--lib/pleroma/web/templates/feed/feed/_tag_activity.atom.eex70
-rw-r--r--lib/pleroma/web/templates/feed/feed/_tag_activity.xml.eex4
-rw-r--r--lib/pleroma/web/templates/feed/feed/_tag_author.atom.eex28
-rw-r--r--lib/pleroma/web/templates/feed/feed/tag.atom.eex34
-rw-r--r--lib/pleroma/web/templates/feed/feed/tag.rss.eex11
-rw-r--r--lib/pleroma/web/templates/feed/feed/user.atom.eex13
-rw-r--r--lib/pleroma/web/templates/feed/feed/user.rss.eex26
-rw-r--r--lib/pleroma/web/templates/layout/app.html.eex2
-rw-r--r--lib/pleroma/web/templates/layout/email.html.eex6
-rw-r--r--lib/pleroma/web/templates/layout/embed.html.eex2
-rw-r--r--lib/pleroma/web/templates/mailer/subscription/unsubscribe_failure.html.eex2
-rw-r--r--lib/pleroma/web/templates/mailer/subscription/unsubscribe_success.html.eex2
-rw-r--r--lib/pleroma/web/templates/masto_fe/index.html.eex35
-rw-r--r--lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex12
-rw-r--r--lib/pleroma/web/templates/o_auth/mfa/totp.html.eex14
-rw-r--r--lib/pleroma/web/templates/o_auth/o_auth/_scopes.html.eex2
-rw-r--r--lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex8
-rw-r--r--lib/pleroma/web/templates/o_auth/o_auth/oob_authorization_created.html.eex4
-rw-r--r--lib/pleroma/web/templates/o_auth/o_auth/oob_token_exists.html.eex4
-rw-r--r--lib/pleroma/web/templates/o_auth/o_auth/register.html.eex28
-rw-r--r--lib/pleroma/web/templates/o_auth/o_auth/show.html.eex28
-rw-r--r--lib/pleroma/web/templates/static_fe/static_fe/profile.html.eex2
-rw-r--r--lib/pleroma/web/templates/twitter_api/password/invalid_token.html.eex2
-rw-r--r--lib/pleroma/web/templates/twitter_api/password/reset.html.eex8
-rw-r--r--lib/pleroma/web/templates/twitter_api/password/reset_failed.html.eex8
-rw-r--r--lib/pleroma/web/templates/twitter_api/password/reset_success.html.eex4
-rw-r--r--lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex8
-rw-r--r--lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex10
-rw-r--r--lib/pleroma/web/templates/twitter_api/remote_follow/follow_mfa.html.eex8
-rw-r--r--lib/pleroma/web/templates/twitter_api/remote_follow/followed.html.eex5
-rw-r--r--lib/pleroma/web/templates/twitter_api/util/status_interact.html.eex10
-rw-r--r--lib/pleroma/web/templates/twitter_api/util/subscribe.html.eex10
-rw-r--r--lib/pleroma/web/translation_helpers.ex2
-rw-r--r--lib/pleroma/web/twitter_api/controller.ex44
-rw-r--r--lib/pleroma/web/twitter_api/controllers/password_controller.ex16
-rw-r--r--lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex8
-rw-r--r--lib/pleroma/web/twitter_api/controllers/util_controller.ex234
-rw-r--r--lib/pleroma/web/twitter_api/twitter_api.ex11
-rw-r--r--lib/pleroma/web/twitter_api/views/password_view.ex3
-rw-r--r--lib/pleroma/web/twitter_api/views/remote_follow_view.ex9
-rw-r--r--lib/pleroma/web/twitter_api/views/token_view.ex2
-rw-r--r--lib/pleroma/web/twitter_api/views/util_view.ex9
-rw-r--r--lib/pleroma/web/uploader_controller.ex2
-rw-r--r--lib/pleroma/web/utils/guards.ex13
-rw-r--r--lib/pleroma/web/utils/params.ex16
-rw-r--r--lib/pleroma/web/views/email_view.ex3
-rw-r--r--lib/pleroma/web/views/embed_view.ex9
-rw-r--r--lib/pleroma/web/views/error_helpers.ex2
-rw-r--r--lib/pleroma/web/views/error_view.ex2
-rw-r--r--lib/pleroma/web/views/layout_view.ex2
-rw-r--r--lib/pleroma/web/views/mailer/subscription_view.ex3
-rw-r--r--lib/pleroma/web/views/manifest_view.ex28
-rw-r--r--lib/pleroma/web/views/masto_fe_view.ex91
-rw-r--r--lib/pleroma/web/views/streamer_view.ex29
-rw-r--r--lib/pleroma/web/web_finger.ex145
-rw-r--r--lib/pleroma/web/web_finger/web_finger_controller.ex2
-rw-r--r--lib/pleroma/web/xml.ex4
-rw-r--r--lib/pleroma/workers/attachments_cleanup_worker.ex29
-rw-r--r--lib/pleroma/workers/background_worker.ex14
-rw-r--r--lib/pleroma/workers/backup_worker.ex30
-rw-r--r--lib/pleroma/workers/cron/digest_emails_worker.ex2
-rw-r--r--lib/pleroma/workers/cron/new_users_digest_worker.ex2
-rw-r--r--lib/pleroma/workers/mailer_worker.ex5
-rw-r--r--lib/pleroma/workers/mute_expire_worker.ex5
-rw-r--r--lib/pleroma/workers/poll_worker.ex48
-rw-r--r--lib/pleroma/workers/publisher_worker.ex5
-rw-r--r--lib/pleroma/workers/purge_expired_activity.ex7
-rw-r--r--lib/pleroma/workers/purge_expired_filter.ex46
-rw-r--r--lib/pleroma/workers/purge_expired_token.ex5
-rw-r--r--lib/pleroma/workers/receiver_worker.ex13
-rw-r--r--lib/pleroma/workers/remote_fetcher_worker.ex5
-rw-r--r--lib/pleroma/workers/scheduled_activity_worker.ex59
-rw-r--r--lib/pleroma/workers/transmogrifier_worker.ex5
-rw-r--r--lib/pleroma/workers/web_pusher_worker.ex5
-rw-r--r--lib/pleroma/workers/worker_helper.ex2
-rw-r--r--lib/pleroma/xml_builder.ex2
636 files changed, 14387 insertions, 5223 deletions
diff --git a/lib/mix/pleroma.ex b/lib/mix/pleroma.ex
index a33a9951c..2976085ba 100644
--- a/lib/mix/pleroma.ex
+++ b/lib/mix/pleroma.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Mix.Pleroma do
@@ -13,7 +13,8 @@ defmodule Mix.Pleroma do
:flake_id,
:swoosh,
:timex,
- :fast_html
+ :fast_html,
+ :oban
]
@cachex_children ["object", "user", "scrubber", "web_resp"]
@doc "Common functions to be reused in mix tasks"
diff --git a/lib/mix/tasks/pleroma/app.ex b/lib/mix/tasks/pleroma/app.ex
index 463e2449f..885d071bc 100644
--- a/lib/mix/tasks/pleroma/app.ex
+++ b/lib/mix/tasks/pleroma/app.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Mix.Tasks.Pleroma.App do
diff --git a/lib/mix/tasks/pleroma/benchmark.ex b/lib/mix/tasks/pleroma/benchmark.ex
index a607d5d4f..f32492169 100644
--- a/lib/mix/tasks/pleroma/benchmark.ex
+++ b/lib/mix/tasks/pleroma/benchmark.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Mix.Tasks.Pleroma.Benchmark do
diff --git a/lib/mix/tasks/pleroma/config.ex b/lib/mix/tasks/pleroma/config.ex
index d7e2e97e7..3a2ea44f8 100644
--- a/lib/mix/tasks/pleroma/config.ex
+++ b/lib/mix/tasks/pleroma/config.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Mix.Tasks.Pleroma.Config do
@@ -27,7 +27,7 @@ defmodule Mix.Tasks.Pleroma.Config do
{opts, _} =
OptionParser.parse!(options,
- strict: [env: :string, delete: :boolean],
+ strict: [env: :string, delete: :boolean, path: :string],
aliases: [d: :delete]
)
@@ -259,18 +259,41 @@ defmodule Mix.Tasks.Pleroma.Config do
defp migrate_from_db(opts) do
env = opts[:env] || Pleroma.Config.get(:env)
+ filename = "#{env}.exported_from_db.secret.exs"
+
config_path =
- if Pleroma.Config.get(:release) do
- :config_path
- |> Pleroma.Config.get()
- |> Path.dirname()
- else
- "config"
+ cond do
+ opts[:path] ->
+ opts[:path]
+
+ Pleroma.Config.get(:release) ->
+ :config_path
+ |> Pleroma.Config.get()
+ |> Path.dirname()
+
+ true ->
+ "config"
end
- |> Path.join("#{env}.exported_from_db.secret.exs")
+ |> Path.join(filename)
- file = File.open!(config_path, [:write, :utf8])
+ with {:ok, file} <- File.open(config_path, [:write, :utf8]) do
+ write_config(file, config_path, opts)
+ shell_info("Database configuration settings have been exported to #{config_path}")
+ else
+ _ ->
+ shell_error("Impossible to save settings to this directory #{Path.dirname(config_path)}")
+ tmp_config_path = Path.join(System.tmp_dir!(), filename)
+ file = File.open!(tmp_config_path)
+
+ shell_info(
+ "Saving database configuration settings to #{tmp_config_path}. Copy it to the #{Path.dirname(config_path)} manually."
+ )
+ write_config(file, tmp_config_path, opts)
+ end
+ end
+
+ defp write_config(file, path, opts) do
IO.write(file, config_header())
ConfigDB
@@ -278,20 +301,11 @@ defmodule Mix.Tasks.Pleroma.Config do
|> Enum.each(&write_and_delete(&1, file, opts[:delete]))
:ok = File.close(file)
- System.cmd("mix", ["format", config_path])
-
- shell_info(
- "Database configuration settings have been exported to config/#{env}.exported_from_db.secret.exs"
- )
+ System.cmd("mix", ["format", path])
end
- if Code.ensure_loaded?(Config.Reader) do
- defp config_header, do: "import Config\r\n\r\n"
- defp read_file(config_file), do: Config.Reader.read_imports!(config_file)
- else
- defp config_header, do: "use Mix.Config\r\n\r\n"
- defp read_file(config_file), do: Mix.Config.eval!(config_file)
- end
+ defp config_header, do: "import Config\r\n\r\n"
+ defp read_file(config_file), do: Config.Reader.read_imports!(config_file)
defp write_and_delete(config, file, delete?) do
config
diff --git a/lib/mix/tasks/pleroma/count_statuses.ex b/lib/mix/tasks/pleroma/count_statuses.ex
index 8761d8f17..c5ab8b7ab 100644
--- a/lib/mix/tasks/pleroma/count_statuses.ex
+++ b/lib/mix/tasks/pleroma/count_statuses.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Mix.Tasks.Pleroma.CountStatuses do
diff --git a/lib/mix/tasks/pleroma/database.ex b/lib/mix/tasks/pleroma/database.ex
index 22151ce08..ed560c177 100644
--- a/lib/mix/tasks/pleroma/database.ex
+++ b/lib/mix/tasks/pleroma/database.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Mix.Tasks.Pleroma.Database do
@@ -8,10 +8,13 @@ defmodule Mix.Tasks.Pleroma.Database do
alias Pleroma.Object
alias Pleroma.Repo
alias Pleroma.User
+
require Logger
require Pleroma.Constants
+
import Ecto.Query
import Mix.Pleroma
+
use Mix.Task
@shortdoc "A collection of database related tasks"
@@ -93,6 +96,15 @@ defmodule Mix.Tasks.Pleroma.Database do
)
|> Repo.delete_all(timeout: :infinity)
+ prune_hashtags_query = """
+ DELETE FROM hashtags AS ht
+ WHERE NOT EXISTS (
+ SELECT 1 FROM hashtags_objects hto
+ WHERE ht.id = hto.hashtag_id)
+ """
+
+ Repo.query(prune_hashtags_query)
+
if Keyword.get(options, :vacuum) do
Maintenance.vacuum("full")
end
@@ -142,9 +154,8 @@ defmodule Mix.Tasks.Pleroma.Database do
|> join(:inner, [a], o in Object,
on:
fragment(
- "(?->>'id') = COALESCE((?)->'object'->> 'id', (?)->>'object')",
+ "(?->>'id') = associated_object_id((?))",
o.data,
- a.data,
a.data
)
)
@@ -167,4 +178,83 @@ defmodule Mix.Tasks.Pleroma.Database do
end)
|> Stream.run()
end
+
+ def run(["set_text_search_config", tsconfig]) do
+ start_pleroma()
+ %{rows: [[tsc]]} = Ecto.Adapters.SQL.query!(Pleroma.Repo, "SHOW default_text_search_config;")
+ shell_info("Current default_text_search_config: #{tsc}")
+
+ %{rows: [[db]]} = Ecto.Adapters.SQL.query!(Pleroma.Repo, "SELECT current_database();")
+ shell_info("Update default_text_search_config: #{tsconfig}")
+
+ %{messages: msg} =
+ Ecto.Adapters.SQL.query!(
+ Pleroma.Repo,
+ "ALTER DATABASE #{db} SET default_text_search_config = '#{tsconfig}';"
+ )
+
+ # non-exist config will not raise excpetion but only give >0 messages
+ if length(msg) > 0 do
+ shell_info("Error: #{inspect(msg, pretty: true)}")
+ else
+ rum_enabled = Pleroma.Config.get([:database, :rum_enabled])
+ shell_info("Recreate index, RUM: #{rum_enabled}")
+
+ # Note SQL below needs to be kept up-to-date with latest GIN or RUM index definition in future
+ if rum_enabled do
+ Ecto.Adapters.SQL.query!(
+ Pleroma.Repo,
+ "CREATE OR REPLACE FUNCTION objects_fts_update() RETURNS trigger AS $$ BEGIN
+ new.fts_content := to_tsvector(new.data->>'content');
+ RETURN new;
+ END
+ $$ LANGUAGE plpgsql",
+ [],
+ timeout: :infinity
+ )
+
+ shell_info("Refresh RUM index")
+ Ecto.Adapters.SQL.query!(Pleroma.Repo, "UPDATE objects SET updated_at = NOW();")
+ else
+ Ecto.Adapters.SQL.query!(Pleroma.Repo, "DROP INDEX IF EXISTS objects_fts;")
+
+ Ecto.Adapters.SQL.query!(
+ Pleroma.Repo,
+ "CREATE INDEX CONCURRENTLY objects_fts ON objects USING gin(to_tsvector('#{tsconfig}', data->>'content')); ",
+ [],
+ timeout: :infinity
+ )
+ end
+
+ shell_info('Done.')
+ end
+ end
+
+ # Rolls back a specific migration (leaving subsequent migrations applied).
+ # WARNING: imposes a risk of unrecoverable data loss — proceed at your own responsibility.
+ # Based on https://stackoverflow.com/a/53825840
+ def run(["rollback", version]) do
+ prompt = "SEVERE WARNING: this operation may result in unrecoverable data loss. Continue?"
+
+ if shell_prompt(prompt, "n") in ~w(Yn Y y) do
+ {_, result, _} =
+ Ecto.Migrator.with_repo(Pleroma.Repo, fn repo ->
+ version = String.to_integer(version)
+ re = ~r/^#{version}_.*\.exs/
+ path = Ecto.Migrator.migrations_path(repo)
+
+ with {_, "" <> file} <- {:find, Enum.find(File.ls!(path), &String.match?(&1, re))},
+ {_, [{mod, _} | _]} <- {:compile, Code.compile_file(Path.join(path, file))},
+ {_, :ok} <- {:rollback, Ecto.Migrator.down(repo, version, mod)} do
+ {:ok, "Reversed migration: #{file}"}
+ else
+ {:find, _} -> {:error, "No migration found with version prefix: #{version}"}
+ {:compile, e} -> {:error, "Problem compiling migration module: #{inspect(e)}"}
+ {:rollback, e} -> {:error, "Problem reversing migration: #{inspect(e)}"}
+ end
+ end)
+
+ shell_info(inspect(result))
+ end
+ end
end
diff --git a/lib/mix/tasks/pleroma/digest.ex b/lib/mix/tasks/pleroma/digest.ex
index cac148b88..aea9c8ac5 100644
--- a/lib/mix/tasks/pleroma/digest.ex
+++ b/lib/mix/tasks/pleroma/digest.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Mix.Tasks.Pleroma.Digest do
diff --git a/lib/mix/tasks/pleroma/docs.ex b/lib/mix/tasks/pleroma/docs.ex
index ad5c37fc9..32c02a512 100644
--- a/lib/mix/tasks/pleroma/docs.ex
+++ b/lib/mix/tasks/pleroma/docs.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Mix.Tasks.Pleroma.Docs do
diff --git a/lib/mix/tasks/pleroma/ecto.ex b/lib/mix/tasks/pleroma/ecto.ex
index 3363cd45f..8cf77d11c 100644
--- a/lib/mix/tasks/pleroma/ecto.ex
+++ b/lib/mix/tasks/pleroma/ecto.ex
@@ -1,6 +1,6 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-onl
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
defmodule Mix.Tasks.Pleroma.Ecto do
@doc """
diff --git a/lib/mix/tasks/pleroma/ecto/migrate.ex b/lib/mix/tasks/pleroma/ecto/migrate.ex
index e903bd171..c45c930a3 100644
--- a/lib/mix/tasks/pleroma/ecto/migrate.ex
+++ b/lib/mix/tasks/pleroma/ecto/migrate.ex
@@ -1,6 +1,6 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-onl
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
defmodule Mix.Tasks.Pleroma.Ecto.Migrate do
use Mix.Task
diff --git a/lib/mix/tasks/pleroma/ecto/rollback.ex b/lib/mix/tasks/pleroma/ecto/rollback.ex
index 3dba952cb..3d78eaec4 100644
--- a/lib/mix/tasks/pleroma/ecto/rollback.ex
+++ b/lib/mix/tasks/pleroma/ecto/rollback.ex
@@ -1,6 +1,6 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-onl
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
defmodule Mix.Tasks.Pleroma.Ecto.Rollback do
use Mix.Task
@@ -20,7 +20,8 @@ defmodule Mix.Tasks.Pleroma.Ecto.Rollback do
start: :boolean,
quiet: :boolean,
log_sql: :boolean,
- migrations_path: :string
+ migrations_path: :string,
+ env: :string
]
@moduledoc """
@@ -59,7 +60,7 @@ defmodule Mix.Tasks.Pleroma.Ecto.Rollback do
level = Logger.level()
Logger.configure(level: :info)
- if Pleroma.Config.get(:env) == :test do
+ if opts[:env] == "test" do
Logger.info("Rollback succesfully")
else
{:ok, _, _} =
diff --git a/lib/mix/tasks/pleroma/email.ex b/lib/mix/tasks/pleroma/email.ex
index bc5facc09..37272c124 100644
--- a/lib/mix/tasks/pleroma/email.ex
+++ b/lib/mix/tasks/pleroma/email.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Mix.Tasks.Pleroma.Email do
@@ -33,12 +33,12 @@ defmodule Mix.Tasks.Pleroma.Email do
Pleroma.User.Query.build(%{
local: true,
- deactivated: false,
- confirmation_pending: true,
+ is_active: true,
+ is_confirmed: false,
invisible: false
})
|> Pleroma.Repo.chunk_stream(500)
- |> Stream.each(&Pleroma.User.try_send_confirmation_email(&1))
+ |> Stream.each(&Pleroma.User.maybe_send_confirmation_email(&1))
|> Stream.run()
end
end
diff --git a/lib/mix/tasks/pleroma/emoji.ex b/lib/mix/tasks/pleroma/emoji.ex
index 1750373f9..537f0715e 100644
--- a/lib/mix/tasks/pleroma/emoji.ex
+++ b/lib/mix/tasks/pleroma/emoji.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Mix.Tasks.Pleroma.Emoji do
diff --git a/lib/mix/tasks/pleroma/frontend.ex b/lib/mix/tasks/pleroma/frontend.ex
index f15dbc38b..3c71801ed 100644
--- a/lib/mix/tasks/pleroma/frontend.ex
+++ b/lib/mix/tasks/pleroma/frontend.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Mix.Tasks.Pleroma.Frontend do
diff --git a/lib/mix/tasks/pleroma/instance.ex b/lib/mix/tasks/pleroma/instance.ex
index 853c4eaa2..5c93f19ff 100644
--- a/lib/mix/tasks/pleroma/instance.ex
+++ b/lib/mix/tasks/pleroma/instance.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Mix.Tasks.Pleroma.Instance do
@@ -34,7 +34,8 @@ defmodule Mix.Tasks.Pleroma.Instance do
static_dir: :string,
listen_ip: :string,
listen_port: :string,
- strip_uploads: :string,
+ strip_uploads_location: :string,
+ read_uploads_description: :string,
anonymize_uploads: :string,
dedupe_uploads: :string
],
@@ -161,7 +162,7 @@ defmodule Mix.Tasks.Pleroma.Instance do
)
|> Path.expand()
- {strip_uploads_message, strip_uploads_default} =
+ {strip_uploads_location_message, strip_uploads_location_default} =
if Pleroma.Utils.command_available?("exiftool") do
{"Do you want to strip location (GPS) data from uploaded images? This requires exiftool, it was detected as installed. (y/n)",
"y"}
@@ -170,12 +171,29 @@ defmodule Mix.Tasks.Pleroma.Instance do
"n"}
end
- strip_uploads =
+ strip_uploads_location =
get_option(
options,
- :strip_uploads,
- strip_uploads_message,
- strip_uploads_default
+ :strip_uploads_location,
+ strip_uploads_location_message,
+ strip_uploads_location_default
+ ) === "y"
+
+ {read_uploads_description_message, read_uploads_description_default} =
+ if Pleroma.Utils.command_available?("exiftool") do
+ {"Do you want to read data from uploaded files so clients can use it to prefill fields like image description? This requires exiftool, it was detected as installed. (y/n)",
+ "y"}
+ else
+ {"Do you want to read data from uploaded files so clients can use it to prefill fields like image description? This requires exiftool, it was detected as not installed, please install it if you answer yes. (y/n)",
+ "n"}
+ end
+
+ read_uploads_description =
+ get_option(
+ options,
+ :read_uploads_description,
+ read_uploads_description_message,
+ read_uploads_description_default
) === "y"
anonymize_uploads =
@@ -199,6 +217,7 @@ defmodule Mix.Tasks.Pleroma.Instance do
secret = :crypto.strong_rand_bytes(64) |> Base.encode64() |> binary_part(0, 64)
jwt_secret = :crypto.strong_rand_bytes(64) |> Base.encode64() |> binary_part(0, 64)
signing_salt = :crypto.strong_rand_bytes(8) |> Base.encode64() |> binary_part(0, 8)
+ lv_signing_salt = :crypto.strong_rand_bytes(8) |> Base.encode64() |> binary_part(0, 8)
{web_push_public_key, web_push_private_key} = :crypto.generate_key(:ecdh, :prime256v1)
template_dir = Application.app_dir(:pleroma, "priv") <> "/templates"
@@ -217,6 +236,7 @@ defmodule Mix.Tasks.Pleroma.Instance do
secret: secret,
jwt_secret: jwt_secret,
signing_salt: signing_salt,
+ lv_signing_salt: lv_signing_salt,
web_push_public_key: Base.url_encode64(web_push_public_key, padding: false),
web_push_private_key: Base.url_encode64(web_push_private_key, padding: false),
db_configurable?: db_configurable?,
@@ -227,7 +247,8 @@ defmodule Mix.Tasks.Pleroma.Instance do
listen_port: listen_port,
upload_filters:
upload_filters(%{
- strip: strip_uploads,
+ strip_location: strip_uploads_location,
+ read_description: read_uploads_description,
anonymize: anonymize_uploads,
dedupe: dedupe_uploads
})
@@ -242,6 +263,13 @@ defmodule Mix.Tasks.Pleroma.Instance do
rum_enabled: rum_enabled
)
+ config_dir = Path.dirname(config_path)
+ psql_dir = Path.dirname(psql_path)
+
+ [config_dir, psql_dir, static_dir, uploads_dir]
+ |> Enum.reject(&File.exists?/1)
+ |> Enum.map(&File.mkdir_p!/1)
+
shell_info("Writing config to #{config_path}.")
File.write(config_path, result_config)
@@ -275,10 +303,6 @@ defmodule Mix.Tasks.Pleroma.Instance do
indexable: indexable
)
- unless File.exists?(static_dir) do
- File.mkdir_p!(static_dir)
- end
-
robots_txt_path = Path.join(static_dir, "robots.txt")
if File.exists?(robots_txt_path) do
@@ -292,13 +316,20 @@ defmodule Mix.Tasks.Pleroma.Instance do
defp upload_filters(filters) when is_map(filters) do
enabled_filters =
- if filters.strip do
- [Pleroma.Upload.Filter.Exiftool]
+ if filters.strip_location do
+ [Pleroma.Upload.Filter.Exiftool.StripLocation]
else
[]
end
enabled_filters =
+ if filters.read_description do
+ enabled_filters ++ [Pleroma.Upload.Filter.Exiftool.ReadDescription]
+ else
+ enabled_filters
+ end
+
+ enabled_filters =
if filters.anonymize do
enabled_filters ++ [Pleroma.Upload.Filter.AnonymizeFilename]
else
diff --git a/lib/mix/tasks/pleroma/notification_settings.ex b/lib/mix/tasks/pleroma/notification_settings.ex
index f99275de1..f0a7cc4ca 100644
--- a/lib/mix/tasks/pleroma/notification_settings.ex
+++ b/lib/mix/tasks/pleroma/notification_settings.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Mix.Tasks.Pleroma.NotificationSettings do
diff --git a/lib/mix/tasks/pleroma/openapi_spec.ex b/lib/mix/tasks/pleroma/openapi_spec.ex
new file mode 100644
index 000000000..884f931f8
--- /dev/null
+++ b/lib/mix/tasks/pleroma/openapi_spec.ex
@@ -0,0 +1,12 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Mix.Tasks.Pleroma.OpenapiSpec do
+ def run([path]) do
+ # Load Pleroma application to get version info
+ Application.load(:pleroma)
+ spec = Pleroma.Web.ApiSpec.spec(server_specific: false) |> Jason.encode!()
+ File.write(path, spec)
+ end
+end
diff --git a/lib/mix/tasks/pleroma/refresh_counter_cache.ex b/lib/mix/tasks/pleroma/refresh_counter_cache.ex
index efcbaa3b1..ad37cd20b 100644
--- a/lib/mix/tasks/pleroma/refresh_counter_cache.ex
+++ b/lib/mix/tasks/pleroma/refresh_counter_cache.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Mix.Tasks.Pleroma.RefreshCounterCache do
diff --git a/lib/mix/tasks/pleroma/relay.ex b/lib/mix/tasks/pleroma/relay.ex
index bb808ca47..29cc6668c 100644
--- a/lib/mix/tasks/pleroma/relay.ex
+++ b/lib/mix/tasks/pleroma/relay.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Mix.Tasks.Pleroma.Relay do
diff --git a/lib/mix/tasks/pleroma/robots_txt.ex b/lib/mix/tasks/pleroma/robots_txt.ex
index 24f08180e..5124c7c40 100644
--- a/lib/mix/tasks/pleroma/robots_txt.ex
+++ b/lib/mix/tasks/pleroma/robots_txt.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Mix.Tasks.Pleroma.RobotsTxt do
diff --git a/lib/mix/tasks/pleroma/uploads.ex b/lib/mix/tasks/pleroma/uploads.ex
index c47b7531e..bf02912fa 100644
--- a/lib/mix/tasks/pleroma/uploads.ex
+++ b/lib/mix/tasks/pleroma/uploads.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Mix.Tasks.Pleroma.Uploads do
diff --git a/lib/mix/tasks/pleroma/user.ex b/lib/mix/tasks/pleroma/user.ex
index 20fe6c6e4..929fa1717 100644
--- a/lib/mix/tasks/pleroma/user.ex
+++ b/lib/mix/tasks/pleroma/user.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Mix.Tasks.Pleroma.User do
@@ -51,9 +51,7 @@ defmodule Mix.Tasks.Pleroma.User do
A user will be created with the following information:
- nickname: #{nickname}
- email: #{email}
- - password: #{
- if(generated_password?, do: "[generated; a reset link will be created]", else: password)
- }
+ - password: #{if(generated_password?, do: "[generated; a reset link will be created]", else: password)}
- name: #{name}
- bio: #{bio}
- moderator: #{if(moderator?, do: "true", else: "false")}
@@ -74,7 +72,7 @@ defmodule Mix.Tasks.Pleroma.User do
bio: bio
}
- changeset = User.register_changeset(%User{}, params, need_confirmation: false)
+ changeset = User.register_changeset(%User{}, params, is_confirmed: true)
{:ok, _user} = User.register(changeset)
shell_info("User #{nickname} created")
@@ -107,21 +105,6 @@ defmodule Mix.Tasks.Pleroma.User do
end
end
- def run(["toggle_activated", nickname]) do
- start_pleroma()
-
- with %User{} = user <- User.get_cached_by_nickname(nickname) do
- {:ok, user} = User.deactivate(user, !user.deactivated)
-
- shell_info(
- "Activation status of #{nickname}: #{if(user.deactivated, do: "de", else: "")}activated"
- )
- else
- _ ->
- shell_error("No user #{nickname}")
- end
- end
-
def run(["reset_password", nickname]) do
start_pleroma()
@@ -129,15 +112,10 @@ defmodule Mix.Tasks.Pleroma.User do
{:ok, token} <- Pleroma.PasswordResetToken.create_token(user) do
shell_info("Generated password reset token for #{user.nickname}")
- IO.puts(
- "URL: #{
- Pleroma.Web.Router.Helpers.reset_password_url(
- Pleroma.Web.Endpoint,
- :reset,
- token.token
- )
- }"
- )
+ url =
+ Pleroma.Web.Router.Helpers.reset_password_url(Pleroma.Web.Endpoint, :reset, token.token)
+
+ IO.puts("URL: #{url}")
else
_ ->
shell_error("No local user #{nickname}")
@@ -156,20 +134,41 @@ defmodule Mix.Tasks.Pleroma.User do
end
end
+ def run(["activate", nickname]) do
+ start_pleroma()
+
+ with %User{} = user <- User.get_cached_by_nickname(nickname),
+ false <- user.is_active do
+ User.set_activation(user, true)
+ :timer.sleep(500)
+
+ shell_info("Successfully activated #{nickname}")
+ else
+ true ->
+ shell_info("User #{nickname} already activated")
+
+ _ ->
+ shell_error("No user #{nickname}")
+ end
+ end
+
def run(["deactivate", nickname]) do
start_pleroma()
- with %User{} = user <- User.get_cached_by_nickname(nickname) do
- shell_info("Deactivating #{user.nickname}")
- User.deactivate(user)
+ with %User{} = user <- User.get_cached_by_nickname(nickname),
+ true <- user.is_active do
+ User.set_activation(user, false)
:timer.sleep(500)
user = User.get_cached_by_id(user.id)
if Enum.empty?(Enum.filter(User.get_friends(user), & &1.local)) do
- shell_info("Successfully unsubscribed all local followers from #{user.nickname}")
+ shell_info("Successfully deactivated #{nickname} and unsubscribed all local followers")
end
else
+ false ->
+ shell_info("User #{nickname} already deactivated")
+
_ ->
shell_error("No user #{nickname}")
end
@@ -213,7 +212,7 @@ defmodule Mix.Tasks.Pleroma.User do
user =
case Keyword.get(options, :confirmed) do
nil -> user
- value -> set_confirmed(user, value)
+ value -> set_confirmation(user, value)
end
user =
@@ -315,9 +314,7 @@ defmodule Mix.Tasks.Pleroma.User do
end
shell_info(
- "ID: #{invite.id} | Token: #{invite.token} | Token type: #{invite.invite_type} | Used: #{
- invite.used
- }#{expire_info}#{using_info}"
+ "ID: #{invite.id} | Token: #{invite.token} | Token type: #{invite.invite_type} | Used: #{invite.used}#{expire_info}#{using_info}"
)
end)
end
@@ -351,7 +348,7 @@ defmodule Mix.Tasks.Pleroma.User do
with %User{} = user <- User.get_cached_by_nickname(nickname) do
{:ok, user} = User.confirm(user)
- message = if user.confirmation_pending, do: "needs", else: "doesn't need"
+ message = if !user.is_confirmed, do: "needs", else: "doesn't need"
shell_info("#{nickname} #{message} confirmation.")
else
@@ -365,7 +362,7 @@ defmodule Mix.Tasks.Pleroma.User do
Pleroma.User.Query.build(%{
local: true,
- deactivated: false,
+ is_active: true,
is_moderator: false,
is_admin: false,
invisible: false
@@ -373,7 +370,7 @@ defmodule Mix.Tasks.Pleroma.User do
|> Pleroma.Repo.chunk_stream(500, :batches)
|> Stream.each(fn users ->
users
- |> Enum.each(fn user -> User.need_confirmation(user, false) end)
+ |> Enum.each(fn user -> User.set_confirmation(user, true) end)
end)
|> Stream.run()
end
@@ -383,7 +380,7 @@ defmodule Mix.Tasks.Pleroma.User do
Pleroma.User.Query.build(%{
local: true,
- deactivated: false,
+ is_active: true,
is_moderator: false,
is_admin: false,
invisible: false
@@ -391,7 +388,7 @@ defmodule Mix.Tasks.Pleroma.User do
|> Pleroma.Repo.chunk_stream(500, :batches)
|> Stream.each(fn users ->
users
- |> Enum.each(fn user -> User.need_confirmation(user, true) end)
+ |> Enum.each(fn user -> User.set_confirmation(user, false) end)
end)
|> Stream.run()
end
@@ -418,15 +415,45 @@ defmodule Mix.Tasks.Pleroma.User do
users
|> Enum.each(fn user ->
shell_info(
- "#{user.nickname} moderator: #{user.is_moderator}, admin: #{user.is_admin}, locked: #{
- user.is_locked
- }, deactivated: #{user.deactivated}"
+ "#{user.nickname} moderator: #{user.is_moderator}, admin: #{user.is_admin}, locked: #{user.is_locked}, is_active: #{user.is_active}"
)
end)
end)
|> Stream.run()
end
+ def run(["fix_follow_state", local_user, remote_user]) do
+ start_pleroma()
+
+ with {:local, %User{} = local} <- {:local, User.get_by_nickname(local_user)},
+ {:remote, %User{} = remote} <- {:remote, User.get_by_nickname(remote_user)},
+ {:follow_data, %{data: %{"state" => request_state}}} <-
+ {:follow_data, Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(local, remote)} do
+ calculated_state = User.following?(local, remote)
+
+ shell_info(
+ "Request state is #{request_state}, vs calculated state of following=#{calculated_state}"
+ )
+
+ if calculated_state == false && request_state == "accept" do
+ shell_info("Discrepancy found, fixing")
+ Pleroma.Web.CommonAPI.reject_follow_request(local, remote)
+ shell_info("Relationship fixed")
+ else
+ shell_info("No discrepancy found")
+ end
+ else
+ {:local, _} ->
+ shell_error("No local user #{local_user}")
+
+ {:remote, _} ->
+ shell_error("No remote user #{remote_user}")
+
+ {:follow_data, _} ->
+ shell_error("No follow data for #{local_user} and #{remote_user}")
+ end
+ end
+
defp set_moderator(user, value) do
{:ok, user} =
user
@@ -454,10 +481,10 @@ defmodule Mix.Tasks.Pleroma.User do
user
end
- defp set_confirmed(user, value) do
- {:ok, user} = User.need_confirmation(user, !value)
+ defp set_confirmation(user, value) do
+ {:ok, user} = User.set_confirmation(user, value)
- shell_info("Confirmation pending status of #{user.nickname}: #{user.confirmation_pending}")
+ shell_info("Confirmation status of #{user.nickname}: #{user.is_confirmed}")
user
end
end
diff --git a/lib/phoenix/transports/web_socket/raw.ex b/lib/phoenix/transports/web_socket/raw.ex
index c3665bebe..8cf9c32a2 100644
--- a/lib/phoenix/transports/web_socket/raw.ex
+++ b/lib/phoenix/transports/web_socket/raw.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Phoenix.Transports.WebSocket.Raw do
diff --git a/lib/pleroma/activity.ex b/lib/pleroma/activity.ex
index 9d970a808..3556aaf9e 100644
--- a/lib/pleroma/activity.ex
+++ b/lib/pleroma/activity.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Activity do
@@ -53,7 +53,7 @@ defmodule Pleroma.Activity do
#
# ```
# |> join(:inner, [activity], o in Object,
- # on: fragment("(?->>'id') = COALESCE((?)->'object'->> 'id', (?)->>'object')",
+ # on: fragment("(?->>'id') = associated_object_id((?))",
# o.data, activity.data, activity.data))
# |> preload([activity, object], [object: object])
# ```
@@ -69,9 +69,8 @@ defmodule Pleroma.Activity do
join(query, join_type, [activity], o in Object,
on:
fragment(
- "(?->>'id') = COALESCE(?->'object'->>'id', ?->>'object')",
+ "(?->>'id') = associated_object_id(?)",
o.data,
- activity.data,
activity.data
),
as: :object
@@ -113,6 +112,7 @@ defmodule Pleroma.Activity do
from([a] in query,
left_join: b in Bookmark,
on: b.user_id == ^user.id and b.activity_id == a.id,
+ as: :bookmark,
preload: [bookmark: b]
)
end
@@ -123,6 +123,7 @@ defmodule Pleroma.Activity do
from([a] in query,
left_join: r in ReportNote,
on: a.id == r.activity_id,
+ as: :report_note,
preload: [report_notes: r]
)
end
@@ -182,40 +183,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)
@@ -267,6 +276,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
@@ -274,10 +288,11 @@ defmodule Pleroma.Activity do
defp get_in_reply_to_activity_from_object(_), do: nil
def get_in_reply_to_activity(%Activity{} = activity) do
- get_in_reply_to_activity_from_object(Object.normalize(activity))
+ get_in_reply_to_activity_from_object(Object.normalize(activity, fetch: false))
end
- def normalize(obj) when is_map(obj), do: get_by_ap_id_with_object(obj["id"])
+ def normalize(%Activity{data: %{"id" => ap_id}}), do: get_by_ap_id_with_object(ap_id)
+ def normalize(%{"id" => ap_id}), do: get_by_ap_id_with_object(ap_id)
def normalize(ap_id) when is_binary(ap_id), do: get_by_ap_id_with_object(ap_id)
def normalize(_), do: nil
@@ -286,7 +301,7 @@ defmodule Pleroma.Activity do
|> Queries.by_object_id()
|> Queries.exclude_type("Delete")
|> select([u], u)
- |> Repo.delete_all()
+ |> Repo.delete_all(timeout: :infinity)
|> elem(1)
|> Enum.find(fn
%{data: %{"type" => "Create", "object" => ap_id}} when is_binary(ap_id) -> ap_id == id
@@ -298,13 +313,15 @@ defmodule Pleroma.Activity do
def delete_all_by_object_ap_id(_), do: nil
- defp purge_web_resp_cache(%Activity{} = activity) do
- %{path: path} = URI.parse(activity.data["id"])
- @cachex.del(:web_resp_cache, path)
+ defp purge_web_resp_cache(%Activity{data: %{"id" => id}} = activity) when is_binary(id) do
+ with %{path: path} <- URI.parse(id) do
+ @cachex.del(:web_resp_cache, path)
+ end
+
activity
end
- defp purge_web_resp_cache(nil), do: nil
+ defp purge_web_resp_cache(activity), do: activity
def follow_accepted?(
%Activity{data: %{"type" => "Follow", "object" => followed_ap_id}} = activity
@@ -344,11 +361,11 @@ defmodule Pleroma.Activity do
end
def restrict_deactivated_users(query) do
- deactivated_users =
- from(u in User.Query.build(%{deactivated: true}), select: u.ap_id)
- |> Repo.all()
-
- Activity.Queries.exclude_authors(query, deactivated_users)
+ query
+ |> join(:inner, [activity], user in User,
+ as: :user,
+ on: activity.actor == user.ap_id and user.is_active == true
+ )
end
defdelegate search(user, query, options \\ []), to: Pleroma.Activity.Search
@@ -366,12 +383,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
@@ -382,4 +393,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
index 000000000..706b2d36c
--- /dev/null
+++ b/lib/pleroma/activity/html.ex
@@ -0,0 +1,81 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 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)
+
+ # We store a list of cache keys related to an activity in a
+ # separate cache, scrubber_management_cache. It has the same
+ # size as scrubber_cache (see application.ex). Every time we add
+ # a cache to scrubber_cache, we update scrubber_management_cache.
+ #
+ # The most recent write of a certain key in the management cache
+ # is the same as the most recent write of any record related to that
+ # key in the main cache.
+ # Assuming LRW ( https://hexdocs.pm/cachex/Cachex.Policy.LRW.html ),
+ # this means when the management cache is evicted by cachex, all
+ # related records in the main cache will also have been evicted.
+
+ defp get_cache_keys_for(activity_id) do
+ with {:ok, list} when is_list(list) <- @cachex.get(:scrubber_management_cache, activity_id) do
+ list
+ else
+ _ -> []
+ end
+ end
+
+ defp add_cache_key_for(activity_id, additional_key) do
+ current = get_cache_keys_for(activity_id)
+
+ unless additional_key in current do
+ @cachex.put(:scrubber_management_cache, activity_id, [additional_key | current])
+ end
+ end
+
+ def invalidate_cache_for(activity_id) do
+ keys = get_cache_keys_for(activity_id)
+ Enum.map(keys, &@cachex.del(:scrubber_cache, &1))
+ @cachex.del(:scrubber_management_cache, activity_id)
+ end
+
+ 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)
+
+ add_cache_key_for(activity.id, key)
+ 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
diff --git a/lib/pleroma/activity/ir/topics.ex b/lib/pleroma/activity/ir/topics.ex
index fe2e8cb5c..8249cbe27 100644
--- a/lib/pleroma/activity/ir/topics.ex
+++ b/lib/pleroma/activity/ir/topics.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Activity.Ir.Topics do
@@ -8,11 +8,19 @@ defmodule Pleroma.Activity.Ir.Topics do
def get_activity_topics(activity) do
activity
- |> Object.normalize()
+ |> Object.normalize(fetch: false)
|> generate_topics(activity)
|> List.flatten()
end
+ defp generate_topics(%{data: %{"type" => "ChatMessage"}}, %{data: %{"type" => "Delete"}}) do
+ ["user", "user:pleroma_chat"]
+ end
+
+ defp generate_topics(%{data: %{"type" => "ChatMessage"}}, %{data: %{"type" => "Create"}}) do
+ []
+ end
+
defp generate_topics(%{data: %{"type" => "Answer"}}, _) do
[]
end
@@ -21,7 +29,7 @@ defmodule Pleroma.Activity.Ir.Topics do
["user", "list"] ++ visibility_tags(object, activity)
end
- defp visibility_tags(object, activity) do
+ defp visibility_tags(object, %{data: %{"type" => type}} = activity) when type != "Announce" do
case Visibility.get_visibility(activity) do
"public" ->
if activity.local do
@@ -31,6 +39,10 @@ defmodule Pleroma.Activity.Ir.Topics do
end
|> item_creation_tags(object, activity)
+ "local" ->
+ ["public:local"]
+ |> item_creation_tags(object, activity)
+
"direct" ->
["direct"]
@@ -39,6 +51,10 @@ defmodule Pleroma.Activity.Ir.Topics do
end
end
+ defp visibility_tags(_object, _activity) do
+ []
+ end
+
defp item_creation_tags(tags, object, %{data: %{"type" => "Create"}} = activity) do
tags ++
remote_topics(activity) ++ hashtags_to_topics(object) ++ attachment_topics(object, activity)
@@ -48,14 +64,12 @@ defmodule Pleroma.Activity.Ir.Topics do
tags
end
- defp hashtags_to_topics(%{data: %{"tag" => tags}}) do
- tags
- |> Enum.filter(&is_bitstring(&1))
- |> Enum.map(fn tag -> "hashtag:" <> tag end)
+ defp hashtags_to_topics(object) do
+ object
+ |> Object.hashtags()
+ |> Enum.map(fn hashtag -> "hashtag:" <> hashtag end)
end
- defp hashtags_to_topics(_), do: []
-
defp remote_topics(%{local: true}), do: []
defp remote_topics(%{actor: actor}) when is_binary(actor),
@@ -65,7 +79,18 @@ defmodule Pleroma.Activity.Ir.Topics do
defp attachment_topics(%{data: %{"attachment" => []}}, _act), do: []
- defp attachment_topics(_object, %{local: true}), do: ["public:media", "public:local:media"]
+ defp attachment_topics(_object, %{local: true} = activity) do
+ case Visibility.get_visibility(activity) do
+ "public" ->
+ ["public:media", "public:local:media"]
+
+ "local" ->
+ ["public:local:media"]
+
+ _ ->
+ []
+ end
+ end
defp attachment_topics(_object, %{actor: actor}) when is_binary(actor),
do: ["public:media", "public:remote:media:" <> URI.parse(actor).host]
diff --git a/lib/pleroma/activity/queries.ex b/lib/pleroma/activity/queries.ex
index c99aae44b..81c44ac05 100644
--- a/lib/pleroma/activity/queries.ex
+++ b/lib/pleroma/activity/queries.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Activity.Queries do
@@ -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(
@@ -47,8 +52,7 @@ defmodule Pleroma.Activity.Queries do
activity in query,
where:
fragment(
- "coalesce((?)->'object'->>'id', (?)->>'object') = ANY(?)",
- activity.data,
+ "associated_object_id((?)) = ANY(?)",
activity.data,
^object_ids
)
@@ -59,8 +63,7 @@ defmodule Pleroma.Activity.Queries do
from(activity in query,
where:
fragment(
- "coalesce((?)->'object'->>'id', (?)->>'object') = ?",
- activity.data,
+ "associated_object_id((?)) = ?",
activity.data,
^object_id
)
diff --git a/lib/pleroma/activity/search.ex b/lib/pleroma/activity/search.ex
index babf9520b..0b9b24aa4 100644
--- a/lib/pleroma/activity/search.ex
+++ b/lib/pleroma/activity/search.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Activity.Search do
@@ -26,19 +26,23 @@ defmodule Pleroma.Activity.Search do
:plain
end
- Activity
- |> Activity.with_preloaded_object()
- |> Activity.restrict_deactivated_users()
- |> restrict_public()
- |> query_with(index_type, search_query, search_function)
- |> maybe_restrict_local(user)
- |> maybe_restrict_author(author)
- |> maybe_restrict_blocked(user)
- |> Pagination.fetch_paginated(
- %{"offset" => offset, "limit" => limit, "skip_order" => index_type == :rum},
- :offset
- )
- |> maybe_fetch(user, search_query)
+ try do
+ Activity
+ |> Activity.with_preloaded_object()
+ |> Activity.restrict_deactivated_users()
+ |> restrict_public(user)
+ |> query_with(index_type, search_query, search_function)
+ |> maybe_restrict_local(user)
+ |> maybe_restrict_author(author)
+ |> maybe_restrict_blocked(user)
+ |> Pagination.fetch_paginated(
+ %{"offset" => offset, "limit" => limit, "skip_order" => index_type == :rum},
+ :offset
+ )
+ |> maybe_fetch(user, search_query)
+ rescue
+ _ -> maybe_fetch([], user, search_query)
+ end
end
def maybe_restrict_author(query, %User{} = author) do
@@ -53,7 +57,19 @@ defmodule Pleroma.Activity.Search do
def maybe_restrict_blocked(query, _), do: query
- defp restrict_public(q) do
+ defp restrict_public(q, user) when not is_nil(user) do
+ intended_recipients = [
+ Pleroma.Constants.as_public(),
+ Pleroma.Web.ActivityPub.Utils.as_local_public()
+ ]
+
+ from([a, o] in q,
+ where: fragment("?->>'type' = 'Create'", a.data),
+ where: fragment("? && ?", ^intended_recipients, a.recipients)
+ )
+ end
+
+ defp restrict_public(q, _user) do
from([a, o] in q,
where: fragment("?->>'type' = 'Create'", a.data),
where: ^Pleroma.Constants.as_public() in a.recipients
@@ -61,10 +77,17 @@ defmodule Pleroma.Activity.Search do
end
defp query_with(q, :gin, search_query, :plain) do
+ %{rows: [[tsc]]} =
+ Ecto.Adapters.SQL.query!(
+ Pleroma.Repo,
+ "select current_setting('default_text_search_config')::regconfig::oid;"
+ )
+
from([a, o] in q,
where:
fragment(
- "to_tsvector('english', ?->>'content') @@ plainto_tsquery('english', ?)",
+ "to_tsvector(?::oid::regconfig, ?->>'content') @@ plainto_tsquery(?)",
+ ^tsc,
o.data,
^search_query
)
@@ -72,10 +95,17 @@ defmodule Pleroma.Activity.Search do
end
defp query_with(q, :gin, search_query, :websearch) do
+ %{rows: [[tsc]]} =
+ Ecto.Adapters.SQL.query!(
+ Pleroma.Repo,
+ "select current_setting('default_text_search_config')::regconfig::oid;"
+ )
+
from([a, o] in q,
where:
fragment(
- "to_tsvector('english', ?->>'content') @@ websearch_to_tsquery('english', ?)",
+ "to_tsvector(?::oid::regconfig, ?->>'content') @@ websearch_to_tsquery(?)",
+ ^tsc,
o.data,
^search_query
)
@@ -86,7 +116,7 @@ defmodule Pleroma.Activity.Search do
from([a, o] in q,
where:
fragment(
- "? @@ plainto_tsquery('english', ?)",
+ "? @@ plainto_tsquery(?)",
o.fts_content,
^search_query
),
@@ -98,7 +128,7 @@ defmodule Pleroma.Activity.Search do
from([a, o] in q,
where:
fragment(
- "? @@ websearch_to_tsquery('english', ?)",
+ "? @@ websearch_to_tsquery(?)",
o.fts_content,
^search_query
),
diff --git a/lib/pleroma/announcement.ex b/lib/pleroma/announcement.ex
new file mode 100644
index 000000000..d97c5e728
--- /dev/null
+++ b/lib/pleroma/announcement.ex
@@ -0,0 +1,160 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Announcement do
+ use Ecto.Schema
+
+ import Ecto.Changeset, only: [cast: 3, validate_required: 2]
+ import Ecto.Query
+
+ alias Pleroma.AnnouncementReadRelationship
+ alias Pleroma.Repo
+
+ @type t :: %__MODULE__{}
+ @primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true}
+
+ schema "announcements" do
+ field(:data, :map)
+ field(:starts_at, :utc_datetime)
+ field(:ends_at, :utc_datetime)
+ field(:rendered, :map)
+
+ timestamps(type: :utc_datetime)
+ end
+
+ def change(struct, params \\ %{}) do
+ struct
+ |> cast(validate_params(struct, params), [:data, :starts_at, :ends_at, :rendered])
+ |> validate_required([:data])
+ end
+
+ defp validate_params(struct, params) do
+ base_data =
+ %{
+ "content" => "",
+ "all_day" => false
+ }
+ |> Map.merge((struct && struct.data) || %{})
+
+ merged_data =
+ Map.merge(base_data, params.data)
+ |> Map.take(["content", "all_day"])
+
+ params
+ |> Map.merge(%{data: merged_data})
+ |> add_rendered_properties()
+ end
+
+ def add_rendered_properties(params) do
+ {content_html, _, _} =
+ Pleroma.Web.CommonAPI.Utils.format_input(params.data["content"], "text/plain",
+ mentions_format: :full
+ )
+
+ rendered = %{
+ "content" => content_html
+ }
+
+ params
+ |> Map.put(:rendered, rendered)
+ end
+
+ def add(params) do
+ changeset = change(%__MODULE__{}, params)
+
+ Repo.insert(changeset)
+ end
+
+ def update(announcement, params) do
+ changeset = change(announcement, params)
+
+ Repo.update(changeset)
+ end
+
+ def list_all do
+ __MODULE__
+ |> Repo.all()
+ end
+
+ def list_paginated(%{limit: limited_number, offset: offset_number}) do
+ __MODULE__
+ |> limit(^limited_number)
+ |> offset(^offset_number)
+ |> Repo.all()
+ end
+
+ def get_by_id(id) do
+ Repo.get_by(__MODULE__, id: id)
+ end
+
+ def delete_by_id(id) do
+ with announcement when not is_nil(announcement) <- get_by_id(id),
+ {:ok, _} <- Repo.delete(announcement) do
+ :ok
+ else
+ _ ->
+ :error
+ end
+ end
+
+ def read_by?(announcement, user) do
+ AnnouncementReadRelationship.exists?(user, announcement)
+ end
+
+ def mark_read_by(announcement, user) do
+ AnnouncementReadRelationship.mark_read(user, announcement)
+ end
+
+ def render_json(announcement, opts \\ []) do
+ extra_params =
+ case Keyword.fetch(opts, :for) do
+ {:ok, user} when not is_nil(user) ->
+ %{read: read_by?(announcement, user)}
+
+ _ ->
+ %{}
+ end
+
+ admin_extra_params =
+ case Keyword.fetch(opts, :admin) do
+ {:ok, true} ->
+ %{pleroma: %{raw_content: announcement.data["content"]}}
+
+ _ ->
+ %{}
+ end
+
+ base = %{
+ id: announcement.id,
+ content: announcement.rendered["content"],
+ starts_at: announcement.starts_at,
+ ends_at: announcement.ends_at,
+ all_day: announcement.data["all_day"],
+ published_at: announcement.inserted_at,
+ updated_at: announcement.updated_at,
+ mentions: [],
+ statuses: [],
+ tags: [],
+ emojis: [],
+ reactions: []
+ }
+
+ base
+ |> Map.merge(extra_params)
+ |> Map.merge(admin_extra_params)
+ end
+
+ # "visible" means:
+ # starts_at < time < ends_at
+ def list_all_visible_when(time) do
+ __MODULE__
+ |> where([a], is_nil(a.starts_at) or a.starts_at < ^time)
+ |> where([a], is_nil(a.ends_at) or a.ends_at > ^time)
+ |> Repo.all()
+ end
+
+ def list_all_visible do
+ list_all_visible_when(DateTime.now("Etc/UTC") |> elem(1))
+ end
+end
diff --git a/lib/pleroma/announcement_read_relationship.ex b/lib/pleroma/announcement_read_relationship.ex
new file mode 100644
index 000000000..9b64404ce
--- /dev/null
+++ b/lib/pleroma/announcement_read_relationship.ex
@@ -0,0 +1,55 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.AnnouncementReadRelationship do
+ use Ecto.Schema
+
+ import Ecto.Changeset
+
+ alias FlakeId.Ecto.CompatType
+ alias Pleroma.Announcement
+ alias Pleroma.Repo
+ alias Pleroma.User
+
+ @type t :: %__MODULE__{}
+
+ schema "announcement_read_relationships" do
+ belongs_to(:user, User, type: CompatType)
+ belongs_to(:announcement, Announcement, type: CompatType)
+
+ timestamps(updated_at: false)
+ end
+
+ def mark_read(user, announcement) do
+ %__MODULE__{}
+ |> cast(%{user_id: user.id, announcement_id: announcement.id}, [:user_id, :announcement_id])
+ |> validate_required([:user_id, :announcement_id])
+ |> foreign_key_constraint(:user_id)
+ |> foreign_key_constraint(:announcement_id)
+ |> unique_constraint([:user_id, :announcement_id])
+ |> Repo.insert()
+ end
+
+ def mark_unread(user, announcement) do
+ with relationship <- get(user, announcement),
+ {:exists, true} <- {:exists, not is_nil(relationship)},
+ {:ok, _} <- Repo.delete(relationship) do
+ :ok
+ else
+ {:exists, false} ->
+ :ok
+
+ _ ->
+ :error
+ end
+ end
+
+ def get(user, announcement) do
+ Repo.get_by(__MODULE__, user_id: user.id, announcement_id: announcement.id)
+ end
+
+ def exists?(user, announcement) do
+ not is_nil(get(user, announcement))
+ end
+end
diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex
index bd568d858..1c1db8c10 100644
--- a/lib/pleroma/application.ex
+++ b/lib/pleroma/application.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Application do
@@ -14,7 +14,7 @@ defmodule Pleroma.Application do
@name Mix.Project.config()[:name]
@version Mix.Project.config()[:version]
@repository Mix.Project.config()[:source_url]
- @env Mix.env()
+ @mix_env Mix.env()
def name, do: @name
def version, do: @version
@@ -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 ->
@@ -61,6 +61,11 @@ defmodule Pleroma.Application do
adapter = Application.get_env(:tesla, :adapter)
+ if match?({Tesla.Adapter.Finch, _}, adapter) do
+ Logger.info("Starting Finch")
+ Finch.start_link(name: MyFinch)
+ end
+
if adapter == Tesla.Adapter.Gun do
if version = Pleroma.OTPVersion.version() do
[major, minor] =
@@ -89,27 +94,36 @@ defmodule Pleroma.Application do
Pleroma.Repo,
Config.TransferTask,
Pleroma.Emoji,
- Pleroma.Web.Plugs.RateLimiter.Supervisor
+ Pleroma.Web.Plugs.RateLimiter.Supervisor,
+ {Task.Supervisor, name: Pleroma.TaskSupervisor}
] ++
cachex_children() ++
- http_children(adapter, @env) ++
+ http_children(adapter, @mix_env) ++
[
Pleroma.Stats,
Pleroma.JobQueueMonitor,
{Majic.Pool, [name: Pleroma.MajicPool, pool_size: Config.get([:majic_pool, :size], 2)]},
- {Oban, Config.get(Oban)}
+ {Oban, Config.get(Oban)},
+ Pleroma.Web.Endpoint
] ++
- task_children(@env) ++
- dont_run_in_test(@env) ++
- chat_child(chat_enabled?()) ++
- [
- Pleroma.Web.Endpoint,
- Pleroma.Gopher.Server
- ]
+ task_children(@mix_env) ++
+ dont_run_in_test(@mix_env) ++
+ shout_child(shout_enabled?()) ++
+ [Pleroma.Gopher.Server]
# See http://elixir-lang.org/docs/stable/elixir/Supervisor.html
# for other strategies and supported options
- opts = [strategy: :one_for_one, name: Pleroma.Supervisor]
+ # If we have a lot of caches, default max_restarts can cause test
+ # resets to fail.
+ # Go for the default 3 unless we're in test
+ max_restarts =
+ if @mix_env == :test do
+ 100
+ else
+ 3
+ end
+
+ opts = [strategy: :one_for_one, name: Pleroma.Supervisor, max_restarts: max_restarts]
result = Supervisor.start_link(children, opts)
set_postgres_server_version()
@@ -145,7 +159,7 @@ defmodule Pleroma.Application do
raise "Invalid custom modules"
{:ok, modules, _warnings} ->
- if @env != :test do
+ if @mix_env != :test do
Enum.each(modules, fn mod ->
Logger.info("Custom module loaded: #{inspect(mod)}")
end)
@@ -186,6 +200,7 @@ defmodule Pleroma.Application do
build_cachex("object", default_ttl: 25_000, ttl_interval: 1000, limit: 2500),
build_cachex("rich_media", default_ttl: :timer.minutes(120), limit: 5000),
build_cachex("scrubber", limit: 2500),
+ build_cachex("scrubber_management", limit: 2500),
build_cachex("idempotency", expiration: idempotency_expiration(), limit: 2500),
build_cachex("web_resp", limit: 2500),
build_cachex("emoji_packs", expiration: emoji_packs_expiration(), limit: 10),
@@ -218,7 +233,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: []
@@ -230,17 +245,24 @@ defmodule Pleroma.Application do
keys: :duplicate,
partitions: System.schedulers_online()
]}
+ ] ++ background_migrators()
+ end
+
+ defp background_migrators do
+ [
+ Pleroma.Migrators.HashtagsTableMigrator,
+ Pleroma.Migrators.ContextObjectsDeletionMigrator
]
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
[
@@ -297,7 +319,16 @@ defmodule Pleroma.Application do
@spec limiters_setup() :: :ok
def limiters_setup do
- [Pleroma.Web.RichMedia.Helpers, Pleroma.Web.MediaProxy]
- |> Enum.each(&ConcurrentLimiter.new(&1, 1, 0))
+ config = Config.get(ConcurrentLimiter, [])
+
+ [Pleroma.Web.RichMedia.Helpers, Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy]
+ |> Enum.each(fn module ->
+ mod_config = Keyword.get(config, module, [])
+
+ max_running = Keyword.get(mod_config, :max_running, 5)
+ max_waiting = Keyword.get(mod_config, :max_waiting, 5)
+
+ ConcurrentLimiter.new(module, max_running, max_waiting)
+ end)
end
end
diff --git a/lib/pleroma/application_requirements.ex b/lib/pleroma/application_requirements.ex
index e61576644..44b1c1705 100644
--- a/lib/pleroma/application_requirements.ex
+++ b/lib/pleroma/application_requirements.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.ApplicationRequirements do
@@ -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,13 @@ 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.StripLocation, "exiftool"),
+ check_filter(Pleroma.Upload.Filter.Exiftool.ReadDescription, "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"),
+ check_filter(Pleroma.Upload.Filter.AnalyzeMetadata, "ffprobe")
]
preview_proxy_commands_status =
diff --git a/lib/pleroma/bbs/authenticator.ex b/lib/pleroma/bbs/authenticator.ex
index 83ebb756d..0f7543ff5 100644
--- a/lib/pleroma/bbs/authenticator.ex
+++ b/lib/pleroma/bbs/authenticator.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.BBS.Authenticator do
diff --git a/lib/pleroma/bbs/handler.ex b/lib/pleroma/bbs/handler.ex
index cd523cf7d..27799338f 100644
--- a/lib/pleroma/bbs/handler.ex
+++ b/lib/pleroma/bbs/handler.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.BBS.Handler do
@@ -19,9 +19,7 @@ defmodule Pleroma.BBS.Handler do
def on_connect(username, ip, port, method) do
Logger.debug(fn ->
"""
- Incoming SSH shell #{inspect(self())} requested for #{username} from #{inspect(ip)}:#{
- inspect(port)
- } using #{inspect(method)}
+ Incoming SSH shell #{inspect(self())} requested for #{username} from #{inspect(ip)}:#{inspect(port)} using #{inspect(method)}
"""
end)
end
@@ -44,8 +42,45 @@ defmodule Pleroma.BBS.Handler do
def puts_activity(activity) do
status = Pleroma.Web.MastodonAPI.StatusView.render("show.json", %{activity: activity})
+
IO.puts("-- #{status.id} by #{status.account.display_name} (#{status.account.acct})")
- IO.puts(HTML.strip_tags(status.content))
+
+ status.content
+ |> String.split("<br/>")
+ |> Enum.map(&HTML.strip_tags/1)
+ |> Enum.map(&HtmlEntities.decode/1)
+ |> Enum.map(&IO.puts/1)
+ end
+
+ def puts_notification(activity, user) do
+ notification =
+ Pleroma.Web.MastodonAPI.NotificationView.render("show.json", %{
+ notification: activity,
+ for: user
+ })
+
+ IO.puts(
+ "== (#{notification.type}) #{notification.status.id} by #{notification.account.display_name} (#{notification.account.acct})"
+ )
+
+ notification.status.content
+ |> String.split("<br/>")
+ |> Enum.map(&HTML.strip_tags/1)
+ |> Enum.map(&HtmlEntities.decode/1)
+ |> (fn x ->
+ case x do
+ [content] ->
+ "> " <> content
+
+ [head | _tail] ->
+ # "> " <> hd <> "..."
+ head
+ |> String.slice(1, 80)
+ |> (fn x -> "> " <> x <> "..." end).()
+ end
+ end).()
+ |> IO.puts()
+
IO.puts("")
end
@@ -55,6 +90,11 @@ defmodule Pleroma.BBS.Handler do
IO.puts("home - Show the home timeline")
IO.puts("p <text> - Post the given text")
IO.puts("r <id> <text> - Reply to the post with the given id")
+ IO.puts("t <id> - Show a thread from the given id")
+ IO.puts("n - Show notifications")
+ IO.puts("n read - Mark all notifactions as read")
+ IO.puts("f <id> - Favourites the post with the given id")
+ IO.puts("R <id> - Repeat the post with the given id")
IO.puts("quit - Quit")
state
@@ -75,11 +115,53 @@ defmodule Pleroma.BBS.Handler do
state
end
+ def handle_command(%{user: user} = state, "t " <> activity_id) do
+ with %Activity{} = activity <- Activity.get_by_id(activity_id) do
+ activities =
+ ActivityPub.fetch_activities_for_context(activity.data["context"], %{
+ blocking_user: user,
+ user: user,
+ exclude_id: activity.id
+ })
+
+ case activities do
+ [] ->
+ activity_id
+ |> Activity.get_by_id()
+ |> puts_activity()
+
+ _ ->
+ activities
+ |> Enum.reverse()
+ |> Enum.each(&puts_activity/1)
+ end
+ else
+ _e -> IO.puts("Could not show this thread...")
+ end
+
+ state
+ end
+
+ def handle_command(%{user: user} = state, "n read") do
+ Pleroma.Notification.clear(user)
+ IO.puts("All notifications were marked as read")
+
+ state
+ end
+
+ def handle_command(%{user: user} = state, "n") do
+ user
+ |> Pleroma.Web.MastodonAPI.MastodonAPI.get_notifications(%{})
+ |> Enum.each(&puts_notification(&1, user))
+
+ state
+ end
+
def handle_command(%{user: user} = state, "p " <> text) do
text = String.trim(text)
- with {:ok, _activity} <- CommonAPI.post(user, %{status: text}) do
- IO.puts("Posted!")
+ with {:ok, activity} <- CommonAPI.post(user, %{status: text}) do
+ IO.puts("Posted! ID: #{activity.id}")
else
_e -> IO.puts("Could not post...")
end
@@ -87,6 +169,19 @@ defmodule Pleroma.BBS.Handler do
state
end
+ def handle_command(%{user: user} = state, "f " <> id) do
+ id = String.trim(id)
+
+ with %Activity{} = activity <- Activity.get_by_id(id),
+ {:ok, _activity} <- CommonAPI.favorite(user, activity) do
+ IO.puts("Favourited!")
+ else
+ _e -> IO.puts("Could not Favourite...")
+ end
+
+ state
+ end
+
def handle_command(state, "home") do
user = state.user
@@ -125,7 +220,7 @@ defmodule Pleroma.BBS.Handler do
loop(%{state | counter: state.counter + 1})
- {:error, :interrupted} ->
+ {:input, ^input, {:error, :interrupted}} ->
IO.puts("Caught Ctrl+C...")
loop(%{state | counter: state.counter + 1})
diff --git a/lib/pleroma/bookmark.ex b/lib/pleroma/bookmark.ex
index e6ddbce1b..187749e86 100644
--- a/lib/pleroma/bookmark.ex
+++ b/lib/pleroma/bookmark.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Bookmark do
diff --git a/lib/pleroma/caching.ex b/lib/pleroma/caching.ex
index 766d12d1b..eb0588708 100644
--- a/lib/pleroma/caching.ex
+++ b/lib/pleroma/caching.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Caching do
diff --git a/lib/pleroma/captcha.ex b/lib/pleroma/captcha.ex
index 990003dcd..03910f189 100644
--- a/lib/pleroma/captcha.ex
+++ b/lib/pleroma/captcha.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Captcha do
diff --git a/lib/pleroma/captcha/kocaptcha.ex b/lib/pleroma/captcha/kocaptcha.ex
index 201b55ab4..e786e28b9 100644
--- a/lib/pleroma/captcha/kocaptcha.ex
+++ b/lib/pleroma/captcha/kocaptcha.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Captcha.Kocaptcha do
diff --git a/lib/pleroma/captcha/native.ex b/lib/pleroma/captcha/native.ex
index 8d604d2b2..9ba0f30be 100644
--- a/lib/pleroma/captcha/native.ex
+++ b/lib/pleroma/captcha/native.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Captcha.Native do
diff --git a/lib/pleroma/captcha/service.ex b/lib/pleroma/captcha/service.ex
index 959038cef..74797605f 100644
--- a/lib/pleroma/captcha/service.ex
+++ b/lib/pleroma/captcha/service.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Captcha.Service do
diff --git a/lib/pleroma/chat.ex b/lib/pleroma/chat.ex
index 28007cd9f..fe32ec08c 100644
--- a/lib/pleroma/chat.ex
+++ b/lib/pleroma/chat.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Chat do
diff --git a/lib/pleroma/chat/message_reference.ex b/lib/pleroma/chat/message_reference.ex
index 131ae0186..ea65a4a35 100644
--- a/lib/pleroma/chat/message_reference.ex
+++ b/lib/pleroma/chat/message_reference.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Chat.MessageReference do
diff --git a/lib/pleroma/clippy.ex b/lib/pleroma/clippy.ex
index ae96e6ad1..bcf23ca07 100644
--- a/lib/pleroma/clippy.ex
+++ b/lib/pleroma/clippy.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Clippy do
diff --git a/lib/pleroma/config.ex b/lib/pleroma/config.ex
index 86d4f6b72..cf1453c9b 100644
--- a/lib/pleroma/config.ex
+++ b/lib/pleroma/config.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Config do
@@ -100,15 +100,7 @@ defmodule Pleroma.Config do
def oauth_consumer_enabled?, do: oauth_consumer_strategies() != []
- def enforce_oauth_admin_scope_usage?, do: !!get([:auth, :enforce_oauth_admin_scope_usage])
-
- def oauth_admin_scopes(scopes) when is_list(scopes) do
- Enum.flat_map(
- scopes,
- fn scope ->
- ["admin:#{scope}"] ++
- if enforce_oauth_admin_scope_usage?(), do: [], else: [scope]
- end
- )
+ def feature_enabled?(feature_name) do
+ get([:features, feature_name]) not in [nil, false, :disabled, :auto]
end
end
diff --git a/lib/pleroma/config/deprecation_warnings.ex b/lib/pleroma/config/deprecation_warnings.ex
index 59c6b0f58..b53b15d95 100644
--- a/lib/pleroma/config/deprecation_warnings.ex
+++ b/lib/pleroma/config/deprecation_warnings.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Config.DeprecationWarnings do
@@ -20,6 +20,177 @@ defmodule Pleroma.Config.DeprecationWarnings do
"\n* `config :pleroma, :instance, mrf_transparency_exclusions` is now `config :pleroma, :mrf, transparency_exclusions`"}
]
+ def check_exiftool_filter do
+ filters = Config.get([Pleroma.Upload]) |> Keyword.get(:filters, [])
+
+ if Pleroma.Upload.Filter.Exiftool in filters do
+ Logger.warn("""
+ !!!DEPRECATION WARNING!!!
+ Your config is using Exiftool as a filter instead of Exiftool.StripLocation. This should work for now, but you are advised to change to the new configuration to prevent possible issues later:
+
+ ```
+ config :pleroma, Pleroma.Upload,
+ filters: [Pleroma.Upload.Filter.Exiftool]
+ ```
+
+ Is now
+
+
+ ```
+ config :pleroma, Pleroma.Upload,
+ filters: [Pleroma.Upload.Filter.Exiftool.StripLocation]
+ ```
+ """)
+
+ new_config =
+ filters
+ |> Enum.map(fn
+ Pleroma.Upload.Filter.Exiftool -> Pleroma.Upload.Filter.Exiftool.StripLocation
+ filter -> filter
+ end)
+
+ Config.put([Pleroma.Upload, :filters], new_config)
+
+ :error
+ else
+ :ok
+ end
+ end
+
+ def check_simple_policy_tuples do
+ has_strings =
+ Config.get([:mrf_simple])
+ |> Enum.any?(fn {_, v} -> Enum.any?(v, &is_binary/1) end)
+
+ if has_strings do
+ Logger.warn("""
+ !!!DEPRECATION WARNING!!!
+ Your config is using strings in the SimplePolicy configuration instead of tuples. They should work for now, but you are advised to change to the new configuration to prevent possible issues later:
+
+ ```
+ config :pleroma, :mrf_simple,
+ media_removal: ["instance.tld"],
+ media_nsfw: ["instance.tld"],
+ federated_timeline_removal: ["instance.tld"],
+ report_removal: ["instance.tld"],
+ reject: ["instance.tld"],
+ followers_only: ["instance.tld"],
+ accept: ["instance.tld"],
+ avatar_removal: ["instance.tld"],
+ banner_removal: ["instance.tld"],
+ reject_deletes: ["instance.tld"]
+ ```
+
+ Is now
+
+
+ ```
+ config :pleroma, :mrf_simple,
+ media_removal: [{"instance.tld", "Reason for media removal"}],
+ media_nsfw: [{"instance.tld", "Reason for media nsfw"}],
+ federated_timeline_removal: [{"instance.tld", "Reason for federated timeline removal"}],
+ report_removal: [{"instance.tld", "Reason for report removal"}],
+ reject: [{"instance.tld", "Reason for reject"}],
+ followers_only: [{"instance.tld", "Reason for followers only"}],
+ accept: [{"instance.tld", "Reason for accept"}],
+ avatar_removal: [{"instance.tld", "Reason for avatar removal"}],
+ banner_removal: [{"instance.tld", "Reason for banner removal"}],
+ reject_deletes: [{"instance.tld", "Reason for reject deletes"}]
+ ```
+ """)
+
+ new_config =
+ Config.get([:mrf_simple])
+ |> Enum.map(fn {k, v} ->
+ {k,
+ Enum.map(v, fn
+ {instance, reason} -> {instance, reason}
+ instance -> {instance, ""}
+ end)}
+ end)
+
+ Config.put([:mrf_simple], new_config)
+
+ :error
+ else
+ :ok
+ end
+ end
+
+ def check_quarantined_instances_tuples do
+ has_strings = Config.get([:instance, :quarantined_instances]) |> Enum.any?(&is_binary/1)
+
+ if has_strings do
+ Logger.warn("""
+ !!!DEPRECATION WARNING!!!
+ Your config is using strings in the quarantined_instances configuration instead of tuples. They should work for now, but you are advised to change to the new configuration to prevent possible issues later:
+
+ ```
+ config :pleroma, :instance,
+ quarantined_instances: ["instance.tld"]
+ ```
+
+ Is now
+
+
+ ```
+ config :pleroma, :instance,
+ quarantined_instances: [{"instance.tld", "Reason for quarantine"}]
+ ```
+ """)
+
+ new_config =
+ Config.get([:instance, :quarantined_instances])
+ |> Enum.map(fn
+ {instance, reason} -> {instance, reason}
+ instance -> {instance, ""}
+ end)
+
+ Config.put([:instance, :quarantined_instances], new_config)
+
+ :error
+ else
+ :ok
+ end
+ end
+
+ def check_transparency_exclusions_tuples do
+ has_strings = Config.get([:mrf, :transparency_exclusions]) |> Enum.any?(&is_binary/1)
+
+ if has_strings do
+ Logger.warn("""
+ !!!DEPRECATION WARNING!!!
+ Your config is using strings in the transparency_exclusions configuration instead of tuples. They should work for now, but you are advised to change to the new configuration to prevent possible issues later:
+
+ ```
+ config :pleroma, :mrf,
+ transparency_exclusions: ["instance.tld"]
+ ```
+
+ Is now
+
+
+ ```
+ config :pleroma, :mrf,
+ transparency_exclusions: [{"instance.tld", "Reason to exlude transparency"}]
+ ```
+ """)
+
+ new_config =
+ Config.get([:mrf, :transparency_exclusions])
+ |> Enum.map(fn
+ {instance, reason} -> {instance, reason}
+ instance -> {instance, ""}
+ end)
+
+ Config.put([:mrf, :transparency_exclusions], new_config)
+
+ :error
+ else
+ :ok
+ end
+ end
+
def check_hellthread_threshold do
if Config.get([:mrf_hellthread, :threshold]) do
Logger.warn("""
@@ -34,18 +205,25 @@ defmodule Pleroma.Config.DeprecationWarnings do
end
def warn do
- with :ok <- check_hellthread_threshold(),
- :ok <- check_old_mrf_config(),
- :ok <- check_media_proxy_whitelist_config(),
- :ok <- check_welcome_message_config(),
- :ok <- check_gun_pool_options(),
- :ok <- check_activity_expiration_config(),
- :ok <- check_remote_ip_plug_name() do
- :ok
- else
- _ ->
- :error
- end
+ [
+ check_hellthread_threshold(),
+ check_old_mrf_config(),
+ check_media_proxy_whitelist_config(),
+ check_welcome_message_config(),
+ check_gun_pool_options(),
+ check_activity_expiration_config(),
+ check_remote_ip_plug_name(),
+ check_uploders_s3_public_endpoint(),
+ check_old_chat_shoutbox(),
+ check_quarantined_instances_tuples(),
+ check_transparency_exclusions_tuples(),
+ check_simple_policy_tuples(),
+ check_exiftool_filter()
+ ]
+ |> Enum.reduce(:ok, fn
+ :ok, :ok -> :ok
+ _, _ -> :error
+ end)
end
def check_welcome_message_config do
@@ -133,7 +311,7 @@ defmodule Pleroma.Config.DeprecationWarnings do
warning_preface = """
!!!DEPRECATION WARNING!!!
- Your config is using old setting name `timeout` instead of `recv_timeout` in pool settings. Setting should work for now, but you are advised to change format to scheme with port to prevent possible issues later.
+ Your config is using old setting name `timeout` instead of `recv_timeout` in pool settings. The setting will not take effect until updated.
"""
updated_config =
@@ -193,4 +371,48 @@ defmodule Pleroma.Config.DeprecationWarnings do
warning_preface
)
end
+
+ @spec check_uploders_s3_public_endpoint() :: :ok | nil
+ def check_uploders_s3_public_endpoint do
+ s3_config = Pleroma.Config.get([Pleroma.Uploaders.S3])
+
+ use_old_config = Keyword.has_key?(s3_config, :public_endpoint)
+
+ if use_old_config do
+ Logger.error("""
+ !!!DEPRECATION WARNING!!!
+ Your config is using the old setting for controlling the URL of media uploaded to your S3 bucket.\n
+ Please make the following change at your earliest convenience.\n
+ \n* `config :pleroma, Pleroma.Uploaders.S3, public_endpoint` is now equal to:
+ \n* `config :pleroma, Pleroma.Upload, base_url`
+ """)
+
+ :error
+ else
+ :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
diff --git a/lib/pleroma/config/getting.ex b/lib/pleroma/config/getting.ex
index cc557674c..f9b66bba6 100644
--- a/lib/pleroma/config/getting.ex
+++ b/lib/pleroma/config/getting.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Config.Getting do
diff --git a/lib/pleroma/config/helpers.ex b/lib/pleroma/config/helpers.ex
index 3dce40ea0..973a75c99 100644
--- a/lib/pleroma/config/helpers.ex
+++ b/lib/pleroma/config/helpers.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Config.Helpers do
diff --git a/lib/pleroma/config/holder.ex b/lib/pleroma/config/holder.ex
index a99fc0471..7822bde89 100644
--- a/lib/pleroma/config/holder.ex
+++ b/lib/pleroma/config/holder.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Config.Holder do
diff --git a/lib/pleroma/config/loader.ex b/lib/pleroma/config/loader.ex
index 64e7de6df..bd85eccab 100644
--- a/lib/pleroma/config/loader.ex
+++ b/lib/pleroma/config/loader.ex
@@ -1,11 +1,13 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Config.Loader do
+ # These modules are only being used as keys here (for equality check),
+ # so it's okay to use `Module.concat/1` to have the compiler ignore them.
@reject_keys [
- Pleroma.Repo,
- Pleroma.Web.Endpoint,
+ Module.concat(["Pleroma.Repo"]),
+ Module.concat(["Pleroma.Web.Endpoint"]),
:env,
:configurable_from_database,
:database,
@@ -17,21 +19,10 @@ defmodule Pleroma.Config.Loader do
:tesla
]
- if Code.ensure_loaded?(Config.Reader) do
- @reader Config.Reader
-
- def read(path), do: @reader.read!(path)
- else
- # support for Elixir less than 1.9
- @reader Mix.Config
- def read(path) do
- path
- |> @reader.eval!()
- |> elem(0)
- end
- end
+ @reader Config.Reader
@spec read(Path.t()) :: keyword()
+ def read(path), do: @reader.read!(path)
@spec merge(keyword(), keyword()) :: keyword()
def merge(c1, c2), do: @reader.merge(c1, c2)
diff --git a/lib/pleroma/config/oban.ex b/lib/pleroma/config/oban.ex
index 8e0351d52..483d2bb79 100644
--- a/lib/pleroma/config/oban.ex
+++ b/lib/pleroma/config/oban.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Config.Oban do
@@ -21,9 +21,7 @@ defmodule Pleroma.Config.Oban do
"""
!!!OBAN CONFIG WARNING!!!
You are using old workers in Oban crontab settings, which were removed.
- Please, remove setting from crontab in your config file (prod.secret.exs): #{
- inspect(setting)
- }
+ Please, remove setting from crontab in your config file (prod.secret.exs): #{inspect(setting)}
"""
|> Logger.warn()
diff --git a/lib/pleroma/config/release_runtime_provider.ex b/lib/pleroma/config/release_runtime_provider.ex
index 8227195dc..91e5f1a54 100644
--- a/lib/pleroma/config/release_runtime_provider.ex
+++ b/lib/pleroma/config/release_runtime_provider.ex
@@ -1,6 +1,10 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
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 +12,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 +29,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 +38,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
diff --git a/lib/pleroma/config/transfer_task.ex b/lib/pleroma/config/transfer_task.ex
index a0d7b7d71..44a984019 100644
--- a/lib/pleroma/config/transfer_task.ex
+++ b/lib/pleroma/config/transfer_task.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Config.TransferTask do
@@ -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?)
@@ -45,7 +47,7 @@ defmodule Pleroma.Config.TransferTask do
{logger, other} =
(Repo.all(ConfigDB) ++ deleted_settings)
|> Enum.map(&merge_with_default/1)
- |> Enum.split_with(fn {group, _, _, _} -> group in [:logger, :quack] end)
+ |> Enum.split_with(fn {group, _, _, _} -> group in [:logger] end)
logger
|> Enum.sort()
@@ -102,11 +104,6 @@ defmodule Pleroma.Config.TransferTask do
end
# change logger configuration in runtime, without restart
- defp configure({:quack, key, _, merged}) do
- Logger.configure_backend(Quack.Logger, [{key, merged}])
- :ok = update_env(:quack, key, merged)
- end
-
defp configure({_, :backends, _, merged}) do
# removing current backends
Enum.each(Application.get_env(:logger, :backends), &Logger.remove_backend/1)
@@ -146,9 +143,7 @@ defmodule Pleroma.Config.TransferTask do
rescue
error ->
error_msg =
- "updating env causes error, group: #{inspect(group)}, key: #{inspect(key)}, value: #{
- inspect(value)
- } error: #{inspect(error)}"
+ "updating env causes error, group: #{inspect(group)}, key: #{inspect(key)}, value: #{inspect(value)} error: #{inspect(error)}"
Logger.warn(error_msg)
@@ -165,12 +160,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)
diff --git a/lib/pleroma/config_db.ex b/lib/pleroma/config_db.ex
index 8e8bb732f..846cede04 100644
--- a/lib/pleroma/config_db.ex
+++ b/lib/pleroma/config_db.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.ConfigDB do
@@ -163,7 +163,6 @@ defmodule Pleroma.ConfigDB do
defp only_full_update?(%ConfigDB{group: group, key: key}) do
full_key_update = [
{:pleroma, :ecto_repos},
- {:quack, :meta},
{:mime, :types},
{:cors_plug, [:max_age, :methods, :expose, :headers]},
{:swarm, :node_blacklist},
@@ -386,7 +385,7 @@ defmodule Pleroma.ConfigDB do
@spec module_name?(String.t()) :: boolean()
def module_name?(string) do
- Regex.match?(~r/^(Pleroma|Phoenix|Tesla|Quack|Ueberauth|Swoosh)\./, string) or
- string in ["Oban", "Ueberauth", "ExSyslogger"]
+ Regex.match?(~r/^(Pleroma|Phoenix|Tesla|Ueberauth|Swoosh)\./, string) or
+ string in ["Oban", "Ueberauth", "ExSyslogger", "ConcurrentLimiter"]
end
end
diff --git a/lib/pleroma/constants.ex b/lib/pleroma/constants.ex
index cf8182d55..cfb405218 100644
--- a/lib/pleroma/constants.ex
+++ b/lib/pleroma/constants.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Constants do
@@ -18,7 +18,8 @@ defmodule Pleroma.Constants do
"emoji",
"context_id",
"deleted_activity_id",
- "pleroma_internal"
+ "pleroma_internal",
+ "generator"
]
)
@@ -27,5 +28,45 @@ defmodule Pleroma.Constants 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"
+ const(status_updatable_fields,
+ do: [
+ "source",
+ "tag",
+ "updated",
+ "emoji",
+ "content",
+ "summary",
+ "sensitive",
+ "attachment",
+ "generator"
+ ]
+ )
+
+ const(updatable_object_types,
+ do: [
+ "Note",
+ "Question",
+ "Audio",
+ "Video",
+ "Event",
+ "Article",
+ "Page"
+ ]
+ )
+
+ const(actor_types,
+ do: [
+ "Application",
+ "Group",
+ "Organization",
+ "Person",
+ "Service"
+ ]
+ )
+
+ # basic regex, just there to weed out potential mistakes
+ # https://datatracker.ietf.org/doc/html/rfc2045#section-5.1
+ const(mime_regex,
+ do: ~r/^[^[:cntrl:] ()<>@,;:\\"\/\[\]?=]+\/[^[:cntrl:] ()<>@,;:\\"\/\[\]?=]+(; .*)?$/
+ )
end
diff --git a/lib/pleroma/conversation.ex b/lib/pleroma/conversation.ex
index 77933f0be..42028aa51 100644
--- a/lib/pleroma/conversation.ex
+++ b/lib/pleroma/conversation.ex
@@ -1,10 +1,11 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Conversation do
alias Pleroma.Conversation.Participation
alias Pleroma.Conversation.Participation.RecipientShip
+ alias Pleroma.Object
alias Pleroma.Repo
alias Pleroma.User
use Ecto.Schema
@@ -58,11 +59,10 @@ defmodule Pleroma.Conversation do
def create_or_bump_for(activity, opts \\ []) do
with true <- Pleroma.Web.ActivityPub.Visibility.is_direct?(activity),
"Create" <- activity.data["type"],
- object <- Pleroma.Object.normalize(activity),
+ %Object{} = object <- Object.normalize(activity, fetch: false),
true <- object.data["type"] in ["Note", "Question"],
- ap_id when is_binary(ap_id) and byte_size(ap_id) > 0 <- object.data["context"] do
- {:ok, conversation} = create_for_ap_id(ap_id)
-
+ ap_id when is_binary(ap_id) and byte_size(ap_id) > 0 <- object.data["context"],
+ {:ok, conversation} <- create_for_ap_id(ap_id) do
users = User.get_users_from_set(activity.recipients, local_only: false)
participations =
diff --git a/lib/pleroma/conversation/participation.ex b/lib/pleroma/conversation/participation.ex
index 4c32b273a..4ed93e5bd 100644
--- a/lib/pleroma/conversation/participation.ex
+++ b/lib/pleroma/conversation/participation.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Conversation.Participation do
@@ -220,4 +220,8 @@ defmodule Pleroma.Conversation.Participation do
select: %{count: count(p.id)}
)
end
+
+ def delete(%__MODULE__{} = participation) do
+ Repo.delete(participation)
+ end
end
diff --git a/lib/pleroma/conversation/participation/recipient_ship.ex b/lib/pleroma/conversation/participation/recipient_ship.ex
index de40bacac..d9a0fdff8 100644
--- a/lib/pleroma/conversation/participation/recipient_ship.ex
+++ b/lib/pleroma/conversation/participation/recipient_ship.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Conversation.Participation.RecipientShip do
diff --git a/lib/pleroma/counter_cache.ex b/lib/pleroma/counter_cache.ex
index ebd1f603d..d2b14bd54 100644
--- a/lib/pleroma/counter_cache.ex
+++ b/lib/pleroma/counter_cache.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.CounterCache do
diff --git a/lib/pleroma/data_migration.ex b/lib/pleroma/data_migration.ex
new file mode 100644
index 000000000..8451678fc
--- /dev/null
+++ b/lib/pleroma/data_migration.ex
@@ -0,0 +1,46 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.DataMigration do
+ use Ecto.Schema
+
+ alias Pleroma.DataMigration
+ alias Pleroma.DataMigration.State
+ alias Pleroma.Repo
+
+ import Ecto.Changeset
+ import Ecto.Query
+
+ schema "data_migrations" do
+ field(:name, :string)
+ field(:state, State, default: :pending)
+ field(:feature_lock, :boolean, default: false)
+ field(:params, :map, default: %{})
+ field(:data, :map, default: %{})
+
+ timestamps()
+ end
+
+ def changeset(data_migration, params \\ %{}) do
+ data_migration
+ |> cast(params, [:name, :state, :feature_lock, :params, :data])
+ |> validate_required([:name])
+ |> unique_constraint(:name)
+ end
+
+ def update_one_by_id(id, params \\ %{}) do
+ with {1, _} <-
+ from(dm in DataMigration, where: dm.id == ^id)
+ |> Repo.update_all(set: params) do
+ :ok
+ end
+ end
+
+ def get_by_name(name) do
+ Repo.get_by(DataMigration, name: name)
+ end
+
+ def populate_hashtags_table, do: get_by_name("populate_hashtags_table")
+ def delete_context_objects, do: get_by_name("delete_context_objects")
+end
diff --git a/lib/pleroma/delivery.ex b/lib/pleroma/delivery.ex
index 0ded2855c..5d1853b76 100644
--- a/lib/pleroma/delivery.ex
+++ b/lib/pleroma/delivery.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Delivery do
@@ -9,7 +9,6 @@ defmodule Pleroma.Delivery do
alias Pleroma.Object
alias Pleroma.Repo
alias Pleroma.User
- alias Pleroma.User
import Ecto.Changeset
import Ecto.Query
diff --git a/lib/pleroma/docs/generator.ex b/lib/pleroma/docs/generator.ex
index a70f83b73..6508f1947 100644
--- a/lib/pleroma/docs/generator.ex
+++ b/lib/pleroma/docs/generator.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Docs.Generator do
diff --git a/lib/pleroma/docs/json.ex b/lib/pleroma/docs/json.ex
index a583e2a5b..05f46f39b 100644
--- a/lib/pleroma/docs/json.ex
+++ b/lib/pleroma/docs/json.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Docs.JSON do
diff --git a/lib/pleroma/docs/markdown.ex b/lib/pleroma/docs/markdown.ex
index eac0789a6..949388eb9 100644
--- a/lib/pleroma/docs/markdown.ex
+++ b/lib/pleroma/docs/markdown.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Docs.Markdown do
diff --git a/lib/pleroma/docs/translator.ex b/lib/pleroma/docs/translator.ex
new file mode 100644
index 000000000..13e33c87e
--- /dev/null
+++ b/lib/pleroma/docs/translator.ex
@@ -0,0 +1,10 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Docs.Translator do
+ require Pleroma.Docs.Translator.Compiler
+ require Pleroma.Web.Gettext
+
+ @before_compile Pleroma.Docs.Translator.Compiler
+end
diff --git a/lib/pleroma/docs/translator/compiler.ex b/lib/pleroma/docs/translator/compiler.ex
new file mode 100644
index 000000000..5d27d9fa2
--- /dev/null
+++ b/lib/pleroma/docs/translator/compiler.ex
@@ -0,0 +1,119 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Docs.Translator.Compiler do
+ @external_resource "config/description.exs"
+ @raw_config Pleroma.Config.Loader.read("config/description.exs")
+ @raw_descriptions @raw_config[:pleroma][:config_description]
+
+ defmacro __before_compile__(_env) do
+ strings =
+ __MODULE__.descriptions()
+ |> __MODULE__.extract_strings()
+
+ quote do
+ def placeholder do
+ unquote do
+ Enum.map(
+ strings,
+ fn {path, type, string} ->
+ ctxt = msgctxt_for(path, type)
+
+ quote do
+ Pleroma.Web.Gettext.dpgettext_noop(
+ "config_descriptions",
+ unquote(ctxt),
+ unquote(string)
+ )
+ end
+ end
+ )
+ end
+ end
+ end
+ end
+
+ def descriptions do
+ Pleroma.Web.ActivityPub.MRF.config_descriptions()
+ |> Enum.reduce(@raw_descriptions, fn description, acc -> [description | acc] end)
+ |> Pleroma.Docs.Generator.convert_to_strings()
+ end
+
+ def extract_strings(descriptions) do
+ descriptions
+ |> Enum.reduce(%{strings: [], path: []}, &process_item/2)
+ |> Map.get(:strings)
+ end
+
+ defp process_item(entity, acc) do
+ current_level =
+ acc
+ |> process_desc(entity)
+ |> process_label(entity)
+
+ process_children(entity, current_level)
+ end
+
+ defp process_desc(acc, %{description: desc} = item) do
+ %{
+ strings: [{acc.path ++ [key_for(item)], "description", desc} | acc.strings],
+ path: acc.path
+ }
+ end
+
+ defp process_desc(acc, _) do
+ acc
+ end
+
+ defp process_label(acc, %{label: label} = item) do
+ %{
+ strings: [{acc.path ++ [key_for(item)], "label", label} | acc.strings],
+ path: acc.path
+ }
+ end
+
+ defp process_label(acc, _) do
+ acc
+ end
+
+ defp process_children(%{children: children} = item, acc) do
+ current_level = Map.put(acc, :path, acc.path ++ [key_for(item)])
+
+ children
+ |> Enum.reduce(current_level, &process_item/2)
+ |> Map.put(:path, acc.path)
+ end
+
+ defp process_children(_, acc) do
+ acc
+ end
+
+ def msgctxt_for(path, type) do
+ "config #{type} at #{Enum.join(path, " > ")}"
+ end
+
+ defp convert_group({_, group}) do
+ group
+ end
+
+ defp convert_group(group) do
+ group
+ end
+
+ def key_for(%{group: group, key: key}) do
+ "#{convert_group(group)}-#{key}"
+ end
+
+ def key_for(%{group: group}) do
+ convert_group(group)
+ end
+
+ def key_for(%{key: key}) do
+ key
+ end
+
+ def key_for(_) do
+ nil
+ end
+end
diff --git a/lib/pleroma/earmark_renderer.ex b/lib/pleroma/earmark_renderer.ex
deleted file mode 100644
index 6211a3b4a..000000000
--- a/lib/pleroma/earmark_renderer.ex
+++ /dev/null
@@ -1,256 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2020 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
diff --git a/lib/pleroma/ecto_enums.ex b/lib/pleroma/ecto_enums.ex
index 6fc47620c..a4890b489 100644
--- a/lib/pleroma/ecto_enums.ex
+++ b/lib/pleroma/ecto_enums.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
import EctoEnum
@@ -9,7 +9,9 @@ defenum(Pleroma.UserRelationship.Type,
mute: 2,
reblog_mute: 3,
notification_mute: 4,
- inverse_subscription: 5
+ inverse_subscription: 5,
+ suggestion_dismiss: 6,
+ endorsement: 7
)
defenum(Pleroma.FollowingRelationship.State,
@@ -17,3 +19,11 @@ defenum(Pleroma.FollowingRelationship.State,
follow_accept: 2,
follow_reject: 3
)
+
+defenum(Pleroma.DataMigration.State,
+ pending: 1,
+ running: 2,
+ complete: 3,
+ failed: 4,
+ manual: 5
+)
diff --git a/lib/pleroma/ecto_type/activity_pub/object_validators/date_time.ex b/lib/pleroma/ecto_type/activity_pub/object_validators/date_time.ex
index d852c0abd..b0258e86d 100644
--- a/lib/pleroma/ecto_type/activity_pub/object_validators/date_time.ex
+++ b/lib/pleroma/ecto_type/activity_pub/object_validators/date_time.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.EctoType.ActivityPub.ObjectValidators.DateTime do
diff --git a/lib/pleroma/ecto_type/activity_pub/object_validators/emoji.ex b/lib/pleroma/ecto_type/activity_pub/object_validators/emoji.ex
index 4aacc5c88..e0e4449dc 100644
--- a/lib/pleroma/ecto_type/activity_pub/object_validators/emoji.ex
+++ b/lib/pleroma/ecto_type/activity_pub/object_validators/emoji.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.EctoType.ActivityPub.ObjectValidators.Emoji do
diff --git a/lib/pleroma/ecto_type/activity_pub/object_validators/mime.ex b/lib/pleroma/ecto_type/activity_pub/object_validators/mime.ex
new file mode 100644
index 000000000..31d51577d
--- /dev/null
+++ b/lib/pleroma/ecto_type/activity_pub/object_validators/mime.ex
@@ -0,0 +1,25 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.EctoType.ActivityPub.ObjectValidators.MIME do
+ use Ecto.Type
+
+ require Pleroma.Constants
+
+ def type, do: :string
+
+ def cast(mime) when is_binary(mime) do
+ if mime =~ Pleroma.Constants.mime_regex() do
+ {:ok, mime}
+ else
+ {:ok, "application/octet-stream"}
+ end
+ end
+
+ def cast(_), do: :error
+
+ def dump(data), do: {:ok, data}
+
+ def load(data), do: {:ok, data}
+end
diff --git a/lib/pleroma/ecto_type/activity_pub/object_validators/object_id.ex b/lib/pleroma/ecto_type/activity_pub/object_validators/object_id.ex
index 8034235b0..663dc0dee 100644
--- a/lib/pleroma/ecto_type/activity_pub/object_validators/object_id.ex
+++ b/lib/pleroma/ecto_type/activity_pub/object_validators/object_id.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.EctoType.ActivityPub.ObjectValidators.ObjectID do
diff --git a/lib/pleroma/ecto_type/activity_pub/object_validators/recipients.ex b/lib/pleroma/ecto_type/activity_pub/object_validators/recipients.ex
index 205527a96..447d536ed 100644
--- a/lib/pleroma/ecto_type/activity_pub/object_validators/recipients.ex
+++ b/lib/pleroma/ecto_type/activity_pub/object_validators/recipients.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.EctoType.ActivityPub.ObjectValidators.Recipients do
@@ -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
diff --git a/lib/pleroma/ecto_type/activity_pub/object_validators/safe_text.ex b/lib/pleroma/ecto_type/activity_pub/object_validators/safe_text.ex
index 7f0405c7b..95bd3ba23 100644
--- a/lib/pleroma/ecto_type/activity_pub/object_validators/safe_text.ex
+++ b/lib/pleroma/ecto_type/activity_pub/object_validators/safe_text.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.EctoType.ActivityPub.ObjectValidators.SafeText do
diff --git a/lib/pleroma/ecto_type/activity_pub/object_validators/uri.ex b/lib/pleroma/ecto_type/activity_pub/object_validators/uri.ex
index 2054c26be..b8e5c9dad 100644
--- a/lib/pleroma/ecto_type/activity_pub/object_validators/uri.ex
+++ b/lib/pleroma/ecto_type/activity_pub/object_validators/uri.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.EctoType.ActivityPub.ObjectValidators.Uri do
diff --git a/lib/pleroma/ecto_type/config/atom.ex b/lib/pleroma/ecto_type/config/atom.ex
index df565d432..c44d655e0 100644
--- a/lib/pleroma/ecto_type/config/atom.ex
+++ b/lib/pleroma/ecto_type/config/atom.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.EctoType.Config.Atom do
diff --git a/lib/pleroma/ecto_type/config/binary_value.ex b/lib/pleroma/ecto_type/config/binary_value.ex
index bbd2608c5..4aad0cfce 100644
--- a/lib/pleroma/ecto_type/config/binary_value.ex
+++ b/lib/pleroma/ecto_type/config/binary_value.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.EctoType.Config.BinaryValue do
diff --git a/lib/pleroma/emails/admin_email.ex b/lib/pleroma/emails/admin_email.ex
index d5757c12a..372e5529d 100644
--- a/lib/pleroma/emails/admin_email.ex
+++ b/lib/pleroma/emails/admin_email.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Emails.AdminEmail do
@@ -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()
diff --git a/lib/pleroma/emails/mailer.ex b/lib/pleroma/emails/mailer.ex
index 5108c71c8..101442130 100644
--- a/lib/pleroma/emails/mailer.ex
+++ b/lib/pleroma/emails/mailer.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Emails.Mailer do
diff --git a/lib/pleroma/emails/new_users_digest_email.ex b/lib/pleroma/emails/new_users_digest_email.ex
index 348cbac9c..a9e57c7d4 100644
--- a/lib/pleroma/emails/new_users_digest_email.ex
+++ b/lib/pleroma/emails/new_users_digest_email.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Emails.NewUsersDigestEmail do
diff --git a/lib/pleroma/emails/user_email.ex b/lib/pleroma/emails/user_email.ex
index d3625dbf2..95b963764 100644
--- a/lib/pleroma/emails/user_email.ex
+++ b/lib/pleroma/emails/user_email.ex
@@ -1,48 +1,104 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Emails.UserEmail do
@moduledoc "User emails"
- use Phoenix.Swoosh, view: Pleroma.Web.EmailView, layout: {Pleroma.Web.LayoutView, :email}
+ require Pleroma.Web.Gettext
alias Pleroma.Config
alias Pleroma.User
alias Pleroma.Web.Endpoint
+ alias Pleroma.Web.Gettext
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)
@spec welcome(User.t(), map()) :: Swoosh.Email.t()
def welcome(user, opts \\ %{}) do
- new()
- |> to(recipient(user))
- |> from(Map.get(opts, :sender, sender()))
- |> subject(Map.get(opts, :subject, "Welcome to #{instance_name()}!"))
- |> html_body(Map.get(opts, :html, "Welcome to #{instance_name()}!"))
- |> text_body(Map.get(opts, :text, "Welcome to #{instance_name()}!"))
+ Gettext.with_locale_or_default user.language do
+ new()
+ |> to(recipient(user))
+ |> from(Map.get(opts, :sender, sender()))
+ |> subject(
+ Map.get(
+ opts,
+ :subject,
+ Gettext.dpgettext(
+ "static_pages",
+ "welcome email subject",
+ "Welcome to %{instance_name}!",
+ instance_name: instance_name()
+ )
+ )
+ )
+ |> html_body(
+ Map.get(
+ opts,
+ :html,
+ Gettext.dpgettext(
+ "static_pages",
+ "welcome email html body",
+ "Welcome to %{instance_name}!",
+ instance_name: instance_name()
+ )
+ )
+ )
+ |> text_body(
+ Map.get(
+ opts,
+ :text,
+ Gettext.dpgettext(
+ "static_pages",
+ "welcome email text body",
+ "Welcome to %{instance_name}!",
+ instance_name: instance_name()
+ )
+ )
+ )
+ end
end
def password_reset_email(user, token) when is_binary(token) do
- password_reset_url = Router.Helpers.reset_password_url(Endpoint, :reset, token)
-
- html_body = """
- <h3>Reset your password at #{instance_name()}</h3>
- <p>Someone has requested password change for your account at #{instance_name()}.</p>
- <p>If it was you, visit the following link to proceed: <a href="#{password_reset_url}">reset password</a>.</p>
- <p>If it was someone else, nothing to worry about: your data is secure and your password has not been changed.</p>
- """
-
- new()
- |> to(recipient(user))
- |> from(sender())
- |> subject("Password reset")
- |> html_body(html_body)
+ Gettext.with_locale_or_default user.language do
+ password_reset_url = Router.Helpers.reset_password_url(Endpoint, :reset, token)
+
+ html_body =
+ Gettext.dpgettext(
+ "static_pages",
+ "password reset email body",
+ """
+ <h3>Reset your password at %{instance_name}</h3>
+ <p>Someone has requested password change for your account at %{instance_name}.</p>
+ <p>If it was you, visit the following link to proceed: <a href="%{password_reset_url}">reset password</a>.</p>
+ <p>If it was someone else, nothing to worry about: your data is secure and your password has not been changed.</p>
+ """,
+ instance_name: instance_name(),
+ password_reset_url: password_reset_url
+ )
+
+ new()
+ |> to(recipient(user))
+ |> from(sender())
+ |> subject(
+ Gettext.dpgettext("static_pages", "password reset email subject", "Password reset")
+ )
+ |> html_body(html_body)
+ end
end
def user_invitation_email(
@@ -51,59 +107,136 @@ defmodule Pleroma.Emails.UserEmail do
to_email,
to_name \\ nil
) do
- registration_url =
- Router.Helpers.redirect_url(
- Endpoint,
- :registration_page,
- user_invite_token.token
- )
+ Gettext.with_locale_or_default user.language do
+ registration_url =
+ Router.Helpers.redirect_url(
+ Endpoint,
+ :registration_page,
+ user_invite_token.token
+ )
+
+ html_body =
+ Gettext.dpgettext(
+ "static_pages",
+ "user invitation email body",
+ """
+ <h3>You are invited to %{instance_name}</h3>
+ <p>%{inviter_name} invites you to join %{instance_name}, an instance of Pleroma federated social networking platform.</p>
+ <p>Click the following link to register: <a href="%{registration_url}">accept invitation</a>.</p>
+ """,
+ instance_name: instance_name(),
+ inviter_name: user.name,
+ registration_url: registration_url
+ )
- html_body = """
- <h3>You are invited to #{instance_name()}</h3>
- <p>#{user.name} invites you to join #{instance_name()}, an instance of Pleroma federated social networking platform.</p>
- <p>Click the following link to register: <a href="#{registration_url}">accept invitation</a>.</p>
- """
-
- new()
- |> to(recipient(to_email, to_name))
- |> from(sender())
- |> subject("Invitation to #{instance_name()}")
- |> html_body(html_body)
+ new()
+ |> to(recipient(to_email, to_name))
+ |> from(sender())
+ |> subject(
+ Gettext.dpgettext(
+ "static_pages",
+ "user invitation email subject",
+ "Invitation to %{instance_name}",
+ instance_name: instance_name()
+ )
+ )
+ |> html_body(html_body)
+ end
end
def account_confirmation_email(user) do
- confirmation_url =
- Router.Helpers.confirm_email_url(
- Endpoint,
- :confirm_email,
- user.id,
- to_string(user.confirmation_token)
- )
+ Gettext.with_locale_or_default user.language do
+ confirmation_url =
+ Router.Helpers.confirm_email_url(
+ Endpoint,
+ :confirm_email,
+ user.id,
+ to_string(user.confirmation_token)
+ )
+
+ html_body =
+ Gettext.dpgettext(
+ "static_pages",
+ "confirmation email body",
+ """
+ <h3>Thank you for registering on %{instance_name}</h3>
+ <p>Email confirmation is required to activate the account.</p>
+ <p>Please click the following link to <a href="%{confirmation_url}">activate your account</a>.</p>
+ """,
+ instance_name: instance_name(),
+ confirmation_url: confirmation_url
+ )
- html_body = """
- <h3>Welcome to #{instance_name()}!</h3>
- <p>Email confirmation is required to activate the account.</p>
- <p>Click the following link to proceed: <a href="#{confirmation_url}">activate your account</a>.</p>
- """
-
- new()
- |> to(recipient(user))
- |> from(sender())
- |> subject("#{instance_name()} account confirmation")
- |> html_body(html_body)
+ new()
+ |> to(recipient(user))
+ |> from(sender())
+ |> subject(
+ Gettext.dpgettext(
+ "static_pages",
+ "confirmation email subject",
+ "%{instance_name} account confirmation",
+ instance_name: instance_name()
+ )
+ )
+ |> html_body(html_body)
+ end
end
def approval_pending_email(user) do
- html_body = """
- <h3>Awaiting Approval</h3>
- <p>Your account at #{instance_name()} is being reviewed by staff. You will receive another email once your account is approved.</p>
- """
-
- new()
- |> to(recipient(user))
- |> from(sender())
- |> subject("Your account is awaiting approval")
- |> html_body(html_body)
+ Gettext.with_locale_or_default user.language do
+ html_body =
+ Gettext.dpgettext(
+ "static_pages",
+ "approval pending email body",
+ """
+ <h3>Awaiting Approval</h3>
+ <p>Your account at %{instance_name} is being reviewed by staff. You will receive another email once your account is approved.</p>
+ """,
+ instance_name: instance_name()
+ )
+
+ new()
+ |> to(recipient(user))
+ |> from(sender())
+ |> subject(
+ Gettext.dpgettext(
+ "static_pages",
+ "approval pending email subject",
+ "Your account is awaiting approval"
+ )
+ )
+ |> html_body(html_body)
+ end
+ end
+
+ def successful_registration_email(user) do
+ Gettext.with_locale_or_default user.language do
+ html_body =
+ Gettext.dpgettext(
+ "static_pages",
+ "successful registration email body",
+ """
+ <h3>Hello @%{nickname},</h3>
+ <p>Your account at %{instance_name} has been registered successfully.</p>
+ <p>No further action is required to activate your account.</p>
+ """,
+ nickname: user.nickname,
+ instance_name: instance_name()
+ )
+
+ new()
+ |> to(recipient(user))
+ |> from(sender())
+ |> subject(
+ Gettext.dpgettext(
+ "static_pages",
+ "successful registration email subject",
+ "Account registered on %{instance_name}",
+ instance_name: instance_name()
+ )
+ )
+ |> html_body(html_body)
+ end
end
@doc """
@@ -113,69 +246,78 @@ defmodule Pleroma.Emails.UserEmail do
"""
@spec digest_email(User.t()) :: Swoosh.Email.t() | nil
def digest_email(user) do
- notifications = Pleroma.Notification.for_user_since(user, user.last_digest_emailed_at)
-
- mentions =
- notifications
- |> Enum.filter(&(&1.activity.data["type"] == "Create"))
- |> Enum.map(fn notification ->
- object = Pleroma.Object.normalize(notification.activity)
-
- if not is_nil(object) do
- object = update_in(object.data["content"], &format_links/1)
-
- %{
- data: notification,
- object: object,
- from: User.get_by_ap_id(notification.activity.actor)
- }
- end
- end)
- |> Enum.filter(& &1)
-
- followers =
- notifications
- |> Enum.filter(&(&1.activity.data["type"] == "Follow"))
- |> Enum.map(fn notification ->
- from = User.get_by_ap_id(notification.activity.actor)
-
- if not is_nil(from) do
- %{
- data: notification,
- object: Pleroma.Object.normalize(notification.activity),
- from: User.get_by_ap_id(notification.activity.actor)
- }
- end
- end)
- |> Enum.filter(& &1)
-
- unless Enum.empty?(mentions) do
- styling = Config.get([__MODULE__, :styling])
- logo = Config.get([__MODULE__, :logo])
-
- html_data = %{
- instance: instance_name(),
- user: user,
- mentions: mentions,
- followers: followers,
- unsubscribe_link: unsubscribe_url(user, "digest"),
- styling: styling
- }
-
- logo_path =
- if is_nil(logo) do
- Path.join(:code.priv_dir(:pleroma), "static/static/logo.svg")
- else
- Path.join(Config.get([:instance, :static_dir]), logo)
- end
-
- new()
- |> to(recipient(user))
- |> from(sender())
- |> subject("Your digest from #{instance_name()}")
- |> put_layout(false)
- |> render_body("digest.html", html_data)
- |> attachment(Swoosh.Attachment.new(logo_path, filename: "logo.svg", type: :inline))
+ Gettext.with_locale_or_default user.language do
+ notifications = Pleroma.Notification.for_user_since(user, user.last_digest_emailed_at)
+
+ mentions =
+ notifications
+ |> Enum.filter(&(&1.activity.data["type"] == "Create"))
+ |> Enum.map(fn notification ->
+ object = Pleroma.Object.normalize(notification.activity, fetch: false)
+
+ if not is_nil(object) do
+ object = update_in(object.data["content"], &format_links/1)
+
+ %{
+ data: notification,
+ object: object,
+ from: User.get_by_ap_id(notification.activity.actor)
+ }
+ end
+ end)
+ |> Enum.filter(& &1)
+
+ followers =
+ notifications
+ |> Enum.filter(&(&1.activity.data["type"] == "Follow"))
+ |> Enum.map(fn notification ->
+ from = User.get_by_ap_id(notification.activity.actor)
+
+ if not is_nil(from) do
+ %{
+ data: notification,
+ object: Pleroma.Object.normalize(notification.activity, fetch: false),
+ from: User.get_by_ap_id(notification.activity.actor)
+ }
+ end
+ end)
+ |> Enum.filter(& &1)
+
+ unless Enum.empty?(mentions) do
+ styling = Config.get([__MODULE__, :styling])
+ logo = Config.get([__MODULE__, :logo])
+
+ html_data = %{
+ instance: instance_name(),
+ user: user,
+ mentions: mentions,
+ followers: followers,
+ unsubscribe_link: unsubscribe_url(user, "digest"),
+ styling: styling
+ }
+
+ logo_path =
+ if is_nil(logo) do
+ Path.join(:code.priv_dir(:pleroma), "static/static/logo.svg")
+ else
+ Path.join(Config.get([:instance, :static_dir]), logo)
+ end
+
+ new()
+ |> to(recipient(user))
+ |> from(sender())
+ |> subject(
+ Gettext.dpgettext(
+ "static_pages",
+ "digest email subject",
+ "Your digest from %{instance_name}",
+ instance_name: instance_name()
+ )
+ )
+ |> put_layout(false)
+ |> render_body("digest.html", html_data)
+ |> attachment(Swoosh.Attachment.new(logo_path, filename: "logo.svg", type: :inline))
+ end
end
end
@@ -205,27 +347,47 @@ defmodule Pleroma.Emails.UserEmail do
def backup_is_ready_email(backup, admin_user_id \\ nil) do
%{user: user} = Pleroma.Repo.preload(backup, :user)
- download_url = Pleroma.Web.PleromaAPI.BackupView.download_url(backup)
-
- html_body =
- if is_nil(admin_user_id) do
- """
- <p>You requested a full backup of your Pleroma account. It's ready for download:</p>
- <p><a href="#{download_url}">#{download_url}</a></p>
- """
- else
- admin = Pleroma.Repo.get(User, admin_user_id)
-
- """
- <p>Admin @#{admin.nickname} requested a full backup of your Pleroma account. It's ready for download:</p>
- <p><a href="#{download_url}">#{download_url}</a></p>
- """
- end
- new()
- |> to(recipient(user))
- |> from(sender())
- |> subject("Your account archive is ready")
- |> html_body(html_body)
+ Gettext.with_locale_or_default user.language do
+ download_url = Pleroma.Web.PleromaAPI.BackupView.download_url(backup)
+
+ html_body =
+ if is_nil(admin_user_id) do
+ Gettext.dpgettext(
+ "static_pages",
+ "account archive email body - self-requested",
+ """
+ <p>You requested a full backup of your Pleroma account. It's ready for download:</p>
+ <p><a href="%{download_url}">%{download_url}</a></p>
+ """,
+ download_url: download_url
+ )
+ else
+ admin = Pleroma.Repo.get(User, admin_user_id)
+
+ Gettext.dpgettext(
+ "static_pages",
+ "account archive email body - admin requested",
+ """
+ <p>Admin @%{admin_nickname} requested a full backup of your Pleroma account. It's ready for download:</p>
+ <p><a href="%{download_url}">%{download_url}</a></p>
+ """,
+ admin_nickname: admin.nickname,
+ download_url: download_url
+ )
+ end
+
+ new()
+ |> to(recipient(user))
+ |> from(sender())
+ |> subject(
+ Gettext.dpgettext(
+ "static_pages",
+ "account archive email subject",
+ "Your account archive is ready"
+ )
+ )
+ |> html_body(html_body)
+ end
end
end
diff --git a/lib/pleroma/emoji-test.txt b/lib/pleroma/emoji-test.txt
index d3c6d12bd..87d093d64 100644
--- a/lib/pleroma/emoji-test.txt
+++ b/lib/pleroma/emoji-test.txt
@@ -1,13 +1,13 @@
# emoji-test.txt
-# Date: 2020-09-12, 22:19:50 GMT
-# © 2020 Unicode®, Inc.
+# Date: 2022-08-12, 20:24:39 GMT
+# © 2022 Unicode®, Inc.
# Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries.
-# For terms of use, see http://www.unicode.org/terms_of_use.html
+# For terms of use, see https://www.unicode.org/terms_of_use.html
#
# Emoji Keyboard/Display Test Data for UTS #51
-# Version: 13.1
+# Version: 15.0
#
-# For documentation and usage, see http://www.unicode.org/reports/tr51
+# For documentation and usage, see https://www.unicode.org/reports/tr51
#
# This file provides data for testing which emoji forms should be in keyboards and which should also be displayed/processed.
# Format: code points; status # emoji name
@@ -43,6 +43,7 @@
1F602 ; fully-qualified # 😂 E0.6 face with tears of joy
1F642 ; fully-qualified # 🙂 E1.0 slightly smiling face
1F643 ; fully-qualified # 🙃 E1.0 upside-down face
+1FAE0 ; fully-qualified # 🫠 E14.0 melting face
1F609 ; fully-qualified # 😉 E0.6 winking face
1F60A ; fully-qualified # 😊 E0.6 smiling face with smiling eyes
1F607 ; fully-qualified # 😇 E1.0 smiling face with halo
@@ -68,10 +69,13 @@
1F911 ; fully-qualified # 🤑 E1.0 money-mouth face
# subgroup: face-hand
-1F917 ; fully-qualified # 🤗 E1.0 hugging face
+1F917 ; fully-qualified # 🤗 E1.0 smiling face with open hands
1F92D ; fully-qualified # 🤭 E5.0 face with hand over mouth
+1FAE2 ; fully-qualified # 🫢 E14.0 face with open eyes and hand over mouth
+1FAE3 ; fully-qualified # 🫣 E14.0 face with peeking eye
1F92B ; fully-qualified # 🤫 E5.0 shushing face
1F914 ; fully-qualified # 🤔 E1.0 thinking face
+1FAE1 ; fully-qualified # 🫡 E14.0 saluting face
# subgroup: face-neutral-skeptical
1F910 ; fully-qualified # 🤐 E1.0 zipper-mouth face
@@ -79,6 +83,7 @@
1F610 ; fully-qualified # 😐 E0.7 neutral face
1F611 ; fully-qualified # 😑 E1.0 expressionless face
1F636 ; fully-qualified # 😶 E1.0 face without mouth
+1FAE5 ; fully-qualified # 🫥 E14.0 dotted line face
1F636 200D 1F32B FE0F ; fully-qualified # 😶‍🌫️ E13.1 face in clouds
1F636 200D 1F32B ; minimally-qualified # 😶‍🌫 E13.1 face in clouds
1F60F ; fully-qualified # 😏 E0.6 smirking face
@@ -87,6 +92,7 @@
1F62C ; fully-qualified # 😬 E1.0 grimacing face
1F62E 200D 1F4A8 ; fully-qualified # 😮‍💨 E13.1 face exhaling
1F925 ; fully-qualified # 🤥 E3.0 lying face
+1FAE8 ; fully-qualified # 🫨 E15.0 shaking face
# subgroup: face-sleepy
1F60C ; fully-qualified # 😌 E0.6 relieved face
@@ -105,7 +111,7 @@
1F975 ; fully-qualified # 🥵 E11.0 hot face
1F976 ; fully-qualified # 🥶 E11.0 cold face
1F974 ; fully-qualified # 🥴 E11.0 woozy face
-1F635 ; fully-qualified # 😵 E0.6 knocked-out face
+1F635 ; fully-qualified # 😵 E0.6 face with crossed-out eyes
1F635 200D 1F4AB ; fully-qualified # 😵‍💫 E13.1 face with spiral eyes
1F92F ; fully-qualified # 🤯 E5.0 exploding head
@@ -121,6 +127,7 @@
# subgroup: face-concerned
1F615 ; fully-qualified # 😕 E1.0 confused face
+1FAE4 ; fully-qualified # 🫤 E14.0 face with diagonal mouth
1F61F ; fully-qualified # 😟 E1.0 worried face
1F641 ; fully-qualified # 🙁 E1.0 slightly frowning face
2639 FE0F ; fully-qualified # ☹️ E0.7 frowning face
@@ -130,6 +137,7 @@
1F632 ; fully-qualified # 😲 E0.6 astonished face
1F633 ; fully-qualified # 😳 E0.6 flushed face
1F97A ; fully-qualified # 🥺 E11.0 pleading face
+1F979 ; fully-qualified # 🥹 E14.0 face holding back tears
1F626 ; fully-qualified # 😦 E1.0 frowning face with open mouth
1F627 ; fully-qualified # 😧 E1.0 anguished face
1F628 ; fully-qualified # 😨 E0.6 fearful face
@@ -148,7 +156,7 @@
# subgroup: face-negative
1F624 ; fully-qualified # 😤 E0.6 face with steam from nose
-1F621 ; fully-qualified # 😡 E0.6 pouting face
+1F621 ; fully-qualified # 😡 E0.6 enraged face
1F620 ; fully-qualified # 😠 E0.6 angry face
1F92C ; fully-qualified # 🤬 E5.0 face with symbols on mouth
1F608 ; fully-qualified # 😈 E1.0 smiling face with horns
@@ -183,8 +191,7 @@
1F649 ; fully-qualified # 🙉 E0.6 hear-no-evil monkey
1F64A ; fully-qualified # 🙊 E0.6 speak-no-evil monkey
-# subgroup: emotion
-1F48B ; fully-qualified # 💋 E0.6 kiss mark
+# subgroup: heart
1F48C ; fully-qualified # 💌 E0.6 love letter
1F498 ; fully-qualified # 💘 E0.6 heart with arrow
1F49D ; fully-qualified # 💝 E0.6 heart with ribbon
@@ -203,14 +210,20 @@
2764 200D 1FA79 ; unqualified # ❤‍🩹 E13.1 mending heart
2764 FE0F ; fully-qualified # ❤️ E0.6 red heart
2764 ; unqualified # ❤ E0.6 red heart
+1FA77 ; fully-qualified # 🩷 E15.0 pink heart
1F9E1 ; fully-qualified # 🧡 E5.0 orange heart
1F49B ; fully-qualified # 💛 E0.6 yellow heart
1F49A ; fully-qualified # 💚 E0.6 green heart
1F499 ; fully-qualified # 💙 E0.6 blue heart
+1FA75 ; fully-qualified # 🩵 E15.0 light blue heart
1F49C ; fully-qualified # 💜 E0.6 purple heart
1F90E ; fully-qualified # 🤎 E12.0 brown heart
1F5A4 ; fully-qualified # 🖤 E3.0 black heart
+1FA76 ; fully-qualified # 🩶 E15.0 grey heart
1F90D ; fully-qualified # 🤍 E12.0 white heart
+
+# subgroup: emotion
+1F48B ; fully-qualified # 💋 E0.6 kiss mark
1F4AF ; fully-qualified # 💯 E0.6 hundred points
1F4A2 ; fully-qualified # 💢 E0.6 anger symbol
1F4A5 ; fully-qualified # 💥 E0.6 collision
@@ -219,21 +232,20 @@
1F4A8 ; fully-qualified # 💨 E0.6 dashing away
1F573 FE0F ; fully-qualified # 🕳️ E0.7 hole
1F573 ; unqualified # 🕳 E0.7 hole
-1F4A3 ; fully-qualified # 💣 E0.6 bomb
1F4AC ; fully-qualified # 💬 E0.6 speech balloon
1F441 FE0F 200D 1F5E8 FE0F ; fully-qualified # 👁️‍🗨️ E2.0 eye in speech bubble
1F441 200D 1F5E8 FE0F ; unqualified # 👁‍🗨️ E2.0 eye in speech bubble
-1F441 FE0F 200D 1F5E8 ; unqualified # 👁️‍🗨 E2.0 eye in speech bubble
+1F441 FE0F 200D 1F5E8 ; minimally-qualified # 👁️‍🗨 E2.0 eye in speech bubble
1F441 200D 1F5E8 ; unqualified # 👁‍🗨 E2.0 eye in speech bubble
1F5E8 FE0F ; fully-qualified # 🗨️ E2.0 left speech bubble
1F5E8 ; unqualified # 🗨 E2.0 left speech bubble
1F5EF FE0F ; fully-qualified # 🗯️ E0.7 right anger bubble
1F5EF ; unqualified # 🗯 E0.7 right anger bubble
1F4AD ; fully-qualified # 💭 E1.0 thought balloon
-1F4A4 ; fully-qualified # 💤 E0.6 zzz
+1F4A4 ; fully-qualified # 💤 E0.6 ZZZ
-# Smileys & Emotion subtotal: 170
-# Smileys & Emotion subtotal: 170 w/o modifiers
+# Smileys & Emotion subtotal: 180
+# Smileys & Emotion subtotal: 180 w/o modifiers
# group: People & Body
@@ -269,6 +281,42 @@
1F596 1F3FD ; fully-qualified # 🖖🏽 E1.0 vulcan salute: medium skin tone
1F596 1F3FE ; fully-qualified # 🖖🏾 E1.0 vulcan salute: medium-dark skin tone
1F596 1F3FF ; fully-qualified # 🖖🏿 E1.0 vulcan salute: dark skin tone
+1FAF1 ; fully-qualified # 🫱 E14.0 rightwards hand
+1FAF1 1F3FB ; fully-qualified # 🫱🏻 E14.0 rightwards hand: light skin tone
+1FAF1 1F3FC ; fully-qualified # 🫱🏼 E14.0 rightwards hand: medium-light skin tone
+1FAF1 1F3FD ; fully-qualified # 🫱🏽 E14.0 rightwards hand: medium skin tone
+1FAF1 1F3FE ; fully-qualified # 🫱🏾 E14.0 rightwards hand: medium-dark skin tone
+1FAF1 1F3FF ; fully-qualified # 🫱🏿 E14.0 rightwards hand: dark skin tone
+1FAF2 ; fully-qualified # 🫲 E14.0 leftwards hand
+1FAF2 1F3FB ; fully-qualified # 🫲🏻 E14.0 leftwards hand: light skin tone
+1FAF2 1F3FC ; fully-qualified # 🫲🏼 E14.0 leftwards hand: medium-light skin tone
+1FAF2 1F3FD ; fully-qualified # 🫲🏽 E14.0 leftwards hand: medium skin tone
+1FAF2 1F3FE ; fully-qualified # 🫲🏾 E14.0 leftwards hand: medium-dark skin tone
+1FAF2 1F3FF ; fully-qualified # 🫲🏿 E14.0 leftwards hand: dark skin tone
+1FAF3 ; fully-qualified # 🫳 E14.0 palm down hand
+1FAF3 1F3FB ; fully-qualified # 🫳🏻 E14.0 palm down hand: light skin tone
+1FAF3 1F3FC ; fully-qualified # 🫳🏼 E14.0 palm down hand: medium-light skin tone
+1FAF3 1F3FD ; fully-qualified # 🫳🏽 E14.0 palm down hand: medium skin tone
+1FAF3 1F3FE ; fully-qualified # 🫳🏾 E14.0 palm down hand: medium-dark skin tone
+1FAF3 1F3FF ; fully-qualified # 🫳🏿 E14.0 palm down hand: dark skin tone
+1FAF4 ; fully-qualified # 🫴 E14.0 palm up hand
+1FAF4 1F3FB ; fully-qualified # 🫴🏻 E14.0 palm up hand: light skin tone
+1FAF4 1F3FC ; fully-qualified # 🫴🏼 E14.0 palm up hand: medium-light skin tone
+1FAF4 1F3FD ; fully-qualified # 🫴🏽 E14.0 palm up hand: medium skin tone
+1FAF4 1F3FE ; fully-qualified # 🫴🏾 E14.0 palm up hand: medium-dark skin tone
+1FAF4 1F3FF ; fully-qualified # 🫴🏿 E14.0 palm up hand: dark skin tone
+1FAF7 ; fully-qualified # 🫷 E15.0 leftwards pushing hand
+1FAF7 1F3FB ; fully-qualified # 🫷🏻 E15.0 leftwards pushing hand: light skin tone
+1FAF7 1F3FC ; fully-qualified # 🫷🏼 E15.0 leftwards pushing hand: medium-light skin tone
+1FAF7 1F3FD ; fully-qualified # 🫷🏽 E15.0 leftwards pushing hand: medium skin tone
+1FAF7 1F3FE ; fully-qualified # 🫷🏾 E15.0 leftwards pushing hand: medium-dark skin tone
+1FAF7 1F3FF ; fully-qualified # 🫷🏿 E15.0 leftwards pushing hand: dark skin tone
+1FAF8 ; fully-qualified # 🫸 E15.0 rightwards pushing hand
+1FAF8 1F3FB ; fully-qualified # 🫸🏻 E15.0 rightwards pushing hand: light skin tone
+1FAF8 1F3FC ; fully-qualified # 🫸🏼 E15.0 rightwards pushing hand: medium-light skin tone
+1FAF8 1F3FD ; fully-qualified # 🫸🏽 E15.0 rightwards pushing hand: medium skin tone
+1FAF8 1F3FE ; fully-qualified # 🫸🏾 E15.0 rightwards pushing hand: medium-dark skin tone
+1FAF8 1F3FF ; fully-qualified # 🫸🏿 E15.0 rightwards pushing hand: dark skin tone
# subgroup: hand-fingers-partial
1F44C ; fully-qualified # 👌 E0.6 OK hand
@@ -302,6 +350,12 @@
1F91E 1F3FD ; fully-qualified # 🤞🏽 E3.0 crossed fingers: medium skin tone
1F91E 1F3FE ; fully-qualified # 🤞🏾 E3.0 crossed fingers: medium-dark skin tone
1F91E 1F3FF ; fully-qualified # 🤞🏿 E3.0 crossed fingers: dark skin tone
+1FAF0 ; fully-qualified # 🫰 E14.0 hand with index finger and thumb crossed
+1FAF0 1F3FB ; fully-qualified # 🫰🏻 E14.0 hand with index finger and thumb crossed: light skin tone
+1FAF0 1F3FC ; fully-qualified # 🫰🏼 E14.0 hand with index finger and thumb crossed: medium-light skin tone
+1FAF0 1F3FD ; fully-qualified # 🫰🏽 E14.0 hand with index finger and thumb crossed: medium skin tone
+1FAF0 1F3FE ; fully-qualified # 🫰🏾 E14.0 hand with index finger and thumb crossed: medium-dark skin tone
+1FAF0 1F3FF ; fully-qualified # 🫰🏿 E14.0 hand with index finger and thumb crossed: dark skin tone
1F91F ; fully-qualified # 🤟 E5.0 love-you gesture
1F91F 1F3FB ; fully-qualified # 🤟🏻 E5.0 love-you gesture: light skin tone
1F91F 1F3FC ; fully-qualified # 🤟🏼 E5.0 love-you gesture: medium-light skin tone
@@ -359,6 +413,12 @@
261D 1F3FD ; fully-qualified # ☝🏽 E1.0 index pointing up: medium skin tone
261D 1F3FE ; fully-qualified # ☝🏾 E1.0 index pointing up: medium-dark skin tone
261D 1F3FF ; fully-qualified # ☝🏿 E1.0 index pointing up: dark skin tone
+1FAF5 ; fully-qualified # 🫵 E14.0 index pointing at the viewer
+1FAF5 1F3FB ; fully-qualified # 🫵🏻 E14.0 index pointing at the viewer: light skin tone
+1FAF5 1F3FC ; fully-qualified # 🫵🏼 E14.0 index pointing at the viewer: medium-light skin tone
+1FAF5 1F3FD ; fully-qualified # 🫵🏽 E14.0 index pointing at the viewer: medium skin tone
+1FAF5 1F3FE ; fully-qualified # 🫵🏾 E14.0 index pointing at the viewer: medium-dark skin tone
+1FAF5 1F3FF ; fully-qualified # 🫵🏿 E14.0 index pointing at the viewer: dark skin tone
# subgroup: hand-fingers-closed
1F44D ; fully-qualified # 👍 E0.6 thumbs up
@@ -411,6 +471,12 @@
1F64C 1F3FD ; fully-qualified # 🙌🏽 E1.0 raising hands: medium skin tone
1F64C 1F3FE ; fully-qualified # 🙌🏾 E1.0 raising hands: medium-dark skin tone
1F64C 1F3FF ; fully-qualified # 🙌🏿 E1.0 raising hands: dark skin tone
+1FAF6 ; fully-qualified # 🫶 E14.0 heart hands
+1FAF6 1F3FB ; fully-qualified # 🫶🏻 E14.0 heart hands: light skin tone
+1FAF6 1F3FC ; fully-qualified # 🫶🏼 E14.0 heart hands: medium-light skin tone
+1FAF6 1F3FD ; fully-qualified # 🫶🏽 E14.0 heart hands: medium skin tone
+1FAF6 1F3FE ; fully-qualified # 🫶🏾 E14.0 heart hands: medium-dark skin tone
+1FAF6 1F3FF ; fully-qualified # 🫶🏿 E14.0 heart hands: dark skin tone
1F450 ; fully-qualified # 👐 E0.6 open hands
1F450 1F3FB ; fully-qualified # 👐🏻 E1.0 open hands: light skin tone
1F450 1F3FC ; fully-qualified # 👐🏼 E1.0 open hands: medium-light skin tone
@@ -424,6 +490,31 @@
1F932 1F3FE ; fully-qualified # 🤲🏾 E5.0 palms up together: medium-dark skin tone
1F932 1F3FF ; fully-qualified # 🤲🏿 E5.0 palms up together: dark skin tone
1F91D ; fully-qualified # 🤝 E3.0 handshake
+1F91D 1F3FB ; fully-qualified # 🤝🏻 E14.0 handshake: light skin tone
+1F91D 1F3FC ; fully-qualified # 🤝🏼 E14.0 handshake: medium-light skin tone
+1F91D 1F3FD ; fully-qualified # 🤝🏽 E14.0 handshake: medium skin tone
+1F91D 1F3FE ; fully-qualified # 🤝🏾 E14.0 handshake: medium-dark skin tone
+1F91D 1F3FF ; fully-qualified # 🤝🏿 E14.0 handshake: dark skin tone
+1FAF1 1F3FB 200D 1FAF2 1F3FC ; fully-qualified # 🫱🏻‍🫲🏼 E14.0 handshake: light skin tone, medium-light skin tone
+1FAF1 1F3FB 200D 1FAF2 1F3FD ; fully-qualified # 🫱🏻‍🫲🏽 E14.0 handshake: light skin tone, medium skin tone
+1FAF1 1F3FB 200D 1FAF2 1F3FE ; fully-qualified # 🫱🏻‍🫲🏾 E14.0 handshake: light skin tone, medium-dark skin tone
+1FAF1 1F3FB 200D 1FAF2 1F3FF ; fully-qualified # 🫱🏻‍🫲🏿 E14.0 handshake: light skin tone, dark skin tone
+1FAF1 1F3FC 200D 1FAF2 1F3FB ; fully-qualified # 🫱🏼‍🫲🏻 E14.0 handshake: medium-light skin tone, light skin tone
+1FAF1 1F3FC 200D 1FAF2 1F3FD ; fully-qualified # 🫱🏼‍🫲🏽 E14.0 handshake: medium-light skin tone, medium skin tone
+1FAF1 1F3FC 200D 1FAF2 1F3FE ; fully-qualified # 🫱🏼‍🫲🏾 E14.0 handshake: medium-light skin tone, medium-dark skin tone
+1FAF1 1F3FC 200D 1FAF2 1F3FF ; fully-qualified # 🫱🏼‍🫲🏿 E14.0 handshake: medium-light skin tone, dark skin tone
+1FAF1 1F3FD 200D 1FAF2 1F3FB ; fully-qualified # 🫱🏽‍🫲🏻 E14.0 handshake: medium skin tone, light skin tone
+1FAF1 1F3FD 200D 1FAF2 1F3FC ; fully-qualified # 🫱🏽‍🫲🏼 E14.0 handshake: medium skin tone, medium-light skin tone
+1FAF1 1F3FD 200D 1FAF2 1F3FE ; fully-qualified # 🫱🏽‍🫲🏾 E14.0 handshake: medium skin tone, medium-dark skin tone
+1FAF1 1F3FD 200D 1FAF2 1F3FF ; fully-qualified # 🫱🏽‍🫲🏿 E14.0 handshake: medium skin tone, dark skin tone
+1FAF1 1F3FE 200D 1FAF2 1F3FB ; fully-qualified # 🫱🏾‍🫲🏻 E14.0 handshake: medium-dark skin tone, light skin tone
+1FAF1 1F3FE 200D 1FAF2 1F3FC ; fully-qualified # 🫱🏾‍🫲🏼 E14.0 handshake: medium-dark skin tone, medium-light skin tone
+1FAF1 1F3FE 200D 1FAF2 1F3FD ; fully-qualified # 🫱🏾‍🫲🏽 E14.0 handshake: medium-dark skin tone, medium skin tone
+1FAF1 1F3FE 200D 1FAF2 1F3FF ; fully-qualified # 🫱🏾‍🫲🏿 E14.0 handshake: medium-dark skin tone, dark skin tone
+1FAF1 1F3FF 200D 1FAF2 1F3FB ; fully-qualified # 🫱🏿‍🫲🏻 E14.0 handshake: dark skin tone, light skin tone
+1FAF1 1F3FF 200D 1FAF2 1F3FC ; fully-qualified # 🫱🏿‍🫲🏼 E14.0 handshake: dark skin tone, medium-light skin tone
+1FAF1 1F3FF 200D 1FAF2 1F3FD ; fully-qualified # 🫱🏿‍🫲🏽 E14.0 handshake: dark skin tone, medium skin tone
+1FAF1 1F3FF 200D 1FAF2 1F3FE ; fully-qualified # 🫱🏿‍🫲🏾 E14.0 handshake: dark skin tone, medium-dark skin tone
1F64F ; fully-qualified # 🙏 E0.6 folded hands
1F64F 1F3FB ; fully-qualified # 🙏🏻 E1.0 folded hands: light skin tone
1F64F 1F3FC ; fully-qualified # 🙏🏼 E1.0 folded hands: medium-light skin tone
@@ -501,6 +592,7 @@
1F441 ; unqualified # 👁 E0.7 eye
1F445 ; fully-qualified # 👅 E0.6 tongue
1F444 ; fully-qualified # 👄 E0.6 mouth
+1FAE6 ; fully-qualified # 🫦 E14.0 biting lip
# subgroup: person
1F476 ; fully-qualified # 👶 E0.6 baby
@@ -1380,7 +1472,7 @@
1F575 1F3FF ; fully-qualified # 🕵🏿 E2.0 detective: dark skin tone
1F575 FE0F 200D 2642 FE0F ; fully-qualified # 🕵️‍♂️ E4.0 man detective
1F575 200D 2642 FE0F ; unqualified # 🕵‍♂️ E4.0 man detective
-1F575 FE0F 200D 2642 ; unqualified # 🕵️‍♂ E4.0 man detective
+1F575 FE0F 200D 2642 ; minimally-qualified # 🕵️‍♂ E4.0 man detective
1F575 200D 2642 ; unqualified # 🕵‍♂ E4.0 man detective
1F575 1F3FB 200D 2642 FE0F ; fully-qualified # 🕵🏻‍♂️ E4.0 man detective: light skin tone
1F575 1F3FB 200D 2642 ; minimally-qualified # 🕵🏻‍♂ E4.0 man detective: light skin tone
@@ -1394,7 +1486,7 @@
1F575 1F3FF 200D 2642 ; minimally-qualified # 🕵🏿‍♂ E4.0 man detective: dark skin tone
1F575 FE0F 200D 2640 FE0F ; fully-qualified # 🕵️‍♀️ E4.0 woman detective
1F575 200D 2640 FE0F ; unqualified # 🕵‍♀️ E4.0 woman detective
-1F575 FE0F 200D 2640 ; unqualified # 🕵️‍♀ E4.0 woman detective
+1F575 FE0F 200D 2640 ; minimally-qualified # 🕵️‍♀ E4.0 woman detective
1F575 200D 2640 ; unqualified # 🕵‍♀ E4.0 woman detective
1F575 1F3FB 200D 2640 FE0F ; fully-qualified # 🕵🏻‍♀️ E4.0 woman detective: light skin tone
1F575 1F3FB 200D 2640 ; minimally-qualified # 🕵🏻‍♀ E4.0 woman detective: light skin tone
@@ -1472,6 +1564,12 @@
1F477 1F3FE 200D 2640 ; minimally-qualified # 👷🏾‍♀ E4.0 woman construction worker: medium-dark skin tone
1F477 1F3FF 200D 2640 FE0F ; fully-qualified # 👷🏿‍♀️ E4.0 woman construction worker: dark skin tone
1F477 1F3FF 200D 2640 ; minimally-qualified # 👷🏿‍♀ E4.0 woman construction worker: dark skin tone
+1FAC5 ; fully-qualified # 🫅 E14.0 person with crown
+1FAC5 1F3FB ; fully-qualified # 🫅🏻 E14.0 person with crown: light skin tone
+1FAC5 1F3FC ; fully-qualified # 🫅🏼 E14.0 person with crown: medium-light skin tone
+1FAC5 1F3FD ; fully-qualified # 🫅🏽 E14.0 person with crown: medium skin tone
+1FAC5 1F3FE ; fully-qualified # 🫅🏾 E14.0 person with crown: medium-dark skin tone
+1FAC5 1F3FF ; fully-qualified # 🫅🏿 E14.0 person with crown: dark skin tone
1F934 ; fully-qualified # 🤴 E3.0 prince
1F934 1F3FB ; fully-qualified # 🤴🏻 E3.0 prince: light skin tone
1F934 1F3FC ; fully-qualified # 🤴🏼 E3.0 prince: medium-light skin tone
@@ -1592,6 +1690,18 @@
1F930 1F3FD ; fully-qualified # 🤰🏽 E3.0 pregnant woman: medium skin tone
1F930 1F3FE ; fully-qualified # 🤰🏾 E3.0 pregnant woman: medium-dark skin tone
1F930 1F3FF ; fully-qualified # 🤰🏿 E3.0 pregnant woman: dark skin tone
+1FAC3 ; fully-qualified # 🫃 E14.0 pregnant man
+1FAC3 1F3FB ; fully-qualified # 🫃🏻 E14.0 pregnant man: light skin tone
+1FAC3 1F3FC ; fully-qualified # 🫃🏼 E14.0 pregnant man: medium-light skin tone
+1FAC3 1F3FD ; fully-qualified # 🫃🏽 E14.0 pregnant man: medium skin tone
+1FAC3 1F3FE ; fully-qualified # 🫃🏾 E14.0 pregnant man: medium-dark skin tone
+1FAC3 1F3FF ; fully-qualified # 🫃🏿 E14.0 pregnant man: dark skin tone
+1FAC4 ; fully-qualified # 🫄 E14.0 pregnant person
+1FAC4 1F3FB ; fully-qualified # 🫄🏻 E14.0 pregnant person: light skin tone
+1FAC4 1F3FC ; fully-qualified # 🫄🏼 E14.0 pregnant person: medium-light skin tone
+1FAC4 1F3FD ; fully-qualified # 🫄🏽 E14.0 pregnant person: medium skin tone
+1FAC4 1F3FE ; fully-qualified # 🫄🏾 E14.0 pregnant person: medium-dark skin tone
+1FAC4 1F3FF ; fully-qualified # 🫄🏿 E14.0 pregnant person: dark skin tone
1F931 ; fully-qualified # 🤱 E5.0 breast-feeding
1F931 1F3FB ; fully-qualified # 🤱🏻 E5.0 breast-feeding: light skin tone
1F931 1F3FC ; fully-qualified # 🤱🏼 E5.0 breast-feeding: medium-light skin tone
@@ -1862,6 +1972,7 @@
1F9DF 200D 2642 ; minimally-qualified # 🧟‍♂ E5.0 man zombie
1F9DF 200D 2640 FE0F ; fully-qualified # 🧟‍♀️ E5.0 woman zombie
1F9DF 200D 2640 ; minimally-qualified # 🧟‍♀ E5.0 woman zombie
+1F9CC ; fully-qualified # 🧌 E14.0 troll
# subgroup: person-activity
1F486 ; fully-qualified # 💆 E0.6 person getting massage
@@ -2208,7 +2319,7 @@
1F3CC 1F3FF ; fully-qualified # 🏌🏿 E4.0 person golfing: dark skin tone
1F3CC FE0F 200D 2642 FE0F ; fully-qualified # 🏌️‍♂️ E4.0 man golfing
1F3CC 200D 2642 FE0F ; unqualified # 🏌‍♂️ E4.0 man golfing
-1F3CC FE0F 200D 2642 ; unqualified # 🏌️‍♂ E4.0 man golfing
+1F3CC FE0F 200D 2642 ; minimally-qualified # 🏌️‍♂ E4.0 man golfing
1F3CC 200D 2642 ; unqualified # 🏌‍♂ E4.0 man golfing
1F3CC 1F3FB 200D 2642 FE0F ; fully-qualified # 🏌🏻‍♂️ E4.0 man golfing: light skin tone
1F3CC 1F3FB 200D 2642 ; minimally-qualified # 🏌🏻‍♂ E4.0 man golfing: light skin tone
@@ -2222,7 +2333,7 @@
1F3CC 1F3FF 200D 2642 ; minimally-qualified # 🏌🏿‍♂ E4.0 man golfing: dark skin tone
1F3CC FE0F 200D 2640 FE0F ; fully-qualified # 🏌️‍♀️ E4.0 woman golfing
1F3CC 200D 2640 FE0F ; unqualified # 🏌‍♀️ E4.0 woman golfing
-1F3CC FE0F 200D 2640 ; unqualified # 🏌️‍♀ E4.0 woman golfing
+1F3CC FE0F 200D 2640 ; minimally-qualified # 🏌️‍♀ E4.0 woman golfing
1F3CC 200D 2640 ; unqualified # 🏌‍♀ E4.0 woman golfing
1F3CC 1F3FB 200D 2640 FE0F ; fully-qualified # 🏌🏻‍♀️ E4.0 woman golfing: light skin tone
1F3CC 1F3FB 200D 2640 ; minimally-qualified # 🏌🏻‍♀ E4.0 woman golfing: light skin tone
@@ -2333,7 +2444,7 @@
26F9 1F3FF ; fully-qualified # ⛹🏿 E2.0 person bouncing ball: dark skin tone
26F9 FE0F 200D 2642 FE0F ; fully-qualified # ⛹️‍♂️ E4.0 man bouncing ball
26F9 200D 2642 FE0F ; unqualified # ⛹‍♂️ E4.0 man bouncing ball
-26F9 FE0F 200D 2642 ; unqualified # ⛹️‍♂ E4.0 man bouncing ball
+26F9 FE0F 200D 2642 ; minimally-qualified # ⛹️‍♂ E4.0 man bouncing ball
26F9 200D 2642 ; unqualified # ⛹‍♂ E4.0 man bouncing ball
26F9 1F3FB 200D 2642 FE0F ; fully-qualified # ⛹🏻‍♂️ E4.0 man bouncing ball: light skin tone
26F9 1F3FB 200D 2642 ; minimally-qualified # ⛹🏻‍♂ E4.0 man bouncing ball: light skin tone
@@ -2347,7 +2458,7 @@
26F9 1F3FF 200D 2642 ; minimally-qualified # ⛹🏿‍♂ E4.0 man bouncing ball: dark skin tone
26F9 FE0F 200D 2640 FE0F ; fully-qualified # ⛹️‍♀️ E4.0 woman bouncing ball
26F9 200D 2640 FE0F ; unqualified # ⛹‍♀️ E4.0 woman bouncing ball
-26F9 FE0F 200D 2640 ; unqualified # ⛹️‍♀ E4.0 woman bouncing ball
+26F9 FE0F 200D 2640 ; minimally-qualified # ⛹️‍♀ E4.0 woman bouncing ball
26F9 200D 2640 ; unqualified # ⛹‍♀ E4.0 woman bouncing ball
26F9 1F3FB 200D 2640 FE0F ; fully-qualified # ⛹🏻‍♀️ E4.0 woman bouncing ball: light skin tone
26F9 1F3FB 200D 2640 ; minimally-qualified # ⛹🏻‍♀ E4.0 woman bouncing ball: light skin tone
@@ -2368,7 +2479,7 @@
1F3CB 1F3FF ; fully-qualified # 🏋🏿 E2.0 person lifting weights: dark skin tone
1F3CB FE0F 200D 2642 FE0F ; fully-qualified # 🏋️‍♂️ E4.0 man lifting weights
1F3CB 200D 2642 FE0F ; unqualified # 🏋‍♂️ E4.0 man lifting weights
-1F3CB FE0F 200D 2642 ; unqualified # 🏋️‍♂ E4.0 man lifting weights
+1F3CB FE0F 200D 2642 ; minimally-qualified # 🏋️‍♂ E4.0 man lifting weights
1F3CB 200D 2642 ; unqualified # 🏋‍♂ E4.0 man lifting weights
1F3CB 1F3FB 200D 2642 FE0F ; fully-qualified # 🏋🏻‍♂️ E4.0 man lifting weights: light skin tone
1F3CB 1F3FB 200D 2642 ; minimally-qualified # 🏋🏻‍♂ E4.0 man lifting weights: light skin tone
@@ -2382,7 +2493,7 @@
1F3CB 1F3FF 200D 2642 ; minimally-qualified # 🏋🏿‍♂ E4.0 man lifting weights: dark skin tone
1F3CB FE0F 200D 2640 FE0F ; fully-qualified # 🏋️‍♀️ E4.0 woman lifting weights
1F3CB 200D 2640 FE0F ; unqualified # 🏋‍♀️ E4.0 woman lifting weights
-1F3CB FE0F 200D 2640 ; unqualified # 🏋️‍♀ E4.0 woman lifting weights
+1F3CB FE0F 200D 2640 ; minimally-qualified # 🏋️‍♀ E4.0 woman lifting weights
1F3CB 200D 2640 ; unqualified # 🏋‍♀ E4.0 woman lifting weights
1F3CB 1F3FB 200D 2640 FE0F ; fully-qualified # 🏋🏻‍♀️ E4.0 woman lifting weights: light skin tone
1F3CB 1F3FB 200D 2640 ; minimally-qualified # 🏋🏻‍♀ E4.0 woman lifting weights: light skin tone
@@ -3168,8 +3279,8 @@
1FAC2 ; fully-qualified # 🫂 E13.0 people hugging
1F463 ; fully-qualified # 👣 E0.6 footprints
-# People & Body subtotal: 2899
-# People & Body subtotal: 494 w/o modifiers
+# People & Body subtotal: 2998
+# People & Body subtotal: 508 w/o modifiers
# group: Component
@@ -3212,6 +3323,8 @@
1F405 ; fully-qualified # 🐅 E1.0 tiger
1F406 ; fully-qualified # 🐆 E1.0 leopard
1F434 ; fully-qualified # 🐴 E0.6 horse face
+1FACE ; fully-qualified # 🫎 E15.0 moose
+1FACF ; fully-qualified # 🫏 E15.0 donkey
1F40E ; fully-qualified # 🐎 E0.6 horse
1F984 ; fully-qualified # 🦄 E1.0 unicorn
1F993 ; fully-qualified # 🦓 E5.0 zebra
@@ -3279,6 +3392,9 @@
1F9A9 ; fully-qualified # 🦩 E12.0 flamingo
1F99A ; fully-qualified # 🦚 E11.0 peacock
1F99C ; fully-qualified # 🦜 E11.0 parrot
+1FABD ; fully-qualified # 🪽 E15.0 wing
+1F426 200D 2B1B ; fully-qualified # 🐦‍⬛ E15.0 black bird
+1FABF ; fully-qualified # 🪿 E15.0 goose
# subgroup: animal-amphibian
1F438 ; fully-qualified # 🐸 E0.6 frog
@@ -3304,6 +3420,8 @@
1F988 ; fully-qualified # 🦈 E3.0 shark
1F419 ; fully-qualified # 🐙 E0.6 octopus
1F41A ; fully-qualified # 🐚 E0.6 spiral shell
+1FAB8 ; fully-qualified # 🪸 E14.0 coral
+1FABC ; fully-qualified # 🪼 E15.0 jellyfish
# subgroup: animal-bug
1F40C ; fully-qualified # 🐌 E0.6 snail
@@ -3329,6 +3447,7 @@
1F490 ; fully-qualified # 💐 E0.6 bouquet
1F338 ; fully-qualified # 🌸 E0.6 cherry blossom
1F4AE ; fully-qualified # 💮 E0.6 white flower
+1FAB7 ; fully-qualified # 🪷 E14.0 lotus
1F3F5 FE0F ; fully-qualified # 🏵️ E0.7 rosette
1F3F5 ; unqualified # 🏵 E0.7 rosette
1F339 ; fully-qualified # 🌹 E0.6 rose
@@ -3337,6 +3456,7 @@
1F33B ; fully-qualified # 🌻 E0.6 sunflower
1F33C ; fully-qualified # 🌼 E0.6 blossom
1F337 ; fully-qualified # 🌷 E0.6 tulip
+1FABB ; fully-qualified # 🪻 E15.0 hyacinth
# subgroup: plant-other
1F331 ; fully-qualified # 🌱 E0.6 seedling
@@ -3353,9 +3473,12 @@
1F341 ; fully-qualified # 🍁 E0.6 maple leaf
1F342 ; fully-qualified # 🍂 E0.6 fallen leaf
1F343 ; fully-qualified # 🍃 E0.6 leaf fluttering in wind
+1FAB9 ; fully-qualified # 🪹 E14.0 empty nest
+1FABA ; fully-qualified # 🪺 E14.0 nest with eggs
+1F344 ; fully-qualified # 🍄 E0.6 mushroom
-# Animals & Nature subtotal: 147
-# Animals & Nature subtotal: 147 w/o modifiers
+# Animals & Nature subtotal: 159
+# Animals & Nature subtotal: 159 w/o modifiers
# group: Food & Drink
@@ -3394,9 +3517,11 @@
1F966 ; fully-qualified # 🥦 E5.0 broccoli
1F9C4 ; fully-qualified # 🧄 E12.0 garlic
1F9C5 ; fully-qualified # 🧅 E12.0 onion
-1F344 ; fully-qualified # 🍄 E0.6 mushroom
1F95C ; fully-qualified # 🥜 E3.0 peanuts
+1FAD8 ; fully-qualified # 🫘 E14.0 beans
1F330 ; fully-qualified # 🌰 E0.6 chestnut
+1FADA ; fully-qualified # 🫚 E15.0 ginger root
+1FADB ; fully-qualified # 🫛 E15.0 pea pod
# subgroup: food-prepared
1F35E ; fully-qualified # 🍞 E0.6 bread
@@ -3491,6 +3616,7 @@
1F37B ; fully-qualified # 🍻 E0.6 clinking beer mugs
1F942 ; fully-qualified # 🥂 E3.0 clinking glasses
1F943 ; fully-qualified # 🥃 E3.0 tumbler glass
+1FAD7 ; fully-qualified # 🫗 E14.0 pouring liquid
1F964 ; fully-qualified # 🥤 E5.0 cup with straw
1F9CB ; fully-qualified # 🧋 E13.0 bubble tea
1F9C3 ; fully-qualified # 🧃 E12.0 beverage box
@@ -3504,10 +3630,11 @@
1F374 ; fully-qualified # 🍴 E0.6 fork and knife
1F944 ; fully-qualified # 🥄 E3.0 spoon
1F52A ; fully-qualified # 🔪 E0.6 kitchen knife
+1FAD9 ; fully-qualified # 🫙 E14.0 jar
1F3FA ; fully-qualified # 🏺 E1.0 amphora
-# Food & Drink subtotal: 131
-# Food & Drink subtotal: 131 w/o modifiers
+# Food & Drink subtotal: 135
+# Food & Drink subtotal: 135 w/o modifiers
# group: Travel & Places
@@ -3597,6 +3724,7 @@
2668 FE0F ; fully-qualified # ♨️ E0.6 hot springs
2668 ; unqualified # ♨ E0.6 hot springs
1F3A0 ; fully-qualified # 🎠 E0.6 carousel horse
+1F6DD ; fully-qualified # 🛝 E14.0 playground slide
1F3A1 ; fully-qualified # 🎡 E0.6 ferris wheel
1F3A2 ; fully-qualified # 🎢 E0.6 roller coaster
1F488 ; fully-qualified # 💈 E0.6 barber pole
@@ -3652,6 +3780,7 @@
1F6E2 FE0F ; fully-qualified # 🛢️ E0.7 oil drum
1F6E2 ; unqualified # 🛢 E0.7 oil drum
26FD ; fully-qualified # ⛽ E0.6 fuel pump
+1F6DE ; fully-qualified # 🛞 E14.0 wheel
1F6A8 ; fully-qualified # 🚨 E0.6 police car light
1F6A5 ; fully-qualified # 🚥 E0.6 horizontal traffic light
1F6A6 ; fully-qualified # 🚦 E1.0 vertical traffic light
@@ -3660,6 +3789,7 @@
# subgroup: transport-water
2693 ; fully-qualified # ⚓ E0.6 anchor
+1F6DF ; fully-qualified # 🛟 E14.0 ring buoy
26F5 ; fully-qualified # ⛵ E0.6 sailboat
1F6F6 ; fully-qualified # 🛶 E3.0 canoe
1F6A4 ; fully-qualified # 🚤 E0.6 speedboat
@@ -3797,8 +3927,8 @@
1F4A7 ; fully-qualified # 💧 E0.6 droplet
1F30A ; fully-qualified # 🌊 E0.6 water wave
-# Travel & Places subtotal: 264
-# Travel & Places subtotal: 264 w/o modifiers
+# Travel & Places subtotal: 267
+# Travel & Places subtotal: 267 w/o modifiers
# group: Activities
@@ -3870,10 +4000,10 @@
1F3AF ; fully-qualified # 🎯 E0.6 bullseye
1FA80 ; fully-qualified # 🪀 E12.0 yo-yo
1FA81 ; fully-qualified # 🪁 E12.0 kite
+1F52B ; fully-qualified # 🔫 E0.6 water pistol
1F3B1 ; fully-qualified # 🎱 E0.6 pool 8 ball
1F52E ; fully-qualified # 🔮 E0.6 crystal ball
1FA84 ; fully-qualified # 🪄 E13.0 magic wand
-1F9FF ; fully-qualified # 🧿 E11.0 nazar amulet
1F3AE ; fully-qualified # 🎮 E0.6 video game
1F579 FE0F ; fully-qualified # 🕹️ E0.7 joystick
1F579 ; unqualified # 🕹 E0.7 joystick
@@ -3882,6 +4012,7 @@
1F9E9 ; fully-qualified # 🧩 E11.0 puzzle piece
1F9F8 ; fully-qualified # 🧸 E11.0 teddy bear
1FA85 ; fully-qualified # 🪅 E13.0 piñata
+1FAA9 ; fully-qualified # 🪩 E14.0 mirror ball
1FA86 ; fully-qualified # 🪆 E13.0 nesting dolls
2660 FE0F ; fully-qualified # ♠️ E0.6 spade suit
2660 ; unqualified # ♠ E0.6 spade suit
@@ -3907,8 +4038,8 @@
1F9F6 ; fully-qualified # 🧶 E11.0 yarn
1FAA2 ; fully-qualified # 🪢 E13.0 knot
-# Activities subtotal: 95
-# Activities subtotal: 95 w/o modifiers
+# Activities subtotal: 96
+# Activities subtotal: 96 w/o modifiers
# group: Objects
@@ -3934,6 +4065,7 @@
1FA73 ; fully-qualified # 🩳 E12.0 shorts
1F459 ; fully-qualified # 👙 E0.6 bikini
1F45A ; fully-qualified # 👚 E0.6 woman’s clothes
+1FAAD ; fully-qualified # 🪭 E15.0 folding hand fan
1F45B ; fully-qualified # 👛 E0.6 purse
1F45C ; fully-qualified # 👜 E0.6 handbag
1F45D ; fully-qualified # 👝 E0.6 clutch bag
@@ -3949,6 +4081,7 @@
1F461 ; fully-qualified # 👡 E0.6 woman’s sandal
1FA70 ; fully-qualified # 🩰 E12.0 ballet shoes
1F462 ; fully-qualified # 👢 E0.6 woman’s boot
+1FAAE ; fully-qualified # 🪮 E15.0 hair pick
1F451 ; fully-qualified # 👑 E0.6 crown
1F452 ; fully-qualified # 👒 E0.6 woman’s hat
1F3A9 ; fully-qualified # 🎩 E0.6 top hat
@@ -3997,6 +4130,8 @@
1FA95 ; fully-qualified # 🪕 E12.0 banjo
1F941 ; fully-qualified # 🥁 E3.0 drum
1FA98 ; fully-qualified # 🪘 E13.0 long drum
+1FA87 ; fully-qualified # 🪇 E15.0 maracas
+1FA88 ; fully-qualified # 🪈 E15.0 flute
# subgroup: phone
1F4F1 ; fully-qualified # 📱 E0.6 mobile phone
@@ -4009,6 +4144,7 @@
# subgroup: computer
1F50B ; fully-qualified # 🔋 E0.6 battery
+1FAAB ; fully-qualified # 🪫 E14.0 low battery
1F50C ; fully-qualified # 🔌 E0.6 electric plug
1F4BB ; fully-qualified # 💻 E0.6 laptop
1F5A5 FE0F ; fully-qualified # 🖥️ E0.7 desktop computer
@@ -4168,7 +4304,7 @@
1F5E1 ; unqualified # 🗡 E0.7 dagger
2694 FE0F ; fully-qualified # ⚔️ E1.0 crossed swords
2694 ; unqualified # ⚔ E1.0 crossed swords
-1F52B ; fully-qualified # 🔫 E0.6 water pistol
+1F4A3 ; fully-qualified # 💣 E0.6 bomb
1FA83 ; fully-qualified # 🪃 E13.0 boomerang
1F3F9 ; fully-qualified # 🏹 E1.0 bow and arrow
1F6E1 FE0F ; fully-qualified # 🛡️ E0.7 shield
@@ -4207,7 +4343,9 @@
1FA78 ; fully-qualified # 🩸 E12.0 drop of blood
1F48A ; fully-qualified # 💊 E0.6 pill
1FA79 ; fully-qualified # 🩹 E12.0 adhesive bandage
+1FA7C ; fully-qualified # 🩼 E14.0 crutch
1FA7A ; fully-qualified # 🩺 E12.0 stethoscope
+1FA7B ; fully-qualified # 🩻 E14.0 x-ray
# subgroup: household
1F6AA ; fully-qualified # 🚪 E0.6 door
@@ -4232,6 +4370,7 @@
1F9FB ; fully-qualified # 🧻 E11.0 roll of paper
1FAA3 ; fully-qualified # 🪣 E13.0 bucket
1F9FC ; fully-qualified # 🧼 E11.0 soap
+1FAE7 ; fully-qualified # 🫧 E14.0 bubbles
1FAA5 ; fully-qualified # 🪥 E13.0 toothbrush
1F9FD ; fully-qualified # 🧽 E11.0 sponge
1F9EF ; fully-qualified # 🧯 E11.0 fire extinguisher
@@ -4244,11 +4383,14 @@
1FAA6 ; fully-qualified # 🪦 E13.0 headstone
26B1 FE0F ; fully-qualified # ⚱️ E1.0 funeral urn
26B1 ; unqualified # ⚱ E1.0 funeral urn
+1F9FF ; fully-qualified # 🧿 E11.0 nazar amulet
+1FAAC ; fully-qualified # 🪬 E14.0 hamsa
1F5FF ; fully-qualified # 🗿 E0.6 moai
1FAA7 ; fully-qualified # 🪧 E13.0 placard
+1FAAA ; fully-qualified # 🪪 E14.0 identification card
-# Objects subtotal: 299
-# Objects subtotal: 299 w/o modifiers
+# Objects subtotal: 310
+# Objects subtotal: 310 w/o modifiers
# group: Symbols
@@ -4344,6 +4486,7 @@
262E ; unqualified # ☮ E1.0 peace symbol
1F54E ; fully-qualified # 🕎 E1.0 menorah
1F52F ; fully-qualified # 🔯 E0.6 dotted six-pointed star
+1FAAF ; fully-qualified # 🪯 E15.0 khanda
# subgroup: zodiac
2648 ; fully-qualified # ♈ E0.6 Aries
@@ -4392,6 +4535,7 @@
1F505 ; fully-qualified # 🔅 E1.0 dim button
1F506 ; fully-qualified # 🔆 E1.0 bright button
1F4F6 ; fully-qualified # 📶 E0.6 antenna bars
+1F6DC ; fully-qualified # 🛜 E15.0 wireless
1F4F3 ; fully-qualified # 📳 E0.6 vibration mode
1F4F4 ; fully-qualified # 📴 E0.6 mobile phone off
@@ -4409,6 +4553,7 @@
2795 ; fully-qualified # ➕ E0.6 plus
2796 ; fully-qualified # ➖ E0.6 minus
2797 ; fully-qualified # ➗ E0.6 divide
+1F7F0 ; fully-qualified # 🟰 E14.0 heavy equals sign
267E FE0F ; fully-qualified # ♾️ E11.0 infinity
267E ; unqualified # ♾ E11.0 infinity
@@ -4581,8 +4726,8 @@
1F533 ; fully-qualified # 🔳 E0.6 white square button
1F532 ; fully-qualified # 🔲 E0.6 black square button
-# Symbols subtotal: 301
-# Symbols subtotal: 301 w/o modifiers
+# Symbols subtotal: 304
+# Symbols subtotal: 304 w/o modifiers
# group: Flags
@@ -4597,7 +4742,7 @@
1F3F3 200D 1F308 ; unqualified # 🏳‍🌈 E4.0 rainbow flag
1F3F3 FE0F 200D 26A7 FE0F ; fully-qualified # 🏳️‍⚧️ E13.0 transgender flag
1F3F3 200D 26A7 FE0F ; unqualified # 🏳‍⚧️ E13.0 transgender flag
-1F3F3 FE0F 200D 26A7 ; unqualified # 🏳️‍⚧ E13.0 transgender flag
+1F3F3 FE0F 200D 26A7 ; minimally-qualified # 🏳️‍⚧ E13.0 transgender flag
1F3F3 200D 26A7 ; unqualified # 🏳‍⚧ E13.0 transgender flag
1F3F4 200D 2620 FE0F ; fully-qualified # 🏴‍☠️ E11.0 pirate flag
1F3F4 200D 2620 ; minimally-qualified # 🏴‍☠ E11.0 pirate flag
@@ -4871,9 +5016,9 @@
# Flags subtotal: 275 w/o modifiers
# Status Counts
-# fully-qualified : 3512
-# minimally-qualified : 817
-# unqualified : 252
+# fully-qualified : 3655
+# minimally-qualified : 827
+# unqualified : 242
# component : 9
#EOF
diff --git a/lib/pleroma/emoji.ex b/lib/pleroma/emoji.ex
index 513fb59f8..dd65d56ae 100644
--- a/lib/pleroma/emoji.ex
+++ b/lib/pleroma/emoji.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Emoji do
@@ -9,6 +9,7 @@ defmodule Pleroma.Emoji do
"""
use GenServer
+ alias Pleroma.Emoji.Combinations
alias Pleroma.Emoji.Loader
require Logger
@@ -137,4 +138,17 @@ defmodule Pleroma.Emoji do
end
def is_unicode_emoji?(_), do: false
+
+ emoji_qualification_map =
+ emojis
+ |> Enum.filter(&String.contains?(&1, "\uFE0F"))
+ |> Combinations.variate_emoji_qualification()
+
+ for {qualified, unqualified_list} <- emoji_qualification_map do
+ for unqualified <- unqualified_list do
+ def fully_qualify_emoji(unquote(unqualified)), do: unquote(qualified)
+ end
+ end
+
+ def fully_qualify_emoji(emoji), do: emoji
end
diff --git a/lib/pleroma/emoji/combinations.ex b/lib/pleroma/emoji/combinations.ex
new file mode 100644
index 000000000..981c73596
--- /dev/null
+++ b/lib/pleroma/emoji/combinations.ex
@@ -0,0 +1,45 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Emoji.Combinations do
+ # FE0F is the emoji variation sequence. It is used for fully-qualifying
+ # emoji, and that includes emoji combinations.
+ # This code generates combinations per emoji: for each FE0F, all possible
+ # combinations of the character being removed or staying will be generated.
+ # This is made as an attempt to find all partially-qualified and unqualified
+ # versions of a fully-qualified emoji.
+ # I have found *no cases* for which this would be a problem, after browsing
+ # the entire emoji list in emoji-test.txt. This is safe, and, sadly, most
+ # likely sane too.
+
+ defp qualification_combinations(codepoints) do
+ qualification_combinations([[]], codepoints)
+ end
+
+ defp qualification_combinations(acc, []), do: acc
+
+ defp qualification_combinations(acc, ["\uFE0F" | tail]) do
+ acc
+ |> Enum.flat_map(fn x -> [x, x ++ ["\uFE0F"]] end)
+ |> qualification_combinations(tail)
+ end
+
+ defp qualification_combinations(acc, [codepoint | tail]) do
+ acc
+ |> Enum.map(&Kernel.++(&1, [codepoint]))
+ |> qualification_combinations(tail)
+ end
+
+ def variate_emoji_qualification(emoji) when is_binary(emoji) do
+ emoji
+ |> String.codepoints()
+ |> qualification_combinations()
+ |> Enum.map(&List.to_string/1)
+ end
+
+ def variate_emoji_qualification(emoji) when is_list(emoji) do
+ emoji
+ |> Enum.map(fn emoji -> {emoji, variate_emoji_qualification(emoji)} end)
+ end
+end
diff --git a/lib/pleroma/emoji/formatter.ex b/lib/pleroma/emoji/formatter.ex
index dc45b8a38..87fd35f13 100644
--- a/lib/pleroma/emoji/formatter.ex
+++ b/lib/pleroma/emoji/formatter.ex
@@ -1,10 +1,11 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Emoji.Formatter do
alias Pleroma.Emoji
alias Pleroma.HTML
+ alias Pleroma.Web.Endpoint
alias Pleroma.Web.MediaProxy
def emojify(text) do
@@ -43,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, "#{Pleroma.Web.Endpoint.static_url()}#{file}")
+ Map.put(acc, name, to_string(URI.merge(Endpoint.url(), file)))
end)
end
diff --git a/lib/pleroma/emoji/loader.ex b/lib/pleroma/emoji/loader.ex
index 03a6bca0b..97d4b8f70 100644
--- a/lib/pleroma/emoji/loader.ex
+++ b/lib/pleroma/emoji/loader.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Emoji.Loader do
@@ -15,6 +15,8 @@ defmodule Pleroma.Emoji.Loader do
require Logger
+ @mix_env Mix.env()
+
@type pattern :: Regex.t() | module() | String.t()
@type patterns :: pattern() | [pattern()]
@type group_patterns :: keyword(patterns())
@@ -58,9 +60,7 @@ defmodule Pleroma.Emoji.Loader do
if not Enum.empty?(files) do
Logger.warn(
- "Found files in the emoji folder. These will be ignored, please move them to a subdirectory\nFound files: #{
- Enum.join(files, ", ")
- }"
+ "Found files in the emoji folder. These will be ignored, please move them to a subdirectory\nFound files: #{Enum.join(files, ", ")}"
)
end
@@ -77,10 +77,19 @@ defmodule Pleroma.Emoji.Loader do
# it should run even if there are no emoji packs
shortcode_globs = Config.get([:emoji, :shortcode_globs], [])
+ # for testing emoji.txt entries we do not want exposed in normal operation
+ test_emoji =
+ if @mix_env == :test do
+ load_from_file("test/config/emoji.txt", emoji_groups)
+ else
+ []
+ end
+
emojis_txt =
(load_from_file("config/emoji.txt", emoji_groups) ++
load_from_file("config/custom_emoji.txt", emoji_groups) ++
- load_from_globs(shortcode_globs, emoji_groups))
+ load_from_globs(shortcode_globs, emoji_groups) ++
+ test_emoji)
|> Enum.reject(fn value -> value == nil end)
Enum.map(emojis ++ emojis_txt, &prepare_emoji/1)
@@ -94,6 +103,7 @@ defmodule Pleroma.Emoji.Loader do
pack_file = Path.join(pack_dir, "pack.json")
if File.exists?(pack_file) do
+ Logger.info("Loading emoji pack from JSON: #{pack_file}")
contents = Jason.decode!(File.read!(pack_file))
contents["files"]
@@ -106,14 +116,13 @@ defmodule Pleroma.Emoji.Loader do
emoji_txt = Path.join(pack_dir, "emoji.txt")
if File.exists?(emoji_txt) do
+ Logger.info("Loading emoji pack from emoji.txt: #{emoji_txt}")
load_from_file(emoji_txt, emoji_groups)
else
extensions = Config.get([:emoji, :pack_extensions])
Logger.info(
- "No emoji.txt found for pack \"#{pack_name}\", assuming all #{
- Enum.join(extensions, ", ")
- } files are emoji"
+ "No emoji.txt found for pack \"#{pack_name}\", assuming all #{Enum.join(extensions, ", ")} files are emoji"
)
make_shortcode_to_file_map(pack_dir, extensions)
diff --git a/lib/pleroma/emoji/pack.ex b/lib/pleroma/emoji/pack.ex
index ec97aa652..a361ea200 100644
--- a/lib/pleroma/emoji/pack.ex
+++ b/lib/pleroma/emoji/pack.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Emoji.Pack do
diff --git a/lib/pleroma/filter.ex b/lib/pleroma/filter.ex
index 5d6df9530..db88bc021 100644
--- a/lib/pleroma/filter.ex
+++ b/lib/pleroma/filter.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Filter do
@@ -11,6 +11,9 @@ defmodule Pleroma.Filter do
alias Pleroma.Repo
alias Pleroma.User
+ @type t() :: %__MODULE__{}
+ @type format() :: :postgres | :re
+
schema "filters" do
belongs_to(:user, User, type: FlakeId.Ecto.CompatType)
field(:filter_id, :integer)
@@ -18,15 +21,16 @@ defmodule Pleroma.Filter do
field(:whole_word, :boolean, default: true)
field(:phrase, :string)
field(:context, {:array, :string})
- field(:expires_at, :utc_datetime)
+ field(:expires_at, :naive_datetime)
timestamps()
end
+ @spec get(integer() | String.t(), User.t()) :: t() | nil
def get(id, %{id: user_id} = _user) do
query =
from(
- f in Pleroma.Filter,
+ f in __MODULE__,
where: f.filter_id == ^id,
where: f.user_id == ^user_id
)
@@ -34,14 +38,17 @@ defmodule Pleroma.Filter do
Repo.one(query)
end
+ @spec get_active(Ecto.Query.t() | module()) :: Ecto.Query.t()
def get_active(query) do
from(f in query, where: is_nil(f.expires_at) or f.expires_at > ^NaiveDateTime.utc_now())
end
+ @spec get_irreversible(Ecto.Query.t()) :: Ecto.Query.t()
def get_irreversible(query) do
from(f in query, where: f.hide)
end
+ @spec get_filters(Ecto.Query.t() | module(), User.t()) :: [t()]
def get_filters(query \\ __MODULE__, %User{id: user_id}) do
query =
from(
@@ -53,7 +60,32 @@ defmodule Pleroma.Filter do
Repo.all(query)
end
- def create(%Pleroma.Filter{user_id: user_id, filter_id: nil} = filter) do
+ @spec create(map()) :: {:ok, t()} | {:error, Ecto.Changeset.t()}
+ def create(attrs \\ %{}) do
+ Repo.transaction(fn -> create_with_expiration(attrs) end)
+ end
+
+ defp create_with_expiration(attrs) do
+ with {:ok, filter} <- do_create(attrs),
+ {:ok, _} <- maybe_add_expiration_job(filter) do
+ filter
+ else
+ {:error, error} -> Repo.rollback(error)
+ end
+ end
+
+ defp do_create(attrs) do
+ %__MODULE__{}
+ |> cast(attrs, [:phrase, :context, :hide, :expires_at, :whole_word, :user_id, :filter_id])
+ |> maybe_add_filter_id()
+ |> validate_required([:phrase, :context, :user_id, :filter_id])
+ |> maybe_add_expires_at(attrs)
+ |> Repo.insert()
+ end
+
+ defp maybe_add_filter_id(%{changes: %{filter_id: _}} = changeset), do: changeset
+
+ defp maybe_add_filter_id(%{changes: %{user_id: user_id}} = changeset) do
# If filter_id wasn't given, use the max filter_id for this user plus 1.
# XXX This could result in a race condition if a user tries to add two
# different filters for their account from two different clients at the
@@ -61,7 +93,7 @@ defmodule Pleroma.Filter do
max_id_query =
from(
- f in Pleroma.Filter,
+ f in __MODULE__,
where: f.user_id == ^user_id,
select: max(f.filter_id)
)
@@ -76,34 +108,92 @@ defmodule Pleroma.Filter do
max_id + 1
end
- filter
- |> Map.put(:filter_id, filter_id)
- |> Repo.insert()
+ change(changeset, filter_id: filter_id)
+ end
+
+ # don't override expires_at, if passed expires_at and expires_in
+ defp maybe_add_expires_at(%{changes: %{expires_at: %NaiveDateTime{} = _}} = changeset, _) do
+ changeset
end
- def create(%Pleroma.Filter{} = filter) do
- Repo.insert(filter)
+ defp maybe_add_expires_at(changeset, %{expires_in: expires_in})
+ when is_integer(expires_in) and expires_in > 0 do
+ expires_at =
+ NaiveDateTime.utc_now()
+ |> NaiveDateTime.add(expires_in)
+ |> NaiveDateTime.truncate(:second)
+
+ change(changeset, expires_at: expires_at)
end
- def delete(%Pleroma.Filter{id: filter_key} = filter) when is_number(filter_key) do
- Repo.delete(filter)
+ defp maybe_add_expires_at(changeset, %{expires_in: nil}) do
+ change(changeset, expires_at: nil)
end
- def delete(%Pleroma.Filter{id: filter_key} = filter) when is_nil(filter_key) do
- %Pleroma.Filter{id: id} = get(filter.filter_id, %{id: filter.user_id})
+ defp maybe_add_expires_at(changeset, _), do: changeset
- filter
- |> Map.put(:id, id)
- |> Repo.delete()
+ defp maybe_add_expiration_job(%{expires_at: %NaiveDateTime{} = expires_at} = filter) do
+ Pleroma.Workers.PurgeExpiredFilter.enqueue(%{
+ filter_id: filter.id,
+ expires_at: DateTime.from_naive!(expires_at, "Etc/UTC")
+ })
end
- def update(%Pleroma.Filter{} = filter, params) do
+ defp maybe_add_expiration_job(_), do: {:ok, nil}
+
+ @spec delete(t()) :: {:ok, t()} | {:error, Ecto.Changeset.t()}
+ def delete(%__MODULE__{} = filter) do
+ Repo.transaction(fn -> delete_with_expiration(filter) end)
+ end
+
+ defp delete_with_expiration(filter) do
+ with {:ok, _} <- maybe_delete_old_expiration_job(filter, nil),
+ {:ok, filter} <- Repo.delete(filter) do
+ filter
+ else
+ {:error, error} -> Repo.rollback(error)
+ end
+ end
+
+ @spec update(t(), map()) :: {:ok, t()} | {:error, Ecto.Changeset.t()}
+ def update(%__MODULE__{} = filter, params) do
+ Repo.transaction(fn -> update_with_expiration(filter, params) end)
+ end
+
+ defp update_with_expiration(filter, params) do
+ with {:ok, updated} <- do_update(filter, params),
+ {:ok, _} <- maybe_delete_old_expiration_job(filter, updated),
+ {:ok, _} <-
+ maybe_add_expiration_job(updated) do
+ updated
+ else
+ {:error, error} -> Repo.rollback(error)
+ end
+ end
+
+ defp do_update(filter, params) do
filter
|> cast(params, [:phrase, :context, :hide, :expires_at, :whole_word])
|> validate_required([:phrase, :context])
+ |> maybe_add_expires_at(params)
|> Repo.update()
end
+ defp maybe_delete_old_expiration_job(%{expires_at: nil}, _), do: {:ok, nil}
+
+ defp maybe_delete_old_expiration_job(%{expires_at: expires_at}, %{expires_at: expires_at}) do
+ {:ok, nil}
+ end
+
+ defp maybe_delete_old_expiration_job(%{id: id}, _) do
+ with %Oban.Job{} = job <- Pleroma.Workers.PurgeExpiredFilter.get_expiration(id) do
+ Repo.delete(job)
+ else
+ nil -> {:ok, nil}
+ end
+ end
+
+ @spec compose_regex(User.t() | [t()], format()) :: String.t() | Regex.t() | nil
def compose_regex(user_or_filters, format \\ :postgres)
def compose_regex(%User{} = user, format) do
diff --git a/lib/pleroma/following_relationship.ex b/lib/pleroma/following_relationship.ex
index 5390a58e1..15664c876 100644
--- a/lib/pleroma/following_relationship.ex
+++ b/lib/pleroma/following_relationship.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.FollowingRelationship do
@@ -152,7 +152,7 @@ defmodule Pleroma.FollowingRelationship do
|> join(:inner, [r], f in assoc(r, :follower))
|> where([r], r.state == ^:follow_pending)
|> where([r], r.following_id == ^id)
- |> where([r, f], f.deactivated != true)
+ |> where([r, f], f.is_active == true)
|> select([r, f], f)
|> Repo.all()
end
@@ -194,12 +194,13 @@ defmodule Pleroma.FollowingRelationship do
|> join(:inner, [r], f in assoc(r, :follower))
|> where(following_id: ^origin.id)
|> where([r, f], f.allow_following_move == true)
+ |> where([r, f], f.local == true)
|> limit(50)
|> preload([:follower])
|> Repo.all()
|> Enum.map(fn following_relationship ->
- Repo.delete(following_relationship)
Pleroma.Web.CommonAPI.follow(following_relationship.follower, target)
+ Pleroma.Web.CommonAPI.unfollow(following_relationship.follower, origin)
end)
|> case do
[] ->
diff --git a/lib/pleroma/formatter.ex b/lib/pleroma/formatter.ex
index 0c450eae4..a46c3e381 100644
--- a/lib/pleroma/formatter.ex
+++ b/lib/pleroma/formatter.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Formatter do
@@ -34,35 +34,37 @@ defmodule Pleroma.Formatter do
def mention_handler("@" <> nickname, buffer, opts, acc) do
case User.get_cached_by_nickname(nickname) do
- %User{id: id} = user ->
- user_url = user.uri || user.ap_id
- nickname_text = get_nickname_text(nickname, opts)
-
- link =
- Phoenix.HTML.Tag.content_tag(
- :span,
- Phoenix.HTML.Tag.content_tag(
- :a,
- ["@", Phoenix.HTML.Tag.content_tag(:span, nickname_text)],
- "data-user": id,
- class: "u-url mention",
- href: user_url,
- rel: "ugc"
- ),
- class: "h-card"
- )
- |> Phoenix.HTML.safe_to_string()
-
- {link, %{acc | mentions: MapSet.put(acc.mentions, {"@" <> nickname, user})}}
+ %User{} = user ->
+ {mention_from_user(user, opts),
+ %{acc | mentions: MapSet.put(acc.mentions, {"@" <> nickname, user})}}
_ ->
{buffer, acc}
end
end
+ def mention_from_user(%User{id: id} = user, opts \\ %{mentions_format: :full}) do
+ user_url = user.uri || user.ap_id
+ nickname_text = get_nickname_text(user.nickname, opts)
+
+ Phoenix.HTML.Tag.content_tag(
+ :span,
+ Phoenix.HTML.Tag.content_tag(
+ :a,
+ ["@", Phoenix.HTML.Tag.content_tag(:span, nickname_text)],
+ "data-user": id,
+ class: "u-url mention",
+ href: user_url,
+ rel: "ugc"
+ ),
+ class: "h-card"
+ )
+ |> Phoenix.HTML.safe_to_string()
+ end
+
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 +123,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
diff --git a/lib/pleroma/frontend.ex b/lib/pleroma/frontend.ex
index bf935a728..ec72fb6a4 100644
--- a/lib/pleroma/frontend.ex
+++ b/lib/pleroma/frontend.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Frontend do
diff --git a/lib/pleroma/gopher/server.ex b/lib/pleroma/gopher/server.ex
index e9f54c4c0..0fde0adcf 100644
--- a/lib/pleroma/gopher/server.ex
+++ b/lib/pleroma/gopher/server.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Gopher.Server do
@@ -76,7 +76,7 @@ defmodule Pleroma.Gopher.Server.ProtocolHandler do
|> Enum.map(fn activity ->
user = User.get_cached_by_ap_id(activity.data["actor"])
- object = Object.normalize(activity)
+ object = Object.normalize(activity, fetch: false)
like_count = object.data["like_count"] || 0
announcement_count = object.data["announcement_count"] || 0
diff --git a/lib/pleroma/gun.ex b/lib/pleroma/gun.ex
index 4043e4880..c2d6b4bf2 100644
--- a/lib/pleroma/gun.ex
+++ b/lib/pleroma/gun.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Gun do
@@ -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)
diff --git a/lib/pleroma/gun/api.ex b/lib/pleroma/gun/api.ex
index 09be74392..ff2100623 100644
--- a/lib/pleroma/gun/api.ex
+++ b/lib/pleroma/gun/api.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Gun.API do
diff --git a/lib/pleroma/gun/conn.ex b/lib/pleroma/gun/conn.ex
index 477e19c6e..7c5785def 100644
--- a/lib/pleroma/gun/conn.ex
+++ b/lib/pleroma/gun/conn.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Gun.Conn do
@@ -57,9 +57,7 @@ defmodule Pleroma.Gun.Conn do
else
error ->
Logger.warn(
- "Opening proxied connection to #{compose_uri_log(uri)} failed with error #{
- inspect(error)
- }"
+ "Opening proxied connection to #{compose_uri_log(uri)} failed with error #{inspect(error)}"
)
error
@@ -93,9 +91,7 @@ defmodule Pleroma.Gun.Conn do
else
error ->
Logger.warn(
- "Opening socks proxied connection to #{compose_uri_log(uri)} failed with error #{
- inspect(error)
- }"
+ "Opening socks proxied connection to #{compose_uri_log(uri)} failed with error #{inspect(error)}"
)
error
diff --git a/lib/pleroma/gun/connection_pool.ex b/lib/pleroma/gun/connection_pool.ex
index e322f192a..2e851de19 100644
--- a/lib/pleroma/gun/connection_pool.ex
+++ b/lib/pleroma/gun/connection_pool.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Gun.ConnectionPool do
diff --git a/lib/pleroma/gun/connection_pool/reclaimer.ex b/lib/pleroma/gun/connection_pool/reclaimer.ex
index 241e8b04f..efd5c9fb8 100644
--- a/lib/pleroma/gun/connection_pool/reclaimer.ex
+++ b/lib/pleroma/gun/connection_pool/reclaimer.ex
@@ -1,15 +1,15 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
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"}}]}
]
diff --git a/lib/pleroma/gun/connection_pool/worker.ex b/lib/pleroma/gun/connection_pool/worker.ex
index b71816bed..38527ec1d 100644
--- a/lib/pleroma/gun/connection_pool/worker.ex
+++ b/lib/pleroma/gun/connection_pool/worker.ex
@@ -1,15 +1,15 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
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)
diff --git a/lib/pleroma/gun/connection_pool/worker_supervisor.ex b/lib/pleroma/gun/connection_pool/worker_supervisor.ex
index 4c23bcbd9..d26a70be3 100644
--- a/lib/pleroma/gun/connection_pool/worker_supervisor.ex
+++ b/lib/pleroma/gun/connection_pool/worker_supervisor.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Gun.ConnectionPool.WorkerSupervisor do
diff --git a/lib/pleroma/hashtag.ex b/lib/pleroma/hashtag.ex
new file mode 100644
index 000000000..a43d88220
--- /dev/null
+++ b/lib/pleroma/hashtag.ex
@@ -0,0 +1,106 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Hashtag do
+ use Ecto.Schema
+
+ import Ecto.Changeset
+ import Ecto.Query
+
+ alias Ecto.Multi
+ alias Pleroma.Hashtag
+ alias Pleroma.Object
+ alias Pleroma.Repo
+
+ schema "hashtags" do
+ field(:name, :string)
+
+ many_to_many(:objects, Object, join_through: "hashtags_objects", on_replace: :delete)
+
+ timestamps()
+ end
+
+ def normalize_name(name) do
+ name
+ |> String.downcase()
+ |> String.trim()
+ end
+
+ def get_or_create_by_name(name) do
+ changeset = changeset(%Hashtag{}, %{name: name})
+
+ Repo.insert(
+ changeset,
+ on_conflict: [set: [name: get_field(changeset, :name)]],
+ conflict_target: :name,
+ returning: true
+ )
+ end
+
+ def get_or_create_by_names(names) when is_list(names) do
+ names = Enum.map(names, &normalize_name/1)
+ timestamp = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
+
+ structs =
+ Enum.map(names, fn name ->
+ %Hashtag{}
+ |> changeset(%{name: name})
+ |> Map.get(:changes)
+ |> Map.merge(%{inserted_at: timestamp, updated_at: timestamp})
+ end)
+
+ try do
+ with {:ok, %{query_op: hashtags}} <-
+ Multi.new()
+ |> Multi.insert_all(:insert_all_op, Hashtag, structs,
+ on_conflict: :nothing,
+ conflict_target: :name
+ )
+ |> Multi.run(:query_op, fn _repo, _changes ->
+ {:ok, Repo.all(from(ht in Hashtag, where: ht.name in ^names))}
+ end)
+ |> Repo.transaction() do
+ {:ok, hashtags}
+ else
+ {:error, _name, value, _changes_so_far} -> {:error, value}
+ end
+ rescue
+ e -> {:error, e}
+ end
+ end
+
+ def changeset(%Hashtag{} = struct, params) do
+ struct
+ |> cast(params, [:name])
+ |> update_change(:name, &normalize_name/1)
+ |> validate_required([:name])
+ |> unique_constraint(:name)
+ end
+
+ def unlink(%Object{id: object_id}) do
+ with {_, hashtag_ids} <-
+ from(hto in "hashtags_objects",
+ where: hto.object_id == ^object_id,
+ select: hto.hashtag_id
+ )
+ |> Repo.delete_all(),
+ {:ok, unreferenced_count} <- delete_unreferenced(hashtag_ids) do
+ {:ok, length(hashtag_ids), unreferenced_count}
+ end
+ end
+
+ @delete_unreferenced_query """
+ DELETE FROM hashtags WHERE id IN
+ (SELECT hashtags.id FROM hashtags
+ LEFT OUTER JOIN hashtags_objects
+ ON hashtags_objects.hashtag_id = hashtags.id
+ WHERE hashtags_objects.hashtag_id IS NULL AND hashtags.id = ANY($1));
+ """
+
+ def delete_unreferenced(ids) do
+ with {:ok, %{num_rows: deleted_count}} <- Repo.query(@delete_unreferenced_query, [ids]) do
+ {:ok, deleted_count}
+ end
+ end
+end
diff --git a/lib/pleroma/healthcheck.ex b/lib/pleroma/healthcheck.ex
index 92ce83cb7..8e9ab8225 100644
--- a/lib/pleroma/healthcheck.ex
+++ b/lib/pleroma/healthcheck.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Healthcheck do
diff --git a/lib/pleroma/helpers/auth_helper.ex b/lib/pleroma/helpers/auth_helper.ex
index 8f87b38be..61599e71c 100644
--- a/lib/pleroma/helpers/auth_helper.ex
+++ b/lib/pleroma/helpers/auth_helper.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Helpers.AuthHelper do
diff --git a/lib/pleroma/helpers/inet_helper.ex b/lib/pleroma/helpers/inet_helper.ex
index 126f82381..704d37f8a 100644
--- a/lib/pleroma/helpers/inet_helper.ex
+++ b/lib/pleroma/helpers/inet_helper.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Helpers.InetHelper do
diff --git a/lib/pleroma/helpers/media_helper.ex b/lib/pleroma/helpers/media_helper.ex
index 6b799173e..24c845fcd 100644
--- a/lib/pleroma/helpers/media_helper.ex
+++ b/lib/pleroma/helpers/media_helper.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Helpers.MediaHelper do
diff --git a/lib/pleroma/helpers/qt_fast_start.ex b/lib/pleroma/helpers/qt_fast_start.ex
index bb93224b5..5711c7162 100644
--- a/lib/pleroma/helpers/qt_fast_start.ex
+++ b/lib/pleroma/helpers/qt_fast_start.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Helpers.QtFastStart do
diff --git a/lib/pleroma/helpers/uri_helper.ex b/lib/pleroma/helpers/uri_helper.ex
index f1301f055..c8d10d307 100644
--- a/lib/pleroma/helpers/uri_helper.ex
+++ b/lib/pleroma/helpers/uri_helper.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Helpers.UriHelper do
diff --git a/lib/pleroma/html.ex b/lib/pleroma/html.ex
index c848c782c..5bf735c4f 100644
--- a/lib/pleroma/html.ex
+++ b/lib/pleroma/html.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.HTML do
@@ -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)
- 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
diff --git a/lib/pleroma/http.ex b/lib/pleroma/http.ex
index 052597191..d41061538 100644
--- a/lib/pleroma/http.ex
+++ b/lib/pleroma/http.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.HTTP do
@@ -106,5 +106,12 @@ defmodule Pleroma.HTTP do
[Tesla.Middleware.FollowRedirects, Pleroma.Tesla.Middleware.ConnectionPool]
end
- defp adapter_middlewares(_), do: []
+ defp adapter_middlewares(_) do
+ if Pleroma.Config.get(:env) == :test do
+ # Emulate redirects in test env, which are handled by adapters in other environments
+ [Tesla.Middleware.FollowRedirects]
+ else
+ []
+ end
+ end
end
diff --git a/lib/pleroma/http/adapter_helper.ex b/lib/pleroma/http/adapter_helper.ex
index 08b51578a..252a6aba5 100644
--- a/lib/pleroma/http/adapter_helper.ex
+++ b/lib/pleroma/http/adapter_helper.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.HTTP.AdapterHelper do
diff --git a/lib/pleroma/http/adapter_helper/default.ex b/lib/pleroma/http/adapter_helper/default.ex
index 8567a616b..9c9414738 100644
--- a/lib/pleroma/http/adapter_helper/default.ex
+++ b/lib/pleroma/http/adapter_helper/default.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.HTTP.AdapterHelper.Default do
diff --git a/lib/pleroma/http/adapter_helper/gun.ex b/lib/pleroma/http/adapter_helper/gun.ex
index 1dbb71362..74ab9851e 100644
--- a/lib/pleroma/http/adapter_helper/gun.ex
+++ b/lib/pleroma/http/adapter_helper/gun.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.HTTP.AdapterHelper.Gun do
@@ -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
)
diff --git a/lib/pleroma/http/adapter_helper/hackney.ex b/lib/pleroma/http/adapter_helper/hackney.ex
index ff60513fd..f3be1f3d0 100644
--- a/lib/pleroma/http/adapter_helper/hackney.ex
+++ b/lib/pleroma/http/adapter_helper/hackney.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.HTTP.AdapterHelper.Hackney do
@@ -24,10 +24,6 @@ defmodule Pleroma.HTTP.AdapterHelper.Hackney do
|> Pleroma.HTTP.AdapterHelper.maybe_add_proxy(proxy)
end
- defp add_scheme_opts(opts, %URI{scheme: "https"}) do
- Keyword.put(opts, :ssl_options, versions: [:"tlsv1.2", :"tlsv1.1", :tlsv1])
- end
-
defp add_scheme_opts(opts, _), do: opts
defp maybe_add_with_body(opts) do
diff --git a/lib/pleroma/http/ex_aws.ex b/lib/pleroma/http/ex_aws.ex
index 5cac3532f..469c13819 100644
--- a/lib/pleroma/http/ex_aws.ex
+++ b/lib/pleroma/http/ex_aws.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.HTTP.ExAws do
diff --git a/lib/pleroma/http/request.ex b/lib/pleroma/http/request.ex
index 761bd6ccf..01045f8c9 100644
--- a/lib/pleroma/http/request.ex
+++ b/lib/pleroma/http/request.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.HTTP.Request do
diff --git a/lib/pleroma/http/request_builder.ex b/lib/pleroma/http/request_builder.ex
index 8a44a001d..f16fb3b35 100644
--- a/lib/pleroma/http/request_builder.ex
+++ b/lib/pleroma/http/request_builder.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.HTTP.RequestBuilder do
diff --git a/lib/pleroma/http/tzdata.ex b/lib/pleroma/http/tzdata.ex
index 09cfdadf7..5d2529c08 100644
--- a/lib/pleroma/http/tzdata.ex
+++ b/lib/pleroma/http/tzdata.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.HTTP.Tzdata do
diff --git a/lib/pleroma/http/web_push.ex b/lib/pleroma/http/web_push.ex
index 78148a12e..ca399b6c8 100644
--- a/lib/pleroma/http/web_push.ex
+++ b/lib/pleroma/http/web_push.ex
@@ -1,12 +1,12 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
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
diff --git a/lib/pleroma/instances.ex b/lib/pleroma/instances.ex
index 7315bd7cb..782948f83 100644
--- a/lib/pleroma/instances.ex
+++ b/lib/pleroma/instances.ex
@@ -1,17 +1,22 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Instances do
@moduledoc "Instances context."
- @adapter Pleroma.Instances.Instance
+ alias Pleroma.Instances.Instance
- defdelegate filter_reachable(urls_or_hosts), to: @adapter
- defdelegate reachable?(url_or_host), to: @adapter
- defdelegate set_reachable(url_or_host), to: @adapter
- defdelegate set_unreachable(url_or_host, unreachable_since \\ nil), to: @adapter
- defdelegate get_consistently_unreachable(), to: @adapter
+ def filter_reachable(urls_or_hosts), do: Instance.filter_reachable(urls_or_hosts)
+
+ def reachable?(url_or_host), do: Instance.reachable?(url_or_host)
+
+ def set_reachable(url_or_host), do: Instance.set_reachable(url_or_host)
+
+ def set_unreachable(url_or_host, unreachable_since \\ nil),
+ do: Instance.set_unreachable(url_or_host, unreachable_since)
+
+ def get_consistently_unreachable, do: Instance.get_consistently_unreachable()
def set_consistently_unreachable(url_or_host),
do: set_unreachable(url_or_host, reachability_datetime_threshold())
diff --git a/lib/pleroma/instances/instance.ex b/lib/pleroma/instances/instance.ex
index 2e1696fe2..a5529ad44 100644
--- a/lib/pleroma/instances/instance.ex
+++ b/lib/pleroma/instances/instance.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Instances.Instance do
@@ -8,6 +8,8 @@ defmodule Pleroma.Instances.Instance do
alias Pleroma.Instances
alias Pleroma.Instances.Instance
alias Pleroma.Repo
+ alias Pleroma.User
+ alias Pleroma.Workers.BackgroundWorker
use Ecto.Schema
@@ -195,4 +197,24 @@ defmodule Pleroma.Instances.Instance do
nil
end
end
+
+ @doc """
+ Deletes all users from an instance in a background task, thus also deleting
+ all of those users' activities and notifications.
+ """
+ def delete_users_and_activities(host) when is_binary(host) do
+ BackgroundWorker.enqueue("delete_instance", %{"host" => host})
+ end
+
+ def perform(:delete_instance, host) when is_binary(host) do
+ User.Query.build(%{nickname: "@#{host}"})
+ |> Repo.chunk_stream(100, :batches)
+ |> Stream.each(fn users ->
+ users
+ |> Enum.each(fn user ->
+ User.perform(:delete, user)
+ end)
+ end)
+ |> Stream.run()
+ end
end
diff --git a/lib/pleroma/job_queue_monitor.ex b/lib/pleroma/job_queue_monitor.ex
index c255a61ec..6233cdcf5 100644
--- a/lib/pleroma/job_queue_monitor.ex
+++ b/lib/pleroma/job_queue_monitor.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.JobQueueMonitor do
diff --git a/lib/pleroma/jwt.ex b/lib/pleroma/jwt.ex
index faeb77781..7ec6245d3 100644
--- a/lib/pleroma/jwt.ex
+++ b/lib/pleroma/jwt.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.JWT do
diff --git a/lib/pleroma/keys.ex b/lib/pleroma/keys.ex
index c9af79f00..496d20a71 100644
--- a/lib/pleroma/keys.ex
+++ b/lib/pleroma/keys.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Keys do
diff --git a/lib/pleroma/list.ex b/lib/pleroma/list.ex
index 89aa7b5d4..b446b91a0 100644
--- a/lib/pleroma/list.ex
+++ b/lib/pleroma/list.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.List do
@@ -113,11 +113,15 @@ defmodule Pleroma.List do
end
end
- def follow(%Pleroma.List{following: following} = list, %User{} = followed) do
+ def follow(%Pleroma.List{id: id}, %User{} = followed) do
+ list = Repo.get(Pleroma.List, id)
+ %{following: following} = list
update_follows(list, %{following: Enum.uniq([followed.follower_address | following])})
end
- def unfollow(%Pleroma.List{following: following} = list, %User{} = unfollowed) do
+ def unfollow(%Pleroma.List{id: id}, %User{} = unfollowed) do
+ list = Repo.get(Pleroma.List, id)
+ %{following: following} = list
update_follows(list, %{following: List.delete(following, unfollowed.follower_address)})
end
diff --git a/lib/pleroma/logging.ex b/lib/pleroma/logging.ex
index 37b201c29..ac09c0415 100644
--- a/lib/pleroma/logging.ex
+++ b/lib/pleroma/logging.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Logging do
diff --git a/lib/pleroma/maintenance.ex b/lib/pleroma/maintenance.ex
index 326c17825..eb5a6ef42 100644
--- a/lib/pleroma/maintenance.ex
+++ b/lib/pleroma/maintenance.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Maintenance do
@@ -9,7 +9,7 @@ defmodule Pleroma.Maintenance do
def vacuum(args) do
case args do
"analyze" ->
- Logger.info("Runnning VACUUM ANALYZE.")
+ Logger.info("Running VACUUM ANALYZE.")
Repo.query!(
"vacuum analyze;",
@@ -18,7 +18,7 @@ defmodule Pleroma.Maintenance do
)
"full" ->
- Logger.info("Runnning VACUUM FULL.")
+ Logger.info("Running VACUUM FULL.")
Logger.warn(
"Re-packing your entire database may take a while and will consume extra disk space during the process."
diff --git a/lib/pleroma/maps.ex b/lib/pleroma/maps.ex
index ab2e32e2f..6d586e53e 100644
--- a/lib/pleroma/maps.ex
+++ b/lib/pleroma/maps.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Maps do
@@ -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
diff --git a/lib/pleroma/marker.ex b/lib/pleroma/marker.ex
index 4d82860f5..68b054e4d 100644
--- a/lib/pleroma/marker.ex
+++ b/lib/pleroma/marker.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Marker do
diff --git a/lib/pleroma/mfa.ex b/lib/pleroma/mfa.ex
index 01b743f4f..01b730c76 100644
--- a/lib/pleroma/mfa.ex
+++ b/lib/pleroma/mfa.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.MFA do
@@ -71,7 +71,7 @@ defmodule Pleroma.MFA do
@spec generate_backup_codes(User.t()) :: {:ok, list(binary)} | {:error, String.t()}
def generate_backup_codes(%User{} = user) do
with codes <- BackupCodes.generate(),
- hashed_codes <- Enum.map(codes, &Pbkdf2.hash_pwd_salt/1),
+ hashed_codes <- Enum.map(codes, &Pleroma.Password.Pbkdf2.hash_pwd_salt/1),
changeset <- Changeset.cast_backup_codes(user, hashed_codes),
{:ok, _} <- User.update_and_set_cache(changeset) do
{:ok, codes}
diff --git a/lib/pleroma/mfa/backup_codes.ex b/lib/pleroma/mfa/backup_codes.ex
index 9875310ff..2f6962c1f 100644
--- a/lib/pleroma/mfa/backup_codes.ex
+++ b/lib/pleroma/mfa/backup_codes.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.MFA.BackupCodes do
diff --git a/lib/pleroma/mfa/changeset.ex b/lib/pleroma/mfa/changeset.ex
index 77c4fa202..3ec3cfe91 100644
--- a/lib/pleroma/mfa/changeset.ex
+++ b/lib/pleroma/mfa/changeset.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.MFA.Changeset do
diff --git a/lib/pleroma/mfa/settings.ex b/lib/pleroma/mfa/settings.ex
index de6e2228f..2c7f13e3f 100644
--- a/lib/pleroma/mfa/settings.ex
+++ b/lib/pleroma/mfa/settings.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.MFA.Settings do
diff --git a/lib/pleroma/mfa/token.ex b/lib/pleroma/mfa/token.ex
index 69b64c0e8..57bc11ed5 100644
--- a/lib/pleroma/mfa/token.ex
+++ b/lib/pleroma/mfa/token.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.MFA.Token do
diff --git a/lib/pleroma/mfa/totp.ex b/lib/pleroma/mfa/totp.ex
index d2ea2b3aa..429c4b700 100644
--- a/lib/pleroma/mfa/totp.ex
+++ b/lib/pleroma/mfa/totp.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.MFA.TOTP do
diff --git a/lib/pleroma/migration_helper/notification_backfill.ex b/lib/pleroma/migration_helper/notification_backfill.ex
index 24f4733fe..9f4976d9c 100644
--- a/lib/pleroma/migration_helper/notification_backfill.ex
+++ b/lib/pleroma/migration_helper/notification_backfill.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.MigrationHelper.NotificationBackfill do
diff --git a/lib/pleroma/migrators/context_objects_deletion_migrator.ex b/lib/pleroma/migrators/context_objects_deletion_migrator.ex
new file mode 100644
index 000000000..fb224795a
--- /dev/null
+++ b/lib/pleroma/migrators/context_objects_deletion_migrator.ex
@@ -0,0 +1,139 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Migrators.ContextObjectsDeletionMigrator do
+ defmodule State do
+ use Pleroma.Migrators.Support.BaseMigratorState
+
+ @impl Pleroma.Migrators.Support.BaseMigratorState
+ defdelegate data_migration(), to: Pleroma.DataMigration, as: :delete_context_objects
+ end
+
+ use Pleroma.Migrators.Support.BaseMigrator
+
+ alias Pleroma.Migrators.Support.BaseMigrator
+ alias Pleroma.Object
+
+ @doc "This migration removes objects created exclusively for contexts, containing only an `id` field."
+
+ @impl BaseMigrator
+ def feature_config_path, do: [:features, :delete_context_objects]
+
+ @impl BaseMigrator
+ def fault_rate_allowance, do: Config.get([:delete_context_objects, :fault_rate_allowance], 0)
+
+ @impl BaseMigrator
+ def perform do
+ data_migration_id = data_migration_id()
+ max_processed_id = get_stat(:max_processed_id, 0)
+
+ Logger.info("Deleting context objects from `objects` (from oid: #{max_processed_id})...")
+
+ query()
+ |> where([object], object.id > ^max_processed_id)
+ |> Repo.chunk_stream(100, :batches, timeout: :infinity)
+ |> Stream.each(fn objects ->
+ object_ids = Enum.map(objects, & &1.id)
+
+ results = Enum.map(object_ids, &delete_context_object(&1))
+
+ failed_ids =
+ results
+ |> Enum.filter(&(elem(&1, 0) == :error))
+ |> Enum.map(&elem(&1, 1))
+
+ chunk_affected_count =
+ results
+ |> Enum.filter(&(elem(&1, 0) == :ok))
+ |> length()
+
+ for failed_id <- failed_ids do
+ _ =
+ Repo.query(
+ "INSERT INTO data_migration_failed_ids(data_migration_id, record_id) " <>
+ "VALUES ($1, $2) ON CONFLICT DO NOTHING;",
+ [data_migration_id, failed_id]
+ )
+ end
+
+ _ =
+ Repo.query(
+ "DELETE FROM data_migration_failed_ids " <>
+ "WHERE data_migration_id = $1 AND record_id = ANY($2)",
+ [data_migration_id, object_ids -- failed_ids]
+ )
+
+ max_object_id = Enum.at(object_ids, -1)
+
+ put_stat(:max_processed_id, max_object_id)
+ increment_stat(:iteration_processed_count, length(object_ids))
+ increment_stat(:processed_count, length(object_ids))
+ increment_stat(:failed_count, length(failed_ids))
+ increment_stat(:affected_count, chunk_affected_count)
+ put_stat(:records_per_second, records_per_second())
+ persist_state()
+
+ # A quick and dirty approach to controlling the load this background migration imposes
+ sleep_interval = Config.get([:delete_context_objects, :sleep_interval_ms], 0)
+ Process.sleep(sleep_interval)
+ end)
+ |> Stream.run()
+ end
+
+ @impl BaseMigrator
+ def query do
+ # Context objects have no activity type, and only one field, `id`.
+ # Only those context objects are without types.
+ from(
+ object in Object,
+ where: fragment("(?)->'type' IS NULL", object.data),
+ select: %{
+ id: object.id
+ }
+ )
+ end
+
+ @spec delete_context_object(integer()) :: {:ok | :error, integer()}
+ defp delete_context_object(id) do
+ result =
+ %Object{id: id}
+ |> Repo.delete()
+ |> elem(0)
+
+ {result, id}
+ end
+
+ @impl BaseMigrator
+ def retry_failed do
+ data_migration_id = data_migration_id()
+
+ failed_objects_query()
+ |> Repo.chunk_stream(100, :one)
+ |> Stream.each(fn object ->
+ with {res, _} when res != :error <- delete_context_object(object.id) do
+ _ =
+ Repo.query(
+ "DELETE FROM data_migration_failed_ids " <>
+ "WHERE data_migration_id = $1 AND record_id = $2",
+ [data_migration_id, object.id]
+ )
+ end
+ end)
+ |> Stream.run()
+
+ put_stat(:failed_count, failures_count())
+ persist_state()
+
+ force_continue()
+ end
+
+ defp failed_objects_query do
+ from(o in Object)
+ |> join(:inner, [o], dmf in fragment("SELECT * FROM data_migration_failed_ids"),
+ on: dmf.record_id == o.id
+ )
+ |> where([_o, dmf], dmf.data_migration_id == ^data_migration_id())
+ |> order_by([o], asc: o.id)
+ end
+end
diff --git a/lib/pleroma/migrators/hashtags_table_migrator.ex b/lib/pleroma/migrators/hashtags_table_migrator.ex
new file mode 100644
index 000000000..dca4bfa6f
--- /dev/null
+++ b/lib/pleroma/migrators/hashtags_table_migrator.ex
@@ -0,0 +1,208 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Migrators.HashtagsTableMigrator do
+ defmodule State do
+ use Pleroma.Migrators.Support.BaseMigratorState
+
+ @impl Pleroma.Migrators.Support.BaseMigratorState
+ defdelegate data_migration(), to: Pleroma.DataMigration, as: :populate_hashtags_table
+ end
+
+ use Pleroma.Migrators.Support.BaseMigrator
+
+ alias Pleroma.Hashtag
+ alias Pleroma.Migrators.Support.BaseMigrator
+ alias Pleroma.Object
+
+ @impl BaseMigrator
+ def feature_config_path, do: [:features, :improved_hashtag_timeline]
+
+ @impl BaseMigrator
+ def fault_rate_allowance, do: Config.get([:populate_hashtags_table, :fault_rate_allowance], 0)
+
+ @impl BaseMigrator
+ def perform do
+ data_migration_id = data_migration_id()
+ max_processed_id = get_stat(:max_processed_id, 0)
+
+ Logger.info("Transferring embedded hashtags to `hashtags` (from oid: #{max_processed_id})...")
+
+ query()
+ |> where([object], object.id > ^max_processed_id)
+ |> Repo.chunk_stream(100, :batches, timeout: :infinity)
+ |> Stream.each(fn objects ->
+ object_ids = Enum.map(objects, & &1.id)
+
+ results = Enum.map(objects, &transfer_object_hashtags(&1))
+
+ failed_ids =
+ results
+ |> Enum.filter(&(elem(&1, 0) == :error))
+ |> Enum.map(&elem(&1, 1))
+
+ # Count of objects with hashtags: `{:noop, id}` is returned for objects having other AS2 tags
+ chunk_affected_count =
+ results
+ |> Enum.filter(&(elem(&1, 0) == :ok))
+ |> length()
+
+ for failed_id <- failed_ids do
+ _ =
+ Repo.query(
+ "INSERT INTO data_migration_failed_ids(data_migration_id, record_id) " <>
+ "VALUES ($1, $2) ON CONFLICT DO NOTHING;",
+ [data_migration_id, failed_id]
+ )
+ end
+
+ _ =
+ Repo.query(
+ "DELETE FROM data_migration_failed_ids " <>
+ "WHERE data_migration_id = $1 AND record_id = ANY($2)",
+ [data_migration_id, object_ids -- failed_ids]
+ )
+
+ max_object_id = Enum.at(object_ids, -1)
+
+ put_stat(:max_processed_id, max_object_id)
+ increment_stat(:iteration_processed_count, length(object_ids))
+ increment_stat(:processed_count, length(object_ids))
+ increment_stat(:failed_count, length(failed_ids))
+ increment_stat(:affected_count, chunk_affected_count)
+ put_stat(:records_per_second, records_per_second())
+ persist_state()
+
+ # A quick and dirty approach to controlling the load this background migration imposes
+ sleep_interval = Config.get([:populate_hashtags_table, :sleep_interval_ms], 0)
+ Process.sleep(sleep_interval)
+ end)
+ |> Stream.run()
+ end
+
+ @impl BaseMigrator
+ def query do
+ # Note: most objects have Mention-type AS2 tags and no hashtags (but we can't filter them out)
+ # Note: not checking activity type, expecting remove_non_create_objects_hashtags/_ to clean up
+ from(
+ object in Object,
+ where:
+ fragment("(?)->'tag' IS NOT NULL AND (?)->'tag' != '[]'::jsonb", object.data, object.data),
+ select: %{
+ id: object.id,
+ tag: fragment("(?)->'tag'", object.data)
+ }
+ )
+ |> join(:left, [o], hashtags_objects in fragment("SELECT object_id FROM hashtags_objects"),
+ on: hashtags_objects.object_id == o.id
+ )
+ |> where([_o, hashtags_objects], is_nil(hashtags_objects.object_id))
+ end
+
+ @spec transfer_object_hashtags(Map.t()) :: {:noop | :ok | :error, integer()}
+ defp transfer_object_hashtags(object) do
+ embedded_tags = if Map.has_key?(object, :tag), do: object.tag, else: object.data["tag"]
+ hashtags = Object.object_data_hashtags(%{"tag" => embedded_tags})
+
+ if Enum.any?(hashtags) do
+ transfer_object_hashtags(object, hashtags)
+ else
+ {:noop, object.id}
+ end
+ end
+
+ defp transfer_object_hashtags(object, hashtags) do
+ Repo.transaction(fn ->
+ with {:ok, hashtag_records} <- Hashtag.get_or_create_by_names(hashtags) do
+ maps = Enum.map(hashtag_records, &%{hashtag_id: &1.id, object_id: object.id})
+ base_error = "ERROR when inserting hashtags_objects for object with id #{object.id}"
+
+ try do
+ with {rows_count, _} when is_integer(rows_count) <-
+ Repo.insert_all("hashtags_objects", maps, on_conflict: :nothing) do
+ object.id
+ else
+ e ->
+ Logger.error("#{base_error}: #{inspect(e)}")
+ Repo.rollback(object.id)
+ end
+ rescue
+ e ->
+ Logger.error("#{base_error}: #{inspect(e)}")
+ Repo.rollback(object.id)
+ end
+ else
+ e ->
+ error = "ERROR: could not create hashtags for object #{object.id}: #{inspect(e)}"
+ Logger.error(error)
+ Repo.rollback(object.id)
+ end
+ end)
+ end
+
+ @impl BaseMigrator
+ def retry_failed do
+ data_migration_id = data_migration_id()
+
+ failed_objects_query()
+ |> Repo.chunk_stream(100, :one)
+ |> Stream.each(fn object ->
+ with {res, _} when res != :error <- transfer_object_hashtags(object) do
+ _ =
+ Repo.query(
+ "DELETE FROM data_migration_failed_ids " <>
+ "WHERE data_migration_id = $1 AND record_id = $2",
+ [data_migration_id, object.id]
+ )
+ end
+ end)
+ |> Stream.run()
+
+ put_stat(:failed_count, failures_count())
+ persist_state()
+
+ force_continue()
+ end
+
+ defp failed_objects_query do
+ from(o in Object)
+ |> join(:inner, [o], dmf in fragment("SELECT * FROM data_migration_failed_ids"),
+ on: dmf.record_id == o.id
+ )
+ |> where([_o, dmf], dmf.data_migration_id == ^data_migration_id())
+ |> order_by([o], asc: o.id)
+ end
+
+ @doc """
+ Service func to delete `hashtags_objects` for legacy objects not associated with Create activity.
+ Also deletes unreferenced `hashtags` records (might occur after deletion of `hashtags_objects`).
+ """
+ def delete_non_create_activities_hashtags do
+ hashtags_objects_cleanup_query = """
+ DELETE FROM hashtags_objects WHERE object_id IN
+ (SELECT DISTINCT objects.id FROM objects
+ JOIN hashtags_objects ON hashtags_objects.object_id = objects.id LEFT JOIN activities
+ ON associated_object_id(activities) =
+ (objects.data->>'id')
+ AND activities.data->>'type' = 'Create'
+ WHERE activities.id IS NULL);
+ """
+
+ hashtags_cleanup_query = """
+ DELETE FROM hashtags WHERE id IN
+ (SELECT hashtags.id FROM hashtags
+ LEFT OUTER JOIN hashtags_objects
+ ON hashtags_objects.hashtag_id = hashtags.id
+ WHERE hashtags_objects.hashtag_id IS NULL);
+ """
+
+ {:ok, %{num_rows: hashtags_objects_count}} =
+ Repo.query(hashtags_objects_cleanup_query, [], timeout: :infinity)
+
+ {:ok, %{num_rows: hashtags_count}} =
+ Repo.query(hashtags_cleanup_query, [], timeout: :infinity)
+
+ {:ok, hashtags_objects_count, hashtags_count}
+ end
+end
diff --git a/lib/pleroma/migrators/support/base_migrator.ex b/lib/pleroma/migrators/support/base_migrator.ex
new file mode 100644
index 000000000..3bcd59fd0
--- /dev/null
+++ b/lib/pleroma/migrators/support/base_migrator.ex
@@ -0,0 +1,210 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Migrators.Support.BaseMigrator do
+ @moduledoc """
+ Base background migrator functionality.
+ """
+
+ @callback perform() :: any()
+ @callback retry_failed() :: any()
+ @callback feature_config_path() :: list(atom())
+ @callback query() :: Ecto.Query.t()
+ @callback fault_rate_allowance() :: integer() | float()
+
+ defmacro __using__(_opts) do
+ quote do
+ use GenServer
+
+ require Logger
+
+ import Ecto.Query
+
+ alias __MODULE__.State
+ alias Pleroma.Config
+ alias Pleroma.Repo
+
+ @behaviour Pleroma.Migrators.Support.BaseMigrator
+
+ defdelegate data_migration(), to: State
+ defdelegate data_migration_id(), to: State
+ defdelegate state(), to: State
+ defdelegate persist_state(), to: State, as: :persist_to_db
+ defdelegate get_stat(key, value \\ nil), to: State, as: :get_data_key
+ defdelegate put_stat(key, value), to: State, as: :put_data_key
+ defdelegate increment_stat(key, increment), to: State, as: :increment_data_key
+
+ @reg_name {:global, __MODULE__}
+
+ def whereis, do: GenServer.whereis(@reg_name)
+
+ def start_link(_) do
+ case whereis() do
+ nil ->
+ GenServer.start_link(__MODULE__, nil, name: @reg_name)
+
+ pid ->
+ {:ok, pid}
+ end
+ end
+
+ @impl true
+ def init(_) do
+ {:ok, nil, {:continue, :init_state}}
+ end
+
+ @impl true
+ def handle_continue(:init_state, _state) do
+ {:ok, _} = State.start_link(nil)
+
+ data_migration = data_migration()
+ manual_migrations = Config.get([:instance, :manual_data_migrations], [])
+
+ cond do
+ Config.get(:env) == :test ->
+ update_status(:noop)
+
+ is_nil(data_migration) ->
+ message = "Data migration does not exist."
+ update_status(:failed, message)
+ Logger.error("#{__MODULE__}: #{message}")
+
+ data_migration.state == :manual or data_migration.name in manual_migrations ->
+ message = "Data migration is in manual execution or manual fix mode."
+ update_status(:manual, message)
+ Logger.warn("#{__MODULE__}: #{message}")
+
+ data_migration.state == :complete ->
+ on_complete(data_migration)
+
+ true ->
+ send(self(), :perform)
+ end
+
+ {:noreply, nil}
+ end
+
+ @impl true
+ def handle_info(:perform, state) do
+ State.reinit()
+
+ update_status(:running)
+ put_stat(:iteration_processed_count, 0)
+ put_stat(:started_at, NaiveDateTime.utc_now())
+
+ perform()
+
+ fault_rate = fault_rate()
+ put_stat(:fault_rate, fault_rate)
+ fault_rate_allowance = fault_rate_allowance()
+
+ cond do
+ fault_rate == 0 ->
+ set_complete()
+
+ is_float(fault_rate) and fault_rate <= fault_rate_allowance ->
+ message = """
+ Done with fault rate of #{fault_rate} which doesn't exceed #{fault_rate_allowance}.
+ Putting data migration to manual fix mode. Try running `#{__MODULE__}.retry_failed/0`.
+ """
+
+ Logger.warn("#{__MODULE__}: #{message}")
+ update_status(:manual, message)
+ on_complete(data_migration())
+
+ true ->
+ message = "Too many failures. Try running `#{__MODULE__}.retry_failed/0`."
+ Logger.error("#{__MODULE__}: #{message}")
+ update_status(:failed, message)
+ end
+
+ persist_state()
+ {:noreply, state}
+ end
+
+ defp on_complete(data_migration) do
+ if data_migration.feature_lock || feature_state() == :disabled do
+ Logger.warn(
+ "#{__MODULE__}: migration complete but feature is locked; consider enabling."
+ )
+
+ :noop
+ else
+ Config.put(feature_config_path(), :enabled)
+ :ok
+ end
+ end
+
+ @doc "Approximate count for current iteration (including processed records count)"
+ def count(force \\ false, timeout \\ :infinity) do
+ stored_count = get_stat(:count)
+
+ if stored_count && !force do
+ stored_count
+ else
+ processed_count = get_stat(:processed_count, 0)
+ max_processed_id = get_stat(:max_processed_id, 0)
+ query = where(query(), [entity], entity.id > ^max_processed_id)
+
+ count = Repo.aggregate(query, :count, :id, timeout: timeout) + processed_count
+ put_stat(:count, count)
+ persist_state()
+
+ count
+ end
+ end
+
+ def failures_count do
+ with {:ok, %{rows: [[count]]}} <-
+ Repo.query(
+ "SELECT COUNT(record_id) FROM data_migration_failed_ids WHERE data_migration_id = $1;",
+ [data_migration_id()]
+ ) do
+ count
+ end
+ end
+
+ def feature_state, do: Config.get(feature_config_path())
+
+ def force_continue do
+ send(whereis(), :perform)
+ end
+
+ def force_restart do
+ :ok = State.reset()
+ force_continue()
+ end
+
+ def set_complete do
+ update_status(:complete)
+ persist_state()
+ on_complete(data_migration())
+ end
+
+ defp update_status(status, message \\ nil) do
+ put_stat(:state, status)
+ put_stat(:message, message)
+ end
+
+ defp fault_rate do
+ with failures_count when is_integer(failures_count) <- failures_count() do
+ failures_count / Enum.max([get_stat(:affected_count, 0), 1])
+ else
+ _ -> :error
+ end
+ end
+
+ defp records_per_second do
+ get_stat(:iteration_processed_count, 0) / Enum.max([running_time(), 1])
+ end
+
+ defp running_time do
+ NaiveDateTime.diff(
+ NaiveDateTime.utc_now(),
+ get_stat(:started_at, NaiveDateTime.utc_now())
+ )
+ end
+ end
+ end
+end
diff --git a/lib/pleroma/migrators/support/base_migrator_state.ex b/lib/pleroma/migrators/support/base_migrator_state.ex
new file mode 100644
index 000000000..3d7769fc6
--- /dev/null
+++ b/lib/pleroma/migrators/support/base_migrator_state.ex
@@ -0,0 +1,117 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Migrators.Support.BaseMigratorState do
+ @moduledoc """
+ Base background migrator state functionality.
+ """
+
+ @callback data_migration() :: Pleroma.DataMigration.t()
+
+ defmacro __using__(_opts) do
+ quote do
+ use Agent
+
+ alias Pleroma.DataMigration
+
+ @behaviour Pleroma.Migrators.Support.BaseMigratorState
+ @reg_name {:global, __MODULE__}
+
+ def start_link(_) do
+ Agent.start_link(fn -> load_state_from_db() end, name: @reg_name)
+ end
+
+ def data_migration, do: raise("data_migration/0 is not implemented")
+ defoverridable data_migration: 0
+
+ defp load_state_from_db do
+ data_migration = data_migration()
+
+ data =
+ if data_migration do
+ Map.new(data_migration.data, fn {k, v} -> {String.to_atom(k), v} end)
+ else
+ %{}
+ end
+
+ %{
+ data_migration_id: data_migration && data_migration.id,
+ data: data
+ }
+ end
+
+ def persist_to_db do
+ %{data_migration_id: data_migration_id, data: data} = state()
+
+ if data_migration_id do
+ DataMigration.update_one_by_id(data_migration_id, data: data)
+ else
+ {:error, :nil_data_migration_id}
+ end
+ end
+
+ def reset do
+ %{data_migration_id: data_migration_id} = state()
+
+ with false <- is_nil(data_migration_id),
+ :ok <-
+ DataMigration.update_one_by_id(data_migration_id,
+ state: :pending,
+ data: %{}
+ ) do
+ reinit()
+ else
+ true -> {:error, :nil_data_migration_id}
+ e -> e
+ end
+ end
+
+ def reinit do
+ Agent.update(@reg_name, fn _state -> load_state_from_db() end)
+ end
+
+ def state do
+ Agent.get(@reg_name, & &1)
+ end
+
+ def get_data_key(key, default \\ nil) do
+ get_in(state(), [:data, key]) || default
+ end
+
+ def put_data_key(key, value) do
+ _ = persist_non_data_change(key, value)
+
+ Agent.update(@reg_name, fn state ->
+ put_in(state, [:data, key], value)
+ end)
+ end
+
+ def increment_data_key(key, increment \\ 1) do
+ Agent.update(@reg_name, fn state ->
+ initial_value = get_in(state, [:data, key]) || 0
+ updated_value = initial_value + increment
+ put_in(state, [:data, key], updated_value)
+ end)
+ end
+
+ defp persist_non_data_change(:state, value) do
+ with true <- get_data_key(:state) != value,
+ true <- value in Pleroma.DataMigration.State.__valid_values__(),
+ %{data_migration_id: data_migration_id} when not is_nil(data_migration_id) <-
+ state() do
+ DataMigration.update_one_by_id(data_migration_id, state: value)
+ else
+ false -> :ok
+ _ -> {:error, :nil_data_migration_id}
+ end
+ end
+
+ defp persist_non_data_change(_, _) do
+ nil
+ end
+
+ def data_migration_id, do: Map.get(state(), :data_migration_id)
+ end
+ end
+end
diff --git a/lib/pleroma/moderation_log.ex b/lib/pleroma/moderation_log.ex
index a7f26793d..7203423e2 100644
--- a/lib/pleroma/moderation_log.ex
+++ b/lib/pleroma/moderation_log.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.ModerationLog do
@@ -341,6 +341,26 @@ defmodule Pleroma.ModerationLog do
def get_log_entry_message(%ModerationLog{
data: %{
"actor" => %{"nickname" => actor_nickname},
+ "action" => "add_suggestion",
+ "subject" => users
+ }
+ }) do
+ "@#{actor_nickname} added suggested users: #{users_to_nicknames_string(users)}"
+ end
+
+ def get_log_entry_message(%ModerationLog{
+ data: %{
+ "actor" => %{"nickname" => actor_nickname},
+ "action" => "remove_suggestion",
+ "subject" => users
+ }
+ }) do
+ "@#{actor_nickname} removed suggested users: #{users_to_nicknames_string(users)}"
+ end
+
+ def get_log_entry_message(%ModerationLog{
+ data: %{
+ "actor" => %{"nickname" => actor_nickname},
"nicknames" => nicknames,
"tags" => tags,
"action" => "tag"
@@ -481,9 +501,7 @@ defmodule Pleroma.ModerationLog do
"visibility" => visibility
}
}) do
- "@#{actor_nickname} updated status ##{subject_id}, set sensitive: '#{sensitive}', visibility: '#{
- visibility
- }'"
+ "@#{actor_nickname} updated status ##{subject_id}, set sensitive: '#{sensitive}', visibility: '#{visibility}'"
end
def get_log_entry_message(%ModerationLog{
@@ -523,9 +541,7 @@ defmodule Pleroma.ModerationLog do
"subject" => subjects
}
}) do
- "@#{actor_nickname} re-sent confirmation email for users: #{
- users_to_nicknames_string(subjects)
- }"
+ "@#{actor_nickname} re-sent confirmation email for users: #{users_to_nicknames_string(subjects)}"
end
def get_log_entry_message(%ModerationLog{
diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex
index dd7a1c824..c2d4d86a3 100644
--- a/lib/pleroma/notification.ex
+++ b/lib/pleroma/notification.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Notification do
@@ -72,6 +72,7 @@ defmodule Pleroma.Notification do
pleroma:emoji_reaction
pleroma:report
reblog
+ poll
}
def changeset(%Notification{} = notification, attrs) do
@@ -112,26 +113,21 @@ defmodule Pleroma.Notification do
Notification
|> where(user_id: ^user.id)
- |> where(
- [n, a],
- fragment(
- "? not in (SELECT ap_id FROM users WHERE deactivated = 'true')",
- a.actor
- )
- )
|> join(:inner, [n], activity in assoc(n, :activity))
|> join(:left, [n, a], object in Object,
on:
fragment(
- "(?->>'id') = COALESCE(?->'object'->>'id', ?->>'object')",
+ "(?->>'id') = associated_object_id(?)",
object.data,
- a.data,
a.data
)
)
+ |> join(:inner, [_n, a], u in User, on: u.ap_id == a.actor, as: :user_actor)
|> preload([n, a, o], activity: {a, object: o})
+ |> where([user_actor: user_actor], user_actor.is_active)
|> exclude_notification_muted(user, exclude_notification_muted_opts)
|> exclude_blocked(user, exclude_blocked_opts)
+ |> exclude_blockers(user)
|> exclude_filtered(user)
|> exclude_visibility(opts)
end
@@ -145,6 +141,17 @@ defmodule Pleroma.Notification do
|> FollowingRelationship.keep_following_or_not_domain_blocked(user)
end
+ defp exclude_blockers(query, user) do
+ if Pleroma.Config.get([:activitypub, :blockers_visible]) == true do
+ query
+ else
+ blocker_ap_ids = User.incoming_relationships_ungrouped_ap_ids(user, [:block])
+
+ query
+ |> where([n, a], a.actor not in ^blocker_ap_ids)
+ end
+ end
+
defp exclude_notification_muted(query, _, %{@include_muted_option => true}) do
query
end
@@ -156,9 +163,10 @@ defmodule Pleroma.Notification do
query
|> where([n, a], a.actor not in ^notification_muted_ap_ids)
|> join(:left, [n, a], tm in ThreadMute,
- on: tm.user_id == ^user.id and tm.context == fragment("?->>'context'", a.data)
+ on: tm.user_id == ^user.id and tm.context == fragment("?->>'context'", a.data),
+ as: :thread_mute
)
- |> where([n, a, o, tm], is_nil(tm.user_id))
+ |> where([thread_mute: thread_mute], is_nil(thread_mute.user_id))
end
defp exclude_filtered(query, user) do
@@ -184,13 +192,11 @@ defmodule Pleroma.Notification do
|> join(:left, [n, a], mutated_activity in Pleroma.Activity,
on:
fragment(
- "COALESCE((?->'object')->>'id', ?->>'object')",
- a.data,
+ "associated_object_id(?)",
a.data
) ==
fragment(
- "COALESCE((?->'object')->>'id', ?->>'object')",
- mutated_activity.data,
+ "associated_object_id(?)",
mutated_activity.data
) and
fragment("(?->>'type' = 'Like' or ?->>'type' = 'Announce')", a.data, a.data) and
@@ -332,6 +338,14 @@ defmodule Pleroma.Notification do
|> Repo.delete_all()
end
+ def destroy_multiple_from_types(%{id: user_id}, types) do
+ from(n in Notification,
+ where: n.user_id == ^user_id,
+ where: n.type in ^types
+ )
+ |> Repo.delete_all()
+ end
+
def dismiss(%Pleroma.Activity{} = activity) do
Notification
|> where([n], n.activity_id == ^activity.id)
@@ -358,7 +372,7 @@ defmodule Pleroma.Notification do
def create_notifications(activity, options \\ [])
def create_notifications(%Activity{data: %{"to" => _, "type" => "Create"}} = activity, options) do
- object = Object.normalize(activity, false)
+ object = Object.normalize(activity, fetch: false)
if object && object.data["type"] == "Answer" do
{:ok, []}
@@ -368,7 +382,7 @@ defmodule Pleroma.Notification do
end
def create_notifications(%Activity{data: %{"type" => type}} = activity, options)
- when type in ["Follow", "Like", "Announce", "Move", "EmojiReact", "Flag"] do
+ when type in ["Follow", "Like", "Announce", "Move", "EmojiReact", "Flag", "Update"] do
do_create_notifications(activity, options)
end
@@ -383,7 +397,7 @@ defmodule Pleroma.Notification do
notifications =
Enum.map(potential_receivers, fn user ->
do_send = do_send && user in enabled_receivers
- create_notification(activity, user, do_send)
+ create_notification(activity, user, do_send: do_send)
end)
|> Enum.reject(&is_nil/1)
@@ -422,6 +436,9 @@ defmodule Pleroma.Notification do
activity
|> type_from_activity_object()
+ "Update" ->
+ "update"
+
t ->
raise "No notification type for activity type #{t}"
end
@@ -439,15 +456,18 @@ defmodule Pleroma.Notification do
end
# TODO move to sql, too.
- def create_notification(%Activity{} = activity, %User{} = user, do_send \\ true) do
- unless skip?(activity, user) do
+ def create_notification(%Activity{} = activity, %User{} = user, opts \\ []) do
+ do_send = Keyword.get(opts, :do_send, true)
+ type = Keyword.get(opts, :type, type_from_activity(activity))
+
+ unless skip?(activity, user, opts) do
{:ok, %{notification: notification}} =
Multi.new()
|> Multi.insert(:notification, %Notification{
user_id: user.id,
activity: activity,
seen: mark_as_read?(activity, user),
- type: type_from_activity(activity)
+ type: type
})
|> Marker.multi_set_last_read_id(user, "notifications")
|> Repo.transaction()
@@ -461,6 +481,28 @@ defmodule Pleroma.Notification do
end
end
+ def create_poll_notifications(%Activity{} = activity) do
+ with %Object{data: %{"type" => "Question", "actor" => actor} = data} <-
+ Object.normalize(activity) do
+ voters =
+ case data do
+ %{"voters" => voters} when is_list(voters) -> voters
+ _ -> []
+ end
+
+ notifications =
+ Enum.reduce([actor | voters], [], fn ap_id, acc ->
+ with %User{local: true} = user <- User.get_by_ap_id(ap_id) do
+ [create_notification(activity, user, type: "poll") | acc]
+ else
+ _ -> acc
+ end
+ end)
+
+ {:ok, notifications}
+ end
+ end
+
@doc """
Returns a tuple with 2 elements:
{notification-enabled receivers, currently disabled receivers (blocking / [thread] muting)}
@@ -471,7 +513,16 @@ defmodule Pleroma.Notification do
def get_notified_from_activity(activity, local_only \\ true)
def get_notified_from_activity(%Activity{data: %{"type" => type}} = activity, local_only)
- when type in ["Create", "Like", "Announce", "Follow", "Move", "EmojiReact", "Flag"] do
+ when type in [
+ "Create",
+ "Like",
+ "Announce",
+ "Follow",
+ "Move",
+ "EmojiReact",
+ "Flag",
+ "Update"
+ ] do
potential_receiver_ap_ids = get_potential_receiver_ap_ids(activity)
potential_receivers =
@@ -507,8 +558,23 @@ defmodule Pleroma.Notification do
[object_id]
end
- def get_potential_receiver_ap_ids(%{data: %{"type" => "Flag"}}) do
- User.all_superusers() |> Enum.map(fn user -> user.ap_id end)
+ def get_potential_receiver_ap_ids(%{data: %{"type" => "Flag", "actor" => actor}}) do
+ (User.all_superusers() |> Enum.map(fn user -> user.ap_id end)) -- [actor]
+ end
+
+ # Update activity: notify all who repeated this
+ def get_potential_receiver_ap_ids(%{data: %{"type" => "Update", "actor" => actor}} = activity) do
+ with %Object{data: %{"id" => object_id}} <- Object.normalize(activity, fetch: false) do
+ repeaters =
+ Activity.Queries.by_type("Announce")
+ |> Activity.Queries.by_object_id(object_id)
+ |> Activity.with_joined_user_actor()
+ |> where([a, u], u.local)
+ |> select([a, u], u.ap_id)
+ |> Repo.all()
+
+ repeaters -- [actor]
+ end
end
def get_potential_receiver_ap_ids(activity) do
@@ -576,8 +642,10 @@ defmodule Pleroma.Notification do
Enum.uniq(ap_ids) -- thread_muter_ap_ids
end
- @spec skip?(Activity.t(), User.t()) :: boolean()
- def skip?(%Activity{} = activity, %User{} = user) do
+ def skip?(activity, user, opts \\ [])
+
+ @spec skip?(Activity.t(), User.t(), Keyword.t()) :: boolean()
+ def skip?(%Activity{} = activity, %User{} = user, opts) do
[
:self,
:invisible,
@@ -585,17 +653,21 @@ defmodule Pleroma.Notification do
:recently_followed,
:filtered
]
- |> Enum.find(&skip?(&1, activity, user))
+ |> Enum.find(&skip?(&1, activity, user, opts))
end
- def skip?(_, _), do: false
+ def skip?(_activity, _user, _opts), do: false
- @spec skip?(atom(), Activity.t(), User.t()) :: boolean()
- def skip?(:self, %Activity{} = activity, %User{} = user) do
- activity.data["actor"] == user.ap_id
+ @spec skip?(atom(), Activity.t(), User.t(), Keyword.t()) :: boolean()
+ def skip?(:self, %Activity{} = activity, %User{} = user, opts) do
+ cond do
+ opts[:type] == "poll" -> false
+ activity.data["actor"] == user.ap_id -> true
+ true -> false
+ end
end
- def skip?(:invisible, %Activity{} = activity, _) do
+ def skip?(:invisible, %Activity{} = activity, _user, _opts) do
actor = activity.data["actor"]
user = User.get_cached_by_ap_id(actor)
User.invisible?(user)
@@ -604,15 +676,27 @@ defmodule Pleroma.Notification do
def skip?(
:block_from_strangers,
%Activity{} = activity,
- %User{notification_settings: %{block_from_strangers: true}} = user
+ %User{notification_settings: %{block_from_strangers: true}} = user,
+ opts
) do
actor = activity.data["actor"]
follower = User.get_cached_by_ap_id(actor)
- !User.following?(follower, user)
+
+ cond do
+ opts[:type] == "poll" -> false
+ user.ap_id == actor -> false
+ !User.following?(follower, user) -> true
+ true -> false
+ end
end
# To do: consider defining recency in hours and checking FollowingRelationship with a single SQL
- def skip?(:recently_followed, %Activity{data: %{"type" => "Follow"}} = activity, %User{} = user) do
+ def skip?(
+ :recently_followed,
+ %Activity{data: %{"type" => "Follow"}} = activity,
+ %User{} = user,
+ _opts
+ ) do
actor = activity.data["actor"]
Notification.for_user(user)
@@ -622,10 +706,11 @@ defmodule Pleroma.Notification do
end)
end
- def skip?(:filtered, %{data: %{"type" => type}}, _) when type in ["Follow", "Move"], do: false
+ def skip?(:filtered, %{data: %{"type" => type}}, _user, _opts) when type in ["Follow", "Move"],
+ do: false
- def skip?(:filtered, activity, user) do
- object = Object.normalize(activity)
+ def skip?(:filtered, activity, user, _opts) do
+ object = Object.normalize(activity, fetch: false)
cond do
is_nil(object) ->
@@ -642,7 +727,7 @@ defmodule Pleroma.Notification do
end
end
- def skip?(_, _, _), do: false
+ def skip?(_type, _activity, _user, _opts), do: false
def mark_as_read?(activity, target_user) do
user = Activity.user_actor(activity)
diff --git a/lib/pleroma/object.ex b/lib/pleroma/object.ex
index b4a994da9..38accae5d 100644
--- a/lib/pleroma/object.ex
+++ b/lib/pleroma/object.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Object do
@@ -10,6 +10,7 @@ defmodule Pleroma.Object do
alias Pleroma.Activity
alias Pleroma.Config
+ alias Pleroma.Hashtag
alias Pleroma.Object
alias Pleroma.Object.Fetcher
alias Pleroma.ObjectTombstone
@@ -28,6 +29,8 @@ defmodule Pleroma.Object do
schema "objects" do
field(:data, :map)
+ many_to_many(:hashtags, Hashtag, join_through: "hashtags_objects", on_replace: :delete)
+
timestamps()
end
@@ -37,8 +40,7 @@ defmodule Pleroma.Object do
join(query, join_type, [{object, object_position}], a in Activity,
on:
fragment(
- "COALESCE(?->'object'->>'id', ?->>'object') = (? ->> 'id') AND (?->>'type' = ?) ",
- a.data,
+ "associated_object_id(?) = (? ->> 'id') AND (?->>'type' = ?) ",
a.data,
object.data,
a.data,
@@ -49,7 +51,8 @@ defmodule Pleroma.Object do
end
def create(data) do
- Object.change(%Object{}, %{data: data})
+ %Object{}
+ |> Object.change(%{data: data})
|> Repo.insert()
end
@@ -58,8 +61,41 @@ defmodule Pleroma.Object do
|> cast(params, [:data])
|> validate_required([:data])
|> unique_constraint(:ap_id, name: :objects_unique_apid_index)
+ # Expecting `maybe_handle_hashtags_change/1` to run last:
+ |> maybe_handle_hashtags_change(struct)
end
+ # Note: not checking activity type (assuming non-legacy objects are associated with Create act.)
+ defp maybe_handle_hashtags_change(changeset, struct) do
+ with %Ecto.Changeset{valid?: true} <- changeset,
+ data_hashtags_change = get_change(changeset, :data),
+ {_, true} <- {:changed, hashtags_changed?(struct, data_hashtags_change)},
+ {:ok, hashtag_records} <-
+ data_hashtags_change
+ |> object_data_hashtags()
+ |> Hashtag.get_or_create_by_names() do
+ put_assoc(changeset, :hashtags, hashtag_records)
+ else
+ %{valid?: false} ->
+ changeset
+
+ {:changed, false} ->
+ changeset
+
+ {:error, _} ->
+ validate_change(changeset, :data, fn _, _ ->
+ [data: "error referencing hashtags"]
+ end)
+ end
+ end
+
+ defp hashtags_changed?(%Object{} = struct, %{"tag" => _} = data) do
+ Enum.sort(embedded_hashtags(struct)) !=
+ Enum.sort(object_data_hashtags(data))
+ end
+
+ defp hashtags_changed?(_, _), do: false
+
def get_by_id(nil), do: nil
def get_by_id(id), do: Repo.get(Object, id)
@@ -108,39 +144,47 @@ defmodule Pleroma.Object do
Logger.debug("Backtrace: #{inspect(Process.info(:erlang.self(), :current_stacktrace))}")
end
- def normalize(_, fetch_remote \\ true, options \\ [])
+ def normalize(_, options \\ [fetch: false, id_only: false])
# If we pass an Activity to Object.normalize(), we can try to use the preloaded object.
# Use this whenever possible, especially when walking graphs in an O(N) loop!
- def normalize(%Object{} = object, _, _), do: object
- def normalize(%Activity{object: %Object{} = object}, _, _), do: object
+ def normalize(%Object{} = object, _), do: object
+ def normalize(%Activity{object: %Object{} = object}, _), do: object
# A hack for fake activities
- def normalize(%Activity{data: %{"object" => %{"fake" => true} = data}}, _, _) do
+ def normalize(%Activity{data: %{"object" => %{"fake" => true} = data}}, _) do
%Object{id: "pleroma:fake_object_id", data: data}
end
# No preloaded object
- def normalize(%Activity{data: %{"object" => %{"id" => ap_id}}}, fetch_remote, _) do
+ def normalize(%Activity{data: %{"object" => %{"id" => ap_id}}}, options) do
warn_on_no_object_preloaded(ap_id)
- normalize(ap_id, fetch_remote)
+ normalize(ap_id, options)
end
# No preloaded object
- def normalize(%Activity{data: %{"object" => ap_id}}, fetch_remote, _) do
+ def normalize(%Activity{data: %{"object" => ap_id}}, options) do
warn_on_no_object_preloaded(ap_id)
- normalize(ap_id, fetch_remote)
+ normalize(ap_id, options)
end
# Old way, try fetching the object through cache.
- def normalize(%{"id" => ap_id}, fetch_remote, _), do: normalize(ap_id, fetch_remote)
- def normalize(ap_id, false, _) when is_binary(ap_id), do: get_cached_by_ap_id(ap_id)
+ def normalize(%{"id" => ap_id}, options), do: normalize(ap_id, options)
- def normalize(ap_id, true, options) when is_binary(ap_id) do
- Fetcher.fetch_object_from_id!(ap_id, options)
+ def normalize(ap_id, options) when is_binary(ap_id) do
+ cond do
+ Keyword.get(options, :id_only) ->
+ ap_id
+
+ Keyword.get(options, :fetch) ->
+ Fetcher.fetch_object_from_id!(ap_id, options)
+
+ true ->
+ get_cached_by_ap_id(ap_id)
+ end
end
- def normalize(_, _, _), do: nil
+ def normalize(_, _), do: nil
# Owned objects can only be accessed by their owner
def authorize_access(%Object{data: %{"actor" => actor}}, %User{ap_id: ap_id}) do
@@ -168,10 +212,6 @@ defmodule Pleroma.Object do
end
end
- def context_mapping(context) do
- Object.change(%Object{}, %{data: %{"id" => context}})
- end
-
def make_tombstone(%Object{data: %{"id" => id, "type" => type}}, deleted \\ DateTime.utc_now()) do
%ObjectTombstone{
id: id,
@@ -184,9 +224,13 @@ defmodule Pleroma.Object do
def swap_object_with_tombstone(object) do
tombstone = make_tombstone(object)
- object
- |> Object.change(%{data: tombstone})
- |> Repo.update()
+ with {:ok, object} <-
+ object
+ |> Object.change(%{data: tombstone})
+ |> Repo.update() do
+ Hashtag.unlink(object)
+ {:ok, object}
+ end
end
def delete(%Object{data: %{"id" => id}} = object) do
@@ -285,7 +329,7 @@ defmodule Pleroma.Object do
end
def increase_vote_count(ap_id, name, actor) do
- with %Object{} = object <- Object.normalize(ap_id),
+ with %Object{} = object <- Object.normalize(ap_id, fetch: false),
"Question" <- object.data["type"] do
key = if poll_is_multiple?(object), do: "anyOf", else: "oneOf"
@@ -322,11 +366,11 @@ 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
- object = Object.normalize(object)
+ object = Object.normalize(object, fetch: false)
query =
Object
@@ -346,4 +390,39 @@ defmodule Pleroma.Object do
def self_replies(object, opts \\ []),
do: replies(object, Keyword.put(opts, :self_only, true))
+
+ def tags(%Object{data: %{"tag" => tags}}) when is_list(tags), do: tags
+
+ def tags(_), do: []
+
+ def hashtags(%Object{} = object) do
+ # Note: always using embedded hashtags regardless whether they are migrated to hashtags table
+ # (embedded hashtags stay in sync anyways, and we avoid extra joins and preload hassle)
+ embedded_hashtags(object)
+ end
+
+ def embedded_hashtags(%Object{data: data}) do
+ object_data_hashtags(data)
+ end
+
+ def embedded_hashtags(_), do: []
+
+ def object_data_hashtags(%{"tag" => tags}) when is_list(tags) do
+ tags
+ |> Enum.filter(fn
+ %{"type" => "Hashtag"} = data -> Map.has_key?(data, "name")
+ plain_text when is_bitstring(plain_text) -> true
+ _ -> false
+ end)
+ |> Enum.map(fn
+ %{"name" => "#" <> hashtag} -> String.downcase(hashtag)
+ %{"name" => hashtag} -> String.downcase(hashtag)
+ hashtag when is_bitstring(hashtag) -> String.downcase(hashtag)
+ end)
+ |> Enum.uniq()
+ # Note: "" elements (plain text) might occur in `data.tag` for incoming objects
+ |> Enum.filter(&(&1 not in [nil, ""]))
+ end
+
+ def object_data_hashtags(_), do: []
end
diff --git a/lib/pleroma/object/containment.ex b/lib/pleroma/object/containment.ex
index 29cb3d672..f6106cb3f 100644
--- a/lib/pleroma/object/containment.ex
+++ b/lib/pleroma/object/containment.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Object.Containment do
@@ -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}),
diff --git a/lib/pleroma/object/fetcher.ex b/lib/pleroma/object/fetcher.ex
index 20d8f687d..a9a9eeeed 100644
--- a/lib/pleroma/object/fetcher.ex
+++ b/lib/pleroma/object/fetcher.ex
@@ -1,9 +1,11 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Object.Fetcher do
alias Pleroma.HTTP
+ alias Pleroma.Instances
+ alias Pleroma.Maps
alias Pleroma.Object
alias Pleroma.Object.Containment
alias Pleroma.Repo
@@ -25,8 +27,42 @@ defmodule Pleroma.Object.Fetcher do
end
defp maybe_reinject_internal_fields(%{data: %{} = old_data}, new_data) do
+ has_history? = fn
+ %{"formerRepresentations" => %{"orderedItems" => list}} when is_list(list) -> true
+ _ -> false
+ end
+
internal_fields = Map.take(old_data, Pleroma.Constants.object_internal_fields())
+ remote_history_exists? = has_history?.(new_data)
+
+ # If the remote history exists, we treat that as the only source of truth.
+ new_data =
+ if has_history?.(old_data) and not remote_history_exists? do
+ Map.put(new_data, "formerRepresentations", old_data["formerRepresentations"])
+ else
+ new_data
+ end
+
+ # If the remote does not have history information, we need to manage it ourselves
+ new_data =
+ if not remote_history_exists? do
+ changed? =
+ Pleroma.Constants.status_updatable_fields()
+ |> Enum.any?(fn field -> Map.get(old_data, field) != Map.get(new_data, field) end)
+
+ %{updated_object: updated_object} =
+ new_data
+ |> Object.Updater.maybe_update_history(old_data,
+ updated: changed?,
+ use_history_in_new_object?: false
+ )
+
+ updated_object
+ else
+ new_data
+ end
+
Map.merge(new_data, internal_fields)
end
@@ -83,13 +119,13 @@ defmodule Pleroma.Object.Fetcher do
with {_, nil} <- {:fetch_object, Object.get_cached_by_ap_id(id)},
{_, true} <- {:allowed_depth, Federator.allowed_thread_distance?(options[:depth])},
{_, {:ok, data}} <- {:fetch, fetch_and_contain_remote_object_from_id(id)},
- {_, nil} <- {:normalize, Object.normalize(data, false)},
+ {_, nil} <- {:normalize, Object.normalize(data, fetch: false)},
params <- prepare_activity_params(data),
{_, :ok} <- {:containment, Containment.contain_origin(id, params)},
{_, {:ok, activity}} <-
{:transmogrifier, Transmogrifier.handle_incoming(params, options)},
{_, _data, %Object{} = object} <-
- {:object, data, Object.normalize(activity, false)} do
+ {:object, data, Object.normalize(activity, fetch: false)} do
{:ok, object}
else
{:allowed_depth, false} ->
@@ -101,6 +137,9 @@ defmodule Pleroma.Object.Fetcher do
{:transmogrifier, {:error, {:reject, e}}} ->
{:reject, e}
+ {:transmogrifier, {:reject, e}} ->
+ {:reject, e}
+
{:transmogrifier, _} = e ->
{:error, e}
@@ -124,12 +163,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
@@ -194,6 +235,10 @@ defmodule Pleroma.Object.Fetcher do
{:ok, body} <- get_object(id),
{:ok, data} <- safe_json_decode(body),
:ok <- Containment.contain_origin_from_id(id, data) do
+ if not Instances.reachable?(id) do
+ Instances.set_reachable(id)
+ end
+
{:ok, data}
else
{:scheme, _} ->
diff --git a/lib/pleroma/object/updater.ex b/lib/pleroma/object/updater.ex
new file mode 100644
index 000000000..ab38d3ed2
--- /dev/null
+++ b/lib/pleroma/object/updater.ex
@@ -0,0 +1,240 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Object.Updater do
+ require Pleroma.Constants
+
+ def update_content_fields(orig_object_data, updated_object) do
+ Pleroma.Constants.status_updatable_fields()
+ |> Enum.reduce(
+ %{data: orig_object_data, updated: false},
+ fn field, %{data: data, updated: updated} ->
+ updated =
+ updated or
+ (field != "updated" and
+ Map.get(updated_object, field) != Map.get(orig_object_data, field))
+
+ data =
+ if Map.has_key?(updated_object, field) do
+ Map.put(data, field, updated_object[field])
+ else
+ Map.drop(data, [field])
+ end
+
+ %{data: data, updated: updated}
+ end
+ )
+ end
+
+ def maybe_history(object) do
+ with history <- Map.get(object, "formerRepresentations"),
+ true <- is_map(history),
+ "OrderedCollection" <- Map.get(history, "type"),
+ true <- is_list(Map.get(history, "orderedItems")),
+ true <- is_integer(Map.get(history, "totalItems")) do
+ history
+ else
+ _ -> nil
+ end
+ end
+
+ def history_for(object) do
+ with history when not is_nil(history) <- maybe_history(object) do
+ history
+ else
+ _ -> history_skeleton()
+ end
+ end
+
+ defp history_skeleton do
+ %{
+ "type" => "OrderedCollection",
+ "totalItems" => 0,
+ "orderedItems" => []
+ }
+ end
+
+ def maybe_update_history(
+ updated_object,
+ orig_object_data,
+ opts
+ ) do
+ updated = opts[:updated]
+ use_history_in_new_object? = opts[:use_history_in_new_object?]
+
+ if not updated do
+ %{updated_object: updated_object, used_history_in_new_object?: false}
+ else
+ # Put edit history
+ # Note that we may have got the edit history by first fetching the object
+ {new_history, used_history_in_new_object?} =
+ with true <- use_history_in_new_object?,
+ updated_history when not is_nil(updated_history) <- maybe_history(opts[:new_data]) do
+ {updated_history, true}
+ else
+ _ ->
+ history = history_for(orig_object_data)
+
+ latest_history_item =
+ orig_object_data
+ |> Map.drop(["id", "formerRepresentations"])
+
+ updated_history =
+ history
+ |> Map.put("orderedItems", [latest_history_item | history["orderedItems"]])
+ |> Map.put("totalItems", history["totalItems"] + 1)
+
+ {updated_history, false}
+ end
+
+ updated_object =
+ updated_object
+ |> Map.put("formerRepresentations", new_history)
+
+ %{updated_object: updated_object, used_history_in_new_object?: used_history_in_new_object?}
+ end
+ end
+
+ defp maybe_update_poll(to_be_updated, updated_object) do
+ choice_key = fn data ->
+ if Map.has_key?(data, "anyOf"), do: "anyOf", else: "oneOf"
+ end
+
+ with true <- to_be_updated["type"] == "Question",
+ key <- choice_key.(updated_object),
+ true <- key == choice_key.(to_be_updated),
+ orig_choices <- to_be_updated[key] |> Enum.map(&Map.drop(&1, ["replies"])),
+ new_choices <- updated_object[key] |> Enum.map(&Map.drop(&1, ["replies"])),
+ true <- orig_choices == new_choices do
+ # Choices are the same, but counts are different
+ to_be_updated
+ |> Map.put(key, updated_object[key])
+ else
+ # Choices (or vote type) have changed, do not allow this
+ _ -> to_be_updated
+ end
+ end
+
+ # This calculates the data to be sent as the object of an Update.
+ # new_data's formerRepresentations is not considered.
+ # formerRepresentations is added to the returned data.
+ def make_update_object_data(original_data, new_data, date) do
+ %{data: updated_data, updated: updated} =
+ original_data
+ |> update_content_fields(new_data)
+
+ if not updated do
+ updated_data
+ else
+ %{updated_object: updated_data} =
+ updated_data
+ |> maybe_update_history(original_data, updated: updated, use_history_in_new_object?: false)
+
+ updated_data
+ |> Map.put("updated", date)
+ end
+ end
+
+ # This calculates the data of the new Object from an Update.
+ # new_data's formerRepresentations is considered.
+ def make_new_object_data_from_update_object(original_data, new_data) do
+ update_is_reasonable =
+ with {_, updated} when not is_nil(updated) <- {:cur_updated, new_data["updated"]},
+ {_, {:ok, updated_time, _}} <- {:cur_updated, DateTime.from_iso8601(updated)},
+ {_, last_updated} when not is_nil(last_updated) <-
+ {:last_updated, original_data["updated"] || original_data["published"]},
+ {_, {:ok, last_updated_time, _}} <-
+ {:last_updated, DateTime.from_iso8601(last_updated)},
+ :gt <- DateTime.compare(updated_time, last_updated_time) do
+ :update_everything
+ else
+ # only allow poll updates
+ {:cur_updated, _} -> :no_content_update
+ :eq -> :no_content_update
+ # allow all updates
+ {:last_updated, _} -> :update_everything
+ # allow no updates
+ _ -> false
+ end
+
+ %{
+ updated_object: updated_data,
+ used_history_in_new_object?: used_history_in_new_object?,
+ updated: updated
+ } =
+ if update_is_reasonable == :update_everything do
+ %{data: updated_data, updated: updated} =
+ original_data
+ |> update_content_fields(new_data)
+
+ updated_data
+ |> maybe_update_history(original_data,
+ updated: updated,
+ use_history_in_new_object?: true,
+ new_data: new_data
+ )
+ |> Map.put(:updated, updated)
+ else
+ %{
+ updated_object: original_data,
+ used_history_in_new_object?: false,
+ updated: false
+ }
+ end
+
+ updated_data =
+ if update_is_reasonable != false do
+ updated_data
+ |> maybe_update_poll(new_data)
+ else
+ updated_data
+ end
+
+ %{
+ updated_data: updated_data,
+ updated: updated,
+ used_history_in_new_object?: used_history_in_new_object?
+ }
+ end
+
+ def for_each_history_item(%{"orderedItems" => items} = history, _object, fun) do
+ new_items =
+ Enum.map(items, fun)
+ |> Enum.reduce_while(
+ {:ok, []},
+ fn
+ {:ok, item}, {:ok, acc} -> {:cont, {:ok, acc ++ [item]}}
+ e, _acc -> {:halt, e}
+ end
+ )
+
+ case new_items do
+ {:ok, items} -> {:ok, Map.put(history, "orderedItems", items)}
+ e -> e
+ end
+ end
+
+ def for_each_history_item(history, _, _) do
+ {:ok, history}
+ end
+
+ def do_with_history(object, fun) do
+ with history <- object["formerRepresentations"],
+ object <- Map.drop(object, ["formerRepresentations"]),
+ {_, {:ok, object}} <- {:main_body, fun.(object)},
+ {_, {:ok, history}} <- {:history_items, for_each_history_item(history, object, fun)} do
+ object =
+ if history do
+ Map.put(object, "formerRepresentations", history)
+ else
+ object
+ end
+
+ {:ok, object}
+ else
+ {:main_body, e} -> e
+ {:history_items, e} -> e
+ end
+ end
+end
diff --git a/lib/pleroma/object_tombstone.ex b/lib/pleroma/object_tombstone.ex
index e26f44057..8bdc8f661 100644
--- a/lib/pleroma/object_tombstone.ex
+++ b/lib/pleroma/object_tombstone.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.ObjectTombstone do
diff --git a/lib/pleroma/otp_version.ex b/lib/pleroma/otp_version.ex
index 114d0054f..80b15275a 100644
--- a/lib/pleroma/otp_version.ex
+++ b/lib/pleroma/otp_version.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.OTPVersion do
diff --git a/lib/pleroma/pagination.ex b/lib/pleroma/pagination.ex
index 9a3795769..f12ca2819 100644
--- a/lib/pleroma/pagination.ex
+++ b/lib/pleroma/pagination.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Pagination do
@@ -93,6 +93,7 @@ defmodule Pleroma.Pagination do
max_id: :string,
offset: :integer,
limit: :integer,
+ skip_extra_order: :boolean,
skip_order: :boolean
}
@@ -114,6 +115,8 @@ defmodule Pleroma.Pagination do
defp restrict(query, :order, %{skip_order: true}, _), do: query
+ defp restrict(%{order_bys: [_ | _]} = query, :order, %{skip_extra_order: true}, _), do: query
+
defp restrict(query, :order, %{min_id: _}, table_binding) do
order_by(
query,
diff --git a/lib/pleroma/password/pbkdf2.ex b/lib/pleroma/password/pbkdf2.ex
new file mode 100644
index 000000000..92e9e1952
--- /dev/null
+++ b/lib/pleroma/password/pbkdf2.ex
@@ -0,0 +1,55 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Password.Pbkdf2 do
+ @moduledoc """
+ This module implements Pbkdf2 passwords in terms of Plug.Crypto.
+ """
+
+ alias Plug.Crypto.KeyGenerator
+
+ def decode64(str) do
+ str
+ |> String.replace(".", "+")
+ |> Base.decode64!(padding: false)
+ end
+
+ def encode64(bin) do
+ bin
+ |> Base.encode64(padding: false)
+ |> String.replace("+", ".")
+ end
+
+ def verify_pass(password, hash) do
+ ["pbkdf2-" <> digest, iterations, salt, hash] = String.split(hash, "$", trim: true)
+
+ salt = decode64(salt)
+
+ iterations = String.to_integer(iterations)
+
+ digest = String.to_atom(digest)
+
+ binary_hash =
+ KeyGenerator.generate(password, salt, digest: digest, iterations: iterations, length: 64)
+
+ encode64(binary_hash) == hash
+ end
+
+ def hash_pwd_salt(password, opts \\ []) do
+ salt =
+ Keyword.get_lazy(opts, :salt, fn ->
+ :crypto.strong_rand_bytes(16)
+ end)
+
+ digest = Keyword.get(opts, :digest, :sha512)
+
+ iterations =
+ Keyword.get(opts, :iterations, Pleroma.Config.get([:password, :iterations], 160_000))
+
+ binary_hash =
+ KeyGenerator.generate(password, salt, digest: digest, iterations: iterations, length: 64)
+
+ "$pbkdf2-#{digest}$#{iterations}$#{encode64(salt)}$#{encode64(binary_hash)}"
+ end
+end
diff --git a/lib/pleroma/password_reset_token.ex b/lib/pleroma/password_reset_token.ex
index fea5b1c22..42a789ea2 100644
--- a/lib/pleroma/password_reset_token.ex
+++ b/lib/pleroma/password_reset_token.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.PasswordResetToken do
diff --git a/lib/pleroma/registration.ex b/lib/pleroma/registration.ex
index 9163040b4..b043c37ac 100644
--- a/lib/pleroma/registration.ex
+++ b/lib/pleroma/registration.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Registration do
diff --git a/lib/pleroma/release_tasks.ex b/lib/pleroma/release_tasks.ex
index 02dd6c325..f9e8d1948 100644
--- a/lib/pleroma/release_tasks.ex
+++ b/lib/pleroma/release_tasks.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.ReleaseTasks do
diff --git a/lib/pleroma/repo.ex b/lib/pleroma/repo.ex
index 4524bd5e2..515b0c1ff 100644
--- a/lib/pleroma/repo.ex
+++ b/lib/pleroma/repo.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Repo do
@@ -63,8 +63,8 @@ defmodule Pleroma.Repo do
iex> Pleroma.Repo.chunk_stream(Pleroma.Activity.Queries.by_actor(ap_id), 500, :batches)
"""
@spec chunk_stream(Ecto.Query.t(), integer(), atom()) :: Enumerable.t()
- def chunk_stream(query, chunk_size, returns_as \\ :one) do
- # We don't actually need start and end funcitons of resource streaming,
+ def chunk_stream(query, chunk_size, returns_as \\ :one, query_options \\ []) do
+ # We don't actually need start and end functions of resource streaming,
# but it seems to be the only way to not fetch records one-by-one and
# have individual records be the elements of the stream, instead of
# lists of records
@@ -76,7 +76,7 @@ defmodule Pleroma.Repo do
|> order_by(asc: :id)
|> where([r], r.id > ^last_id)
|> limit(^chunk_size)
- |> all()
+ |> all(query_options)
|> case do
[] ->
{:halt, last_id}
diff --git a/lib/pleroma/report_note.ex b/lib/pleroma/report_note.ex
index a239bd361..f2ad76fa8 100644
--- a/lib/pleroma/report_note.ex
+++ b/lib/pleroma/report_note.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.ReportNote do
diff --git a/lib/pleroma/reverse_proxy.ex b/lib/pleroma/reverse_proxy.ex
index 3ea897c95..2248c2713 100644
--- a/lib/pleroma/reverse_proxy.ex
+++ b/lib/pleroma/reverse_proxy.ex
@@ -1,10 +1,10 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.ReverseProxy do
@range_headers ~w(range if-range)
- @keep_req_headers ~w(accept user-agent accept-encoding cache-control if-modified-since) ++
+ @keep_req_headers ~w(accept accept-encoding cache-control if-modified-since) ++
~w(if-unmodified-since if-none-match) ++ @range_headers
@resp_cache_headers ~w(etag date last-modified)
@keep_resp_headers @resp_cache_headers ++
@@ -57,9 +57,6 @@ defmodule Pleroma.ReverseProxy do
* `false` will add `content-disposition: attachment` to any request,
* a list of whitelisted content types
- * `keep_user_agent` will forward the client's user-agent to the upstream. This may be useful if the upstream is
- doing content transformation (encoding, …) depending on the request.
-
* `req_headers`, `resp_headers` additional headers.
* `http`: options for [hackney](https://github.com/benoitc/hackney) or [gun](https://github.com/ninenines/gun).
@@ -84,8 +81,7 @@ defmodule Pleroma.ReverseProxy do
import Plug.Conn
@type option() ::
- {:keep_user_agent, boolean}
- | {:max_read_duration, :timer.time() | :infinity}
+ {:max_read_duration, :timer.time() | :infinity}
| {:max_body_length, non_neg_integer() | :infinity}
| {:failed_request_ttl, :timer.time() | :infinity}
| {:http, []}
@@ -291,17 +287,13 @@ defmodule Pleroma.ReverseProxy do
end
end
- defp build_req_user_agent_header(headers, opts) do
- if Keyword.get(opts, :keep_user_agent, false) do
- List.keystore(
- headers,
- "user-agent",
- 0,
- {"user-agent", Pleroma.Application.user_agent()}
- )
- else
- headers
- end
+ defp build_req_user_agent_header(headers, _opts) do
+ List.keystore(
+ headers,
+ "user-agent",
+ 0,
+ {"user-agent", Pleroma.Application.user_agent()}
+ )
end
defp build_resp_headers(headers, opts) do
@@ -419,7 +411,7 @@ defmodule Pleroma.ReverseProxy do
{:ok, :no_duration_limit, :no_duration_limit}
end
- defp client, do: Pleroma.ReverseProxy.Client
+ defp client, do: Pleroma.ReverseProxy.Client.Wrapper
defp track_failed_url(url, error, opts) do
ttl =
diff --git a/lib/pleroma/reverse_proxy/client.ex b/lib/pleroma/reverse_proxy/client.ex
index 0d13ff174..91f6e5a95 100644
--- a/lib/pleroma/reverse_proxy/client.ex
+++ b/lib/pleroma/reverse_proxy/client.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.ReverseProxy.Client do
@@ -17,22 +17,4 @@ defmodule Pleroma.ReverseProxy.Client do
@callback stream_body(map()) :: {:ok, binary(), map()} | :done | {:error, atom() | String.t()}
@callback close(reference() | pid() | map()) :: :ok
-
- def request(method, url, headers, body \\ "", opts \\ []) do
- client().request(method, url, headers, body, opts)
- end
-
- def stream_body(ref), do: client().stream_body(ref)
-
- def close(ref), do: client().close(ref)
-
- defp client do
- :tesla
- |> Application.get_env(:adapter)
- |> client()
- end
-
- defp client(Tesla.Adapter.Hackney), do: Pleroma.ReverseProxy.Client.Hackney
- defp client(Tesla.Adapter.Gun), do: Pleroma.ReverseProxy.Client.Tesla
- defp client(_), do: Pleroma.Config.get!(Pleroma.ReverseProxy.Client)
end
diff --git a/lib/pleroma/reverse_proxy/client/hackney.ex b/lib/pleroma/reverse_proxy/client/hackney.ex
index ad988fac3..d3e986912 100644
--- a/lib/pleroma/reverse_proxy/client/hackney.ex
+++ b/lib/pleroma/reverse_proxy/client/hackney.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.ReverseProxy.Client.Hackney do
@@ -7,7 +7,6 @@ defmodule Pleroma.ReverseProxy.Client.Hackney do
@impl true
def request(method, url, headers, body, opts \\ []) do
- opts = Keyword.put(opts, :ssl_options, versions: [:"tlsv1.2", :"tlsv1.1", :tlsv1])
:hackney.request(method, url, headers, body, opts)
end
diff --git a/lib/pleroma/reverse_proxy/client/tesla.ex b/lib/pleroma/reverse_proxy/client/tesla.ex
index 4b118eec2..4596d7a7f 100644
--- a/lib/pleroma/reverse_proxy/client/tesla.ex
+++ b/lib/pleroma/reverse_proxy/client/tesla.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.ReverseProxy.Client.Tesla do
diff --git a/lib/pleroma/reverse_proxy/client/wrapper.ex b/lib/pleroma/reverse_proxy/client/wrapper.ex
new file mode 100644
index 000000000..1ce476927
--- /dev/null
+++ b/lib/pleroma/reverse_proxy/client/wrapper.ex
@@ -0,0 +1,30 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.ReverseProxy.Client.Wrapper do
+ @moduledoc "Meta-client that calls the appropriate client from the config."
+ @behaviour Pleroma.ReverseProxy.Client
+
+ @impl true
+ def request(method, url, headers, body \\ "", opts \\ []) do
+ client().request(method, url, headers, body, opts)
+ end
+
+ @impl true
+ def stream_body(ref), do: client().stream_body(ref)
+
+ @impl true
+ def close(ref), do: client().close(ref)
+
+ defp client do
+ :tesla
+ |> Application.get_env(:adapter)
+ |> client()
+ end
+
+ defp client(Tesla.Adapter.Hackney), do: Pleroma.ReverseProxy.Client.Hackney
+ defp client(Tesla.Adapter.Gun), do: Pleroma.ReverseProxy.Client.Tesla
+ defp client({Tesla.Adapter.Finch, _}), do: Pleroma.ReverseProxy.Client.Hackney
+ defp client(_), do: Pleroma.Config.get!(Pleroma.ReverseProxy.Client)
+end
diff --git a/lib/pleroma/scheduled_activity.ex b/lib/pleroma/scheduled_activity.ex
index 0937cb7db..a7be58512 100644
--- a/lib/pleroma/scheduled_activity.ex
+++ b/lib/pleroma/scheduled_activity.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.ScheduledActivity do
diff --git a/lib/pleroma/signature.ex b/lib/pleroma/signature.ex
index 3aa6909d2..5cfdae051 100644
--- a/lib/pleroma/signature.ex
+++ b/lib/pleroma/signature.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Signature do
@@ -10,17 +10,14 @@ defmodule Pleroma.Signature do
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
+ @known_suffixes ["/publickey", "/main-key"]
+
def key_id_to_actor_id(key_id) do
uri =
- URI.parse(key_id)
+ key_id
+ |> URI.parse()
|> Map.put(:fragment, nil)
-
- uri =
- if not is_nil(uri.path) and String.ends_with?(uri.path, "/publickey") do
- Map.put(uri, :path, String.replace(uri.path, "/publickey", ""))
- else
- uri
- end
+ |> remove_suffix(@known_suffixes)
maybe_ap_id = URI.to_string(uri)
@@ -36,6 +33,16 @@ defmodule Pleroma.Signature do
end
end
+ defp remove_suffix(uri, [test | rest]) do
+ if not is_nil(uri.path) and String.ends_with?(uri.path, test) do
+ Map.put(uri, :path, String.replace(uri.path, test, ""))
+ else
+ remove_suffix(uri, rest)
+ end
+ end
+
+ defp remove_suffix(uri, []), do: uri
+
def fetch_public_key(conn) do
with %{"keyId" => kid} <- HTTPSignatures.signature_for_conn(conn),
{:ok, actor_id} <- key_id_to_actor_id(kid),
@@ -59,9 +66,8 @@ defmodule Pleroma.Signature do
end
end
- def sign(%User{} = user, headers) do
- with {:ok, %{keys: keys}} <- User.ensure_keys_present(user),
- {:ok, private_key, _} <- Keys.keys_from_pem(keys) do
+ def sign(%User{keys: keys} = user, headers) do
+ with {:ok, private_key, _} <- Keys.keys_from_pem(keys) do
HTTPSignatures.sign(private_key, user.ap_id <> "#main-key", headers)
end
end
diff --git a/lib/pleroma/stats.ex b/lib/pleroma/stats.ex
index 48afe901e..47b30b951 100644
--- a/lib/pleroma/stats.ex
+++ b/lib/pleroma/stats.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Stats do
@@ -23,7 +23,11 @@ defmodule Pleroma.Stats do
@impl true
def init(_args) do
- {:ok, nil, {:continue, :calculate_stats}}
+ if Pleroma.Config.get(:env) != :test do
+ {:ok, nil, {:continue, :calculate_stats}}
+ else
+ {:ok, calculate_stat_data()}
+ end
end
@doc "Performs update stats"
@@ -75,7 +79,7 @@ defmodule Pleroma.Stats do
users_query =
from(u in User,
- where: u.deactivated != true,
+ where: u.is_active == true,
where: u.local == true,
where: not is_nil(u.nickname),
where: not u.invisible
diff --git a/lib/pleroma/telemetry/logger.ex b/lib/pleroma/telemetry/logger.ex
index 003079cf3..384c70fbc 100644
--- a/lib/pleroma/telemetry/logger.ex
+++ b/lib/pleroma/telemetry/logger.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Telemetry.Logger do
@@ -29,9 +29,7 @@ defmodule Pleroma.Telemetry.Logger do
_
) do
Logger.debug(fn ->
- "Connection pool is exhausted (reached #{max_connections} connections). Starting idle connection cleanup to reclaim as much as #{
- reclaim_max
- } connections"
+ "Connection pool is exhausted (reached #{max_connections} connections). Starting idle connection cleanup to reclaim as much as #{reclaim_max} connections"
end)
end
@@ -73,9 +71,7 @@ defmodule Pleroma.Telemetry.Logger do
_
) do
Logger.warn(fn ->
- "Pool worker for #{key}: Client #{inspect(client_pid)} died before releasing the connection with #{
- inspect(reason)
- }"
+ "Pool worker for #{key}: Client #{inspect(client_pid)} died before releasing the connection with #{inspect(reason)}"
end)
end
diff --git a/lib/pleroma/tesla/middleware/connection_pool.ex b/lib/pleroma/tesla/middleware/connection_pool.ex
index 056e736ce..de74270f8 100644
--- a/lib/pleroma/tesla/middleware/connection_pool.ex
+++ b/lib/pleroma/tesla/middleware/connection_pool.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Tesla.Middleware.ConnectionPool do
diff --git a/lib/pleroma/tests/auth_test_controller.ex b/lib/pleroma/tests/auth_test_controller.ex
index b30d83567..d244badf4 100644
--- a/lib/pleroma/tests/auth_test_controller.ex
+++ b/lib/pleroma/tests/auth_test_controller.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
# A test controller reachable only in :test env.
@@ -9,7 +9,6 @@ defmodule Pleroma.Tests.AuthTestController do
use Pleroma.Web, :controller
alias Pleroma.User
- alias Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug
alias Pleroma.Web.Plugs.OAuthScopesPlug
# Serves only with proper OAuth token (:api and :authenticated_api)
@@ -47,10 +46,7 @@ defmodule Pleroma.Tests.AuthTestController do
# Via :authenticated_api, serves if token is present and has requested scopes
#
# Suggested use: as :fallback_oauth_check but open with nil :user for :api on private instances
- plug(
- :skip_plug,
- EnsurePublicOrAuthenticatedPlug when action == :fallback_oauth_skip_publicity_check
- )
+ plug(:skip_public_check when action == :fallback_oauth_skip_publicity_check)
plug(
OAuthScopesPlug,
@@ -62,11 +58,7 @@ defmodule Pleroma.Tests.AuthTestController do
# Via :authenticated_api, serves if :user is set (regardless of token presence and its scopes)
#
# Suggested use: making an :api endpoint always accessible (e.g. email confirmation endpoint)
- plug(
- :skip_plug,
- [OAuthScopesPlug, EnsurePublicOrAuthenticatedPlug]
- when action == :skip_oauth_skip_publicity_check
- )
+ plug(:skip_auth when action == :skip_oauth_skip_publicity_check)
# Via :authenticated_api, always fails with 403 (endpoint is insecure)
# Via :api, drops :user if present and serves if public (private instance rejects on no user)
diff --git a/lib/pleroma/thread_mute.ex b/lib/pleroma/thread_mute.ex
index be01d541d..8ea4181bd 100644
--- a/lib/pleroma/thread_mute.ex
+++ b/lib/pleroma/thread_mute.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.ThreadMute do
diff --git a/lib/pleroma/upload.ex b/lib/pleroma/upload.ex
index db2cc1dae..4aee9326f 100644
--- a/lib/pleroma/upload.ex
+++ b/lib/pleroma/upload.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Upload do
@@ -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:
@@ -31,6 +34,9 @@ defmodule Pleroma.Upload do
"""
alias Ecto.UUID
+ alias Pleroma.Config
+ alias Pleroma.Maps
+ alias Pleroma.Web.ActivityPub.Utils
require Logger
@type source ::
@@ -52,12 +58,26 @@ defmodule Pleroma.Upload do
name: String.t(),
tempfile: String.t(),
content_type: String.t(),
+ width: integer(),
+ height: integer(),
+ blurhash: String.t(),
+ description: String.t(),
path: String.t()
}
- defstruct [:id, :name, :tempfile, :content_type, :path]
-
- defp get_description(opts, upload) do
- case {opts[:description], Pleroma.Config.get([Pleroma.Upload, :default_description])} do
+ defstruct [
+ :id,
+ :name,
+ :tempfile,
+ :content_type,
+ :width,
+ :height,
+ :blurhash,
+ :description,
+ :path
+ ]
+
+ defp get_description(upload) do
+ case {upload.description, Pleroma.Config.get([Pleroma.Upload, :default_description])} do
{description, _} when is_binary(description) -> description
{_, :filename} -> upload.name
{_, str} when is_binary(str) -> str
@@ -73,13 +93,14 @@ defmodule Pleroma.Upload do
with {:ok, upload} <- prepare_upload(upload, opts),
upload = %__MODULE__{upload | path: upload.path || "#{upload.id}/#{upload.name}"},
{:ok, upload} <- Pleroma.Upload.Filter.filter(opts.filters, upload),
- description = get_description(opts, upload),
+ description = get_description(upload),
{_, true} <-
{:description_limit,
String.length(description) <= Pleroma.Config.get([:instance, :description_limit])},
{:ok, url_spec} <- Pleroma.Uploaders.Uploader.put_file(opts.uploader, upload) do
{:ok,
%{
+ "id" => Utils.generate_object_id(),
"type" => opts.activity_type,
"mediaType" => upload.content_type,
"url" => [
@@ -88,9 +109,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}
@@ -130,12 +154,7 @@ defmodule Pleroma.Upload do
uploader: Keyword.get(opts, :uploader, Pleroma.Config.get([__MODULE__, :uploader])),
filters: Keyword.get(opts, :filters, Pleroma.Config.get([__MODULE__, :filters])),
description: Keyword.get(opts, :description),
- base_url:
- Keyword.get(
- opts,
- :base_url,
- Pleroma.Config.get([__MODULE__, :base_url], Pleroma.Web.base_url())
- )
+ base_url: base_url()
}
end
@@ -146,7 +165,8 @@ defmodule Pleroma.Upload do
id: UUID.generate(),
name: file.filename,
tempfile: file.path,
- content_type: file.content_type
+ content_type: file.content_type,
+ description: opts.description
}}
end
end
@@ -166,7 +186,8 @@ defmodule Pleroma.Upload do
id: UUID.generate(),
name: hash <> "." <> ext,
tempfile: tmp_path,
- content_type: content_type
+ content_type: content_type,
+ description: opts.description
}}
end
end
@@ -216,16 +237,46 @@ defmodule Pleroma.Upload do
""
end
- prefix =
- if is_nil(Pleroma.Config.get([__MODULE__, :base_url])) do
- "media"
- else
- ""
- end
-
- [base_url, prefix, path]
+ [base_url, path]
|> Path.join()
end
defp url_from_spec(_upload, _base_url, {:url, url}), do: url
+
+ def base_url do
+ uploader = Config.get([Pleroma.Upload, :uploader])
+ upload_base_url = Config.get([Pleroma.Upload, :base_url])
+ public_endpoint = Config.get([uploader, :public_endpoint])
+
+ case uploader do
+ Pleroma.Uploaders.Local ->
+ upload_base_url || Pleroma.Web.Endpoint.url() <> "/media/"
+
+ Pleroma.Uploaders.S3 ->
+ bucket = Config.get([Pleroma.Uploaders.S3, :bucket])
+ truncated_namespace = Config.get([Pleroma.Uploaders.S3, :truncated_namespace])
+ namespace = Config.get([Pleroma.Uploaders.S3, :bucket_namespace])
+
+ bucket_with_namespace =
+ cond do
+ !is_nil(truncated_namespace) ->
+ truncated_namespace
+
+ !is_nil(namespace) ->
+ namespace <> ":" <> bucket
+
+ true ->
+ bucket
+ end
+
+ if public_endpoint do
+ Path.join([public_endpoint, bucket_with_namespace])
+ else
+ Path.join([upload_base_url, bucket_with_namespace])
+ end
+
+ _ ->
+ public_endpoint || upload_base_url || Pleroma.Web.Endpoint.url() <> "/media/"
+ end
+ end
end
diff --git a/lib/pleroma/upload/filter.ex b/lib/pleroma/upload/filter.ex
index 661135634..717f06621 100644
--- a/lib/pleroma/upload/filter.ex
+++ b/lib/pleroma/upload/filter.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Upload.Filter do
@@ -15,13 +15,13 @@ defmodule Pleroma.Upload.Filter do
require Logger
- @callback filter(Pleroma.Upload.t()) ::
+ @callback filter(upload :: struct()) ::
{:ok, :filtered}
| {:ok, :noop}
- | {:ok, :filtered, Pleroma.Upload.t()}
+ | {:ok, :filtered, upload :: struct()}
| {:error, any()}
- @spec filter([module()], Pleroma.Upload.t()) :: {:ok, Pleroma.Upload.t()} | {:error, any()}
+ @spec filter([module()], upload :: struct()) :: {:ok, upload :: struct()} | {:error, any()}
def filter([], upload) do
{:ok, upload}
diff --git a/lib/pleroma/upload/filter/analyze_metadata.ex b/lib/pleroma/upload/filter/analyze_metadata.ex
new file mode 100644
index 000000000..9a76a998b
--- /dev/null
+++ b/lib/pleroma/upload/filter/analyze_metadata.ex
@@ -0,0 +1,83 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 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(%Pleroma.Upload{tempfile: file, content_type: "video" <> _} = upload) do
+ try do
+ result = media_dimensions(file)
+
+ upload =
+ upload
+ |> Map.put(:width, result.width)
+ |> Map.put(:height, result.height)
+
+ {: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
+
+ defp media_dimensions(file) do
+ with executable when is_binary(executable) <- System.find_executable("ffprobe"),
+ args = [
+ "-v",
+ "error",
+ "-show_entries",
+ "stream=width,height",
+ "-of",
+ "csv=p=0:s=x",
+ file
+ ],
+ {result, 0} <- System.cmd(executable, args),
+ [width, height] <-
+ String.split(String.trim(result), "x") |> Enum.map(&String.to_integer(&1)) do
+ %{width: width, height: height}
+ else
+ nil -> {:error, {:ffprobe, :command_not_found}}
+ {:error, _} = error -> error
+ end
+ end
+end
diff --git a/lib/pleroma/upload/filter/anonymize_filename.ex b/lib/pleroma/upload/filter/anonymize_filename.ex
index fc456e4f4..234ccb6bb 100644
--- a/lib/pleroma/upload/filter/anonymize_filename.ex
+++ b/lib/pleroma/upload/filter/anonymize_filename.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Upload.Filter.AnonymizeFilename do
diff --git a/lib/pleroma/upload/filter/dedupe.ex b/lib/pleroma/upload/filter/dedupe.ex
index 86cbc8996..ef793d390 100644
--- a/lib/pleroma/upload/filter/dedupe.ex
+++ b/lib/pleroma/upload/filter/dedupe.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Upload.Filter.Dedupe do
diff --git a/lib/pleroma/upload/filter/exiftool/read_description.ex b/lib/pleroma/upload/filter/exiftool/read_description.ex
new file mode 100644
index 000000000..03d698a81
--- /dev/null
+++ b/lib/pleroma/upload/filter/exiftool/read_description.ex
@@ -0,0 +1,49 @@
+# 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.Exiftool.ReadDescription do
+ @moduledoc """
+ Gets a valid description from the related EXIF tags and provides them in the response if no description is provided yet.
+ It will first check ImageDescription, when that doesn't probide a valid description, it will check iptc:Caption-Abstract.
+ A valid description means the fields are filled in and not too long (see `:instance, :description_limit`).
+ """
+ @behaviour Pleroma.Upload.Filter
+
+ @spec filter(Pleroma.Upload.t()) :: {:ok, any()} | {:error, String.t()}
+
+ def filter(%Pleroma.Upload{description: description})
+ when is_binary(description),
+ do: {:ok, :noop}
+
+ def filter(%Pleroma.Upload{tempfile: file} = upload),
+ do: {:ok, :filtered, upload |> Map.put(:description, read_description_from_exif_data(file))}
+
+ def filter(_, _), do: {:ok, :noop}
+
+ defp read_description_from_exif_data(file) do
+ nil
+ |> read_when_empty(file, "-ImageDescription")
+ |> read_when_empty(file, "-iptc:Caption-Abstract")
+ end
+
+ defp read_when_empty(current_description, _, _) when is_binary(current_description),
+ do: current_description
+
+ defp read_when_empty(_, file, tag) do
+ try do
+ {tag_content, 0} =
+ System.cmd("exiftool", ["-b", "-s3", tag, file], stderr_to_stdout: true, parallelism: true)
+
+ tag_content = String.trim(tag_content)
+
+ if tag_content != "" and
+ String.length(tag_content) <=
+ Pleroma.Config.get([:instance, :description_limit]),
+ do: tag_content,
+ else: nil
+ rescue
+ _ in ErlangError -> nil
+ end
+ end
+end
diff --git a/lib/pleroma/upload/filter/exiftool.ex b/lib/pleroma/upload/filter/exiftool/strip_location.ex
index 1fd0cfdaa..6100527d3 100644
--- a/lib/pleroma/upload/filter/exiftool.ex
+++ b/lib/pleroma/upload/filter/exiftool/strip_location.ex
@@ -1,8 +1,8 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
-defmodule Pleroma.Upload.Filter.Exiftool do
+defmodule Pleroma.Upload.Filter.Exiftool.StripLocation do
@moduledoc """
Strips GPS related EXIF tags and overwrites the file in place.
Also strips or replaces filesystem metadata e.g., timestamps.
@@ -11,7 +11,8 @@ defmodule Pleroma.Upload.Filter.Exiftool do
@spec filter(Pleroma.Upload.t()) :: {:ok, any()} | {:error, String.t()}
- # webp is not compatible with exiftool at this time
+ # Formats not compatible with exiftool at this time
+ def filter(%Pleroma.Upload{content_type: "image/heic"}), do: {:ok, :noop}
def filter(%Pleroma.Upload{content_type: "image/webp"}), do: {:ok, :noop}
def filter(%Pleroma.Upload{tempfile: file, content_type: "image" <> _}) do
@@ -21,8 +22,8 @@ defmodule Pleroma.Upload.Filter.Exiftool do
{error, 1} -> {:error, error}
end
rescue
- _e in ErlangError ->
- {:error, "exiftool command not found"}
+ e in ErlangError ->
+ {:error, "#{__MODULE__}: #{inspect(e)}"}
end
end
diff --git a/lib/pleroma/upload/filter/mogrifun.ex b/lib/pleroma/upload/filter/mogrifun.ex
index 363e5cf0f..a0f247b70 100644
--- a/lib/pleroma/upload/filter/mogrifun.ex
+++ b/lib/pleroma/upload/filter/mogrifun.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Upload.Filter.Mogrifun do
@@ -44,8 +44,8 @@ defmodule Pleroma.Upload.Filter.Mogrifun do
Filter.Mogrify.do_filter(file, [Enum.random(@filters)])
{:ok, :filtered}
rescue
- _e in ErlangError ->
- {:error, "mogrify command not found"}
+ e in ErlangError ->
+ {:error, "#{__MODULE__}: #{inspect(e)}"}
end
end
diff --git a/lib/pleroma/upload/filter/mogrify.ex b/lib/pleroma/upload/filter/mogrify.ex
index 71968fd9c..06efbf321 100644
--- a/lib/pleroma/upload/filter/mogrify.ex
+++ b/lib/pleroma/upload/filter/mogrify.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Upload.Filter.Mogrify do
@@ -14,8 +14,8 @@ defmodule Pleroma.Upload.Filter.Mogrify do
do_filter(file, Pleroma.Config.get!([__MODULE__, :args]))
{:ok, :filtered}
rescue
- _e in ErlangError ->
- {:error, "mogrify command not found"}
+ e in ErlangError ->
+ {:error, "#{__MODULE__}: #{inspect(e)}"}
end
end
diff --git a/lib/pleroma/uploaders/local.ex b/lib/pleroma/uploaders/local.ex
index 10b3069f4..e4a309cea 100644
--- a/lib/pleroma/uploaders/local.ex
+++ b/lib/pleroma/uploaders/local.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Uploaders.Local do
diff --git a/lib/pleroma/uploaders/s3.ex b/lib/pleroma/uploaders/s3.ex
index 6dbef9085..19287c532 100644
--- a/lib/pleroma/uploaders/s3.ex
+++ b/lib/pleroma/uploaders/s3.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Uploaders.S3 do
@@ -12,26 +12,10 @@ defmodule Pleroma.Uploaders.S3 do
# links with less strict filenames
@impl true
def get_file(file) do
- config = Config.get([__MODULE__])
- bucket = Keyword.fetch!(config, :bucket)
-
- bucket_with_namespace =
- cond do
- truncated_namespace = Keyword.get(config, :truncated_namespace) ->
- truncated_namespace
-
- namespace = Keyword.get(config, :bucket_namespace) ->
- namespace <> ":" <> bucket
-
- true ->
- bucket
- end
-
{:ok,
{:url,
Path.join([
- Keyword.fetch!(config, :public_endpoint),
- bucket_with_namespace,
+ Pleroma.Upload.base_url(),
strict_encode(URI.decode(file))
])}}
end
diff --git a/lib/pleroma/uploaders/uploader.ex b/lib/pleroma/uploaders/uploader.ex
index 6249eceb1..77f6f02dd 100644
--- a/lib/pleroma/uploaders/uploader.ex
+++ b/lib/pleroma/uploaders/uploader.ex
@@ -1,10 +1,12 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Uploaders.Uploader do
import Pleroma.Web.Gettext
+ @mix_env Mix.env()
+
@moduledoc """
Defines the contract to put and get an uploaded file to any backend.
"""
@@ -33,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()}
@@ -44,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}}
@@ -74,7 +76,7 @@ defmodule Pleroma.Uploaders.Uploader do
end
defp callback_timeout do
- case Mix.env() do
+ case @mix_env do
:test -> 1_000
_ -> 30_000
end
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index 52730fd8d..4f91b3ffc 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.User do
@@ -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
@@ -78,6 +78,10 @@ defmodule Pleroma.User do
inverse_subscription: [
subscribee_subscriptions: :subscriber_users,
subscriber_subscriptions: :subscribee_users
+ ],
+ endorsement: [
+ endorser_endorsements: :endorsed_users,
+ endorsee_endorsements: :endorser_users
]
]
@@ -99,6 +103,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: [])
@@ -110,27 +115,25 @@ defmodule Pleroma.User do
field(:follower_count, :integer, default: 0)
field(:following_count, :integer, default: 0)
field(:is_locked, :boolean, default: false)
- field(:confirmation_pending, :boolean, default: false)
+ field(:is_confirmed, :boolean, default: true)
field(:password_reset_pending, :boolean, default: false)
- field(:approval_pending, :boolean, default: false)
+ field(:is_approved, :boolean, default: true)
field(:registration_reason, :string, default: nil)
field(:confirmation_token, :string, default: nil)
field(:default_scope, :string, default: "public")
field(:domain_blocks, {:array, :string}, default: [])
- field(:deactivated, :boolean, default: false)
+ field(:is_active, :boolean, default: true)
field(:no_rich_text, :boolean, default: false)
field(:ap_enabled, :boolean, default: false)
field(:is_moderator, :boolean, default: false)
field(:is_admin, :boolean, default: false)
field(:show_role, :boolean, default: true)
- field(:mastofe_settings, :map, default: nil)
field(:uri, ObjectValidators.Uri, default: nil)
field(:hide_followers_count, :boolean, default: false)
field(:hide_follows_count, :boolean, default: false)
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: %{})
@@ -146,6 +149,14 @@ defmodule Pleroma.User do
field(:inbox, :string)
field(:shared_inbox, :string)
field(:accepts_chat_messages, :boolean, default: nil)
+ field(:last_active_at, :naive_datetime)
+ field(:disclose_client, :boolean, default: true)
+ field(:pinned_objects, :map, default: %{})
+ field(:is_suggested, :boolean, default: false)
+ field(:last_status_at, :naive_datetime)
+ field(:birthday, :date)
+ field(:show_birthday, :boolean, default: false)
+ field(:language, :string)
embeds_one(
:notification_settings,
@@ -166,25 +177,25 @@ defmodule Pleroma.User do
{incoming_relation, incoming_relation_source}
]} <- @user_relationships_config do
# Definitions of `has_many` relations: :blocker_blocks, :muter_mutes, :reblog_muter_mutes,
- # :notification_muter_mutes, :subscribee_subscriptions
+ # :notification_muter_mutes, :subscribee_subscriptions, :endorser_endorsements
has_many(outgoing_relation, UserRelationship,
foreign_key: :source_id,
where: [relationship_type: relationship_type]
)
# Definitions of `has_many` relations: :blockee_blocks, :mutee_mutes, :reblog_mutee_mutes,
- # :notification_mutee_mutes, :subscriber_subscriptions
+ # :notification_mutee_mutes, :subscriber_subscriptions, :endorsee_endorsements
has_many(incoming_relation, UserRelationship,
foreign_key: :target_id,
where: [relationship_type: relationship_type]
)
# Definitions of `has_many` relations: :blocked_users, :muted_users, :reblog_muted_users,
- # :notification_muted_users, :subscriber_users
+ # :notification_muted_users, :subscriber_users, :endorsed_users
has_many(outgoing_relation_target, through: [outgoing_relation, :target])
# Definitions of `has_many` relations: :blocker_users, :muter_users, :reblog_muter_users,
- # :notification_muter_users, :subscribee_users
+ # :notification_muter_users, :subscribee_users, :endorser_users
has_many(incoming_relation_source, through: [incoming_relation, :source])
end
@@ -212,19 +223,20 @@ defmodule Pleroma.User do
@user_relationships_config do
# `def blocked_users_relation/2`, `def muted_users_relation/2`,
# `def reblog_muted_users_relation/2`, `def notification_muted_users/2`,
- # `def subscriber_users/2`
+ # `def subscriber_users/2`, `def endorsed_users_relation/2`
def unquote(:"#{outgoing_relation_target}_relation")(user, restrict_deactivated? \\ false) do
target_users_query = assoc(user, unquote(outgoing_relation_target))
if restrict_deactivated? do
- restrict_deactivated(target_users_query)
+ target_users_query
+ |> User.Query.build(%{deactivated: false})
else
target_users_query
end
end
# `def blocked_users/2`, `def muted_users/2`, `def reblog_muted_users/2`,
- # `def notification_muted_users/2`, `def subscriber_users/2`
+ # `def notification_muted_users/2`, `def subscriber_users/2`, `def endorsed_users/2`
def unquote(outgoing_relation_target)(user, restrict_deactivated? \\ false) do
__MODULE__
|> apply(unquote(:"#{outgoing_relation_target}_relation"), [
@@ -235,7 +247,8 @@ defmodule Pleroma.User do
end
# `def blocked_users_ap_ids/2`, `def muted_users_ap_ids/2`, `def reblog_muted_users_ap_ids/2`,
- # `def notification_muted_users_ap_ids/2`, `def subscriber_users_ap_ids/2`
+ # `def notification_muted_users_ap_ids/2`, `def subscriber_users_ap_ids/2`,
+ # `def endorsed_users_ap_ids/2`
def unquote(:"#{outgoing_relation_target}_ap_ids")(user, restrict_deactivated? \\ false) do
__MODULE__
|> apply(unquote(:"#{outgoing_relation_target}_relation"), [
@@ -286,18 +299,10 @@ defmodule Pleroma.User do
@doc "Returns status account"
@spec account_status(User.t()) :: account_status()
- def account_status(%User{deactivated: true}), do: :deactivated
+ def account_status(%User{is_active: false}), do: :deactivated
def account_status(%User{password_reset_pending: true}), do: :password_reset_pending
- def account_status(%User{local: true, approval_pending: true}), do: :approval_pending
-
- def account_status(%User{local: true, confirmation_pending: true}) do
- if Config.get([:instance, :account_activation_required]) do
- :confirmation_pending
- else
- :active
- end
- end
-
+ def account_status(%User{local: true, is_approved: false}), do: :approval_pending
+ def account_status(%User{local: true, is_confirmed: false}), do: :confirmation_pending
def account_status(%User{}), do: :active
@spec visible_for(User.t(), User.t() | nil) ::
@@ -364,7 +369,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
@@ -372,13 +377,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"
@@ -386,10 +393,10 @@ 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 restrict_deactivated(Ecto.Query.t()) :: Ecto.Query.t()
- def restrict_deactivated(query) do
- from(u in query, where: u.deactivated != ^true)
- end
+ @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
@@ -453,6 +460,7 @@ defmodule Pleroma.User do
:uri,
:follower_address,
:following_address,
+ :featured_address,
:hide_followers,
:hide_follows,
:hide_followers_count,
@@ -464,7 +472,10 @@ defmodule Pleroma.User do
:invisible,
:actor_type,
:also_known_as,
- :accepts_chat_messages
+ :accepts_chat_messages,
+ :pinned_objects,
+ :birthday,
+ :show_birthday
]
)
|> cast(params, [:name], empty_values: [])
@@ -524,9 +535,13 @@ defmodule Pleroma.User do
:pleroma_settings_store,
:is_discoverable,
:actor_type,
- :accepts_chat_messages
+ :accepts_chat_messages,
+ :disclose_client,
+ :birthday,
+ :show_birthday
]
)
+ |> validate_min_age()
|> unique_constraint(:nickname)
|> validate_format(:nickname, local_nickname_regex())
|> validate_length(:bio, max: bio_limit)
@@ -596,7 +611,13 @@ defmodule Pleroma.User do
{:ok, new_value} <- value_function.(value) do
put_change(changeset, map_field, new_value)
else
- _ -> changeset
+ {:error, :file_too_large} ->
+ Ecto.Changeset.validate_change(changeset, map_field, fn map_field, _value ->
+ [{map_field, "file is too large"}]
+ end)
+
+ _ ->
+ changeset
end
end
@@ -691,11 +712,12 @@ defmodule Pleroma.User do
])
|> validate_required([:name, :nickname])
|> unique_constraint(:nickname)
- |> validate_exclusion(:nickname, Config.get([User, :restricted_nicknames]))
+ |> validate_not_restricted_nickname(:nickname)
|> 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()
+ |> put_private_key()
end
def register_changeset(struct, params \\ %{}, opts \\ []) do
@@ -704,23 +726,23 @@ defmodule Pleroma.User do
reason_limit = Config.get([:instance, :registration_reason_length], 500)
params = Map.put_new(params, :accepts_chat_messages, true)
- need_confirmation? =
- if is_nil(opts[:need_confirmation]) do
- Config.get([:instance, :account_activation_required])
+ confirmed? =
+ if is_nil(opts[:confirmed]) do
+ !Config.get([:instance, :account_activation_required])
else
- opts[:need_confirmation]
+ opts[:confirmed]
end
- need_approval? =
- if is_nil(opts[:need_approval]) do
- Config.get([:instance, :account_approval_required])
+ approved? =
+ if is_nil(opts[:approved]) do
+ !Config.get([:instance, :account_approval_required])
else
- opts[:need_approval]
+ opts[:approved]
end
struct
- |> confirmation_changeset(need_confirmation: need_confirmation?)
- |> approval_changeset(need_approval: need_approval?)
+ |> confirmation_changeset(set_confirmation: confirmed?)
+ |> approval_changeset(set_approval: approved?)
|> cast(params, [
:bio,
:raw_bio,
@@ -731,32 +753,58 @@ defmodule Pleroma.User do
:password_confirmation,
:emoji,
:accepts_chat_messages,
- :registration_reason
+ :registration_reason,
+ :birthday,
+ :language
])
|> validate_required([:name, :nickname, :password, :password_confirmation])
|> validate_confirmation(:password)
|> unique_constraint(:email)
|> validate_format(:email, @email_regex)
- |> validate_change(:email, fn :email, email ->
- valid? =
- Config.get([User, :email_blacklist])
- |> Enum.all?(fn blacklisted_domain ->
- !String.ends_with?(email, ["@" <> blacklisted_domain, "." <> blacklisted_domain])
- end)
-
- if valid?, do: [], else: [email: "Invalid email"]
- end)
+ |> validate_email_not_in_blacklisted_domain(:email)
|> unique_constraint(:nickname)
- |> validate_exclusion(:nickname, Config.get([User, :restricted_nicknames]))
+ |> validate_not_restricted_nickname(:nickname)
|> validate_format(:nickname, local_nickname_regex())
|> validate_length(:bio, max: bio_limit)
|> validate_length(:name, min: 1, max: name_limit)
|> validate_length(:registration_reason, max: reason_limit)
|> maybe_validate_required_email(opts[:external])
+ |> maybe_validate_required_birthday
+ |> validate_min_age()
|> put_password_hash
|> put_ap_id()
|> unique_constraint(:ap_id)
- |> put_following_and_follower_address()
+ |> put_following_and_follower_and_featured_address()
+ |> put_private_key()
+ end
+
+ def validate_not_restricted_nickname(changeset, field) do
+ validate_change(changeset, field, fn _, value ->
+ valid? =
+ Config.get([User, :restricted_nicknames])
+ |> Enum.all?(fn restricted_nickname ->
+ String.downcase(value) != String.downcase(restricted_nickname)
+ end)
+
+ if valid?, do: [], else: [nickname: "Invalid nickname"]
+ end)
+ end
+
+ def validate_email_not_in_blacklisted_domain(changeset, field) do
+ validate_change(changeset, field, fn _, value ->
+ valid? =
+ Config.get([User, :email_blacklist])
+ |> Enum.all?(fn blacklisted_domain ->
+ blacklisted_domain_downcase = String.downcase(blacklisted_domain)
+
+ !String.ends_with?(String.downcase(value), [
+ "@" <> blacklisted_domain_downcase,
+ "." <> blacklisted_domain_downcase
+ ])
+ end)
+
+ if valid?, do: [], else: [email: "Invalid email"]
+ end)
end
def maybe_validate_required_email(changeset, true), do: changeset
@@ -769,23 +817,53 @@ defmodule Pleroma.User do
end
end
+ defp maybe_validate_required_birthday(changeset) do
+ if Config.get([:instance, :birthday_required]) do
+ validate_required(changeset, [:birthday])
+ else
+ changeset
+ end
+ end
+
+ defp validate_min_age(changeset) do
+ changeset
+ |> validate_change(:birthday, fn :birthday, birthday ->
+ valid? =
+ Date.utc_today()
+ |> Date.diff(birthday) >=
+ Config.get([:instance, :birthday_min_age])
+
+ if valid?, do: [], else: [birthday: "Invalid age"]
+ end)
+ end
+
defp put_ap_id(changeset) do
ap_id = ap_id(%User{nickname: get_field(changeset, :nickname)})
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 put_private_key(changeset) do
+ {:ok, pem} = Keys.generate_rsa_pem()
+ put_change(changeset, :keys, pem)
end
defp autofollow_users(user) do
candidates = Config.get([:instance, :autofollowed_nicknames])
autofollowed_users =
- User.Query.build(%{nickname: candidates, local: true, deactivated: false})
+ User.Query.build(%{nickname: candidates, local: true, is_active: true})
|> Repo.all()
follow_all(user, autofollowed_users)
@@ -808,31 +886,32 @@ defmodule Pleroma.User do
end
end
- def post_register_action(%User{confirmation_pending: true} = user) do
- with {:ok, _} <- try_send_confirmation_email(user) do
+ def post_register_action(%User{is_confirmed: false} = user) do
+ with {:ok, _} <- maybe_send_confirmation_email(user) do
{:ok, user}
end
end
- def post_register_action(%User{approval_pending: true} = user) do
+ def post_register_action(%User{is_approved: false} = user) do
with {:ok, _} <- send_user_approval_email(user),
{:ok, _} <- send_admin_approval_emails(user) do
{:ok, user}
end
end
- def post_register_action(%User{approval_pending: false, confirmation_pending: false} = user) do
+ def post_register_action(%User{is_approved: true, is_confirmed: true} = user) do
with {:ok, user} <- autofollow_users(user),
{:ok, _} <- autofollowing_users(user),
{:ok, user} <- set_cache(user),
- {:ok, _} <- send_welcome_email(user),
- {:ok, _} <- send_welcome_message(user),
- {:ok, _} <- send_welcome_chat_message(user) do
+ {:ok, _} <- maybe_send_registration_email(user),
+ {:ok, _} <- maybe_send_welcome_email(user),
+ {:ok, _} <- maybe_send_welcome_message(user),
+ {:ok, _} <- maybe_send_welcome_chat_message(user) do
{:ok, user}
end
end
- defp send_user_approval_email(user) do
+ defp send_user_approval_email(%User{email: email} = user) when is_binary(email) do
user
|> Pleroma.Emails.UserEmail.approval_pending_email()
|> Pleroma.Emails.Mailer.deliver_async()
@@ -840,6 +919,10 @@ defmodule Pleroma.User do
{:ok, :enqueued}
end
+ defp send_user_approval_email(_user) do
+ {:ok, :skipped}
+ end
+
defp send_admin_approval_emails(user) do
all_superusers()
|> Enum.filter(fn user -> not is_nil(user.email) end)
@@ -852,7 +935,7 @@ defmodule Pleroma.User do
{:ok, :enqueued}
end
- def send_welcome_message(user) do
+ defp maybe_send_welcome_message(user) do
if User.WelcomeMessage.enabled?() do
User.WelcomeMessage.post_message(user)
{:ok, :enqueued}
@@ -861,7 +944,7 @@ defmodule Pleroma.User do
end
end
- def send_welcome_chat_message(user) do
+ defp maybe_send_welcome_chat_message(user) do
if User.WelcomeChatMessage.enabled?() do
User.WelcomeChatMessage.post_message(user)
{:ok, :enqueued}
@@ -870,7 +953,7 @@ defmodule Pleroma.User do
end
end
- def send_welcome_email(%User{email: email} = user) when is_binary(email) do
+ defp maybe_send_welcome_email(%User{email: email} = user) when is_binary(email) do
if User.WelcomeEmail.enabled?() do
User.WelcomeEmail.send_email(user)
{:ok, :enqueued}
@@ -879,10 +962,10 @@ defmodule Pleroma.User do
end
end
- def send_welcome_email(_), do: {:ok, :noop}
+ defp maybe_send_welcome_email(_), do: {:ok, :noop}
- @spec try_send_confirmation_email(User.t()) :: {:ok, :enqueued | :noop}
- def try_send_confirmation_email(%User{confirmation_pending: true, email: email} = user)
+ @spec maybe_send_confirmation_email(User.t()) :: {:ok, :enqueued | :noop}
+ def maybe_send_confirmation_email(%User{is_confirmed: false, email: email} = user)
when is_binary(email) do
if Config.get([:instance, :account_activation_required]) do
send_confirmation_email(user)
@@ -892,7 +975,7 @@ defmodule Pleroma.User do
end
end
- def try_send_confirmation_email(_), do: {:ok, :noop}
+ def maybe_send_confirmation_email(_), do: {:ok, :noop}
@spec send_confirmation_email(Uset.t()) :: User.t()
def send_confirmation_email(%User{} = user) do
@@ -903,6 +986,24 @@ defmodule Pleroma.User do
user
end
+ @spec maybe_send_registration_email(User.t()) :: {:ok, :enqueued | :noop}
+ defp maybe_send_registration_email(%User{email: email} = user) when is_binary(email) do
+ with false <- User.WelcomeEmail.enabled?(),
+ false <- Config.get([:instance, :account_activation_required], false),
+ false <- Config.get([:instance, :account_approval_required], false) do
+ user
+ |> Pleroma.Emails.UserEmail.successful_registration_email()
+ |> Pleroma.Emails.Mailer.deliver_async()
+
+ {:ok, :enqueued}
+ else
+ _ ->
+ {:ok, :noop}
+ end
+ end
+
+ defp maybe_send_registration_email(_), do: {:ok, :noop}
+
def needs_update?(%User{local: true}), do: false
def needs_update?(%User{local: false, last_refreshed_at: nil}), do: true
@@ -946,7 +1047,7 @@ defmodule Pleroma.User do
deny_follow_blocked = Config.get([:user, :deny_follow_blocked])
cond do
- followed.deactivated ->
+ not followed.is_active ->
{:error, "Could not follow user: #{followed.nickname} is deactivated."}
deny_follow_blocked and blocks?(followed, follower) ->
@@ -1024,6 +1125,10 @@ defmodule Pleroma.User do
Repo.get_by(User, ap_id: ap_id)
end
+ def get_by_uri(uri) do
+ Repo.get_by(User, uri: uri)
+ end
+
def get_all_by_ap_id(ap_ids) do
from(u in __MODULE__,
where: u.ap_id in ^ap_ids
@@ -1062,10 +1167,24 @@ defmodule Pleroma.User do
|> update_and_set_cache()
end
- def update_and_set_cache(changeset) do
+ def update_and_set_cache(%{data: %Pleroma.User{} = user} = changeset) do
+ was_superuser_before_update = User.superuser?(user)
+
with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do
set_cache(user)
end
+ |> maybe_remove_report_notifications(was_superuser_before_update)
+ end
+
+ defp maybe_remove_report_notifications({:ok, %Pleroma.User{} = user} = result, true) do
+ if not User.superuser?(user),
+ do: user |> Notification.destroy_multiple_from_types(["pleroma:report"])
+
+ result
+ end
+
+ defp maybe_remove_report_notifications(result, _) do
+ result
end
def get_user_friends_ap_ids(user) do
@@ -1181,7 +1300,7 @@ defmodule Pleroma.User do
@spec get_followers_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
def get_followers_query(%User{} = user, nil) do
- User.Query.build(%{followers: user, deactivated: false})
+ User.Query.build(%{followers: user, is_active: true})
end
def get_followers_query(%User{} = user, page) do
@@ -1357,7 +1476,7 @@ defmodule Pleroma.User do
@spec get_users_from_set([String.t()], keyword()) :: [User.t()]
def get_users_from_set(ap_ids, opts \\ []) do
local_only = Keyword.get(opts, :local_only, true)
- criteria = %{ap_id: ap_ids, deactivated: false}
+ criteria = %{ap_id: ap_ids, is_active: true}
criteria = if local_only, do: Map.put(criteria, :local, true), else: criteria
User.Query.build(criteria)
@@ -1368,7 +1487,7 @@ defmodule Pleroma.User do
def get_recipients_from_activity(%Activity{recipients: to, actor: actor}) do
to = [actor | to]
- query = User.Query.build(%{recipients_from_activity: to, local: true, deactivated: false})
+ query = User.Query.build(%{recipients_from_activity: to, local: true, is_active: true})
query
|> Repo.all()
@@ -1378,17 +1497,30 @@ defmodule Pleroma.User do
{:ok, list(UserRelationship.t())} | {:error, String.t()}
def mute(%User{} = muter, %User{} = mutee, params \\ %{}) do
notifications? = Map.get(params, :notifications, true)
- expires_in = Map.get(params, :expires_in, 0)
+ duration = Map.get(params, :duration, 0)
+
+ expires_at =
+ if duration > 0 do
+ DateTime.utc_now()
+ |> DateTime.add(duration)
+ else
+ nil
+ end
- with {:ok, user_mute} <- UserRelationship.create_mute(muter, mutee),
+ with {:ok, user_mute} <- UserRelationship.create_mute(muter, mutee, expires_at),
{:ok, user_notification_mute} <-
- (notifications? && UserRelationship.create_notification_mute(muter, mutee)) ||
+ (notifications? &&
+ UserRelationship.create_notification_mute(
+ muter,
+ mutee,
+ expires_at
+ )) ||
{:ok, nil} do
- if expires_in > 0 do
+ if duration > 0 do
Pleroma.Workers.MuteExpireWorker.enqueue(
"unmute_user",
%{"muter_id" => muter.id, "mutee_id" => mutee.id},
- schedule_in: expires_in
+ scheduled_at: expires_at
)
end
@@ -1459,13 +1591,19 @@ defmodule Pleroma.User do
blocker
end
- # clear any requested follows as well
+ # clear any requested follows from both sides as well
blocked =
case CommonAPI.reject_follow_request(blocked, blocker) do
{:ok, %User{} = updated_blocked} -> updated_blocked
nil -> blocked
end
+ blocker =
+ case CommonAPI.reject_follow_request(blocker, blocked) do
+ {:ok, %User{} = updated_blocker} -> updated_blocker
+ nil -> blocker
+ end
+
unsubscribe(blocked, blocker)
unfollowing_blocked = Config.get([:activitypub, :unfollow_blocked], true)
@@ -1490,6 +1628,40 @@ defmodule Pleroma.User do
unblock(blocker, get_cached_by_ap_id(ap_id))
end
+ def endorse(%User{} = endorser, %User{} = target) do
+ with max_endorsed_users <- Pleroma.Config.get([:instance, :max_endorsed_users], 0),
+ endorsed_users <-
+ User.endorsed_users_relation(endorser)
+ |> Repo.aggregate(:count, :id) do
+ cond do
+ endorsed_users >= max_endorsed_users ->
+ {:error, "You have already pinned the maximum number of users"}
+
+ not following?(endorser, target) ->
+ {:error, "Could not endorse: You are not following #{target.nickname}"}
+
+ true ->
+ UserRelationship.create_endorsement(endorser, target)
+ end
+ end
+ end
+
+ def endorse(%User{} = endorser, %{ap_id: ap_id}) do
+ with %User{} = endorsed <- get_cached_by_ap_id(ap_id) do
+ endorse(endorser, endorsed)
+ end
+ end
+
+ def unendorse(%User{} = unendorser, %User{} = target) do
+ UserRelationship.delete_endorsement(unendorser, target)
+ end
+
+ def unendorse(%User{} = unendorser, %{ap_id: ap_id}) do
+ with %User{} = user <- get_cached_by_ap_id(ap_id) do
+ unendorse(unendorser, user)
+ end
+ end
+
def mutes?(nil, _), do: false
def mutes?(%User{} = user, %User{} = target), do: mutes_user?(user, target)
@@ -1535,6 +1707,10 @@ defmodule Pleroma.User do
end
end
+ def endorses?(%User{} = user, %User{} = target) do
+ UserRelationship.endorsement_exists?(user, target)
+ end
+
@doc """
Returns map of outgoing (blocked, muted etc.) relationships' user AP IDs by relation type.
E.g. `outgoing_relationships_ap_ids(user, [:block])` -> `%{block: ["https://some.site/users/userapid"]}`
@@ -1587,19 +1763,19 @@ defmodule Pleroma.User do
defp maybe_filter_on_ap_id(query, _ap_ids), do: query
- def deactivate_async(user, status \\ true) do
- BackgroundWorker.enqueue("deactivate_user", %{"user_id" => user.id, "status" => status})
+ def set_activation_async(user, status \\ true) do
+ BackgroundWorker.enqueue("user_activation", %{"user_id" => user.id, "status" => status})
end
- def deactivate(user, status \\ true)
-
- def deactivate(users, status) when is_list(users) do
+ @spec set_activation([User.t()], boolean()) :: {:ok, User.t()} | {:error, Changeset.t()}
+ def set_activation(users, status) when is_list(users) do
Repo.transaction(fn ->
- for user <- users, do: deactivate(user, status)
+ for user <- users, do: set_activation(user, status)
end)
end
- def deactivate(%User{} = user, status) do
+ @spec set_activation(User.t(), boolean()) :: {:ok, User.t()} | {:error, Changeset.t()}
+ def set_activation(%User{} = user, status) do
with {:ok, user} <- set_activation_status(user, status) do
user
|> get_followers()
@@ -1624,8 +1800,8 @@ defmodule Pleroma.User do
end)
end
- def approve(%User{approval_pending: true} = user) do
- with chg <- change(user, approval_pending: false),
+ def approve(%User{is_approved: false} = user) do
+ with chg <- change(user, is_approved: true),
{:ok, user} <- update_and_set_cache(chg) do
post_register_action(user)
{:ok, user}
@@ -1642,8 +1818,8 @@ defmodule Pleroma.User do
end)
end
- def confirm(%User{confirmation_pending: true} = user) do
- with chg <- confirmation_changeset(user, need_confirmation: false),
+ def confirm(%User{is_confirmed: false} = user) do
+ with chg <- confirmation_changeset(user, set_confirmation: true),
{:ok, user} <- update_and_set_cache(chg) do
post_register_action(user)
{:ok, user}
@@ -1652,6 +1828,22 @@ defmodule Pleroma.User do
def confirm(%User{} = user), do: {:ok, user}
+ def set_suggestion(users, is_suggested) when is_list(users) do
+ Repo.transaction(fn ->
+ Enum.map(users, fn user ->
+ with {:ok, user} <- set_suggestion(user, is_suggested), do: user
+ end)
+ end)
+ end
+
+ def set_suggestion(%User{is_suggested: is_suggested} = user, is_suggested), do: {:ok, user}
+
+ def set_suggestion(%User{} = user, is_suggested) when is_boolean(is_suggested) do
+ user
+ |> change(is_suggested: is_suggested)
+ |> update_and_set_cache()
+ end
+
def update_notification_settings(%User{} = user, settings) do
user
|> cast(%{notification_settings: settings}, [])
@@ -1670,8 +1862,6 @@ defmodule Pleroma.User do
email: nil,
name: nil,
password_hash: nil,
- keys: nil,
- public_key: nil,
avatar: %{},
tags: [],
last_refreshed_at: nil,
@@ -1682,17 +1872,14 @@ defmodule Pleroma.User do
follower_count: 0,
following_count: 0,
is_locked: false,
- confirmation_pending: false,
password_reset_pending: false,
- approval_pending: false,
registration_reason: nil,
confirmation_token: nil,
domain_blocks: [],
- deactivated: true,
+ is_active: false,
ap_enabled: false,
is_moderator: false,
is_admin: false,
- mastofe_settings: nil,
mascot: nil,
emoji: %{},
pleroma_settings_store: %{},
@@ -1700,45 +1887,53 @@ defmodule Pleroma.User do
raw_fields: [],
is_discoverable: false,
also_known_as: []
+ # id: preserved
+ # ap_id: preserved
+ # nickname: preserved
})
end
+ # Purge doesn't delete the user from the database.
+ # It just nulls all its fields and deactivates it.
+ # See `User.purge_user_changeset/1` above.
+ defp purge(%User{} = user) do
+ user
+ |> purge_user_changeset()
+ |> update_and_set_cache()
+ end
+
def delete(users) when is_list(users) do
for user <- users, do: delete(user)
end
def delete(%User{} = user) do
+ # Purge the user immediately
+ purge(user)
BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id})
end
- defp delete_and_invalidate_cache(%User{} = user) do
+ # *Actually* delete the user from the DB
+ defp delete_from_db(%User{} = user) do
invalidate_cache(user)
Repo.delete(user)
end
- defp delete_or_deactivate(%User{local: false} = user), do: delete_and_invalidate_cache(user)
-
- defp delete_or_deactivate(%User{local: true} = user) do
- status = account_status(user)
-
- case status do
- :confirmation_pending ->
- delete_and_invalidate_cache(user)
+ # If the user never finalized their account, it's safe to delete them.
+ defp maybe_delete_from_db(%User{local: true, is_confirmed: false} = user),
+ do: delete_from_db(user)
- :approval_pending ->
- delete_and_invalidate_cache(user)
+ defp maybe_delete_from_db(%User{local: true, is_approved: false} = user),
+ do: delete_from_db(user)
- _ ->
- user
- |> purge_user_changeset()
- |> update_and_set_cache()
- end
- end
+ defp maybe_delete_from_db(user), do: {:ok, user}
def perform(:force_password_reset, user), do: force_password_reset(user)
@spec perform(atom(), User.t()) :: {:ok, User.t()}
def perform(:delete, %User{} = user) do
+ # Purge the user again, in case perform/2 is called directly
+ purge(user)
+
# Remove all relationships
user
|> get_followers()
@@ -1756,13 +1951,12 @@ defmodule Pleroma.User do
delete_user_activities(user)
delete_notifications_from_user_activities(user)
-
delete_outgoing_pending_follow_requests(user)
- delete_or_deactivate(user)
+ maybe_delete_from_db(user)
end
- def perform(:deactivate_async, user, status), do: deactivate(user, status)
+ def perform(:set_activation_async, user, status), do: set_activation(user, status)
@spec external_users_query() :: Ecto.Query.t()
def external_users_query do
@@ -1909,6 +2103,7 @@ defmodule Pleroma.User do
follower_address: uri <> "/followers"
}
|> change
+ |> put_private_key()
|> unique_constraint(:nickname)
|> Repo.insert()
|> set_cache()
@@ -1941,7 +2136,8 @@ defmodule Pleroma.User do
@doc "Gets or fetch a user by uri or nickname."
@spec get_or_fetch(String.t()) :: {:ok, User.t()} | {:error, String.t()}
- def get_or_fetch("http" <> _host = uri), do: get_or_fetch_by_ap_id(uri)
+ def get_or_fetch("http://" <> _host = uri), do: get_or_fetch_by_ap_id(uri)
+ def get_or_fetch("https://" <> _host = uri), do: get_or_fetch_by_ap_id(uri)
def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname)
# wait a period of time and return newest version of the User structs
@@ -2042,6 +2238,15 @@ defmodule Pleroma.User do
|> hd()
end
+ def full_nickname(%User{} = user) do
+ if String.contains?(user.nickname, "@") do
+ user.nickname
+ else
+ %{host: host} = URI.parse(user.ap_id)
+ user.nickname <> "@" <> host
+ end
+ end
+
def full_nickname(nickname_or_mention),
do: String.trim_leading(nickname_or_mention, "@")
@@ -2056,7 +2261,7 @@ defmodule Pleroma.User do
@spec all_superusers() :: [User.t()]
def all_superusers do
- User.Query.build(%{super_users: true, local: true, deactivated: false})
+ User.Query.build(%{super_users: true, local: true, is_active: true})
|> Repo.all()
end
@@ -2097,7 +2302,7 @@ defmodule Pleroma.User do
left_join: a in Pleroma.Activity,
on: u.ap_id == a.actor,
where: not is_nil(u.nickname),
- where: u.deactivated != ^true,
+ where: u.is_active == ^true,
where: u.id not in ^has_read_notifications,
group_by: u.id,
having:
@@ -2138,10 +2343,10 @@ defmodule Pleroma.User do
updated_user
end
- @spec need_confirmation(User.t(), boolean()) :: {:ok, User.t()} | {:error, Changeset.t()}
- def need_confirmation(%User{} = user, bool) do
+ @spec set_confirmation(User.t(), boolean()) :: {:ok, User.t()} | {:error, Changeset.t()}
+ def set_confirmation(%User{} = user, bool) do
user
- |> confirmation_changeset(need_confirmation: bool)
+ |> confirmation_changeset(set_confirmation: bool)
|> update_and_set_cache()
end
@@ -2165,20 +2370,10 @@ defmodule Pleroma.User do
}
end
- def ensure_keys_present(%{keys: keys} = user) when not is_nil(keys), do: {:ok, user}
-
- def ensure_keys_present(%User{} = user) do
- with {:ok, pem} <- Keys.generate_rsa_pem() do
- user
- |> cast(%{keys: pem}, [:keys])
- |> validate_required([:keys])
- |> update_and_set_cache()
- end
- end
-
def get_ap_ids_by_nicknames(nicknames) do
from(u in User,
where: u.nickname in ^nicknames,
+ order_by: fragment("array_position(?, ?)", ^nicknames, u.nickname),
select: u.ap_id
)
|> Repo.all()
@@ -2187,7 +2382,7 @@ defmodule Pleroma.User do
defp put_password_hash(
%Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
) do
- change(changeset, password_hash: Pbkdf2.hash_pwd_salt(password))
+ change(changeset, password_hash: Pleroma.Password.Pbkdf2.hash_pwd_salt(password))
end
defp put_password_hash(changeset), do: changeset
@@ -2211,16 +2406,48 @@ defmodule Pleroma.User do
def change_email(user, email) do
user
|> cast(%{email: email}, [:email])
- |> validate_required([:email])
+ |> maybe_validate_required_email(false)
|> unique_constraint(:email)
|> validate_format(:email, @email_regex)
|> update_and_set_cache()
end
+ def alias_users(user) do
+ user.also_known_as
+ |> Enum.map(&User.get_cached_by_ap_id/1)
+ |> Enum.filter(fn user -> user != nil end)
+ end
+
+ def add_alias(user, new_alias_user) do
+ current_aliases = user.also_known_as || []
+ new_alias_ap_id = new_alias_user.ap_id
+
+ if new_alias_ap_id in current_aliases do
+ {:ok, user}
+ else
+ user
+ |> cast(%{also_known_as: current_aliases ++ [new_alias_ap_id]}, [:also_known_as])
+ |> update_and_set_cache()
+ end
+ end
+
+ def delete_alias(user, alias_user) do
+ current_aliases = user.also_known_as || []
+ alias_ap_id = alias_user.ap_id
+
+ if alias_ap_id in current_aliases do
+ user
+ |> cast(%{also_known_as: current_aliases -- [alias_ap_id]}, [:also_known_as])
+ |> update_and_set_cache()
+ else
+ {:error, :no_such_alias}
+ end
+ end
+
# Internal function; public one is `deactivate/2`
- defp set_activation_status(user, deactivated) do
+ defp set_activation_status(user, status) do
user
- |> cast(%{deactivated: deactivated}, [:deactivated])
+ |> cast(%{is_active: status}, [:is_active])
|> update_and_set_cache()
end
@@ -2236,13 +2463,6 @@ defmodule Pleroma.User do
|> update_and_set_cache()
end
- def roles(%{is_moderator: is_moderator, is_admin: is_admin}) do
- %{
- admin: is_admin,
- moderator: is_moderator
- }
- end
-
def validate_fields(changeset, remote? \\ false) do
limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
limit = Config.get([:instance, limit_name], 0)
@@ -2301,76 +2521,58 @@ defmodule Pleroma.User do
|> update_and_set_cache()
end
- def mastodon_settings_update(user, settings) do
- user
- |> cast(%{mastofe_settings: settings}, [:mastofe_settings])
- |> validate_required([:mastofe_settings])
- |> update_and_set_cache()
- end
-
@spec confirmation_changeset(User.t(), keyword()) :: Changeset.t()
- def confirmation_changeset(user, need_confirmation: need_confirmation?) do
+ def confirmation_changeset(user, set_confirmation: confirmed?) do
params =
- if need_confirmation? do
+ if confirmed? do
%{
- confirmation_pending: true,
- confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
+ is_confirmed: true,
+ confirmation_token: nil
}
else
%{
- confirmation_pending: false,
- confirmation_token: nil
+ is_confirmed: false,
+ confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
}
end
- cast(user, params, [:confirmation_pending, :confirmation_token])
+ cast(user, params, [:is_confirmed, :confirmation_token])
end
@spec approval_changeset(User.t(), keyword()) :: Changeset.t()
- def approval_changeset(user, need_approval: need_approval?) do
- params = if need_approval?, do: %{approval_pending: true}, else: %{approval_pending: false}
- cast(user, params, [:approval_pending])
+ def approval_changeset(user, set_approval: approved?) 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
@@ -2457,4 +2659,40 @@ defmodule Pleroma.User do
def get_host(%User{ap_id: ap_id} = _user) do
URI.parse(ap_id).host
end
+
+ def update_last_active_at(%__MODULE__{local: true} = user) do
+ user
+ |> cast(%{last_active_at: NaiveDateTime.utc_now()}, [:last_active_at])
+ |> update_and_set_cache()
+ end
+
+ def active_user_count(days \\ 30) do
+ active_after = Timex.shift(NaiveDateTime.utc_now(), days: -days)
+
+ __MODULE__
+ |> where([u], u.last_active_at >= ^active_after)
+ |> where([u], u.local == true)
+ |> Repo.aggregate(:count)
+ end
+
+ def update_last_status_at(user) do
+ User
+ |> where(id: ^user.id)
+ |> update([u], set: [last_status_at: fragment("NOW()")])
+ |> select([u], u)
+ |> Repo.update_all([])
+ |> case do
+ {1, [user]} -> set_cache(user)
+ _ -> {:error, user}
+ end
+ end
+
+ def get_friends_birthdays_query(%User{} = user, day, month) do
+ User.Query.build(%{
+ friends: user,
+ deactivated: false,
+ birthday_day: day,
+ birthday_month: month
+ })
+ end
end
diff --git a/lib/pleroma/user/backup.ex b/lib/pleroma/user/backup.ex
index a9041fd94..9df010605 100644
--- a/lib/pleroma/user/backup.ex
+++ b/lib/pleroma/user/backup.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.User.Backup do
@@ -32,9 +32,7 @@ defmodule Pleroma.User.Backup do
end
def create(user, admin_id \\ nil) do
- with :ok <- validate_email_enabled(),
- :ok <- validate_user_email(user),
- :ok <- validate_limit(user, admin_id),
+ with :ok <- validate_limit(user, admin_id),
{:ok, backup} <- user |> new() |> Repo.insert() do
BackupWorker.process(backup, admin_id)
end
@@ -86,20 +84,6 @@ defmodule Pleroma.User.Backup do
end
end
- defp validate_email_enabled do
- if Pleroma.Config.get([Pleroma.Emails.Mailer, :enabled]) do
- :ok
- else
- {:error, dgettext("errors", "Backups require enabled email")}
- end
- end
-
- defp validate_user_email(%User{email: nil}) do
- {:error, dgettext("errors", "Email is required")}
- end
-
- defp validate_user_email(%User{email: email}) when is_binary(email), do: :ok
-
def get_last(user_id) do
__MODULE__
|> where(user_id: ^user_id)
diff --git a/lib/pleroma/user/import.ex b/lib/pleroma/user/import.ex
index 86b49d8ae..4baa7e3a4 100644
--- a/lib/pleroma/user/import.ex
+++ b/lib/pleroma/user/import.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.User.Import do
diff --git a/lib/pleroma/user/notification_setting.ex b/lib/pleroma/user/notification_setting.ex
index 7d9e8a000..3fb845d71 100644
--- a/lib/pleroma/user/notification_setting.ex
+++ b/lib/pleroma/user/notification_setting.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.User.NotificationSetting do
diff --git a/lib/pleroma/user/query.ex b/lib/pleroma/user/query.ex
index 7ef2a1455..20bc1ea61 100644
--- a/lib/pleroma/user/query.ex
+++ b/lib/pleroma/user/query.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.User.Query do
@@ -27,7 +27,7 @@ defmodule Pleroma.User.Query do
- e.g. Pleroma.User.Query.build(%{ap_id: ["http://ap_id1", "http://ap_id2"]})
"""
import Ecto.Query
- import Pleroma.Web.AdminAPI.Search, only: [not_empty_string: 1]
+ import Pleroma.Web.Utils.Guards, only: [not_empty_string: 1]
alias Pleroma.FollowingRelationship
alias Pleroma.User
@@ -46,6 +46,8 @@ defmodule Pleroma.User.Query do
unconfirmed: boolean(),
is_admin: boolean(),
is_moderator: boolean(),
+ is_suggested: boolean(),
+ is_discoverable: boolean(),
super_users: boolean(),
invisible: boolean(),
internal: boolean(),
@@ -57,7 +59,9 @@ defmodule Pleroma.User.Query do
order_by: term(),
select: term(),
limit: pos_integer(),
- actor_types: [String.t()]
+ actor_types: [String.t()],
+ birthday_day: pos_integer(),
+ birthday_month: pos_integer()
}
| map()
@@ -137,8 +141,9 @@ defmodule Pleroma.User.Query do
defp compose_query({:external, _}, query), do: location_query(query, false)
defp compose_query({:active, _}, query) do
- User.restrict_deactivated(query)
- |> where([u], u.approval_pending == false)
+ where(query, [u], u.is_active == true)
+ |> where([u], u.is_approved == true)
+ |> where([u], u.is_confirmed == true)
end
defp compose_query({:legacy_active, _}, query) do
@@ -147,23 +152,31 @@ defmodule Pleroma.User.Query do
end
defp compose_query({:deactivated, false}, query) do
- User.restrict_deactivated(query)
+ where(query, [u], u.is_active == true)
end
defp compose_query({:deactivated, true}, query) do
- where(query, [u], u.deactivated == ^true)
+ where(query, [u], u.is_active == false)
end
defp compose_query({:confirmation_pending, bool}, query) do
- where(query, [u], u.confirmation_pending == ^bool)
+ where(query, [u], u.is_confirmed != ^bool)
end
defp compose_query({:need_approval, _}, query) do
- where(query, [u], u.approval_pending)
+ where(query, [u], u.is_approved == false)
end
defp compose_query({:unconfirmed, _}, query) do
- where(query, [u], u.confirmation_pending)
+ where(query, [u], u.is_confirmed == false)
+ end
+
+ defp compose_query({:is_suggested, bool}, query) do
+ where(query, [u], u.is_suggested == ^bool)
+ end
+
+ defp compose_query({:is_discoverable, bool}, query) do
+ where(query, [u], u.is_discoverable == ^bool)
end
defp compose_query({:followers, %User{id: id}}, query) do
@@ -219,6 +232,20 @@ defmodule Pleroma.User.Query do
|> where([u], not like(u.nickname, "internal.%"))
end
+ defp compose_query({:birthday_day, day}, query) do
+ query
+ |> where([u], u.show_birthday == true)
+ |> where([u], not is_nil(u.birthday))
+ |> where([u], fragment("date_part('day', ?)", u.birthday) == ^day)
+ end
+
+ defp compose_query({:birthday_month, month}, query) do
+ query
+ |> where([u], u.show_birthday == true)
+ |> where([u], not is_nil(u.birthday))
+ |> where([u], fragment("date_part('month', ?)", u.birthday) == ^month)
+ end
+
defp compose_query(_unsupported_param, query), do: query
defp location_query(query, local) do
diff --git a/lib/pleroma/user/search.ex b/lib/pleroma/user/search.ex
index f1761ef03..a7fb8fb83 100644
--- a/lib/pleroma/user/search.ex
+++ b/lib/pleroma/user/search.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.User.Search do
@@ -94,6 +94,7 @@ defmodule Pleroma.User.Search do
|> subquery()
|> order_by(desc: :search_rank)
|> maybe_restrict_local(for_user)
+ |> filter_deactivated_users()
end
defp select_top_users(query, top_user_ids) do
@@ -166,6 +167,10 @@ defmodule Pleroma.User.Search do
from(q in query, where: q.actor_type != "Application")
end
+ defp filter_deactivated_users(query) do
+ from(q in query, where: q.is_active == true)
+ end
+
defp filter_blocked_user(query, %User{} = blocker) do
query
|> join(:left, [u], b in Pleroma.UserRelationship,
diff --git a/lib/pleroma/user/welcome_chat_message.ex b/lib/pleroma/user/welcome_chat_message.ex
index 3e7d1f424..31e0bfaf6 100644
--- a/lib/pleroma/user/welcome_chat_message.ex
+++ b/lib/pleroma/user/welcome_chat_message.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.User.WelcomeChatMessage do
diff --git a/lib/pleroma/user/welcome_email.ex b/lib/pleroma/user/welcome_email.ex
index 5322000d4..970975a49 100644
--- a/lib/pleroma/user/welcome_email.ex
+++ b/lib/pleroma/user/welcome_email.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.User.WelcomeEmail do
diff --git a/lib/pleroma/user/welcome_message.ex b/lib/pleroma/user/welcome_message.ex
index 86e1c0678..6010e808c 100644
--- a/lib/pleroma/user/welcome_message.ex
+++ b/lib/pleroma/user/welcome_message.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.User.WelcomeMessage do
diff --git a/lib/pleroma/user_invite_token.ex b/lib/pleroma/user_invite_token.ex
index a08ba99f1..b242a8848 100644
--- a/lib/pleroma/user_invite_token.ex
+++ b/lib/pleroma/user_invite_token.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.UserInviteToken do
diff --git a/lib/pleroma/user_note.ex b/lib/pleroma/user_note.ex
new file mode 100644
index 000000000..d4b8256b0
--- /dev/null
+++ b/lib/pleroma/user_note.ex
@@ -0,0 +1,52 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.UserNote do
+ use Ecto.Schema
+
+ import Ecto.Changeset
+ import Ecto.Query
+
+ alias Pleroma.Repo
+ alias Pleroma.User
+ alias Pleroma.UserNote
+
+ schema "user_notes" do
+ belongs_to(:source, User, type: FlakeId.Ecto.CompatType)
+ belongs_to(:target, User, type: FlakeId.Ecto.CompatType)
+ field(:comment, :string)
+
+ timestamps()
+ end
+
+ def changeset(%UserNote{} = user_note, params \\ %{}) do
+ user_note
+ |> cast(params, [:source_id, :target_id, :comment])
+ |> validate_required([:source_id, :target_id])
+ end
+
+ def show(%User{} = source, %User{} = target) do
+ with %UserNote{} = note <-
+ UserNote
+ |> where(source_id: ^source.id, target_id: ^target.id)
+ |> Repo.one() do
+ note.comment
+ else
+ _ -> ""
+ end
+ end
+
+ def create(%User{} = source, %User{} = target, comment) do
+ %UserNote{}
+ |> changeset(%{
+ source_id: source.id,
+ target_id: target.id,
+ comment: comment
+ })
+ |> Repo.insert(
+ on_conflict: {:replace, [:comment]},
+ conflict_target: [:source_id, :target_id]
+ )
+ end
+end
diff --git a/lib/pleroma/user_relationship.ex b/lib/pleroma/user_relationship.ex
index 6dfdd2860..fbecf3129 100644
--- a/lib/pleroma/user_relationship.ex
+++ b/lib/pleroma/user_relationship.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.UserRelationship do
@@ -18,25 +18,35 @@ defmodule Pleroma.UserRelationship do
belongs_to(:source, User, type: FlakeId.Ecto.CompatType)
belongs_to(:target, User, type: FlakeId.Ecto.CompatType)
field(:relationship_type, Pleroma.UserRelationship.Type)
+ field(:expires_at, :utc_datetime)
timestamps(updated_at: false)
end
for relationship_type <- Keyword.keys(Pleroma.UserRelationship.Type.__enum_map__()) do
- # `def create_block/2`, `def create_mute/2`, `def create_reblog_mute/2`,
- # `def create_notification_mute/2`, `def create_inverse_subscription/2`
- def unquote(:"create_#{relationship_type}")(source, target),
- do: create(unquote(relationship_type), source, target)
+ # `def create_block/3`, `def create_mute/3`, `def create_reblog_mute/3`,
+ # `def create_notification_mute/3`, `def create_inverse_subscription/3`,
+ # `def endorsement/3`
+ def unquote(:"create_#{relationship_type}")(source, target, expires_at \\ nil),
+ do: create(unquote(relationship_type), source, target, expires_at)
# `def delete_block/2`, `def delete_mute/2`, `def delete_reblog_mute/2`,
- # `def delete_notification_mute/2`, `def delete_inverse_subscription/2`
+ # `def delete_notification_mute/2`, `def delete_inverse_subscription/2`,
+ # `def delete_endorsement/2`
def unquote(:"delete_#{relationship_type}")(source, target),
do: delete(unquote(relationship_type), source, target)
# `def block_exists?/2`, `def mute_exists?/2`, `def reblog_mute_exists?/2`,
- # `def notification_mute_exists?/2`, `def inverse_subscription_exists?/2`
+ # `def notification_mute_exists?/2`, `def inverse_subscription_exists?/2`,
+ # `def inverse_endorsement_exists?/2`
def unquote(:"#{relationship_type}_exists?")(source, target),
do: exists?(unquote(relationship_type), source, target)
+
+ # `def get_block_expire_date/2`, `def get_mute_expire_date/2`,
+ # `def get_reblog_mute_expire_date/2`, `def get_notification_mute_exists?/2`,
+ # `def get_inverse_subscription_expire_date/2`, `def get_inverse_endorsement_expire_date/2`
+ def unquote(:"get_#{relationship_type}_expire_date")(source, target),
+ do: get_expire_date(unquote(relationship_type), source, target)
end
def user_relationship_types, do: Keyword.keys(user_relationship_mappings())
@@ -45,7 +55,7 @@ defmodule Pleroma.UserRelationship do
def changeset(%UserRelationship{} = user_relationship, params \\ %{}) do
user_relationship
- |> cast(params, [:relationship_type, :source_id, :target_id])
+ |> cast(params, [:relationship_type, :source_id, :target_id, :expires_at])
|> validate_required([:relationship_type, :source_id, :target_id])
|> unique_constraint(:relationship_type,
name: :user_relationships_source_id_relationship_type_target_id_index
@@ -59,16 +69,31 @@ defmodule Pleroma.UserRelationship do
|> Repo.exists?()
end
- def create(relationship_type, %User{} = source, %User{} = target) do
+ def get_expire_date(relationship_type, %User{} = source, %User{} = target) do
+ %UserRelationship{expires_at: expires_at} =
+ UserRelationship
+ |> where(
+ relationship_type: ^relationship_type,
+ source_id: ^source.id,
+ target_id: ^target.id
+ )
+ |> Repo.one!()
+
+ expires_at
+ end
+
+ def create(relationship_type, %User{} = source, %User{} = target, expires_at \\ nil) do
%UserRelationship{}
|> changeset(%{
relationship_type: relationship_type,
source_id: source.id,
- target_id: target.id
+ target_id: target.id,
+ expires_at: expires_at
})
|> Repo.insert(
- on_conflict: {:replace_all_except, [:id]},
- conflict_target: [:source_id, :relationship_type, :target_id]
+ on_conflict: {:replace_all_except, [:id, :inserted_at]},
+ conflict_target: [:source_id, :relationship_type, :target_id],
+ returning: true
)
end
diff --git a/lib/pleroma/utils.ex b/lib/pleroma/utils.ex
index fa75a8c99..73001c987 100644
--- a/lib/pleroma/utils.ex
+++ b/lib/pleroma/utils.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Utils do
@@ -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!()
@@ -30,7 +32,10 @@ defmodule Pleroma.Utils do
"""
@spec command_available?(String.t()) :: boolean()
def command_available?(command) do
- match?({_output, 0}, System.cmd("sh", ["-c", "command -v #{command}"]))
+ case :os.find_executable(String.to_charlist(command)) do
+ false -> false
+ _ -> true
+ end
end
@doc "creates the uniq temporary directory"
@@ -60,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
diff --git a/lib/pleroma/web.ex b/lib/pleroma/web.ex
index 3ca20455d..aee41b0fe 100644
--- a/lib/pleroma/web.ex
+++ b/lib/pleroma/web.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web do
@@ -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
@@ -61,9 +62,18 @@ defmodule Pleroma.Web do
)
end
+ defp skip_auth(conn, _) do
+ skip_plug(conn, [OAuthScopesPlug, EnsurePublicOrAuthenticatedPlug])
+ end
+
+ defp skip_public_check(conn, _) do
+ skip_plug(conn, EnsurePublicOrAuthenticatedPlug)
+ end
+
# Executed just before actual controller action, invokes before-action hooks (callbacks)
defp action(conn, params) do
- with %{halted: false} = conn <- maybe_drop_authentication_if_oauth_check_ignored(conn),
+ with %{halted: false} = conn <-
+ maybe_drop_authentication_if_oauth_check_ignored(conn),
%{halted: false} = conn <- maybe_perform_public_or_authenticated_check(conn),
%{halted: false} = conn <- maybe_perform_authenticated_check(conn),
%{halted: false} = conn <- maybe_halt_on_missing_oauth_scopes_check(conn) do
@@ -130,7 +140,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
@@ -228,8 +239,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
end
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index 8c2610eeb..fa251394b 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.ActivityPub do
@@ -10,6 +10,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
alias Pleroma.Conversation
alias Pleroma.Conversation.Participation
alias Pleroma.Filter
+ alias Pleroma.Hashtag
alias Pleroma.Maps
alias Pleroma.Notification
alias Pleroma.Object
@@ -24,6 +25,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
alias Pleroma.Web.Streamer
alias Pleroma.Web.WebFinger
alias Pleroma.Workers.BackgroundWorker
+ alias Pleroma.Workers.PollWorker
import Ecto.Query
import Pleroma.Web.ActivityPub.Utils
@@ -52,15 +54,18 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
{recipients, to, cc}
end
- defp check_actor_is_active(nil), do: true
+ defp check_actor_can_insert(%{"type" => "Delete"}), do: true
+ defp check_actor_can_insert(%{"type" => "Undo"}), do: true
- defp check_actor_is_active(actor) when is_binary(actor) do
+ defp check_actor_can_insert(%{"actor" => actor}) when is_binary(actor) do
case User.get_cached_by_ap_id(actor) do
- %User{deactivated: deactivated} -> not deactivated
+ %User{is_active: true} -> true
_ -> false
end
end
+ defp check_actor_can_insert(_), do: true
+
defp check_remote_limit(%{"object" => %{"content" => content}}) when not is_nil(content) do
limit = Config.get([:instance, :remote_limit])
String.length(content) <= limit
@@ -76,6 +81,10 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
if is_public?(object), do: User.decrease_note_count(actor), else: {:ok, actor}
end
+ def update_last_status_at_if_public(actor, object) do
+ if is_public?(object), do: User.update_last_status_at(actor), else: {:ok, actor}
+ end
+
defp increase_replies_count_if_reply(%{
"object" => %{"inReplyTo" => reply_ap_id} = object,
"type" => "Create"
@@ -87,7 +96,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 Page]
@impl true
def persist(%{"type" => type} = object, meta) when type in @object_types do
with {:ok, object} <- Object.create(object) do
@@ -116,7 +125,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
def insert(map, local \\ true, fake \\ false, bypass_actor_check \\ false) when is_map(map) do
with nil <- Activity.normalize(map),
map <- lazy_put_activity_defaults(map, fake),
- {_, true} <- {:actor_check, bypass_actor_check || check_actor_is_active(map["actor"])},
+ {_, true} <- {:actor_check, bypass_actor_check || check_actor_can_insert(map)},
{_, true} <- {:remote_limit_pass, check_remote_limit(map)},
{:ok, map} <- MRF.filter(map),
{recipients, _, _} = get_recipients(map),
@@ -181,7 +190,16 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
def notify_and_stream(activity) do
Notification.create_notifications(activity)
- conversation = create_or_bump_conversation(activity, activity.actor)
+ original_activity =
+ case activity do
+ %{data: %{"type" => "Update"}, object: %{data: %{"id" => id}}} ->
+ Activity.get_create_by_object_ap_id_with_object(id)
+
+ _ ->
+ activity
+ end
+
+ conversation = create_or_bump_conversation(original_activity, original_activity.actor)
participations = get_participations(conversation)
stream_out(activity)
stream_out_participations(participations)
@@ -247,7 +265,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
@impl true
def stream_out(%Activity{data: %{"type" => data_type}} = activity)
- when data_type in ["Create", "Announce", "Delete"] do
+ when data_type in ["Create", "Announce", "Delete", "Update"] do
activity
|> Topics.get_activity_topics()
|> Streamer.stream(activity)
@@ -283,7 +301,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
_ <- increase_replies_count_if_reply(create_data),
{:quick_insert, false, activity} <- {:quick_insert, quick_insert?, activity},
{:ok, _actor} <- increase_note_count_if_public(actor, activity),
+ {:ok, _actor} <- update_last_status_at_if_public(actor, activity),
_ <- notify_and_stream(activity),
+ :ok <- maybe_schedule_poll_notifications(activity),
:ok <- maybe_federate(activity) do
{:ok, activity}
else
@@ -298,6 +318,11 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
end
end
+ defp maybe_schedule_poll_notifications(activity) do
+ PollWorker.schedule_poll_end(activity)
+ :ok
+ end
+
@spec listen(map()) :: {:ok, Activity.t()} | {:error, any()}
def listen(%{to: to, actor: actor, context: context, object: object} = params) do
additional = params[:additional] || %{}
@@ -377,6 +402,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
:ok <-
maybe_federate(stripped_activity) do
User.all_superusers()
+ |> Enum.filter(fn user -> user.ap_id != actor end)
|> Enum.filter(fn user -> not is_nil(user.email) end)
|> Enum.each(fn superuser ->
superuser
@@ -396,7 +422,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
"type" => "Move",
"actor" => origin.ap_id,
"object" => origin.ap_id,
- "target" => target.ap_id
+ "target" => target.ap_id,
+ "to" => [origin.follower_address]
}
with true <- origin.ap_id in target.also_known_as,
@@ -429,6 +456,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|> maybe_preload_bookmarks(opts)
|> maybe_set_thread_muted_field(opts)
|> restrict_blocked(opts)
+ |> restrict_blockers_visibility(opts)
|> restrict_recipients(recipients, opts[:user])
|> restrict_filtered(opts)
|> where(
@@ -464,14 +492,40 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|> Repo.one()
end
+ defp fetch_paginated_optimized(query, opts, pagination) do
+ # Note: tag-filtering funcs may apply "ORDER BY objects.id DESC",
+ # and extra sorting on "activities.id DESC NULLS LAST" would worse the query plan
+ opts = Map.put(opts, :skip_extra_order, true)
+
+ Pagination.fetch_paginated(query, opts, pagination)
+ end
+
+ def fetch_activities(recipients, opts \\ %{}, pagination \\ :keyset) do
+ list_memberships = Pleroma.List.memberships(opts[:user])
+
+ fetch_activities_query(recipients ++ list_memberships, opts)
+ |> fetch_paginated_optimized(opts, pagination)
+ |> Enum.reverse()
+ |> maybe_update_cc(list_memberships, opts[:user])
+ end
+
@spec fetch_public_or_unlisted_activities(map(), Pagination.type()) :: [Activity.t()]
def fetch_public_or_unlisted_activities(opts \\ %{}, pagination \\ :keyset) do
+ includes_local_public = Map.get(opts, :includes_local_public, false)
+
opts = Map.delete(opts, :user)
- [Constants.as_public()]
+ intended_recipients =
+ if includes_local_public do
+ [Constants.as_public(), as_local_public()]
+ else
+ [Constants.as_public()]
+ end
+
+ intended_recipients
|> fetch_activities_query(opts)
|> restrict_unlisted(opts)
- |> Pagination.fetch_paginated(opts, pagination)
+ |> fetch_paginated_optimized(opts, pagination)
end
@spec fetch_public_activities(map(), Pagination.type()) :: [Activity.t()]
@@ -568,9 +622,11 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
do: query
defp restrict_thread_visibility(query, %{user: %User{ap_id: ap_id}}, _) do
+ local_public = as_local_public()
+
from(
a in query,
- where: fragment("thread_visibility(?, (?)->>'id') = true", ^ap_id, a.data)
+ where: fragment("thread_visibility(?, (?)->>'id', ?) = true", ^ap_id, a.data, ^local_public)
)
end
@@ -591,13 +647,27 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|> Enum.reverse()
end
- def fetch_user_activities(user, reading_user, params \\ %{}) do
+ def fetch_user_activities(user, reading_user, params \\ %{})
+
+ def fetch_user_activities(user, reading_user, %{total: true} = params) do
+ result = fetch_activities_for_user(user, reading_user, params)
+
+ Keyword.put(result, :items, Enum.reverse(result[:items]))
+ end
+
+ def fetch_user_activities(user, reading_user, params) do
+ user
+ |> fetch_activities_for_user(reading_user, params)
+ |> Enum.reverse()
+ end
+
+ defp fetch_activities_for_user(user, reading_user, params) do
params =
params
|> 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
@@ -616,10 +686,20 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
}
|> user_activities_recipients()
|> fetch_activities(params, pagination_type)
- |> Enum.reverse()
+ end
+
+ def fetch_statuses(reading_user, %{total: true} = params) do
+ result = fetch_activities_for_reading_user(reading_user, params)
+ Keyword.put(result, :items, Enum.reverse(result[:items]))
end
def fetch_statuses(reading_user, params) do
+ reading_user
+ |> fetch_activities_for_reading_user(params)
+ |> Enum.reverse()
+ end
+
+ defp fetch_activities_for_reading_user(reading_user, params) do
params = Map.put(params, :type, ["Create", "Announce"])
%{
@@ -628,14 +708,17 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
}
|> user_activities_recipients()
|> fetch_activities(params, :offset)
- |> Enum.reverse()
end
defp user_activities_recipients(%{godmode: true}), do: []
defp user_activities_recipients(%{reading_user: reading_user}) do
- if reading_user do
- [Constants.as_public(), reading_user.ap_id | User.following(reading_user)]
+ if not is_nil(reading_user) and reading_user.local do
+ [
+ Constants.as_public(),
+ as_local_public(),
+ reading_user.ap_id | User.following(reading_user)
+ ]
else
[Constants.as_public()]
end
@@ -669,51 +752,143 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
defp restrict_since(query, _), do: query
- defp restrict_tag_reject(_query, %{tag_reject: _tag_reject, skip_preload: true}) do
- raise "Can't use the child object without preloading!"
+ defp restrict_embedded_tag_all(_query, %{tag_all: _tag_all, skip_preload: true}) do
+ raise_on_missing_preload()
end
- defp restrict_tag_reject(query, %{tag_reject: [_ | _] = tag_reject}) do
+ defp restrict_embedded_tag_all(query, %{tag_all: [_ | _] = tag_all}) do
+ from(
+ [_activity, object] in query,
+ where: fragment("(?)->'tag' \\?& (?)", object.data, ^tag_all)
+ )
+ end
+
+ defp restrict_embedded_tag_all(query, %{tag_all: tag}) when is_binary(tag) do
+ restrict_embedded_tag_any(query, %{tag: tag})
+ end
+
+ defp restrict_embedded_tag_all(query, _), do: query
+
+ defp restrict_embedded_tag_any(_query, %{tag: _tag, skip_preload: true}) do
+ raise_on_missing_preload()
+ end
+
+ defp restrict_embedded_tag_any(query, %{tag: [_ | _] = tag_any}) do
+ from(
+ [_activity, object] in query,
+ where: fragment("(?)->'tag' \\?| (?)", object.data, ^tag_any)
+ )
+ end
+
+ defp restrict_embedded_tag_any(query, %{tag: tag}) when is_binary(tag) do
+ restrict_embedded_tag_any(query, %{tag: [tag]})
+ end
+
+ defp restrict_embedded_tag_any(query, _), do: query
+
+ defp restrict_embedded_tag_reject_any(_query, %{tag_reject: _tag_reject, skip_preload: true}) do
+ raise_on_missing_preload()
+ end
+
+ defp restrict_embedded_tag_reject_any(query, %{tag_reject: [_ | _] = tag_reject}) do
from(
[_activity, object] in query,
where: fragment("not (?)->'tag' \\?| (?)", object.data, ^tag_reject)
)
end
- defp restrict_tag_reject(query, _), do: query
+ defp restrict_embedded_tag_reject_any(query, %{tag_reject: tag_reject})
+ when is_binary(tag_reject) do
+ restrict_embedded_tag_reject_any(query, %{tag_reject: [tag_reject]})
+ end
- defp restrict_tag_all(_query, %{tag_all: _tag_all, skip_preload: true}) do
- raise "Can't use the child object without preloading!"
+ defp restrict_embedded_tag_reject_any(query, _), do: query
+
+ defp object_ids_query_for_tags(tags) do
+ from(hto in "hashtags_objects")
+ |> join(:inner, [hto], ht in Pleroma.Hashtag, on: hto.hashtag_id == ht.id)
+ |> where([hto, ht], ht.name in ^tags)
+ |> select([hto], hto.object_id)
+ |> distinct([hto], true)
+ end
+
+ defp restrict_hashtag_all(_query, %{tag_all: _tag, skip_preload: true}) do
+ raise_on_missing_preload()
+ end
+
+ defp restrict_hashtag_all(query, %{tag_all: [single_tag]}) do
+ restrict_hashtag_any(query, %{tag: single_tag})
end
- defp restrict_tag_all(query, %{tag_all: [_ | _] = tag_all}) do
+ defp restrict_hashtag_all(query, %{tag_all: [_ | _] = tags}) do
from(
[_activity, object] in query,
- where: fragment("(?)->'tag' \\?& (?)", object.data, ^tag_all)
+ where:
+ fragment(
+ """
+ (SELECT array_agg(hashtags.name) FROM hashtags JOIN hashtags_objects
+ ON hashtags_objects.hashtag_id = hashtags.id WHERE hashtags.name = ANY(?)
+ AND hashtags_objects.object_id = ?) @> ?
+ """,
+ ^tags,
+ object.id,
+ ^tags
+ )
)
end
- defp restrict_tag_all(query, _), do: query
+ defp restrict_hashtag_all(query, %{tag_all: tag}) when is_binary(tag) do
+ restrict_hashtag_all(query, %{tag_all: [tag]})
+ end
- defp restrict_tag(_query, %{tag: _tag, skip_preload: true}) do
- raise "Can't use the child object without preloading!"
+ defp restrict_hashtag_all(query, _), do: query
+
+ defp restrict_hashtag_any(_query, %{tag: _tag, skip_preload: true}) do
+ raise_on_missing_preload()
end
- defp restrict_tag(query, %{tag: tag}) when is_list(tag) do
+ defp restrict_hashtag_any(query, %{tag: [_ | _] = tags}) do
+ hashtag_ids =
+ from(ht in Hashtag, where: ht.name in ^tags, select: ht.id)
+ |> Repo.all()
+
+ # Note: NO extra ordering should be done on "activities.id desc nulls last" for optimal plan
from(
[_activity, object] in query,
- where: fragment("(?)->'tag' \\?| (?)", object.data, ^tag)
+ join: hto in "hashtags_objects",
+ on: hto.object_id == object.id,
+ where: hto.hashtag_id in ^hashtag_ids,
+ distinct: [desc: object.id],
+ order_by: [desc: object.id]
)
end
- defp restrict_tag(query, %{tag: tag}) when is_binary(tag) do
+ defp restrict_hashtag_any(query, %{tag: tag}) when is_binary(tag) do
+ restrict_hashtag_any(query, %{tag: [tag]})
+ end
+
+ defp restrict_hashtag_any(query, _), do: query
+
+ defp restrict_hashtag_reject_any(_query, %{tag_reject: _tag_reject, skip_preload: true}) do
+ raise_on_missing_preload()
+ end
+
+ defp restrict_hashtag_reject_any(query, %{tag_reject: [_ | _] = tags_reject}) do
from(
[_activity, object] in query,
- where: fragment("(?)->'tag' \\? (?)", object.data, ^tag)
+ where: object.id not in subquery(object_ids_query_for_tags(tags_reject))
)
end
- defp restrict_tag(query, _), do: query
+ defp restrict_hashtag_reject_any(query, %{tag_reject: tag_reject}) when is_binary(tag_reject) do
+ restrict_hashtag_reject_any(query, %{tag_reject: [tag_reject]})
+ end
+
+ defp restrict_hashtag_reject_any(query, _), do: query
+
+ defp raise_on_missing_preload do
+ raise "Can't use the child object without preloading!"
+ end
defp restrict_recipients(query, [], _user), do: query
@@ -735,6 +910,12 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
defp restrict_local(query, _), do: query
+ defp restrict_remote(query, %{remote: true}) do
+ from(activity in query, where: activity.local == false)
+ end
+
+ defp restrict_remote(query, _), do: query
+
defp restrict_actor(query, %{actor_id: actor_id}) do
from(activity in query, where: activity.actor == ^actor_id)
end
@@ -878,7 +1059,10 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
from(
[activity, object: o] in query,
+ # You don't block the author
where: fragment("not (? = ANY(?))", activity.actor, ^blocked_ap_ids),
+
+ # You don't block any recipients, and didn't author the post
where:
fragment(
"((not (? && ?)) or ? = ?)",
@@ -887,12 +1071,18 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
activity.actor,
^user.ap_id
),
+
+ # You don't block the domain of any recipients, and didn't author the post
where:
fragment(
- "recipients_contain_blocked_domains(?, ?) = false",
+ "(recipients_contain_blocked_domains(?, ?) = false) or ? = ?",
activity.recipients,
- ^domain_blocks
+ ^domain_blocks,
+ activity.actor,
+ ^user.ap_id
),
+
+ # It's not a boost of a user you block
where:
fragment(
"not (?->>'type' = 'Announce' and ?->'to' \\?| ?)",
@@ -900,6 +1090,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
activity.data,
^blocked_ap_ids
),
+
+ # You don't block the author's domain, and also don't follow the author
where:
fragment(
"(not (split_part(?, '/', 3) = ANY(?))) or ? = ANY(?)",
@@ -908,6 +1100,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
activity.actor,
^following_ap_ids
),
+
+ # Same as above, but checks the Object
where:
fragment(
"(not (split_part(?->>'actor', '/', 3) = ANY(?))) or (?->>'actor') = ANY(?)",
@@ -921,6 +1115,31 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
defp restrict_blocked(query, _), do: query
+ defp restrict_blockers_visibility(query, %{blocking_user: %User{} = user}) do
+ if Config.get([:activitypub, :blockers_visible]) == true do
+ query
+ else
+ blocker_ap_ids = User.incoming_relationships_ungrouped_ap_ids(user, [:block])
+
+ from(
+ activity in query,
+ # The author doesn't block you
+ where: fragment("not (? = ANY(?))", activity.actor, ^blocker_ap_ids),
+
+ # It's not a boost of a user that blocks you
+ where:
+ fragment(
+ "not (?->>'type' = 'Announce' and ?->'to' \\?| ?)",
+ activity.data,
+ activity.data,
+ ^blocker_ap_ids
+ )
+ )
+ end
+ end
+
+ defp restrict_blockers_visibility(query, _), do: query
+
defp restrict_unlisted(query, %{restrict_unlisted: true}) do
from(
activity in query,
@@ -935,8 +1154,17 @@ 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 associated_object_id((?)) = any (?)",
+ activity.data,
+ activity.data,
+ ^ids
+ )
+ )
end
defp restrict_pinned(query, _), do: query
@@ -1011,15 +1239,15 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
end
end
+ defp exclude_invisible_actors(query, %{type: "Flag"}), do: query
defp exclude_invisible_actors(query, %{invisible_actors: true}), do: query
defp exclude_invisible_actors(query, _opts) do
- invisible_ap_ids =
- User.Query.build(%{invisible: true, select: [:ap_id]})
- |> Repo.all()
- |> Enum.map(fn %{ap_id: ap_id} -> ap_id end)
-
- from([activity] in query, where: activity.actor not in ^invisible_ap_ids)
+ query
+ |> join(:inner, [activity], u in User,
+ as: :u,
+ on: activity.actor == u.ap_id and u.invisible == false
+ )
end
defp exclude_id(query, %{exclude_id: id}) when is_binary(id) do
@@ -1068,6 +1296,26 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
defp maybe_order(query, _), do: query
+ defp normalize_fetch_activities_query_opts(opts) do
+ Enum.reduce([:tag, :tag_all, :tag_reject], opts, fn key, opts ->
+ case opts[key] do
+ value when is_bitstring(value) ->
+ Map.put(opts, key, Hashtag.normalize_name(value))
+
+ value when is_list(value) ->
+ normalized_value =
+ value
+ |> Enum.map(&Hashtag.normalize_name/1)
+ |> Enum.uniq()
+
+ Map.put(opts, key, normalized_value)
+
+ _ ->
+ opts
+ end
+ end)
+ end
+
defp fetch_activities_query_ap_ids_ops(opts) do
source_user = opts[:muting_user]
ap_id_relationships = if source_user, do: [:mute, :reblog_mute], else: []
@@ -1091,6 +1339,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
end
def fetch_activities_query(recipients, opts \\ %{}) do
+ opts = normalize_fetch_activities_query_opts(opts)
+
{restrict_blocked_opts, restrict_muted_opts, restrict_muted_reblogs_opts} =
fetch_activities_query_ap_ids_ops(opts)
@@ -1098,49 +1348,52 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
skip_thread_containment: Config.get([:instance, :skip_thread_containment])
}
- Activity
- |> maybe_preload_objects(opts)
- |> maybe_preload_bookmarks(opts)
- |> maybe_preload_report_notes(opts)
- |> maybe_set_thread_muted_field(opts)
- |> maybe_order(opts)
- |> restrict_recipients(recipients, opts[:user])
- |> restrict_replies(opts)
- |> restrict_tag(opts)
- |> restrict_tag_reject(opts)
- |> restrict_tag_all(opts)
- |> restrict_since(opts)
- |> restrict_local(opts)
- |> restrict_actor(opts)
- |> restrict_type(opts)
- |> restrict_state(opts)
- |> restrict_favorited_by(opts)
- |> restrict_blocked(restrict_blocked_opts)
- |> restrict_muted(restrict_muted_opts)
- |> restrict_filtered(opts)
- |> restrict_media(opts)
- |> restrict_visibility(opts)
- |> restrict_thread_visibility(opts, config)
- |> restrict_reblogs(opts)
- |> restrict_pinned(opts)
- |> restrict_muted_reblogs(restrict_muted_reblogs_opts)
- |> restrict_instance(opts)
- |> restrict_announce_object_actor(opts)
- |> restrict_filtered(opts)
- |> Activity.restrict_deactivated_users()
- |> exclude_poll_votes(opts)
- |> exclude_chat_messages(opts)
- |> exclude_invisible_actors(opts)
- |> exclude_visibility(opts)
- end
-
- def fetch_activities(recipients, opts \\ %{}, pagination \\ :keyset) do
- list_memberships = Pleroma.List.memberships(opts[:user])
-
- fetch_activities_query(recipients ++ list_memberships, opts)
- |> Pagination.fetch_paginated(opts, pagination)
- |> Enum.reverse()
- |> maybe_update_cc(list_memberships, opts[:user])
+ query =
+ Activity
+ |> maybe_preload_objects(opts)
+ |> maybe_preload_bookmarks(opts)
+ |> maybe_preload_report_notes(opts)
+ |> maybe_set_thread_muted_field(opts)
+ |> maybe_order(opts)
+ |> restrict_recipients(recipients, opts[:user])
+ |> restrict_replies(opts)
+ |> restrict_since(opts)
+ |> restrict_local(opts)
+ |> restrict_remote(opts)
+ |> restrict_actor(opts)
+ |> restrict_type(opts)
+ |> restrict_state(opts)
+ |> restrict_favorited_by(opts)
+ |> restrict_blocked(restrict_blocked_opts)
+ |> restrict_blockers_visibility(opts)
+ |> restrict_muted(restrict_muted_opts)
+ |> restrict_filtered(opts)
+ |> restrict_media(opts)
+ |> restrict_visibility(opts)
+ |> restrict_thread_visibility(opts, config)
+ |> restrict_reblogs(opts)
+ |> restrict_pinned(opts)
+ |> restrict_muted_reblogs(restrict_muted_reblogs_opts)
+ |> restrict_instance(opts)
+ |> restrict_announce_object_actor(opts)
+ |> restrict_filtered(opts)
+ |> maybe_restrict_deactivated_users(opts)
+ |> exclude_poll_votes(opts)
+ |> exclude_chat_messages(opts)
+ |> exclude_invisible_actors(opts)
+ |> exclude_visibility(opts)
+
+ if Config.feature_enabled?(:improved_hashtag_timeline) do
+ query
+ |> restrict_hashtag_any(opts)
+ |> restrict_hashtag_all(opts)
+ |> restrict_hashtag_reject_any(opts)
+ else
+ query
+ |> restrict_embedded_tag_any(opts)
+ |> restrict_embedded_tag_all(opts)
+ |> restrict_embedded_tag_reject_any(opts)
+ end
end
@doc """
@@ -1219,21 +1472,17 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
defp get_actor_url(_url), do: nil
- defp object_to_user_data(data) do
- avatar =
- data["icon"]["url"] &&
- %{
- "type" => "Image",
- "url" => [%{"href" => data["icon"]["url"]}]
- }
+ defp normalize_image(%{"url" => url}) do
+ %{
+ "type" => "Image",
+ "url" => [%{"href" => url}]
+ }
+ end
- banner =
- data["image"]["url"] &&
- %{
- "type" => "Image",
- "url" => [%{"href" => data["image"]["url"]}]
- }
+ defp normalize_image(urls) when is_list(urls), do: urls |> List.first() |> normalize_image()
+ defp normalize_image(_), do: nil
+ defp object_to_user_data(data, additional) do
fields =
data
|> Map.get("attachment", [])
@@ -1259,55 +1508,78 @@ 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"]
- else
- nil
end
shared_inbox =
if is_map(data["endpoints"]) && is_binary(data["endpoints"]["sharedInbox"]) do
data["endpoints"]["sharedInbox"]
- else
- nil
end
- user_data = %{
+ birthday =
+ if is_binary(data["vcard:bday"]) do
+ case Date.from_iso8601(data["vcard:bday"]) do
+ {:ok, date} -> date
+ {:error, _} -> nil
+ end
+ end
+
+ show_birthday = !!birthday
+
+ # if WebFinger request was already done, we probably have acct, otherwise
+ # we request WebFinger here
+ nickname = additional[:nickname_from_acct] || generate_nickname(data)
+
+ %{
ap_id: data["id"],
uri: get_actor_url(data["url"]),
ap_enabled: true,
- banner: banner,
+ banner: normalize_image(data["image"]),
fields: fields,
emoji: emojis,
is_locked: is_locked,
is_discoverable: is_discoverable,
invisible: invisible,
- avatar: avatar,
+ avatar: normalize_image(data["icon"]),
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,
+ birthday: birthday,
+ show_birthday: show_birthday,
+ pinned_objects: pinned_objects,
+ nickname: nickname
}
+ end
- # nickname can be nil because of virtual actors
- if data["preferredUsername"] do
- Map.put(
- user_data,
- :nickname,
- "#{data["preferredUsername"]}@#{URI.parse(data["id"]).host}"
- )
+ defp generate_nickname(%{"preferredUsername" => username} = data) when is_binary(username) do
+ generated = "#{username}@#{URI.parse(data["id"]).host}"
+
+ if Config.get([WebFinger, :update_nickname_on_user_fetch]) do
+ case WebFinger.finger(generated) do
+ {:ok, %{"subject" => "acct:" <> acct}} -> acct
+ _ -> generated
+ end
else
- Map.put(user_data, :nickname, nil)
+ generated
end
end
+ # nickname can be nil because of virtual actors
+ defp generate_nickname(_), do: nil
+
def fetch_follow_information_for_user(user) do
with {:ok, following_data} <-
Fetcher.fetch_and_contain_remote_object_from_id(user.following_address),
@@ -1379,17 +1651,17 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
defp collection_private(_data), do: {:ok, true}
- def user_data_from_user_object(data) do
+ def user_data_from_user_object(data, additional \\ []) do
with {:ok, data} <- MRF.filter(data) do
- {:ok, object_to_user_data(data)}
+ {:ok, object_to_user_data(data, additional)}
else
e -> {:error, e}
end
end
- def fetch_and_prepare_user_from_ap_id(ap_id) do
+ def fetch_and_prepare_user_from_ap_id(ap_id, additional \\ []) do
with {:ok, data} <- Fetcher.fetch_and_contain_remote_object_from_id(ap_id),
- {:ok, data} <- user_data_from_user_object(data) do
+ {:ok, data} <- user_data_from_user_object(data, additional) do
{:ok, maybe_update_follow_information(data)}
else
# If this has been deleted, only log a debug and not an error
@@ -1412,9 +1684,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
%User{} = old_user <- User.get_by_nickname(nickname),
{_, false} <- {:ap_id_comparison, data[:ap_id] == old_user.ap_id} do
Logger.info(
- "Found an old user for #{nickname}, the old ap id is #{old_user.ap_id}, new one is #{
- data[:ap_id]
- }, renaming."
+ "Found an old user for #{nickname}, the old ap id is #{old_user.ap_id}, new one is #{data[:ap_id]}, renaming."
)
old_user
@@ -1431,13 +1701,53 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
end
end
- def make_user_from_ap_id(ap_id) do
+ 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()}
+ object_ap_id when is_binary(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, additional \\ []) do
user = User.get_cached_by_ap_id(ap_id)
if user && !User.ap_enabled?(user) do
Transmogrifier.upgrade_user_from_ap_id(ap_id)
else
- with {:ok, data} <- fetch_and_prepare_user_from_ap_id(ap_id) do
+ with {:ok, data} <- fetch_and_prepare_user_from_ap_id(ap_id, additional) do
+ {:ok, _pid} = Task.start(fn -> pinned_fetch_task(data) end)
+
if user do
user
|> User.remote_user_changeset(data)
@@ -1455,8 +1765,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
end
def make_user_from_nickname(nickname) do
- with {:ok, %{"ap_id" => ap_id}} when not is_nil(ap_id) <- WebFinger.finger(nickname) do
- make_user_from_ap_id(ap_id)
+ with {:ok, %{"ap_id" => ap_id, "subject" => "acct:" <> acct}} when not is_nil(ap_id) <-
+ WebFinger.finger(nickname) do
+ make_user_from_ap_id(ap_id, nickname_from_acct: acct)
else
_e -> {:error, "No AP id in WebFinger"}
end
@@ -1478,4 +1789,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|> restrict_visibility(%{visibility: "direct"})
|> order_by([activity], asc: activity.id)
end
+
+ defp maybe_restrict_deactivated_users(activity, %{type: "Flag"}), do: activity
+
+ defp maybe_restrict_deactivated_users(activity, _opts),
+ do: Activity.restrict_deactivated_users(activity)
end
diff --git a/lib/pleroma/web/activity_pub/activity_pub/persisting.ex b/lib/pleroma/web/activity_pub/activity_pub/persisting.ex
index 3894f48e2..3dbfdee28 100644
--- a/lib/pleroma/web/activity_pub/activity_pub/persisting.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub/persisting.ex
@@ -1,7 +1,7 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# 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
diff --git a/lib/pleroma/web/activity_pub/activity_pub/streaming.ex b/lib/pleroma/web/activity_pub/activity_pub/streaming.ex
index 30009f2fb..d7358171d 100644
--- a/lib/pleroma/web/activity_pub/activity_pub/streaming.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub/streaming.ex
@@ -1,12 +1,8 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# 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
diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex
index 7e5647f8f..1357c379c 100644
--- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.ActivityPubController do
@@ -11,7 +11,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
alias Pleroma.Object.Fetcher
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
- alias Pleroma.Web.ActivityPub.Builder
alias Pleroma.Web.ActivityPub.InternalFetchActor
alias Pleroma.Web.ActivityPub.ObjectView
alias Pleroma.Web.ActivityPub.Pipeline
@@ -67,8 +66,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
end
def user(conn, %{"nickname" => nickname}) do
- with %User{local: true} = user <- User.get_cached_by_nickname(nickname),
- {:ok, user} <- User.ensure_keys_present(user) do
+ with %User{local: true} = user <- User.get_cached_by_nickname(nickname) do
conn
|> put_resp_content_type("application/activity+json")
|> put_view(UserView)
@@ -79,23 +77,21 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
end
end
- def object(conn, _) do
+ def object(%{assigns: assigns} = conn, _) do
with ap_id <- Endpoint.url() <> conn.request_path,
%Object{} = object <- Object.get_cached_by_ap_id(ap_id),
- {_, true} <- {:public?, Visibility.is_public?(object)},
- {_, false} <- {:local?, Visibility.is_local_public?(object)} do
+ user <- Map.get(assigns, :user, nil),
+ {_, true} <- {:visible?, Visibility.visible_for_user?(object, user)} do
conn
+ |> maybe_skip_cache(user)
|> assign(:tracking_fun_data, object.id)
|> set_cache_ttl_for(object)
|> put_resp_content_type("application/activity+json")
|> put_view(ObjectView)
|> render("object.json", object: object)
else
- {:public?, false} ->
- {:error, :not_found}
-
- {:local?, true} ->
- {:error, :not_found}
+ {:visible?, false} -> {:error, :not_found}
+ nil -> {:error, :not_found}
end
end
@@ -109,26 +105,28 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
conn
end
- def activity(conn, _params) do
+ def activity(%{assigns: assigns} = conn, _) do
with ap_id <- Endpoint.url() <> conn.request_path,
%Activity{} = activity <- Activity.normalize(ap_id),
- {_, true} <- {:public?, Visibility.is_public?(activity)},
- {_, false} <- {:local?, Visibility.is_local_public?(activity)} do
+ {_, true} <- {:local?, activity.local},
+ user <- Map.get(assigns, :user, nil),
+ {_, true} <- {:visible?, Visibility.visible_for_user?(activity, user)} do
conn
+ |> maybe_skip_cache(user)
|> maybe_set_tracking_data(activity)
|> set_cache_ttl_for(activity)
|> put_resp_content_type("application/activity+json")
|> put_view(ObjectView)
|> render("object.json", object: activity)
else
- {:public?, false} -> {:error, :not_found}
- {:local?, true} -> {:error, :not_found}
+ {:visible?, false} -> {:error, :not_found}
+ {:local?, false} -> {:error, :not_found}
nil -> {:error, :not_found}
end
end
defp maybe_set_tracking_data(conn, %Activity{data: %{"type" => "Create"}} = activity) do
- object_id = Object.normalize(activity).id
+ object_id = Object.normalize(activity, fetch: false).id
assign(conn, :tracking_fun_data, object_id)
end
@@ -154,6 +152,15 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
assign(conn, :cache_ttl, ttl)
end
+ def maybe_skip_cache(conn, user) do
+ if user do
+ conn
+ |> assign(:skip_cache, true)
+ else
+ conn
+ end
+ end
+
# GET /relay/following
def relay_following(conn, _params) do
with %{halted: false} = conn <- FederatingPlug.call(conn, []) do
@@ -166,7 +173,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
def following(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname, "page" => page}) do
with %User{} = user <- User.get_cached_by_nickname(nickname),
- {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user),
{:show_follows, true} <-
{:show_follows, (for_user && for_user == user) || !user.hide_follows} do
{page, _} = Integer.parse(page)
@@ -184,8 +190,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
end
def following(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname}) do
- with %User{} = user <- User.get_cached_by_nickname(nickname),
- {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do
+ with %User{} = user <- User.get_cached_by_nickname(nickname) do
conn
|> put_resp_content_type("application/activity+json")
|> put_view(UserView)
@@ -205,7 +210,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
def followers(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname, "page" => page}) do
with %User{} = user <- User.get_cached_by_nickname(nickname),
- {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user),
{:show_followers, true} <-
{:show_followers, (for_user && for_user == user) || !user.hide_followers} do
{page, _} = Integer.parse(page)
@@ -223,8 +227,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
end
def followers(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname}) do
- with %User{} = user <- User.get_cached_by_nickname(nickname),
- {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do
+ with %User{} = user <- User.get_cached_by_nickname(nickname) do
conn
|> put_resp_content_type("application/activity+json")
|> put_view(UserView)
@@ -237,8 +240,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
%{"nickname" => nickname, "page" => page?} = params
)
when page? in [true, "true"] do
- with %User{} = user <- User.get_cached_by_nickname(nickname),
- {:ok, user} <- User.ensure_keys_present(user) do
+ with %User{} = user <- User.get_cached_by_nickname(nickname) do
# "include_poll_votes" is a hack because postgres generates inefficient
# queries when filtering by 'Answer', poll votes will be hidden by the
# visibility filter in this case anyway
@@ -262,8 +264,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
end
def outbox(conn, %{"nickname" => nickname}) do
- with %User{} = user <- User.get_cached_by_nickname(nickname),
- {:ok, user} <- User.ensure_keys_present(user) do
+ with %User{} = user <- User.get_cached_by_nickname(nickname) do
conn
|> put_resp_content_type("application/activity+json")
|> put_view(UserView)
@@ -286,15 +287,29 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
json(conn, "ok")
end
+ def inbox(%{assigns: %{valid_signature: false}} = conn, _params) do
+ conn
+ |> put_status(:bad_request)
+ |> json("Invalid HTTP Signature")
+ end
+
# POST /relay/inbox -or- POST /internal/fetch/inbox
- def inbox(conn, params) do
- if params["type"] == "Create" && FederatingPlug.federating?() do
+ def inbox(conn, %{"type" => "Create"} = params) do
+ if FederatingPlug.federating?() do
post_inbox_relayed_create(conn, params)
else
- post_inbox_fallback(conn, params)
+ conn
+ |> put_status(:bad_request)
+ |> json("Not federating")
end
end
+ def inbox(conn, _params) do
+ conn
+ |> put_status(:bad_request)
+ |> json("error, missing HTTP Signature")
+ end
+
defp post_inbox_relayed_create(conn, params) do
Logger.debug(
"Signature missing or not from author, relayed Create message, fetching object from source"
@@ -305,32 +320,11 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
json(conn, "ok")
end
- defp post_inbox_fallback(conn, params) do
- headers = Enum.into(conn.req_headers, %{})
-
- if headers["signature"] && params["actor"] &&
- String.contains?(headers["signature"], params["actor"]) do
- Logger.debug(
- "Signature validation error for: #{params["actor"]}, make sure you are forwarding the HTTP Host header!"
- )
-
- Logger.debug(inspect(conn.req_headers))
- end
-
- conn
- |> put_status(:bad_request)
- |> json(dgettext("errors", "error"))
- end
-
defp represent_service_actor(%User{} = user, conn) do
- with {:ok, user} <- User.ensure_keys_present(user) do
- conn
- |> put_resp_content_type("application/activity+json")
- |> put_view(UserView)
- |> render("user.json", %{user: user})
- else
- nil -> {:error, :not_found}
- end
+ conn
+ |> put_resp_content_type("application/activity+json")
+ |> put_view(UserView)
+ |> render("user.json", %{user: user})
end
defp represent_service_actor(nil, _), do: {:error, :not_found}
@@ -383,12 +377,10 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
def read_inbox(%{assigns: %{user: %User{nickname: nickname} = user}} = conn, %{
"nickname" => nickname
}) do
- with {:ok, user} <- User.ensure_keys_present(user) do
- conn
- |> put_resp_content_type("application/activity+json")
- |> put_view(UserView)
- |> render("activity_collection.json", %{iri: "#{user.ap_id}/inbox"})
- end
+ conn
+ |> put_resp_content_type("application/activity+json")
+ |> put_view(UserView)
+ |> render("activity_collection.json", %{iri: "#{user.ap_id}/inbox"})
end
def read_inbox(%{assigns: %{user: %User{nickname: as_nickname}}} = conn, %{
@@ -405,83 +397,90 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|> json(err)
end
- defp handle_user_activity(
- %User{} = user,
- %{"type" => "Create", "object" => %{"type" => "Note"} = object} = params
- ) do
- content = if is_binary(object["content"]), do: object["content"], else: ""
- name = if is_binary(object["name"]), do: object["name"], else: ""
- summary = if is_binary(object["summary"]), do: object["summary"], else: ""
- length = String.length(content <> name <> summary)
+ defp fix_user_message(%User{ap_id: actor}, %{"type" => "Create", "object" => object} = activity)
+ when is_map(object) do
+ length =
+ [object["content"], object["summary"], object["name"]]
+ |> Enum.filter(&is_binary(&1))
+ |> Enum.join("")
+ |> String.length()
- if length > Pleroma.Config.get([:instance, :limit]) do
- {:error, dgettext("errors", "Note is over the character limit")}
- else
+ limit = Pleroma.Config.get([:instance, :limit])
+
+ if length < limit do
object =
object
- |> Map.merge(Map.take(params, ["to", "cc"]))
- |> Map.put("attributedTo", user.ap_id)
- |> Transmogrifier.fix_object()
-
- ActivityPub.create(%{
- to: params["to"],
- actor: user,
- context: object["context"],
- object: object,
- additional: Map.take(params, ["cc"])
- })
- end
- end
+ |> Transmogrifier.strip_internal_fields()
+ |> Map.put("attributedTo", actor)
+ |> Map.put("actor", actor)
+ |> Map.put("id", Utils.generate_object_id())
- defp handle_user_activity(%User{} = user, %{"type" => "Delete"} = params) do
- with %Object{} = object <- Object.normalize(params["object"]),
- true <- user.is_moderator || user.ap_id == object.data["actor"],
- {:ok, delete_data, _} <- Builder.delete(user, object.data["id"]),
- {:ok, delete, _} <- Pipeline.common_pipeline(delete_data, local: true) do
- {:ok, delete}
+ {:ok, Map.put(activity, "object", object)}
else
- _ -> {:error, dgettext("errors", "Can't delete object")}
+ {:error,
+ dgettext(
+ "errors",
+ "Character limit (%{limit} characters) exceeded, contains %{length} characters",
+ limit: limit,
+ length: length
+ )}
end
end
- defp handle_user_activity(%User{} = user, %{"type" => "Like"} = params) do
- with %Object{} = object <- Object.normalize(params["object"]),
- {_, {:ok, like_object, meta}} <- {:build_object, Builder.like(user, object)},
- {_, {:ok, %Activity{} = activity, _meta}} <-
- {:common_pipeline,
- Pipeline.common_pipeline(like_object, Keyword.put(meta, :local, true))} do
+ defp fix_user_message(
+ %User{ap_id: actor} = user,
+ %{"type" => "Delete", "object" => object} = activity
+ ) do
+ with {_, %Object{data: object_data}} <- {:normalize, Object.normalize(object, fetch: false)},
+ {_, true} <- {:permission, user.is_moderator || actor == object_data["actor"]} do
{:ok, activity}
else
- _ -> {:error, dgettext("errors", "Can't like object")}
+ {:normalize, _} ->
+ {:error, "No such object found"}
+
+ {:permission, _} ->
+ {:forbidden, "You can't delete this object"}
end
end
- defp handle_user_activity(_, _) do
- {:error, dgettext("errors", "Unhandled activity type")}
+ defp fix_user_message(%User{}, activity) do
+ {:ok, activity}
end
def update_outbox(
- %{assigns: %{user: %User{nickname: nickname} = user}} = conn,
+ %{assigns: %{user: %User{nickname: nickname, ap_id: actor} = user}} = conn,
%{"nickname" => nickname} = params
) do
- actor = user.ap_id
-
params =
params
- |> Map.drop(["id"])
+ |> Map.drop(["nickname"])
+ |> Map.put("id", Utils.generate_activity_id())
|> Map.put("actor", actor)
- |> Transmogrifier.fix_addressing()
- with {:ok, %Activity{} = activity} <- handle_user_activity(user, params) do
+ with {:ok, params} <- fix_user_message(user, params),
+ {:ok, activity, _} <- Pipeline.common_pipeline(params, local: true),
+ %Activity{data: activity_data} <- Activity.normalize(activity) do
conn
|> put_status(:created)
- |> put_resp_header("location", activity.data["id"])
- |> json(activity.data)
+ |> put_resp_header("location", activity_data["id"])
+ |> json(activity_data)
else
+ {:forbidden, message} ->
+ conn
+ |> put_status(:forbidden)
+ |> json(message)
+
{:error, message} ->
conn
|> put_status(:bad_request)
|> json(message)
+
+ e ->
+ Logger.warn(fn -> "AP C2S: #{inspect(e)}" end)
+
+ conn
+ |> put_status(:bad_request)
+ |> json("Bad Request")
end
end
@@ -518,19 +517,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
conn
end
- defp ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do
- {:ok, new_user} = User.ensure_keys_present(user)
-
- for_user =
- if new_user != user and match?(%User{}, for_user) do
- User.get_cached_by_nickname(for_user.nickname)
- else
- for_user
- end
-
- {new_user, for_user}
- end
-
def upload_media(%{assigns: %{user: %User{} = user}} = conn, %{"file" => file} = data) do
with {:ok, object} <-
ActivityPub.upload(
@@ -545,4 +531,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
diff --git a/lib/pleroma/web/activity_pub/builder.ex b/lib/pleroma/web/activity_pub/builder.ex
index e99f6fd83..532047599 100644
--- a/lib/pleroma/web/activity_pub/builder.ex
+++ b/lib/pleroma/web/activity_pub/builder.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.Builder do
@@ -15,6 +15,7 @@ defmodule Pleroma.Web.ActivityPub.Builder do
alias Pleroma.Web.ActivityPub.Relay
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.ActivityPub.Visibility
+ alias Pleroma.Web.CommonAPI.ActivityDraft
require Pleroma.Constants
@@ -80,7 +81,7 @@ defmodule Pleroma.Web.ActivityPub.Builder do
@spec delete(User.t(), String.t()) :: {:ok, map(), keyword()}
def delete(actor, object_id) do
- object = Object.normalize(object_id, false)
+ object = Object.normalize(object_id, fetch: false)
user = !object && User.get_cached_by_ap_id(object_id)
@@ -125,6 +126,37 @@ defmodule Pleroma.Web.ActivityPub.Builder do
|> Pleroma.Maps.put_if_present("context", context), []}
end
+ @spec note(ActivityDraft.t()) :: {:ok, map(), keyword()}
+ def note(%ActivityDraft{} = draft) do
+ data =
+ %{
+ "type" => "Note",
+ "to" => draft.to,
+ "cc" => draft.cc,
+ "content" => draft.content_html,
+ "summary" => draft.summary,
+ "sensitive" => draft.sensitive,
+ "context" => draft.context,
+ "attachment" => draft.attachments,
+ "actor" => draft.user.ap_id,
+ "tag" => Keyword.values(draft.tags) |> Enum.uniq()
+ }
+ |> add_in_reply_to(draft.in_reply_to)
+ |> Map.merge(draft.extra)
+
+ {:ok, data, []}
+ end
+
+ defp add_in_reply_to(object, nil), do: object
+
+ defp add_in_reply_to(object, in_reply_to) do
+ with %Object{} = in_reply_to_object <- Object.normalize(in_reply_to, fetch: false) do
+ Map.put(object, "inReplyTo", in_reply_to_object.data["id"])
+ else
+ _ -> object
+ end
+ end
+
def chat_message(actor, recipient, content, opts \\ []) do
basic = %{
"id" => Utils.generate_object_id(),
@@ -186,10 +218,16 @@ defmodule Pleroma.Web.ActivityPub.Builder do
end
end
- # Retricted to user updates for now, always public
@spec update(User.t(), Object.t()) :: {:ok, map(), keyword()}
def update(actor, object) do
- to = [Pleroma.Constants.as_public(), actor.follower_address]
+ {to, cc} =
+ if object["type"] in Pleroma.Constants.actor_types() do
+ # User updates, always public
+ {[Pleroma.Constants.as_public(), actor.follower_address], []}
+ else
+ # Status updates, follow the recipients in the object
+ {object["to"] || [], object["cc"] || []}
+ end
{:ok,
%{
@@ -197,7 +235,8 @@ defmodule Pleroma.Web.ActivityPub.Builder do
"type" => "Update",
"actor" => actor.ap_id,
"object" => object,
- "to" => to
+ "to" => to,
+ "cc" => cc
}, []}
end
@@ -223,7 +262,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 +312,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
diff --git a/lib/pleroma/web/activity_pub/internal_fetch_actor.ex b/lib/pleroma/web/activity_pub/internal_fetch_actor.ex
index c80272b8f..083723894 100644
--- a/lib/pleroma/web/activity_pub/internal_fetch_actor.ex
+++ b/lib/pleroma/web/activity_pub/internal_fetch_actor.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.InternalFetchActor do
diff --git a/lib/pleroma/web/activity_pub/mrf.ex b/lib/pleroma/web/activity_pub/mrf.ex
index 02fdee5fc..ff9f84497 100644
--- a/lib/pleroma/web/activity_pub/mrf.ex
+++ b/lib/pleroma/web/activity_pub/mrf.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF do
@@ -21,7 +21,7 @@ defmodule Pleroma.Web.ActivityPub.MRF do
type: [:module, {:list, :module}],
description:
"A list of MRF policies enabled. Module names are shortened (removed leading `Pleroma.Web.ActivityPub.MRF.` part), but on adding custom module you need to use full name.",
- suggestions: {:list_behaviour_implementations, Pleroma.Web.ActivityPub.MRF}
+ suggestions: {:list_behaviour_implementations, Pleroma.Web.ActivityPub.MRF.Policy}
},
%{
key: :transparency,
@@ -33,9 +33,11 @@ defmodule Pleroma.Web.ActivityPub.MRF do
%{
key: :transparency_exclusions,
label: "MRF transparency exclusions",
- type: {:list, :string},
+ type: {:list, :tuple},
+ key_placeholder: "instance",
+ value_placeholder: "reason",
description:
- "Exclude specific instance names from MRF transparency. The use of the exclusions feature will be disclosed in nodeinfo as a boolean value.",
+ "Exclude specific instance names from MRF transparency. The use of the exclusions feature will be disclosed in nodeinfo as a boolean value. You can also provide a reason for excluding these instance names. The instances and reasons won't be publicly disclosed.",
suggestions: [
"exclusion.com"
]
@@ -51,21 +53,53 @@ defmodule Pleroma.Web.ActivityPub.MRF do
@required_description_keys [:key, :related_policy]
- @callback filter(Map.t()) :: {:ok | :reject, Map.t()}
- @callback describe() :: {:ok | :error, Map.t()}
- @callback config_description() :: %{
- optional(:children) => [map()],
- key: atom(),
- related_policy: String.t(),
- label: String.t(),
- description: String.t()
- }
- @optional_callbacks config_description: 0
+ def filter_one(policy, message) do
+ should_plug_history? =
+ if function_exported?(policy, :history_awareness, 0) do
+ policy.history_awareness()
+ else
+ :manual
+ end
+ |> Kernel.==(:auto)
+
+ if not should_plug_history? do
+ policy.filter(message)
+ else
+ main_result = policy.filter(message)
+
+ with {_, {:ok, main_message}} <- {:main, main_result},
+ {_,
+ %{
+ "formerRepresentations" => %{
+ "orderedItems" => [_ | _]
+ }
+ }} = {_, object} <- {:object, message["object"]},
+ {_, {:ok, new_history}} <-
+ {:history,
+ Pleroma.Object.Updater.for_each_history_item(
+ object["formerRepresentations"],
+ object,
+ fn item ->
+ with {:ok, filtered} <- policy.filter(Map.put(message, "object", item)) do
+ {:ok, filtered["object"]}
+ else
+ e -> e
+ end
+ end
+ )} do
+ {:ok, put_in(main_message, ["object", "formerRepresentations"], new_history)}
+ else
+ {:main, _} -> main_result
+ {:object, _} -> main_result
+ {:history, e} -> e
+ end
+ end
+ end
def filter(policies, %{} = message) do
policies
|> Enum.reduce({:ok, message}, fn
- policy, {:ok, message} -> policy.filter(message)
+ policy, {:ok, message} -> filter_one(policy, message)
_, error -> error
end)
end
@@ -92,7 +126,9 @@ defmodule Pleroma.Web.ActivityPub.MRF do
end
def get_policies do
- Pleroma.Config.get([:mrf, :policies], []) |> get_policies()
+ Pleroma.Config.get([:mrf, :policies], [])
+ |> get_policies()
+ |> Enum.concat([Pleroma.Web.ActivityPub.MRF.HashtagPolicy])
end
defp get_policies(policy) when is_atom(policy), do: [policy]
@@ -109,6 +145,11 @@ defmodule Pleroma.Web.ActivityPub.MRF do
Enum.any?(domains, fn domain -> Regex.match?(domain, host) end)
end
+ @spec instance_list_from_tuples([{String.t(), String.t()}]) :: [String.t()]
+ def instance_list_from_tuples(list) do
+ Enum.map(list, fn {instance, _} -> instance end)
+ end
+
def describe(policies) do
{:ok, policy_configs} =
policies
@@ -140,7 +181,7 @@ defmodule Pleroma.Web.ActivityPub.MRF do
def describe, do: get_policies() |> describe()
def config_descriptions do
- Pleroma.Web.ActivityPub.MRF
+ Pleroma.Web.ActivityPub.MRF.Policy
|> Pleroma.Docs.Generator.list_behaviour_implementations()
|> config_descriptions()
end
@@ -159,9 +200,7 @@ defmodule Pleroma.Web.ActivityPub.MRF do
[description | acc]
else
Logger.warn(
- "#{policy} config description doesn't have one or all required keys #{
- inspect(@required_description_keys)
- }"
+ "#{policy} config description doesn't have one or all required keys #{inspect(@required_description_keys)}"
)
acc
diff --git a/lib/pleroma/web/activity_pub/mrf/activity_expiration_policy.ex b/lib/pleroma/web/activity_pub/mrf/activity_expiration_policy.ex
index 655a2ced0..88f6ca028 100644
--- a/lib/pleroma/web/activity_pub/mrf/activity_expiration_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/activity_expiration_policy.ex
@@ -1,10 +1,10 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF.ActivityExpirationPolicy do
@moduledoc "Adds expiration to all local Create activities"
- @behaviour Pleroma.Web.ActivityPub.MRF
+ @behaviour Pleroma.Web.ActivityPub.MRF.Policy
@impl true
def filter(activity) do
diff --git a/lib/pleroma/web/activity_pub/mrf/anti_followbot_policy.ex b/lib/pleroma/web/activity_pub/mrf/anti_followbot_policy.ex
index b96388489..97d75ecf2 100644
--- a/lib/pleroma/web/activity_pub/mrf/anti_followbot_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/anti_followbot_policy.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF.AntiFollowbotPolicy do
@@ -7,7 +7,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.AntiFollowbotPolicy do
@moduledoc "Prevent followbots from following with a bit of heuristic"
- @behaviour Pleroma.Web.ActivityPub.MRF
+ @behaviour Pleroma.Web.ActivityPub.MRF.Policy
# XXX: this should become User.normalize_by_ap_id() or similar, really.
defp normalize_by_ap_id(%{"id" => id}), do: User.get_cached_by_ap_id(id)
@@ -24,7 +24,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.AntiFollowbotPolicy do
defp score_displayname("fedibot"), do: 1.0
defp score_displayname(_), do: 0.0
- defp determine_if_followbot(%User{nickname: nickname, name: displayname}) do
+ defp determine_if_followbot(%User{nickname: nickname, name: displayname, actor_type: actor_type}) do
# nickname will be a binary string except when following a relay
nick_score =
if is_binary(nickname) do
@@ -45,19 +45,32 @@ defmodule Pleroma.Web.ActivityPub.MRF.AntiFollowbotPolicy do
0.0
end
- nick_score + name_score
+ # actor_type "Service" is a Bot account
+ actor_type_score =
+ if actor_type == "Service" do
+ 1.0
+ else
+ 0.0
+ end
+
+ nick_score + name_score + actor_type_score
end
defp determine_if_followbot(_), do: 0.0
+ defp bot_allowed?(%{"object" => target}, bot_actor) do
+ %User{} = user = normalize_by_ap_id(target)
+
+ User.following?(user, bot_actor)
+ end
+
@impl true
def filter(%{"type" => "Follow", "actor" => actor_id} = message) do
%User{} = actor = normalize_by_ap_id(actor_id)
score = determine_if_followbot(actor)
- # TODO: scan biography data for keywords and score it somehow.
- if score < 0.8 do
+ if score < 0.8 || bot_allowed?(message, actor) do
{:ok, message}
else
{:reject, "[AntiFollowbotPolicy] Scored #{actor_id} as #{score}"}
diff --git a/lib/pleroma/web/activity_pub/mrf/anti_link_spam_policy.ex b/lib/pleroma/web/activity_pub/mrf/anti_link_spam_policy.ex
index b22464111..3ec9c52ee 100644
--- a/lib/pleroma/web/activity_pub/mrf/anti_link_spam_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/anti_link_spam_policy.ex
@@ -1,14 +1,17 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicy do
alias Pleroma.User
- @behaviour Pleroma.Web.ActivityPub.MRF
+ @behaviour Pleroma.Web.ActivityPub.MRF.Policy
require Logger
+ @impl true
+ def history_awareness, do: :auto
+
# has the user successfully posted before?
defp old_user?(%User{} = u) do
u.note_count > 0 || u.follower_count > 0
diff --git a/lib/pleroma/web/activity_pub/mrf/drop_policy.ex b/lib/pleroma/web/activity_pub/mrf/drop_policy.ex
index 5ab9844ff..ad0936839 100644
--- a/lib/pleroma/web/activity_pub/mrf/drop_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/drop_policy.ex
@@ -1,11 +1,11 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF.DropPolicy do
require Logger
@moduledoc "Drop and log everything received"
- @behaviour Pleroma.Web.ActivityPub.MRF
+ @behaviour Pleroma.Web.ActivityPub.MRF.Policy
@impl true
def filter(object) do
diff --git a/lib/pleroma/web/activity_pub/mrf/ensure_re_prepended.ex b/lib/pleroma/web/activity_pub/mrf/ensure_re_prepended.ex
index 3bf70b894..a148cc1e7 100644
--- a/lib/pleroma/web/activity_pub/mrf/ensure_re_prepended.ex
+++ b/lib/pleroma/web/activity_pub/mrf/ensure_re_prepended.ex
@@ -1,15 +1,17 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF.EnsureRePrepended do
alias Pleroma.Object
@moduledoc "Ensure a re: is prepended on replies to a post with a Subject"
- @behaviour Pleroma.Web.ActivityPub.MRF
+ @behaviour Pleroma.Web.ActivityPub.MRF.Policy
@reply_prefix Regex.compile!("^re:[[:space:]]*", [:caseless])
+ def history_awareness, do: :auto
+
def filter_by_summary(
%{data: %{"summary" => parent_summary}} = _in_reply_to,
%{"summary" => child_summary} = child
@@ -27,11 +29,11 @@ defmodule Pleroma.Web.ActivityPub.MRF.EnsureRePrepended do
def filter_by_summary(_in_reply_to, child), do: child
- def filter(%{"type" => "Create", "object" => child_object} = object)
- when is_map(child_object) do
+ def filter(%{"type" => type, "object" => child_object} = object)
+ when type in ["Create", "Update"] and is_map(child_object) do
child =
child_object["inReplyTo"]
- |> Object.normalize(child_object["inReplyTo"])
+ |> Object.normalize(fetch: false)
|> filter_by_summary(child_object)
object = Map.put(object, "object", child)
diff --git a/lib/pleroma/web/activity_pub/mrf/follow_bot_policy.ex b/lib/pleroma/web/activity_pub/mrf/follow_bot_policy.ex
new file mode 100644
index 000000000..5b6adbb4b
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/mrf/follow_bot_policy.ex
@@ -0,0 +1,63 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.MRF.FollowBotPolicy do
+ @behaviour Pleroma.Web.ActivityPub.MRF.Policy
+ alias Pleroma.Config
+ alias Pleroma.User
+ alias Pleroma.Web.CommonAPI
+
+ require Logger
+
+ @impl true
+ def filter(message) do
+ with follower_nickname <- Config.get([:mrf_follow_bot, :follower_nickname]),
+ %User{actor_type: "Service"} = follower <-
+ User.get_cached_by_nickname(follower_nickname),
+ %{"type" => "Create", "object" => %{"type" => "Note"}} <- message do
+ try_follow(follower, message)
+ else
+ nil ->
+ Logger.warn(
+ "#{__MODULE__} skipped because of missing `:mrf_follow_bot, :follower_nickname` configuration, the :follower_nickname
+ account does not exist, or the account is not correctly configured as a bot."
+ )
+
+ {:ok, message}
+
+ _ ->
+ {:ok, message}
+ end
+ end
+
+ defp try_follow(follower, message) do
+ to = Map.get(message, "to", [])
+ cc = Map.get(message, "cc", [])
+ actor = [message["actor"]]
+
+ Enum.concat([to, cc, actor])
+ |> List.flatten()
+ |> Enum.uniq()
+ |> User.get_all_by_ap_id()
+ |> Enum.each(fn user ->
+ with false <- user.local,
+ false <- User.following?(follower, user),
+ false <- User.locked?(user),
+ false <- (user.bio || "") |> String.downcase() |> String.contains?("nobot") do
+ Logger.debug(
+ "#{__MODULE__}: Follow request from #{follower.nickname} to #{user.nickname}"
+ )
+
+ CommonAPI.follow(follower, user)
+ end
+ end)
+
+ {:ok, message}
+ end
+
+ @impl true
+ def describe do
+ {:ok, %{}}
+ end
+end
diff --git a/lib/pleroma/web/activity_pub/mrf/force_bot_unlisted_policy.ex b/lib/pleroma/web/activity_pub/mrf/force_bot_unlisted_policy.ex
index ea9c3d3f5..8cec8eabe 100644
--- a/lib/pleroma/web/activity_pub/mrf/force_bot_unlisted_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/force_bot_unlisted_policy.ex
@@ -1,16 +1,16 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF.ForceBotUnlistedPolicy do
alias Pleroma.User
- @behaviour Pleroma.Web.ActivityPub.MRF
+ @behaviour Pleroma.Web.ActivityPub.MRF.Policy
@moduledoc "Remove bot posts from federated timeline"
require Pleroma.Constants
defp check_by_actor_type(user), do: user.actor_type in ["Application", "Service"]
- defp check_by_nickname(user), do: Regex.match?(~r/bot@|ebooks@/i, user.nickname)
+ defp check_by_nickname(user), do: Regex.match?(~r/.bot@|ebooks@/i, user.nickname)
defp check_if_bot(user), do: check_by_actor_type(user) or check_by_nickname(user)
diff --git a/lib/pleroma/web/activity_pub/mrf/force_mentions_in_content.ex b/lib/pleroma/web/activity_pub/mrf/force_mentions_in_content.ex
new file mode 100644
index 000000000..70224561c
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/mrf/force_mentions_in_content.ex
@@ -0,0 +1,135 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.MRF.ForceMentionsInContent do
+ require Pleroma.Constants
+
+ alias Pleroma.Formatter
+ alias Pleroma.Object
+ alias Pleroma.User
+
+ @behaviour Pleroma.Web.ActivityPub.MRF.Policy
+
+ @impl true
+ def history_awareness, do: :auto
+
+ defp do_extract({:a, attrs, _}, acc) do
+ if Enum.find(attrs, fn {name, value} ->
+ name == "class" && value in ["mention", "u-url mention", "mention u-url"]
+ end) do
+ href = Enum.find(attrs, fn {name, _} -> name == "href" end) |> elem(1)
+ acc ++ [href]
+ else
+ acc
+ end
+ end
+
+ defp do_extract({_, _, children}, acc) do
+ do_extract(children, acc)
+ end
+
+ defp do_extract(nodes, acc) when is_list(nodes) do
+ Enum.reduce(nodes, acc, fn node, acc -> do_extract(node, acc) end)
+ end
+
+ defp do_extract(_, acc), do: acc
+
+ defp extract_mention_uris_from_content(content) do
+ {:ok, tree} = :fast_html.decode(content, format: [:html_atoms])
+ do_extract(tree, [])
+ end
+
+ defp get_replied_to_user(%{"inReplyTo" => in_reply_to}) do
+ case Object.normalize(in_reply_to, fetch: false) do
+ %Object{data: %{"actor" => actor}} -> User.get_cached_by_ap_id(actor)
+ _ -> nil
+ end
+ end
+
+ defp get_replied_to_user(_object), do: nil
+
+ # Ensure the replied-to user is sorted to the left
+ defp sort_replied_user([%User{id: user_id} | _] = users, %User{id: user_id}), do: users
+
+ defp sort_replied_user(users, %User{id: user_id} = user) do
+ if Enum.find(users, fn u -> u.id == user_id end) do
+ users = Enum.reject(users, fn u -> u.id == user_id end)
+ [user | users]
+ else
+ users
+ end
+ end
+
+ defp sort_replied_user(users, _), do: users
+
+ # Drop constants and the actor's own AP ID
+ defp clean_recipients(recipients, object) do
+ Enum.reject(recipients, fn ap_id ->
+ ap_id in [
+ object["object"]["actor"],
+ Pleroma.Constants.as_public(),
+ Pleroma.Web.ActivityPub.Utils.as_local_public()
+ ]
+ end)
+ end
+
+ @impl true
+ def filter(
+ %{
+ "type" => type,
+ "object" => %{"type" => "Note", "to" => to, "inReplyTo" => in_reply_to}
+ } = object
+ )
+ when type in ["Create", "Update"] and is_list(to) and is_binary(in_reply_to) do
+ # image-only posts from pleroma apparently reach this MRF without the content field
+ content = object["object"]["content"] || ""
+
+ # Get the replied-to user for sorting
+ replied_to_user = get_replied_to_user(object["object"])
+
+ mention_users =
+ to
+ |> clean_recipients(object)
+ |> Enum.map(&User.get_cached_by_ap_id/1)
+ |> Enum.reject(&is_nil/1)
+ |> sort_replied_user(replied_to_user)
+
+ explicitly_mentioned_uris = extract_mention_uris_from_content(content)
+
+ added_mentions =
+ Enum.reduce(mention_users, "", fn %User{ap_id: uri} = user, acc ->
+ unless uri in explicitly_mentioned_uris do
+ acc <> Formatter.mention_from_user(user, %{mentions_format: :compact}) <> " "
+ else
+ acc
+ end
+ end)
+
+ recipients_inline =
+ if added_mentions != "",
+ do: "<span class=\"recipients-inline\">#{added_mentions}</span>",
+ else: ""
+
+ content =
+ cond do
+ # For Markdown posts, insert the mentions inside the first <p> tag
+ recipients_inline != "" && String.starts_with?(content, "<p>") ->
+ "<p>" <> recipients_inline <> String.trim_leading(content, "<p>")
+
+ recipients_inline != "" ->
+ recipients_inline <> content
+
+ true ->
+ content
+ end
+
+ {:ok, put_in(object["object"]["content"], content)}
+ end
+
+ @impl true
+ def filter(object), do: {:ok, object}
+
+ @impl true
+ def describe, do: {:ok, %{}}
+end
diff --git a/lib/pleroma/web/activity_pub/mrf/hashtag_policy.ex b/lib/pleroma/web/activity_pub/mrf/hashtag_policy.ex
new file mode 100644
index 000000000..b73fd974c
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/mrf/hashtag_policy.ex
@@ -0,0 +1,143 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.MRF.HashtagPolicy do
+ require Pleroma.Constants
+
+ alias Pleroma.Config
+ alias Pleroma.Object
+
+ @moduledoc """
+ Reject, TWKN-remove or Set-Sensitive messsages with specific hashtags (without the leading #)
+
+ Note: This MRF Policy is always enabled, if you want to disable it you have to set empty lists.
+ """
+
+ @behaviour Pleroma.Web.ActivityPub.MRF.Policy
+
+ @impl true
+ def history_awareness, do: :manual
+
+ defp check_reject(message, hashtags) do
+ if Enum.any?(Config.get([:mrf_hashtag, :reject]), fn match -> match in hashtags end) do
+ {:reject, "[HashtagPolicy] Matches with rejected keyword"}
+ else
+ {:ok, message}
+ end
+ end
+
+ defp check_ftl_removal(%{"to" => to} = message, hashtags) do
+ if Pleroma.Constants.as_public() in to and
+ Enum.any?(Config.get([:mrf_hashtag, :federated_timeline_removal]), fn match ->
+ match in hashtags
+ end) do
+ to = List.delete(to, Pleroma.Constants.as_public())
+ cc = [Pleroma.Constants.as_public() | message["cc"] || []]
+
+ message =
+ message
+ |> Map.put("to", to)
+ |> Map.put("cc", cc)
+ |> Kernel.put_in(["object", "to"], to)
+ |> Kernel.put_in(["object", "cc"], cc)
+
+ {:ok, message}
+ else
+ {:ok, message}
+ end
+ end
+
+ defp check_ftl_removal(message, _hashtags), do: {:ok, message}
+
+ defp check_sensitive(message) do
+ {:ok, new_object} =
+ Object.Updater.do_with_history(message["object"], fn object ->
+ hashtags = Object.hashtags(%Object{data: object})
+
+ if Enum.any?(Config.get([:mrf_hashtag, :sensitive]), fn match -> match in hashtags end) do
+ {:ok, Map.put(object, "sensitive", true)}
+ else
+ {:ok, object}
+ end
+ end)
+
+ {:ok, Map.put(message, "object", new_object)}
+ end
+
+ @impl true
+ def filter(%{"type" => type, "object" => object} = message) when type in ["Create", "Update"] do
+ history_items =
+ with %{"formerRepresentations" => %{"orderedItems" => items}} <- object do
+ items
+ else
+ _ -> []
+ end
+
+ historical_hashtags =
+ Enum.reduce(history_items, [], fn item, acc ->
+ acc ++ Object.hashtags(%Object{data: item})
+ end)
+
+ hashtags = Object.hashtags(%Object{data: object}) ++ historical_hashtags
+
+ if hashtags != [] do
+ with {:ok, message} <- check_reject(message, hashtags),
+ {:ok, message} <-
+ (if "type" == "Create" do
+ check_ftl_removal(message, hashtags)
+ else
+ {:ok, message}
+ end),
+ {:ok, message} <- check_sensitive(message) do
+ {:ok, message}
+ end
+ else
+ {:ok, message}
+ end
+ end
+
+ @impl true
+ def filter(message), do: {:ok, message}
+
+ @impl true
+ def describe do
+ mrf_hashtag =
+ Config.get(:mrf_hashtag)
+ |> Enum.into(%{})
+
+ {:ok, %{mrf_hashtag: mrf_hashtag}}
+ end
+
+ @impl true
+ def config_description do
+ %{
+ key: :mrf_hashtag,
+ related_policy: "Pleroma.Web.ActivityPub.MRF.HashtagPolicy",
+ label: "MRF Hashtag",
+ description: @moduledoc,
+ children: [
+ %{
+ key: :reject,
+ type: {:list, :string},
+ description: "A list of hashtags which result in message being rejected.",
+ suggestions: ["foo"]
+ },
+ %{
+ key: :federated_timeline_removal,
+ type: {:list, :string},
+ description:
+ "A list of hashtags which result in message being removed from federated timelines (a.k.a unlisted).",
+ suggestions: ["foo"]
+ },
+ %{
+ key: :sensitive,
+ type: {:list, :string},
+ description:
+ "A list of hashtags which result in message being set as sensitive (a.k.a NSFW/R-18)",
+ suggestions: ["nsfw", "r18"]
+ }
+ ]
+ }
+ end
+end
diff --git a/lib/pleroma/web/activity_pub/mrf/hellthread_policy.ex b/lib/pleroma/web/activity_pub/mrf/hellthread_policy.ex
index 3fd5c1e0a..80e235d6e 100644
--- a/lib/pleroma/web/activity_pub/mrf/hellthread_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/hellthread_policy.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF.HellthreadPolicy do
@@ -9,7 +9,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.HellthreadPolicy do
@moduledoc "Block messages with too much mentions (configurable)"
- @behaviour Pleroma.Web.ActivityPub.MRF
+ @behaviour Pleroma.Web.ActivityPub.MRF.Policy
defp delist_message(message, threshold) when threshold > 0 do
follower_collection = User.get_cached_by_ap_id(message["actor"]).follower_address
diff --git a/lib/pleroma/web/activity_pub/mrf/keyword_policy.ex b/lib/pleroma/web/activity_pub/mrf/keyword_policy.ex
index ded0fe7f2..687ec6c2f 100644
--- a/lib/pleroma/web/activity_pub/mrf/keyword_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/keyword_policy.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do
@@ -7,7 +7,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do
@moduledoc "Reject or Word-Replace messages with a keyword or regex"
- @behaviour Pleroma.Web.ActivityPub.MRF
+ @behaviour Pleroma.Web.ActivityPub.MRF.Policy
defp string_matches?(string, _) when not is_binary(string) do
false
end
@@ -27,24 +27,46 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do
end
defp check_reject(%{"object" => %{} = object} = message) do
- payload = object_payload(object)
-
- if Enum.any?(Pleroma.Config.get([:mrf_keyword, :reject]), fn pattern ->
- string_matches?(payload, pattern)
- end) do
- {:reject, "[KeywordPolicy] Matches with rejected keyword"}
- else
+ with {:ok, _new_object} <-
+ Pleroma.Object.Updater.do_with_history(object, fn object ->
+ payload = object_payload(object)
+
+ if Enum.any?(Pleroma.Config.get([:mrf_keyword, :reject]), fn pattern ->
+ string_matches?(payload, pattern)
+ end) do
+ {:reject, "[KeywordPolicy] Matches with rejected keyword"}
+ else
+ {:ok, message}
+ end
+ end) do
{:ok, message}
+ else
+ e -> e
end
end
- defp check_ftl_removal(%{"to" => to, "object" => %{} = object} = message) do
- payload = object_payload(object)
+ defp check_ftl_removal(%{"type" => "Create", "to" => to, "object" => %{} = object} = message) do
+ check_keyword = fn object ->
+ payload = object_payload(object)
- if Pleroma.Constants.as_public() in to and
- Enum.any?(Pleroma.Config.get([:mrf_keyword, :federated_timeline_removal]), fn pattern ->
+ if Enum.any?(Pleroma.Config.get([:mrf_keyword, :federated_timeline_removal]), fn pattern ->
string_matches?(payload, pattern)
end) do
+ {:should_delist, nil}
+ else
+ {:ok, %{}}
+ end
+ end
+
+ should_delist? = fn object ->
+ with {:ok, _} <- Pleroma.Object.Updater.do_with_history(object, check_keyword) do
+ false
+ else
+ _ -> true
+ end
+ end
+
+ if Pleroma.Constants.as_public() in to and should_delist?.(object) do
to = List.delete(to, Pleroma.Constants.as_public())
cc = [Pleroma.Constants.as_public() | message["cc"] || []]
@@ -59,8 +81,12 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do
end
end
+ defp check_ftl_removal(message) do
+ {:ok, message}
+ end
+
defp check_replace(%{"object" => %{} = object} = message) do
- object =
+ replace_kw = fn object ->
["content", "name", "summary"]
|> Enum.filter(fn field -> Map.has_key?(object, field) && object[field] end)
|> Enum.reduce(object, fn field, object ->
@@ -73,6 +99,10 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do
Map.put(object, field, data)
end)
+ |> (fn object -> {:ok, object} end).()
+ end
+
+ {:ok, object} = Pleroma.Object.Updater.do_with_history(object, replace_kw)
message = Map.put(message, "object", object)
@@ -80,7 +110,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do
end
@impl true
- def filter(%{"type" => "Create", "object" => %{"content" => _content}} = message) do
+ def filter(%{"type" => type, "object" => %{"content" => _content}} = message)
+ when type in ["Create", "Update"] do
with {:ok, message} <- check_reject(message),
{:ok, message} <- check_ftl_removal(message),
{:ok, message} <- check_replace(message) do
@@ -159,6 +190,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do
%{
key: :replace,
type: {:list, :tuple},
+ key_placeholder: "instance",
+ value_placeholder: "reason",
description: """
**Pattern**: a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`.
diff --git a/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex b/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex
index 816cc89bf..c95d35bb9 100644
--- a/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex
@@ -1,10 +1,10 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy do
@moduledoc "Preloads any attachments in the MediaProxy cache by prefetching them"
- @behaviour Pleroma.Web.ActivityPub.MRF
+ @behaviour Pleroma.Web.ActivityPub.MRF.Policy
alias Pleroma.HTTP
alias Pleroma.Web.MediaProxy
@@ -16,6 +16,9 @@ defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy do
recv_timeout: 10_000
]
+ @impl true
+ def history_awareness, do: :auto
+
defp prefetch(url) do
# Fetching only proxiable resources
if MediaProxy.enabled?() and MediaProxy.url_proxiable?(url) do
@@ -27,7 +30,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy do
if Pleroma.Config.get(:env) == :test do
fetch(prefetch_url)
else
- ConcurrentLimiter.limit(MediaProxy, fn ->
+ ConcurrentLimiter.limit(__MODULE__, fn ->
Task.start(fn -> fetch(prefetch_url) end)
end)
end
@@ -54,10 +57,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy do
end
@impl true
- def filter(
- %{"type" => "Create", "object" => %{"attachment" => attachments} = _object} = message
- )
- when is_list(attachments) and length(attachments) > 0 do
+ def filter(%{"type" => type, "object" => %{"attachment" => attachments} = _object} = message)
+ when type in ["Create", "Update"] and is_list(attachments) and length(attachments) > 0 do
preload(message)
{:ok, message}
diff --git a/lib/pleroma/web/activity_pub/mrf/mention_policy.ex b/lib/pleroma/web/activity_pub/mrf/mention_policy.ex
index 9c096712a..8aa4f347f 100644
--- a/lib/pleroma/web/activity_pub/mrf/mention_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/mention_policy.ex
@@ -1,11 +1,11 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF.MentionPolicy do
@moduledoc "Block messages which mention a user"
- @behaviour Pleroma.Web.ActivityPub.MRF
+ @behaviour Pleroma.Web.ActivityPub.MRF.Policy
@impl true
def filter(%{"type" => "Create"} = message) do
diff --git a/lib/pleroma/web/activity_pub/mrf/no_empty_policy.ex b/lib/pleroma/web/activity_pub/mrf/no_empty_policy.ex
new file mode 100644
index 000000000..855cda3b9
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/mrf/no_empty_policy.ex
@@ -0,0 +1,70 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.MRF.NoEmptyPolicy do
+ @moduledoc "Filter local activities which have no content"
+ @behaviour Pleroma.Web.ActivityPub.MRF.Policy
+
+ alias Pleroma.Web.Endpoint
+
+ @impl true
+ def filter(%{"actor" => actor} = object) do
+ with true <- is_local?(actor),
+ true <- is_eligible_type?(object),
+ true <- is_note?(object),
+ false <- has_attachment?(object),
+ true <- only_mentions?(object) do
+ {:reject, "[NoEmptyPolicy]"}
+ else
+ _ ->
+ {:ok, object}
+ end
+ end
+
+ def filter(object), do: {:ok, object}
+
+ defp is_local?(actor) do
+ if actor |> String.starts_with?("#{Endpoint.url()}") do
+ true
+ else
+ false
+ end
+ end
+
+ defp has_attachment?(%{
+ "object" => %{"type" => "Note", "attachment" => attachments}
+ })
+ when length(attachments) > 0,
+ do: true
+
+ defp has_attachment?(_), do: false
+
+ defp only_mentions?(%{"object" => %{"type" => "Note", "source" => source}}) do
+ source =
+ case source do
+ %{"content" => text} -> text
+ _ -> source
+ end
+
+ non_mentions =
+ source |> String.split() |> Enum.filter(&(not String.starts_with?(&1, "@"))) |> length
+
+ if non_mentions > 0 do
+ false
+ else
+ true
+ end
+ end
+
+ defp only_mentions?(_), do: false
+
+ defp is_note?(%{"object" => %{"type" => "Note"}}), do: true
+ defp is_note?(_), do: false
+
+ defp is_eligible_type?(%{"type" => type}) when type in ["Create", "Update"], do: true
+ defp is_eligible_type?(_), do: false
+
+ @impl true
+ def describe, do: {:ok, %{}}
+end
diff --git a/lib/pleroma/web/activity_pub/mrf/no_op_policy.ex b/lib/pleroma/web/activity_pub/mrf/no_op_policy.ex
index cc2ac9d08..8840c4fac 100644
--- a/lib/pleroma/web/activity_pub/mrf/no_op_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/no_op_policy.ex
@@ -1,10 +1,10 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF.NoOpPolicy do
@moduledoc "Does nothing (lets the messages go through unmodified)"
- @behaviour Pleroma.Web.ActivityPub.MRF
+ @behaviour Pleroma.Web.ActivityPub.MRF.Policy
@impl true
def filter(object) do
diff --git a/lib/pleroma/web/activity_pub/mrf/no_placeholder_text_policy.ex b/lib/pleroma/web/activity_pub/mrf/no_placeholder_text_policy.ex
index fc3475048..f81e9e52a 100644
--- a/lib/pleroma/web/activity_pub/mrf/no_placeholder_text_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/no_placeholder_text_policy.ex
@@ -1,19 +1,22 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF.NoPlaceholderTextPolicy do
@moduledoc "Ensure no content placeholder is present (such as the dot from mastodon)"
- @behaviour Pleroma.Web.ActivityPub.MRF
+ @behaviour Pleroma.Web.ActivityPub.MRF.Policy
+
+ @impl true
+ def history_awareness, do: :auto
@impl true
def filter(
%{
- "type" => "Create",
+ "type" => type,
"object" => %{"content" => content, "attachment" => _} = _child_object
} = object
)
- when content in [".", "<p>.</p>"] do
+ when type in ["Create", "Update"] and content in [".", "<p>.</p>"] do
{:ok, put_in(object, ["object", "content"], "")}
end
diff --git a/lib/pleroma/web/activity_pub/mrf/normalize_markup.ex b/lib/pleroma/web/activity_pub/mrf/normalize_markup.ex
index e00575c2a..2dfc9a901 100644
--- a/lib/pleroma/web/activity_pub/mrf/normalize_markup.ex
+++ b/lib/pleroma/web/activity_pub/mrf/normalize_markup.ex
@@ -1,15 +1,19 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF.NormalizeMarkup do
@moduledoc "Scrub configured hypertext markup"
alias Pleroma.HTML
- @behaviour Pleroma.Web.ActivityPub.MRF
+ @behaviour Pleroma.Web.ActivityPub.MRF.Policy
@impl true
- def filter(%{"type" => "Create", "object" => child_object} = object) do
+ def history_awareness, do: :auto
+
+ @impl true
+ def filter(%{"type" => type, "object" => child_object} = object)
+ when type in ["Create", "Update"] do
scrub_policy = Pleroma.Config.get([:mrf_normalize_markup, :scrub_policy])
content =
diff --git a/lib/pleroma/web/activity_pub/mrf/object_age_policy.ex b/lib/pleroma/web/activity_pub/mrf/object_age_policy.ex
index eb0481f20..df1a6dcbb 100644
--- a/lib/pleroma/web/activity_pub/mrf/object_age_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/object_age_policy.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy do
@@ -9,7 +9,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy do
require Pleroma.Constants
@moduledoc "Filter activities depending on their age"
- @behaviour Pleroma.Web.ActivityPub.MRF
+ @behaviour Pleroma.Web.ActivityPub.MRF.Policy
defp check_date(%{"object" => %{"published" => published}} = message) do
with %DateTime{} = now <- DateTime.utc_now(),
@@ -49,6 +49,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy do
message
|> Map.put("to", to)
|> Map.put("cc", cc)
+ |> Kernel.put_in(["object", "to"], to)
+ |> Kernel.put_in(["object", "cc"], cc)
{:ok, message}
else
@@ -70,6 +72,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy do
message
|> Map.put("to", to)
|> Map.put("cc", cc)
+ |> Kernel.put_in(["object", "to"], to)
+ |> Kernel.put_in(["object", "cc"], cc)
{:ok, message}
else
@@ -82,7 +86,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy do
end
@impl true
- def filter(%{"type" => "Create", "published" => _} = message) do
+ def filter(%{"type" => "Create", "object" => %{"published" => _}} = message) do
with actions <- Config.get([:mrf_object_age, :actions]),
{:reject, _} <- check_date(message),
{:ok, message} <- check_reject(message, actions),
@@ -127,7 +131,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy do
type: {:list, :atom},
description:
"A list of actions to apply to the post. `:delist` removes the post from public timelines; " <>
- "`:strip_followers` removes followers from the ActivityPub recipient list ensuring they won't be delivered to home timelines; " <>
+ "`:strip_followers` removes followers from the ActivityPub recipient list ensuring they won't be delivered to home timelines, additionally for followers-only it degrades to a direct message; " <>
"`:reject` rejects the message entirely",
suggestions: [:delist, :strip_followers, :reject]
}
diff --git a/lib/pleroma/web/activity_pub/mrf/pipeline_filtering.ex b/lib/pleroma/web/activity_pub/mrf/pipeline_filtering.ex
index 8e0069bc5..b2477fed4 100644
--- a/lib/pleroma/web/activity_pub/mrf/pipeline_filtering.ex
+++ b/lib/pleroma/web/activity_pub/mrf/pipeline_filtering.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF.PipelineFiltering do
diff --git a/lib/pleroma/web/activity_pub/mrf/policy.ex b/lib/pleroma/web/activity_pub/mrf/policy.ex
new file mode 100644
index 000000000..0234de4d5
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/mrf/policy.ex
@@ -0,0 +1,17 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.MRF.Policy do
+ @callback filter(Map.t()) :: {:ok | :reject, Map.t()}
+ @callback describe() :: {:ok | :error, Map.t()}
+ @callback config_description() :: %{
+ optional(:children) => [map()],
+ key: atom(),
+ related_policy: String.t(),
+ label: String.t(),
+ description: String.t()
+ }
+ @callback history_awareness() :: :auto | :manual
+ @optional_callbacks config_description: 0, history_awareness: 0
+end
diff --git a/lib/pleroma/web/activity_pub/mrf/reject_non_public.ex b/lib/pleroma/web/activity_pub/mrf/reject_non_public.ex
index cd7665e31..9d4a7a405 100644
--- a/lib/pleroma/web/activity_pub/mrf/reject_non_public.ex
+++ b/lib/pleroma/web/activity_pub/mrf/reject_non_public.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF.RejectNonPublic do
@@ -8,7 +8,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.RejectNonPublic do
alias Pleroma.Config
alias Pleroma.User
- @behaviour Pleroma.Web.ActivityPub.MRF
+ @behaviour Pleroma.Web.ActivityPub.MRF.Policy
require Pleroma.Constants
@@ -47,7 +47,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.RejectNonPublic do
@impl true
def describe,
- do: {:ok, %{mrf_rejectnonpublic: Config.get(:mrf_rejectnonpublic) |> Enum.into(%{})}}
+ do: {:ok, %{mrf_rejectnonpublic: Config.get(:mrf_rejectnonpublic) |> Map.new()}}
@impl true
def config_description do
diff --git a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex
index 6cd91826d..829ddeaea 100644
--- a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex
@@ -1,10 +1,10 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
@moduledoc "Filter activities depending on their origin instance"
- @behaviour Pleroma.Web.ActivityPub.MRF
+ @behaviour Pleroma.Web.ActivityPub.MRF.Policy
alias Pleroma.Config
alias Pleroma.FollowingRelationship
@@ -15,7 +15,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
defp check_accept(%{host: actor_host} = _actor_info, object) do
accepts =
- Config.get([:mrf_simple, :accept])
+ instance_list(:accept)
|> MRF.subdomains_regex()
cond do
@@ -28,7 +28,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
defp check_reject(%{host: actor_host} = _actor_info, object) do
rejects =
- Config.get([:mrf_simple, :reject])
+ instance_list(:reject)
|> MRF.subdomains_regex()
if MRF.subdomain_match?(rejects, actor_host) do
@@ -40,11 +40,11 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
defp check_media_removal(
%{host: actor_host} = _actor_info,
- %{"type" => "Create", "object" => %{"attachment" => child_attachment}} = object
+ %{"type" => type, "object" => %{"attachment" => child_attachment}} = object
)
- when length(child_attachment) > 0 do
+ when length(child_attachment) > 0 and type in ["Create", "Update"] do
media_removal =
- Config.get([:mrf_simple, :media_removal])
+ instance_list(:media_removal)
|> MRF.subdomains_regex()
object =
@@ -63,21 +63,18 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
defp check_media_nsfw(
%{host: actor_host} = _actor_info,
%{
- "type" => "Create",
- "object" => child_object
+ "type" => type,
+ "object" => %{} = _child_object
} = object
)
- when is_map(child_object) do
+ when type in ["Create", "Update"] do
media_nsfw =
- Config.get([:mrf_simple, :media_nsfw])
+ instance_list(:media_nsfw)
|> MRF.subdomains_regex()
object =
if MRF.subdomain_match?(media_nsfw, actor_host) do
- tags = (child_object["tag"] || []) ++ ["nsfw"]
- child_object = Map.put(child_object, "tag", tags)
- child_object = Map.put(child_object, "sensitive", true)
- Map.put(object, "object", child_object)
+ Kernel.put_in(object, ["object", "sensitive"], true)
else
object
end
@@ -89,7 +86,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
defp check_ftl_removal(%{host: actor_host} = _actor_info, object) do
timeline_removal =
- Config.get([:mrf_simple, :federated_timeline_removal])
+ instance_list(:federated_timeline_removal)
|> MRF.subdomains_regex()
object =
@@ -116,7 +113,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
defp check_followers_only(%{host: actor_host} = _actor_info, object) do
followers_only =
- Config.get([:mrf_simple, :followers_only])
+ instance_list(:followers_only)
|> MRF.subdomains_regex()
object =
@@ -141,7 +138,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
defp check_report_removal(%{host: actor_host} = _actor_info, %{"type" => "Flag"} = object) do
report_removal =
- Config.get([:mrf_simple, :report_removal])
+ instance_list(:report_removal)
|> MRF.subdomains_regex()
if MRF.subdomain_match?(report_removal, actor_host) do
@@ -155,7 +152,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
defp check_avatar_removal(%{host: actor_host} = _actor_info, %{"icon" => _icon} = object) do
avatar_removal =
- Config.get([:mrf_simple, :avatar_removal])
+ instance_list(:avatar_removal)
|> MRF.subdomains_regex()
if MRF.subdomain_match?(avatar_removal, actor_host) do
@@ -169,7 +166,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
defp check_banner_removal(%{host: actor_host} = _actor_info, %{"image" => _image} = object) do
banner_removal =
- Config.get([:mrf_simple, :banner_removal])
+ instance_list(:banner_removal)
|> MRF.subdomains_regex()
if MRF.subdomain_match?(banner_removal, actor_host) do
@@ -181,12 +178,25 @@ 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}
+
+ defp instance_list(config_key) do
+ Config.get([:mrf_simple, config_key])
+ |> MRF.instance_list_from_tuples()
+ end
+
@impl true
def filter(%{"type" => "Delete", "actor" => actor} = object) do
%{host: actor_host} = URI.parse(actor)
reject_deletes =
- Config.get([:mrf_simple, :reject_deletes])
+ instance_list(:reject_deletes)
|> MRF.subdomains_regex()
if MRF.subdomain_match?(reject_deletes, actor_host) do
@@ -206,7 +216,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]"}
@@ -231,18 +242,59 @@ 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
def describe do
- exclusions = Config.get([:mrf, :transparency_exclusions])
+ exclusions = Config.get([:mrf, :transparency_exclusions]) |> MRF.instance_list_from_tuples()
- mrf_simple =
+ mrf_simple_excluded =
Config.get(:mrf_simple)
- |> Enum.map(fn {k, v} -> {k, Enum.reject(v, fn v -> v in exclusions end)} end)
- |> Enum.into(%{})
+ |> Enum.map(fn {rule, instances} ->
+ {rule, Enum.reject(instances, fn {host, _} -> host in exclusions end)}
+ end)
- {:ok, %{mrf_simple: mrf_simple}}
+ mrf_simple =
+ mrf_simple_excluded
+ |> Enum.map(fn {rule, instances} ->
+ {rule, Enum.map(instances, fn {host, _} -> host end)}
+ end)
+ |> Map.new()
+
+ # This is for backwards compatibility. We originally didn't sent
+ # extra info like a reason why an instance was rejected/quarantined/etc.
+ # Because we didn't want to break backwards compatibility it was decided
+ # to add an extra "info" key.
+ mrf_simple_info =
+ mrf_simple_excluded
+ |> Enum.map(fn {rule, instances} ->
+ {rule, Enum.reject(instances, fn {_, reason} -> reason == "" end)}
+ end)
+ |> Enum.reject(fn {_, instances} -> instances == [] end)
+ |> Enum.map(fn {rule, instances} ->
+ instances =
+ instances
+ |> Enum.map(fn {host, reason} -> {host, %{"reason" => reason}} end)
+ |> Map.new()
+
+ {rule, instances}
+ end)
+ |> Map.new()
+
+ {:ok, %{mrf_simple: mrf_simple, mrf_simple_info: mrf_simple_info}}
end
@impl true
@@ -252,70 +304,67 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
related_policy: "Pleroma.Web.ActivityPub.MRF.SimplePolicy",
label: "MRF Simple",
description: "Simple ingress policies",
- children: [
- %{
- key: :media_removal,
- type: {:list, :string},
- description: "List of instances to strip media attachments from",
- suggestions: ["example.com", "*.example.com"]
- },
- %{
- key: :media_nsfw,
- label: "Media NSFW",
- type: {:list, :string},
- description: "List of instances to tag all media as NSFW (sensitive) from",
- suggestions: ["example.com", "*.example.com"]
- },
- %{
- key: :federated_timeline_removal,
- type: {:list, :string},
- description:
- "List of instances to remove from the Federated (aka The Whole Known Network) Timeline",
- suggestions: ["example.com", "*.example.com"]
- },
- %{
- key: :reject,
- type: {:list, :string},
- description: "List of instances to reject activities from (except deletes)",
- suggestions: ["example.com", "*.example.com"]
- },
- %{
- key: :accept,
- type: {:list, :string},
- description: "List of instances to only accept activities from (except deletes)",
- suggestions: ["example.com", "*.example.com"]
- },
- %{
- key: :followers_only,
- type: {:list, :string},
- description: "Force posts from the given instances to be visible by followers only",
- suggestions: ["example.com", "*.example.com"]
- },
- %{
- key: :report_removal,
- type: {:list, :string},
- description: "List of instances to reject reports from",
- suggestions: ["example.com", "*.example.com"]
- },
- %{
- key: :avatar_removal,
- type: {:list, :string},
- description: "List of instances to strip avatars from",
- suggestions: ["example.com", "*.example.com"]
- },
- %{
- key: :banner_removal,
- type: {:list, :string},
- description: "List of instances to strip banners from",
- suggestions: ["example.com", "*.example.com"]
- },
- %{
- key: :reject_deletes,
- type: {:list, :string},
- description: "List of instances to reject deletions from",
- suggestions: ["example.com", "*.example.com"]
- }
- ]
+ children:
+ [
+ %{
+ key: :media_removal,
+ description:
+ "List of instances to strip media attachments from and the reason for doing so"
+ },
+ %{
+ key: :media_nsfw,
+ label: "Media NSFW",
+ description:
+ "List of instances to tag all media as NSFW (sensitive) from and the reason for doing so"
+ },
+ %{
+ key: :federated_timeline_removal,
+ description:
+ "List of instances to remove from the Federated (aka The Whole Known Network) Timeline and the reason for doing so"
+ },
+ %{
+ key: :reject,
+ description:
+ "List of instances to reject activities from (except deletes) and the reason for doing so"
+ },
+ %{
+ key: :accept,
+ description:
+ "List of instances to only accept activities from (except deletes) and the reason for doing so"
+ },
+ %{
+ key: :followers_only,
+ description:
+ "Force posts from the given instances to be visible by followers only and the reason for doing so"
+ },
+ %{
+ key: :report_removal,
+ description: "List of instances to reject reports from and the reason for doing so"
+ },
+ %{
+ key: :avatar_removal,
+ description: "List of instances to strip avatars from and the reason for doing so"
+ },
+ %{
+ key: :banner_removal,
+ description: "List of instances to strip banners from and the reason for doing so"
+ },
+ %{
+ key: :reject_deletes,
+ description: "List of instances to reject deletions from and the reason for doing so"
+ }
+ ]
+ |> Enum.map(fn setting ->
+ Map.merge(
+ setting,
+ %{
+ type: {:list, :tuple},
+ key_placeholder: "instance",
+ value_placeholder: "reason",
+ suggestions: [{"example.com", "Some reason"}, {"*.example.com", "Another reason"}]
+ }
+ )
+ end)
}
end
end
diff --git a/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex b/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex
index 2858af9eb..f66c379b5 100644
--- a/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF.StealEmojiPolicy do
@@ -8,75 +8,83 @@ defmodule Pleroma.Web.ActivityPub.MRF.StealEmojiPolicy do
alias Pleroma.Config
@moduledoc "Detect new emojis by their shortcode and steals them"
- @behaviour Pleroma.Web.ActivityPub.MRF
-
- defp remote_host?(host), do: host != Config.get([Pleroma.Web.Endpoint, :url, :host])
+ @behaviour Pleroma.Web.ActivityPub.MRF.Policy
defp accept_host?(host), do: host in Config.get([:mrf_steal_emoji, :hosts], [])
- defp steal_emoji({shortcode, url}) do
- url = Pleroma.Web.MediaProxy.url(url)
- {:ok, response} = Pleroma.HTTP.get(url)
- size_limit = Config.get([:mrf_steal_emoji, :size_limit], 50_000)
-
- if byte_size(response.body) <= size_limit do
- emoji_dir_path =
- Config.get(
- [:mrf_steal_emoji, :path],
- Path.join(Config.get([:instance, :static_dir]), "emoji/stolen")
- )
+ defp shortcode_matches?(shortcode, pattern) when is_binary(pattern) do
+ shortcode == pattern
+ end
- extension =
- url
- |> URI.parse()
- |> Map.get(:path)
- |> Path.basename()
- |> Path.extname()
+ defp shortcode_matches?(shortcode, pattern) do
+ String.match?(shortcode, pattern)
+ end
- file_path = Path.join([emoji_dir_path, shortcode <> (extension || ".png")])
+ defp steal_emoji({shortcode, url}, emoji_dir_path) do
+ url = Pleroma.Web.MediaProxy.url(url)
- try do
- :ok = File.write(file_path, response.body)
+ with {:ok, %{status: status} = response} when status in 200..299 <- Pleroma.HTTP.get(url) do
+ size_limit = Config.get([:mrf_steal_emoji, :size_limit], 50_000)
+
+ if byte_size(response.body) <= size_limit do
+ extension =
+ url
+ |> URI.parse()
+ |> Map.get(:path)
+ |> Path.basename()
+ |> Path.extname()
+
+ file_path = Path.join(emoji_dir_path, shortcode <> (extension || ".png"))
+
+ case File.write(file_path, response.body) do
+ :ok ->
+ shortcode
+
+ e ->
+ Logger.warn("MRF.StealEmojiPolicy: Failed to write to #{file_path}: #{inspect(e)}")
+ nil
+ end
+ else
+ Logger.debug(
+ "MRF.StealEmojiPolicy: :#{shortcode}: at #{url} (#{byte_size(response.body)} B) over size limit (#{size_limit} B)"
+ )
- shortcode
- rescue
- e ->
- Logger.warn("MRF.StealEmojiPolicy: Failed to write to #{file_path}: #{inspect(e)}")
- nil
+ nil
end
else
- Logger.debug(
- "MRF.StealEmojiPolicy: :#{shortcode}: at #{url} (#{byte_size(response.body)} B) over size limit (#{
- size_limit
- } B)"
- )
-
- nil
+ e ->
+ Logger.warn("MRF.StealEmojiPolicy: Failed to fetch #{url}: #{inspect(e)}")
+ nil
end
- rescue
- e ->
- Logger.warn("MRF.StealEmojiPolicy: Failed to fetch #{url}: #{inspect(e)}")
- nil
end
@impl true
def filter(%{"object" => %{"emoji" => foreign_emojis, "actor" => actor}} = message) do
host = URI.parse(actor).host
- if remote_host?(host) and accept_host?(host) do
+ if host != Pleroma.Web.Endpoint.host() and accept_host?(host) do
installed_emoji = Pleroma.Emoji.get_all() |> Enum.map(fn {k, _} -> k end)
+ emoji_dir_path =
+ Config.get(
+ [:mrf_steal_emoji, :path],
+ Path.join(Config.get([:instance, :static_dir]), "emoji/stolen")
+ )
+
+ File.mkdir_p(emoji_dir_path)
+
new_emojis =
foreign_emojis
- |> Enum.filter(fn {shortcode, _url} -> shortcode not in installed_emoji end)
+ |> Enum.reject(fn {shortcode, _url} -> shortcode in installed_emoji end)
|> Enum.filter(fn {shortcode, _url} ->
reject_emoji? =
- Config.get([:mrf_steal_emoji, :rejected_shortcodes], [])
- |> Enum.find(false, fn regex -> String.match?(shortcode, regex) end)
+ [:mrf_steal_emoji, :rejected_shortcodes]
+ |> Config.get([])
+ |> Enum.find(false, fn pattern -> shortcode_matches?(shortcode, pattern) end)
!reject_emoji?
end)
- |> Enum.map(&steal_emoji(&1))
+ |> Enum.map(&steal_emoji(&1, emoji_dir_path))
|> Enum.filter(& &1)
if !Enum.empty?(new_emojis) do
@@ -91,6 +99,55 @@ defmodule Pleroma.Web.ActivityPub.MRF.StealEmojiPolicy do
def filter(message), do: {:ok, message}
@impl true
+ @spec config_description :: %{
+ children: [
+ %{
+ description: <<_::272, _::_*256>>,
+ key: :hosts | :rejected_shortcodes | :size_limit,
+ suggestions: [any(), ...],
+ type: {:list, :string} | {:list, :string} | :integer
+ },
+ ...
+ ],
+ description: <<_::448>>,
+ key: :mrf_steal_emoji,
+ label: <<_::80>>,
+ related_policy: <<_::352>>
+ }
+ def config_description do
+ %{
+ key: :mrf_steal_emoji,
+ related_policy: "Pleroma.Web.ActivityPub.MRF.StealEmojiPolicy",
+ label: "MRF Emojis",
+ description: "Steals emojis from selected instances when it sees them.",
+ children: [
+ %{
+ key: :hosts,
+ type: {:list, :string},
+ description: "List of hosts to steal emojis from",
+ suggestions: [""]
+ },
+ %{
+ key: :rejected_shortcodes,
+ type: {:list, :string},
+ description: """
+ A list of patterns or matches to reject shortcodes with.
+
+ Each pattern can be a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`.
+ """,
+ suggestions: ["foo", ~r/foo/]
+ },
+ %{
+ key: :size_limit,
+ type: :integer,
+ description: "File size limit (in bytes), checked before an emoji is saved to the disk",
+ suggestions: ["100000"]
+ }
+ ]
+ }
+ end
+
+ @impl true
def describe do
{:ok, %{}}
end
diff --git a/lib/pleroma/web/activity_pub/mrf/subchain_policy.ex b/lib/pleroma/web/activity_pub/mrf/subchain_policy.ex
index 2ec45260a..fdb9e5176 100644
--- a/lib/pleroma/web/activity_pub/mrf/subchain_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/subchain_policy.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF.SubchainPolicy do
@@ -8,7 +8,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SubchainPolicy do
require Logger
- @behaviour Pleroma.Web.ActivityPub.MRF
+ @behaviour Pleroma.Web.ActivityPub.MRF.Policy
defp lookup_subchain(actor) do
with matches <- Config.get([:mrf_subchain, :match_actor]),
@@ -23,9 +23,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SubchainPolicy do
def filter(%{"actor" => actor} = message) do
with {:ok, match, subchain} <- lookup_subchain(actor) do
Logger.debug(
- "[SubchainPolicy] Matched #{actor} against #{inspect(match)} with subchain #{
- inspect(subchain)
- }"
+ "[SubchainPolicy] Matched #{actor} against #{inspect(match)} with subchain #{inspect(subchain)}"
)
MRF.filter(subchain, message)
diff --git a/lib/pleroma/web/activity_pub/mrf/tag_policy.ex b/lib/pleroma/web/activity_pub/mrf/tag_policy.ex
index febabda08..73760ca8f 100644
--- a/lib/pleroma/web/activity_pub/mrf/tag_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/tag_policy.ex
@@ -1,10 +1,10 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF.TagPolicy do
alias Pleroma.User
- @behaviour Pleroma.Web.ActivityPub.MRF
+ @behaviour Pleroma.Web.ActivityPub.MRF.Policy
@moduledoc """
Apply policies based on user tags
@@ -27,31 +27,22 @@ defmodule Pleroma.Web.ActivityPub.MRF.TagPolicy do
defp process_tag(
"mrf_tag:media-force-nsfw",
%{
- "type" => "Create",
- "object" => %{"attachment" => child_attachment} = object
+ "type" => type,
+ "object" => %{"attachment" => child_attachment}
} = message
)
- when length(child_attachment) > 0 do
- tags = (object["tag"] || []) ++ ["nsfw"]
-
- object =
- object
- |> Map.put("tag", tags)
- |> Map.put("sensitive", true)
-
- message = Map.put(message, "object", object)
-
- {:ok, message}
+ when length(child_attachment) > 0 and type in ["Create", "Update"] do
+ {:ok, Kernel.put_in(message, ["object", "sensitive"], true)}
end
defp process_tag(
"mrf_tag:media-strip",
%{
- "type" => "Create",
+ "type" => type,
"object" => %{"attachment" => child_attachment} = object
} = message
)
- when length(child_attachment) > 0 do
+ when length(child_attachment) > 0 and type in ["Create", "Update"] do
object = Map.delete(object, "attachment")
message = Map.put(message, "object", object)
@@ -161,7 +152,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.TagPolicy do
do: filter_message(target_actor, message)
@impl true
- def filter(%{"actor" => actor, "type" => "Create"} = message),
+ def filter(%{"actor" => actor, "type" => type} = message) when type in ["Create", "Update"],
do: filter_message(actor, message)
@impl true
diff --git a/lib/pleroma/web/activity_pub/mrf/user_allow_list_policy.ex b/lib/pleroma/web/activity_pub/mrf/user_allow_list_policy.ex
index e9d0d0503..e14047d4e 100644
--- a/lib/pleroma/web/activity_pub/mrf/user_allow_list_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/user_allow_list_policy.ex
@@ -1,12 +1,12 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF.UserAllowListPolicy do
alias Pleroma.Config
@moduledoc "Accept-list of users from specified instances"
- @behaviour Pleroma.Web.ActivityPub.MRF
+ @behaviour Pleroma.Web.ActivityPub.MRF.Policy
defp filter_by_list(object, []), do: {:ok, object}
@@ -37,7 +37,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.UserAllowListPolicy do
def describe do
mrf_user_allowlist =
Config.get([:mrf_user_allowlist], [])
- |> Enum.into(%{}, fn {k, v} -> {k, length(v)} end)
+ |> Map.new(fn {k, v} -> {k, length(v)} end)
{:ok, %{mrf_user_allowlist: mrf_user_allowlist}}
end
diff --git a/lib/pleroma/web/activity_pub/mrf/vocabulary_policy.ex b/lib/pleroma/web/activity_pub/mrf/vocabulary_policy.ex
index f325cb680..d9deff35f 100644
--- a/lib/pleroma/web/activity_pub/mrf/vocabulary_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/vocabulary_policy.ex
@@ -1,11 +1,11 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF.VocabularyPolicy do
@moduledoc "Filter messages which belong to certain activity vocabularies"
- @behaviour Pleroma.Web.ActivityPub.MRF
+ @behaviour Pleroma.Web.ActivityPub.MRF.Policy
@impl true
def filter(%{"type" => "Undo", "object" => child_message} = message) do
@@ -39,7 +39,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.VocabularyPolicy do
@impl true
def describe,
- do: {:ok, %{mrf_vocabulary: Pleroma.Config.get(:mrf_vocabulary) |> Enum.into(%{})}}
+ do: {:ok, %{mrf_vocabulary: Pleroma.Config.get(:mrf_vocabulary) |> Map.new()}}
@impl true
def config_description do
diff --git a/lib/pleroma/web/activity_pub/object_validator.ex b/lib/pleroma/web/activity_pub/object_validator.ex
index ce8e7341b..5bcd6da46 100644
--- a/lib/pleroma/web/activity_pub/object_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validator.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.ObjectValidator do
@@ -17,9 +17,10 @@ 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
+ alias Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.AudioVideoValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.BlockValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.ChatMessageValidator
@@ -37,37 +38,6 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
@impl true
def validate(object, meta)
- def validate(%{"type" => type} = object, meta)
- when type in ~w[Accept Reject] do
- with {:ok, object} <-
- object
- |> AcceptRejectValidator.cast_and_validate()
- |> Ecto.Changeset.apply_action(:insert) do
- object = stringify_keys(object)
- {:ok, object, meta}
- end
- end
-
- def validate(%{"type" => "Event"} = object, meta) do
- with {:ok, object} <-
- object
- |> EventValidator.cast_and_validate()
- |> Ecto.Changeset.apply_action(:insert) do
- object = stringify_keys(object)
- {:ok, object, meta}
- end
- end
-
- def validate(%{"type" => "Follow"} = object, meta) do
- with {:ok, object} <-
- object
- |> FollowValidator.cast_and_validate()
- |> Ecto.Changeset.apply_action(:insert) do
- object = stringify_keys(object)
- {:ok, object, meta}
- end
- end
-
def validate(%{"type" => "Block"} = block_activity, meta) do
with {:ok, block_activity} <-
block_activity
@@ -87,16 +57,6 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
end
end
- def validate(%{"type" => "Update"} = update_activity, meta) do
- with {:ok, update_activity} <-
- update_activity
- |> UpdateValidator.cast_and_validate()
- |> Ecto.Changeset.apply_action(:insert) do
- update_activity = stringify_keys(update_activity)
- {:ok, update_activity, meta}
- end
- end
-
def validate(%{"type" => "Undo"} = object, meta) do
with {:ok, object} <-
object
@@ -123,115 +83,142 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
end
end
- def validate(%{"type" => "Like"} = object, meta) do
- with {:ok, object} <-
- object
- |> LikeValidator.cast_and_validate()
+ def validate(
+ %{"type" => "Create", "object" => %{"type" => "ChatMessage"} = object} = create_activity,
+ meta
+ ) do
+ with {:ok, object_data} <- cast_and_apply(object),
+ meta = Keyword.put(meta, :object_data, object_data |> stringify_keys),
+ {:ok, create_activity} <-
+ create_activity
+ |> CreateChatMessageValidator.cast_and_validate(meta)
|> Ecto.Changeset.apply_action(:insert) do
- object = stringify_keys(object)
- {:ok, object, meta}
+ create_activity = stringify_keys(create_activity)
+ {:ok, create_activity, meta}
end
end
- def validate(%{"type" => "ChatMessage"} = object, meta) do
- with {:ok, object} <-
- object
- |> ChatMessageValidator.cast_and_validate()
+ def validate(
+ %{"type" => "Create", "object" => %{"type" => objtype} = object} = create_activity,
+ meta
+ )
+ when objtype in ~w[Question Answer Audio Video Event Article Note Page] do
+ with {:ok, object_data} <- cast_and_apply_and_stringify_with_history(object),
+ meta = Keyword.put(meta, :object_data, object_data),
+ {:ok, create_activity} <-
+ create_activity
+ |> CreateGenericValidator.cast_and_validate(meta)
|> Ecto.Changeset.apply_action(:insert) do
- object = stringify_keys(object)
- {:ok, object, meta}
+ create_activity = stringify_keys(create_activity)
+ {:ok, create_activity, meta}
end
end
- def validate(%{"type" => "Question"} = object, meta) do
- with {:ok, object} <-
- object
- |> QuestionValidator.cast_and_validate()
- |> Ecto.Changeset.apply_action(:insert) do
- object = stringify_keys(object)
- {:ok, object, meta}
- end
- end
+ def validate(%{"type" => type} = object, meta)
+ when type in ~w[Event Question Audio Video Article Note Page] do
+ validator =
+ case type do
+ "Event" -> EventValidator
+ "Question" -> QuestionValidator
+ "Audio" -> AudioVideoValidator
+ "Video" -> AudioVideoValidator
+ "Article" -> ArticleNotePageValidator
+ "Note" -> ArticleNotePageValidator
+ "Page" -> ArticleNotePageValidator
+ end
- def validate(%{"type" => type} = object, meta) when type in ~w[Audio Video] do
with {:ok, object} <-
- object
- |> AudioVideoValidator.cast_and_validate()
- |> Ecto.Changeset.apply_action(:insert) do
- object = stringify_keys(object)
+ do_separate_with_history(object, fn object ->
+ 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}
+ end
+ end) do
{:ok, object, meta}
end
end
- def validate(%{"type" => "Article"} = object, meta) do
- with {:ok, object} <-
- object
- |> ArticleNoteValidator.cast_and_validate()
+ def validate(
+ %{"type" => "Update", "object" => %{"type" => objtype} = object} = update_activity,
+ meta
+ )
+ when objtype in ~w[Question Answer Audio Video Event Article Note Page] do
+ with {_, false} <- {:local, Access.get(meta, :local, false)},
+ {_, {:ok, object_data, _}} <- {:object_validation, validate(object, meta)},
+ meta = Keyword.put(meta, :object_data, object_data),
+ {:ok, update_activity} <-
+ update_activity
+ |> UpdateValidator.cast_and_validate()
|> Ecto.Changeset.apply_action(:insert) do
- object = stringify_keys(object)
- {:ok, object, meta}
+ update_activity = stringify_keys(update_activity)
+ {:ok, update_activity, meta}
+ else
+ {:local, _} ->
+ with {:ok, object} <-
+ update_activity
+ |> UpdateValidator.cast_and_validate()
+ |> Ecto.Changeset.apply_action(:insert) do
+ object = stringify_keys(object)
+ {:ok, object, meta}
+ end
+
+ {:object_validation, e} ->
+ e
end
end
- def validate(%{"type" => "Answer"} = object, meta) do
+ def validate(%{"type" => type} = object, meta)
+ when type in ~w[Accept Reject Follow Update Like EmojiReact Announce
+ ChatMessage Answer] do
+ validator =
+ case type do
+ "Accept" -> AcceptRejectValidator
+ "Reject" -> AcceptRejectValidator
+ "Follow" -> FollowValidator
+ "Update" -> UpdateValidator
+ "Like" -> LikeValidator
+ "EmojiReact" -> EmojiReactValidator
+ "Announce" -> AnnounceValidator
+ "ChatMessage" -> ChatMessageValidator
+ "Answer" -> AnswerValidator
+ end
+
with {:ok, object} <-
object
- |> AnswerValidator.cast_and_validate()
+ |> validator.cast_and_validate()
|> Ecto.Changeset.apply_action(:insert) do
object = stringify_keys(object)
{:ok, object, meta}
end
end
- def validate(%{"type" => "EmojiReact"} = object, meta) do
+ def validate(%{"type" => type} = object, meta) when type in ~w(Add Remove) do
with {:ok, object} <-
object
- |> EmojiReactValidator.cast_and_validate()
+ |> AddRemoveValidator.cast_and_validate()
|> Ecto.Changeset.apply_action(:insert) do
object = stringify_keys(object)
{:ok, object, meta}
end
end
- def validate(
- %{"type" => "Create", "object" => %{"type" => "ChatMessage"} = object} = create_activity,
- meta
- ) do
- with {:ok, object_data} <- cast_and_apply(object),
- meta = Keyword.put(meta, :object_data, object_data |> stringify_keys),
- {:ok, create_activity} <-
- create_activity
- |> CreateChatMessageValidator.cast_and_validate(meta)
- |> Ecto.Changeset.apply_action(:insert) do
- create_activity = stringify_keys(create_activity)
- {:ok, create_activity, meta}
- end
- end
+ def validate(o, m), do: {:error, {:validator_not_set, {o, m}}}
- def validate(
- %{"type" => "Create", "object" => %{"type" => objtype} = object} = create_activity,
- meta
- )
- when objtype in ~w[Question Answer Audio Video Event Article] do
- with {:ok, object_data} <- cast_and_apply(object),
- meta = Keyword.put(meta, :object_data, object_data |> stringify_keys),
- {:ok, create_activity} <-
- create_activity
- |> CreateGenericValidator.cast_and_validate(meta)
- |> Ecto.Changeset.apply_action(:insert) do
- create_activity = stringify_keys(create_activity)
- {:ok, create_activity, meta}
- end
- end
-
- def validate(%{"type" => "Announce"} = object, meta) do
- with {:ok, object} <-
- object
- |> AnnounceValidator.cast_and_validate()
- |> Ecto.Changeset.apply_action(:insert) do
- object = stringify_keys(object)
- {:ok, object, meta}
- end
+ def cast_and_apply_and_stringify_with_history(object) do
+ do_separate_with_history(object, fn object ->
+ with {:ok, object_data} <- cast_and_apply(object),
+ object_data <- object_data |> stringify_keys() do
+ {:ok, object_data}
+ end
+ end)
end
def cast_and_apply(%{"type" => "ChatMessage"} = object) do
@@ -254,14 +241,13 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
EventValidator.cast_and_apply(object)
end
- def cast_and_apply(%{"type" => "Article"} = object) do
- ArticleNoteValidator.cast_and_apply(object)
+ def cast_and_apply(%{"type" => type} = object) when type in ~w[Article Note Page] do
+ ArticleNotePageValidator.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
- def stringify_keys(%{__struct__: _} = object) do
+ def stringify_keys(object) when is_struct(object) do
object
|> Map.from_struct()
|> stringify_keys
@@ -269,6 +255,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
def stringify_keys(object) when is_map(object) do
object
+ |> Enum.filter(fn {_, v} -> v != nil end)
|> Map.new(fn {key, val} -> {to_string(key), stringify_keys(val)} end)
end
@@ -288,7 +275,57 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
def fetch_actor_and_object(object) do
fetch_actor(object)
- Object.normalize(object["object"], true)
+ Object.normalize(object["object"], fetch: true)
:ok
end
+
+ defp for_each_history_item(
+ %{"type" => "OrderedCollection", "orderedItems" => items} = history,
+ object,
+ fun
+ ) do
+ processed_items =
+ Enum.map(items, fn item ->
+ with item <- Map.put(item, "id", object["id"]),
+ {:ok, item} <- fun.(item) do
+ item
+ else
+ _ -> nil
+ end
+ end)
+
+ if Enum.all?(processed_items, &(not is_nil(&1))) do
+ {:ok, Map.put(history, "orderedItems", processed_items)}
+ else
+ {:error, :invalid_history}
+ end
+ end
+
+ defp for_each_history_item(nil, _object, _fun) do
+ {:ok, nil}
+ end
+
+ defp for_each_history_item(_, _object, _fun) do
+ {:error, :invalid_history}
+ end
+
+ # fun is (object -> {:ok, validated_object_with_string_keys})
+ defp do_separate_with_history(object, fun) do
+ with history <- object["formerRepresentations"],
+ object <- Map.drop(object, ["formerRepresentations"]),
+ {_, {:ok, object}} <- {:main_body, fun.(object)},
+ {_, {:ok, history}} <- {:history_items, for_each_history_item(history, object, fun)} do
+ object =
+ if history do
+ Map.put(object, "formerRepresentations", history)
+ else
+ object
+ end
+
+ {:ok, object}
+ else
+ {:main_body, e} -> e
+ {:history_items, e} -> e
+ end
+ end
end
diff --git a/lib/pleroma/web/activity_pub/object_validator/validating.ex b/lib/pleroma/web/activity_pub/object_validator/validating.ex
index 64c0c30c5..b695946b4 100644
--- a/lib/pleroma/web/activity_pub/object_validator/validating.ex
+++ b/lib/pleroma/web/activity_pub/object_validator/validating.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.ObjectValidator.Validating do
diff --git a/lib/pleroma/web/activity_pub/object_validators/accept_reject_validator.ex b/lib/pleroma/web/activity_pub/object_validators/accept_reject_validator.ex
index 179beda58..d611da051 100644
--- a/lib/pleroma/web/activity_pub/object_validators/accept_reject_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/accept_reject_validator.ex
@@ -1,12 +1,11 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.ObjectValidators.AcceptRejectValidator do
use Ecto.Schema
alias Pleroma.Activity
- alias Pleroma.EctoType.ActivityPub.ObjectValidators
import Ecto.Changeset
import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
@@ -14,12 +13,13 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AcceptRejectValidator do
@primary_key false
embedded_schema do
- field(:id, ObjectValidators.ObjectID, primary_key: true)
- field(:type, :string)
- field(:object, ObjectValidators.ObjectID)
- field(:actor, ObjectValidators.ObjectID)
- field(:to, ObjectValidators.Recipients, default: [])
- field(:cc, ObjectValidators.Recipients, default: [])
+ quote do
+ unquote do
+ import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
+ message_fields()
+ activity_fields()
+ end
+ end
end
def cast_data(data) do
@@ -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
index 000000000..5202db7f1
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/object_validators/add_remove_validator.ex
@@ -0,0 +1,78 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 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.User
+
+ @primary_key false
+
+ embedded_schema do
+ field(:target)
+
+ quote do
+ unquote do
+ import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
+ message_fields()
+ activity_fields()
+ end
+ end
+ 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
diff --git a/lib/pleroma/web/activity_pub/object_validators/announce_validator.ex b/lib/pleroma/web/activity_pub/object_validators/announce_validator.ex
index 338957db8..c2c7ba1a8 100644
--- a/lib/pleroma/web/activity_pub/object_validators/announce_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/announce_validator.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.ObjectValidators.AnnounceValidator do
@@ -8,6 +8,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AnnounceValidator do
alias Pleroma.EctoType.ActivityPub.ObjectValidators
alias Pleroma.Object
alias Pleroma.User
+ alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.ActivityPub.Visibility
@@ -19,13 +20,15 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AnnounceValidator do
@primary_key false
embedded_schema do
- field(:id, ObjectValidators.ObjectID, primary_key: true)
- field(:type, :string)
- field(:object, ObjectValidators.ObjectID)
- field(:actor, ObjectValidators.ObjectID)
- field(:context, :string, autogenerate: {Utils, :generate_context_id, []})
- field(:to, ObjectValidators.Recipients, default: [])
- field(:cc, ObjectValidators.Recipients, default: [])
+ quote do
+ unquote do
+ import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
+ message_fields()
+ activity_fields()
+ end
+ end
+
+ field(:context, :string)
field(:published, ObjectValidators.DateTime)
end
@@ -36,6 +39,10 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AnnounceValidator do
end
def cast_data(data) do
+ data =
+ data
+ |> fix()
+
%__MODULE__{}
|> changeset(data)
end
@@ -43,14 +50,24 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AnnounceValidator do
def changeset(struct, data) do
struct
|> cast(data, __schema__(:fields))
- |> fix_after_cast()
end
- def fix_after_cast(cng) do
- cng
+ defp fix(data) do
+ data =
+ data
+ |> CommonFixes.fix_actor()
+ |> CommonFixes.fix_activity_addressing()
+
+ with %Object{} = object <- Object.normalize(data["object"]) do
+ data
+ |> CommonFixes.fix_activity_context(object)
+ |> CommonFixes.fix_object_action_recipients(object)
+ else
+ _ -> data
+ end
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])
@@ -60,7 +77,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AnnounceValidator do
|> validate_announcable()
end
- def validate_announcable(cng) do
+ defp validate_announcable(cng) do
with actor when is_binary(actor) <- get_field(cng, :actor),
object when is_binary(object) <- get_field(cng, :object),
%User{} = actor <- User.get_cached_by_ap_id(actor),
@@ -68,7 +85,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
@@ -91,7 +108,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AnnounceValidator do
end
end
- def validate_existing_announce(cng) do
+ defp validate_existing_announce(cng) do
actor = get_field(cng, :actor)
object = get_field(cng, :object)
diff --git a/lib/pleroma/web/activity_pub/object_validators/answer_validator.ex b/lib/pleroma/web/activity_pub/object_validators/answer_validator.ex
index b9fbaf4f6..2d9b8ba02 100644
--- a/lib/pleroma/web/activity_pub/object_validators/answer_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/answer_validator.ex
@@ -1,11 +1,12 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
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
@@ -14,15 +15,17 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AnswerValidator do
@derive Jason.Encoder
embedded_schema do
- field(:id, ObjectValidators.ObjectID, primary_key: true)
- field(:to, ObjectValidators.Recipients, default: [])
- field(:cc, ObjectValidators.Recipients, default: [])
- field(:bto, ObjectValidators.Recipients, default: [])
- field(:bcc, ObjectValidators.Recipients, default: [])
- field(:type, :string)
+ quote do
+ unquote do
+ import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
+ message_fields()
+ end
+ end
+
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 +49,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])
diff --git a/lib/pleroma/web/activity_pub/object_validators/article_note_page_validator.ex b/lib/pleroma/web/activity_pub/object_validators/article_note_page_validator.ex
new file mode 100644
index 000000000..2670e3f17
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/object_validators/article_note_page_validator.ex
@@ -0,0 +1,109 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidator do
+ use Ecto.Schema
+
+ alias Pleroma.EctoType.ActivityPub.ObjectValidators
+ alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
+ alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
+ alias Pleroma.Web.ActivityPub.Transmogrifier
+
+ import Ecto.Changeset
+
+ @primary_key false
+ @derive Jason.Encoder
+
+ embedded_schema do
+ quote do
+ unquote do
+ import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
+ message_fields()
+ object_fields()
+ status_object_fields()
+ end
+ end
+
+ field(:replies, {:array, ObjectValidators.ObjectID}, default: [])
+ end
+
+ def cast_and_apply(data) do
+ data
+ |> cast_data
+ |> apply_action(:insert)
+ end
+
+ def cast_and_validate(data) do
+ data
+ |> cast_data()
+ |> validate_data()
+ end
+
+ def cast_data(data) do
+ %__MODULE__{}
+ |> changeset(data)
+ 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
+ Map.put(data, "tag", Enum.filter(tag, &is_map/1))
+ end
+
+ 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)
+
+ # TODO: Pleroma does not have any support for Collections at the moment.
+ # If the `replies` field is not something the ObjectID validator can handle,
+ # the activity/object would be rejected, which is bad behavior.
+ defp fix_replies(%{"replies" => replies} = data) when not is_list(replies),
+ do: Map.drop(data, ["replies"])
+
+ defp fix_replies(data), do: data
+
+ def fix_attachments(%{"attachment" => attachment} = data) when is_map(attachment),
+ do: Map.put(data, "attachment", [attachment])
+
+ def fix_attachments(data), do: data
+
+ defp fix(data) do
+ data
+ |> CommonFixes.fix_actor()
+ |> CommonFixes.fix_object_defaults()
+ |> fix_url()
+ |> fix_tag()
+ |> fix_replies()
+ |> fix_attachments()
+ |> Transmogrifier.fix_emoji()
+ |> Transmogrifier.fix_content_map()
+ end
+
+ def changeset(struct, data) do
+ data = fix(data)
+
+ struct
+ |> cast(data, __schema__(:fields) -- [:attachment, :tag])
+ |> cast_embed(:attachment)
+ |> cast_embed(:tag)
+ end
+
+ defp validate_data(data_cng) do
+ data_cng
+ |> validate_inclusion(:type, ["Article", "Note", "Page"])
+ |> validate_required([:id, :actor, :attributedTo, :type, :context])
+ |> CommonValidations.validate_any_presence([:cc, :to])
+ |> CommonValidations.validate_fields_match([:actor, :attributedTo])
+ |> CommonValidations.validate_actor_presence()
+ |> CommonValidations.validate_host_match()
+ end
+end
diff --git a/lib/pleroma/web/activity_pub/object_validators/article_note_validator.ex b/lib/pleroma/web/activity_pub/object_validators/article_note_validator.ex
deleted file mode 100644
index 5b7dad517..000000000
--- a/lib/pleroma/web/activity_pub/object_validators/article_note_validator.ex
+++ /dev/null
@@ -1,106 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNoteValidator do
- use Ecto.Schema
-
- alias Pleroma.EctoType.ActivityPub.ObjectValidators
- alias Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator
- alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
- alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
- alias Pleroma.Web.ActivityPub.Transmogrifier
-
- import Ecto.Changeset
-
- @primary_key false
- @derive Jason.Encoder
-
- embedded_schema do
- field(:id, ObjectValidators.ObjectID, primary_key: true)
- field(:to, ObjectValidators.Recipients, default: [])
- field(:cc, ObjectValidators.Recipients, default: [])
- field(:bto, ObjectValidators.Recipients, default: [])
- field(:bcc, ObjectValidators.Recipients, default: [])
- # TODO: Write type
- field(:tag, {:array, :map}, default: [])
- field(:type, :string)
-
- field(:name, :string)
- field(:summary, :string)
- field(:content, :string)
-
- field(:context, :string)
- # short identifier for PleromaFE to group statuses by context
- field(:context_id, :integer)
-
- # TODO: Remove actor on objects
- field(:actor, ObjectValidators.ObjectID)
-
- field(:attributedTo, ObjectValidators.ObjectID)
- field(:published, ObjectValidators.DateTime)
- field(:emoji, ObjectValidators.Emoji, default: %{})
- field(:sensitive, :boolean, default: false)
- embeds_many(:attachment, AttachmentValidator)
- field(:replies_count, :integer, default: 0)
- field(:like_count, :integer, default: 0)
- field(:announcement_count, :integer, default: 0)
- field(:inReplyTo, ObjectValidators.ObjectID)
- field(:url, ObjectValidators.Uri)
-
- field(:likes, {:array, ObjectValidators.ObjectID}, default: [])
- field(:announcements, {:array, ObjectValidators.ObjectID}, default: [])
- end
-
- def cast_and_apply(data) do
- data
- |> cast_data
- |> apply_action(:insert)
- end
-
- def cast_and_validate(data) do
- data
- |> cast_data()
- |> validate_data()
- 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(data), do: data
-
- defp fix(data) do
- data
- |> CommonFixes.fix_defaults()
- |> CommonFixes.fix_attribution()
- |> CommonFixes.fix_actor()
- |> fix_url()
- |> Transmogrifier.fix_emoji()
- end
-
- def changeset(struct, data) do
- data = fix(data)
-
- struct
- |> cast(data, __schema__(:fields) -- [:attachment])
- |> cast_embed(:attachment)
- end
-
- def validate_data(data_cng) do
- data_cng
- |> validate_inclusion(:type, ["Article", "Note"])
- |> validate_required([:id, :actor, :attributedTo, :type, :context, :context_id])
- |> CommonValidations.validate_any_presence([:cc, :to])
- |> CommonValidations.validate_fields_match([:actor, :attributedTo])
- |> CommonValidations.validate_actor_presence()
- |> CommonValidations.validate_host_match()
- end
-end
diff --git a/lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex b/lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex
index f96fd54bf..398020bff 100644
--- a/lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex
@@ -1,26 +1,28 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator do
use Ecto.Schema
alias Pleroma.EctoType.ActivityPub.ObjectValidators
- alias Pleroma.Web.ActivityPub.ObjectValidators.UrlObjectValidator
import Ecto.Changeset
@primary_key false
embedded_schema do
+ field(:id, :string)
field(:type, :string)
- field(:mediaType, :string, default: "application/octet-stream")
+ field(:mediaType, ObjectValidators.MIME, default: "application/octet-stream")
field(:name, :string)
field(:blurhash, :string)
embeds_many :url, UrlObjectValidator, primary_key: false do
field(:type, :string)
field(:href, ObjectValidators.Uri)
- field(:mediaType, :string, default: "application/octet-stream")
+ field(:mediaType, ObjectValidators.MIME, default: "application/octet-stream")
+ field(:width, :integer)
+ field(:height, :integer)
end
end
@@ -42,37 +44,33 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator do
|> fix_url()
struct
- |> cast(data, [:type, :mediaType, :name, :blurhash])
- |> cast_embed(:url, with: &url_changeset/2)
+ |> cast(data, [:id, :type, :mediaType, :name, :blurhash])
+ |> cast_embed(:url, with: &url_changeset/2, required: true)
|> validate_inclusion(:type, ~w[Link Document Audio Image Video])
- |> validate_required([:type, :mediaType, :url])
+ |> validate_required([:type, :mediaType])
end
def url_changeset(struct, data) 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
def fix_media_type(data) do
- data = Map.put_new(data, "mediaType", data["mimeType"])
-
- if MIME.valid?(data["mediaType"]) do
- data
- else
- Map.put(data, "mediaType", "application/octet-stream")
- end
+ Map.put_new(data, "mediaType", data["mimeType"] || "application/octet-stream")
end
- defp handle_href(href, mediaType) do
+ defp handle_href(href, mediaType, data) do
[
%{
"href" => href,
"type" => "Link",
- "mediaType" => mediaType
+ "mediaType" => mediaType,
+ "width" => data["width"],
+ "height" => data["height"]
}
]
end
@@ -80,19 +78,19 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator do
defp fix_url(data) do
cond do
is_binary(data["url"]) ->
- Map.put(data, "url", handle_href(data["url"], data["mediaType"]))
+ Map.put(data, "url", handle_href(data["url"], data["mediaType"], data))
is_binary(data["href"]) and data["url"] == nil ->
- Map.put(data, "url", handle_href(data["href"], data["mediaType"]))
+ Map.put(data, "url", handle_href(data["href"], data["mediaType"], data))
true ->
data
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])
+ |> validate_required([:mediaType, :type])
end
end
diff --git a/lib/pleroma/web/activity_pub/object_validators/audio_video_validator.ex b/lib/pleroma/web/activity_pub/object_validators/audio_video_validator.ex
index 16973e5db..671a7ef0c 100644
--- a/lib/pleroma/web/activity_pub/object_validators/audio_video_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/audio_video_validator.ex
@@ -1,13 +1,10 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
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.Transmogrifier
@@ -18,39 +15,14 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AudioVideoValidator do
@derive Jason.Encoder
embedded_schema do
- field(:id, ObjectValidators.ObjectID, primary_key: true)
- field(:to, ObjectValidators.Recipients, default: [])
- field(:cc, ObjectValidators.Recipients, default: [])
- field(:bto, ObjectValidators.Recipients, default: [])
- field(:bcc, ObjectValidators.Recipients, default: [])
- # TODO: Write type
- field(:tag, {:array, :map}, default: [])
- field(:type, :string)
-
- field(:name, :string)
- field(:summary, :string)
- field(:content, :string)
-
- field(:context, :string)
- # short identifier for PleromaFE to group statuses by context
- field(:context_id, :integer)
-
- # TODO: Remove actor on objects
- field(:actor, ObjectValidators.ObjectID)
-
- field(:attributedTo, ObjectValidators.ObjectID)
- field(:published, ObjectValidators.DateTime)
- field(:emoji, ObjectValidators.Emoji, default: %{})
- field(:sensitive, :boolean, default: false)
- embeds_many(:attachment, AttachmentValidator)
- field(:replies_count, :integer, default: 0)
- field(:like_count, :integer, default: 0)
- field(:announcement_count, :integer, default: 0)
- field(:inReplyTo, ObjectValidators.ObjectID)
- field(:url, ObjectValidators.Uri)
-
- field(:likes, {:array, ObjectValidators.ObjectID}, default: [])
- field(:announcements, {:array, ObjectValidators.ObjectID}, default: [])
+ quote do
+ unquote do
+ import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
+ message_fields()
+ object_fields()
+ status_object_fields()
+ end
+ end
end
def cast_and_apply(data) do
@@ -70,19 +42,33 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AudioVideoValidator do
|> changeset(data)
end
- defp fix_url(%{"url" => url} = data) when is_list(url) do
- attachment =
- Enum.find(url, fn x ->
- mime_type = x["mimeType"] || x["mediaType"] || ""
+ defp find_attachment(url) do
+ mpeg_url =
+ Enum.find(url, fn
+ %{"mediaType" => mime_type, "tag" => tags} when is_list(tags) ->
+ mime_type == "application/x-mpegURL"
- is_map(x) and String.starts_with?(mime_type, ["video/", "audio/"])
+ _ ->
+ false
end)
- link_element =
- Enum.find(url, fn x ->
- mime_type = x["mimeType"] || x["mediaType"] || ""
+ url
+ |> Enum.concat(mpeg_url["tag"] || [])
+ |> Enum.find(fn
+ %{"mediaType" => mime_type} -> String.starts_with?(mime_type, ["video/", "audio/"])
+ %{"mimeType" => mime_type} -> String.starts_with?(mime_type, ["video/", "audio/"])
+ _ -> false
+ end)
+ end
- is_map(x) and mime_type == "text/html"
+ defp fix_url(%{"url" => url} = data) when is_list(url) do
+ attachment = find_attachment(url)
+
+ link_element =
+ Enum.find(url, fn
+ %{"mediaType" => "text/html"} -> true
+ %{"mimeType" => "text/html"} -> true
+ _ -> false
end)
data
@@ -96,7 +82,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)
@@ -106,9 +92,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()
@@ -118,14 +103,15 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AudioVideoValidator do
data = fix(data)
struct
- |> cast(data, __schema__(:fields) -- [:attachment])
- |> cast_embed(:attachment)
+ |> cast(data, __schema__(:fields) -- [:attachment, :tag])
+ |> cast_embed(:attachment, required: true)
+ |> 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])
+ |> validate_required([:id, :actor, :attributedTo, :type, :context])
|> CommonValidations.validate_any_presence([:cc, :to])
|> CommonValidations.validate_fields_match([:actor, :attributedTo])
|> CommonValidations.validate_actor_presence()
diff --git a/lib/pleroma/web/activity_pub/object_validators/block_validator.ex b/lib/pleroma/web/activity_pub/object_validators/block_validator.ex
index 1dde77198..0de87a27e 100644
--- a/lib/pleroma/web/activity_pub/object_validators/block_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/block_validator.ex
@@ -1,24 +1,25 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.ObjectValidators.BlockValidator do
use Ecto.Schema
- alias Pleroma.EctoType.ActivityPub.ObjectValidators
+ alias Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
import Ecto.Changeset
- import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
@primary_key false
+ @derive Jason.Encoder
embedded_schema do
- field(:id, ObjectValidators.ObjectID, primary_key: true)
- field(:type, :string)
- field(:actor, ObjectValidators.ObjectID)
- field(:to, ObjectValidators.Recipients, default: [])
- field(:cc, ObjectValidators.Recipients, default: [])
- field(:object, ObjectValidators.ObjectID)
+ quote do
+ unquote do
+ import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
+ message_fields()
+ activity_fields()
+ end
+ end
end
def cast_data(data) do
@@ -26,12 +27,12 @@ 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"])
- |> validate_actor_presence()
- |> validate_actor_presence(field_name: :object)
+ |> CommonValidations.validate_actor_presence()
+ |> CommonValidations.validate_actor_presence(field_name: :object)
end
def cast_and_validate(data) do
diff --git a/lib/pleroma/web/activity_pub/object_validators/chat_message_validator.ex b/lib/pleroma/web/activity_pub/object_validators/chat_message_validator.ex
index 6acd4a771..efae48cae 100644
--- a/lib/pleroma/web/activity_pub/object_validators/chat_message_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/chat_message_validator.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.ObjectValidators.ChatMessageValidator do
@@ -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])
diff --git a/lib/pleroma/web/activity_pub/object_validators/common_fields.ex b/lib/pleroma/web/activity_pub/object_validators/common_fields.ex
new file mode 100644
index 000000000..7b60c139a
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/object_validators/common_fields.ex
@@ -0,0 +1,67 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFields do
+ alias Pleroma.EctoType.ActivityPub.ObjectValidators
+ alias Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator
+ alias Pleroma.Web.ActivityPub.ObjectValidators.TagValidator
+
+ # Activities and Objects, except (Create)ChatMessage
+ defmacro message_fields do
+ quote bind_quoted: binding() do
+ field(:type, :string)
+ field(:id, ObjectValidators.ObjectID, primary_key: true)
+
+ field(:to, ObjectValidators.Recipients, default: [])
+ field(:cc, ObjectValidators.Recipients, default: [])
+ field(:bto, ObjectValidators.Recipients, default: [])
+ field(:bcc, ObjectValidators.Recipients, default: [])
+ end
+ end
+
+ defmacro activity_fields do
+ quote bind_quoted: binding() do
+ field(:object, ObjectValidators.ObjectID)
+ field(:actor, ObjectValidators.ObjectID)
+ end
+ end
+
+ # All objects except Answer and CHatMessage
+ defmacro object_fields do
+ quote bind_quoted: binding() do
+ field(:content, :string)
+
+ field(:published, ObjectValidators.DateTime)
+ field(:updated, ObjectValidators.DateTime)
+ field(:emoji, ObjectValidators.Emoji, default: %{})
+ embeds_many(:attachment, AttachmentValidator)
+ end
+ end
+
+ # Basically objects that aren't ChatMessage and Answer
+ defmacro status_object_fields do
+ quote bind_quoted: binding() do
+ # TODO: Remove actor on objects
+ field(:actor, ObjectValidators.ObjectID)
+ field(:attributedTo, ObjectValidators.ObjectID)
+
+ embeds_many(:tag, TagValidator)
+
+ field(:name, :string)
+ field(:summary, :string)
+
+ field(:context, :string)
+
+ field(:sensitive, :boolean, default: false)
+ field(:replies_count, :integer, default: 0)
+ field(:like_count, :integer, default: 0)
+ field(:announcement_count, :integer, default: 0)
+ field(:inReplyTo, ObjectValidators.ObjectID)
+ field(:url, ObjectValidators.Uri)
+
+ field(:likes, {:array, ObjectValidators.ObjectID}, default: [])
+ field(:announcements, {:array, ObjectValidators.ObjectID}, default: [])
+ end
+ end
+end
diff --git a/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex b/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex
index b3638cfc7..add46d561 100644
--- a/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex
@@ -1,31 +1,79 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes do
+ alias Pleroma.EctoType.ActivityPub.ObjectValidators
+ alias Pleroma.Object
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
- %{data: %{"id" => context}, id: context_id} =
- Utils.create_context(data["context"] || data["conversation"])
+ 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
+ context =
+ Utils.maybe_create_context(
+ data["context"] || data["conversation"] || data["inReplyTo"] || data["id"]
+ )
+
+ %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) 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)
|> Map.put("attributedTo", actor)
end
+
+ def fix_activity_context(data, %Object{data: %{"context" => object_context}}) do
+ data
+ |> Map.put("context", object_context)
+ end
+
+ def fix_object_action_recipients(%{"actor" => actor} = data, %Object{data: %{"actor" => actor}}) do
+ to = ((data["to"] || []) -- [actor]) |> Enum.uniq()
+
+ Map.put(data, "to", to)
+ end
+
+ def fix_object_action_recipients(data, %Object{data: %{"actor" => actor}}) do
+ to = ((data["to"] || []) ++ [actor]) |> Enum.uniq()
+
+ Map.put(data, "to", to)
+ end
end
diff --git a/lib/pleroma/web/activity_pub/object_validators/common_validations.ex b/lib/pleroma/web/activity_pub/object_validators/common_validations.ex
index 603d87b8e..704b3abc9 100644
--- a/lib/pleroma/web/activity_pub/object_validators/common_validations.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/common_validations.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations do
@@ -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,13 +31,14 @@ 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)
cng
|> validate_change(field_name, fn field_name, actor ->
case User.get_cached_by_ap_id(actor) do
- %User{deactivated: true} ->
+ %User{is_active: false} ->
[{field_name, "user is deactivated"}]
%User{} ->
@@ -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))
diff --git a/lib/pleroma/web/activity_pub/object_validators/create_chat_message_validator.ex b/lib/pleroma/web/activity_pub/object_validators/create_chat_message_validator.ex
index 7269f9ff0..b299647a1 100644
--- a/lib/pleroma/web/activity_pub/object_validators/create_chat_message_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/create_chat_message_validator.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
# NOTES
@@ -17,11 +17,16 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CreateChatMessageValidator do
@primary_key false
embedded_schema do
+ quote do
+ unquote do
+ import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
+ activity_fields()
+ end
+ end
+
field(:id, ObjectValidators.ObjectID, primary_key: true)
- field(:actor, ObjectValidators.ObjectID)
field(:type, :string)
field(:to, ObjectValidators.Recipients, default: [])
- field(:object, ObjectValidators.ObjectID)
end
def cast_and_apply(data) do
@@ -39,7 +44,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"])
diff --git a/lib/pleroma/web/activity_pub/object_validators/create_generic_validator.ex b/lib/pleroma/web/activity_pub/object_validators/create_generic_validator.ex
index 422ee07be..2395abfd4 100644
--- a/lib/pleroma/web/activity_pub/object_validators/create_generic_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/create_generic_validator.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
# Code based on CreateChatMessageValidator
@@ -10,20 +10,24 @@ 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
@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(:object, ObjectValidators.ObjectID)
+ quote do
+ unquote do
+ import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
+ message_fields()
+ activity_fields()
+ end
+ end
+
field(:expires_at, ObjectValidators.DateTime)
# Should be moved to object, done for CommonAPI.Utils.make_context
@@ -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("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
index 9b9743c4a..000000000
--- a/lib/pleroma/web/activity_pub/object_validators/create_note_validator.ex
+++ /dev/null
@@ -1,29 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.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
diff --git a/lib/pleroma/web/activity_pub/object_validators/delete_validator.ex b/lib/pleroma/web/activity_pub/object_validators/delete_validator.ex
index 2634e8d4d..035fd5bc9 100644
--- a/lib/pleroma/web/activity_pub/object_validators/delete_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/delete_validator.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.ObjectValidators.DeleteValidator do
@@ -7,6 +7,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.DeleteValidator do
alias Pleroma.Activity
alias Pleroma.EctoType.ActivityPub.ObjectValidators
+ alias Pleroma.User
import Ecto.Changeset
import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
@@ -14,13 +15,15 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.DeleteValidator do
@primary_key false
embedded_schema do
- field(:id, ObjectValidators.ObjectID, primary_key: true)
- field(:type, :string)
- field(:actor, ObjectValidators.ObjectID)
- field(:to, ObjectValidators.Recipients, default: [])
- field(:cc, ObjectValidators.Recipients, default: [])
+ quote do
+ unquote do
+ import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
+ message_fields()
+ activity_fields()
+ end
+ end
+
field(:deleted_activity_id, ObjectValidators.ObjectID)
- field(:object, ObjectValidators.ObjectID)
end
def cast_data(data) do
@@ -53,11 +56,11 @@ 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"])
- |> validate_actor_presence()
+ |> validate_delete_actor(:actor)
|> validate_modification_rights()
|> validate_object_or_user_presence(allowed_types: @deletable_types)
|> add_deleted_activity_id()
@@ -72,4 +75,13 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.DeleteValidator do
|> cast_data
|> validate_data
end
+
+ defp validate_delete_actor(cng, field_name) do
+ validate_change(cng, field_name, fn field_name, actor ->
+ case User.get_cached_by_ap_id(actor) do
+ %User{} -> []
+ _ -> [{field_name, "can't find user"}]
+ end
+ end)
+ end
end
diff --git a/lib/pleroma/web/activity_pub/object_validators/emoji_react_validator.ex b/lib/pleroma/web/activity_pub/object_validators/emoji_react_validator.ex
index 336c92d35..0858281e5 100644
--- a/lib/pleroma/web/activity_pub/object_validators/emoji_react_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/emoji_react_validator.ex
@@ -1,12 +1,12 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator do
use Ecto.Schema
- alias Pleroma.EctoType.ActivityPub.ObjectValidators
alias Pleroma.Object
+ alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
import Ecto.Changeset
import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
@@ -14,14 +14,16 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator do
@primary_key false
embedded_schema do
- field(:id, ObjectValidators.ObjectID, primary_key: true)
- field(:type, :string)
- field(:object, ObjectValidators.ObjectID)
- field(:actor, ObjectValidators.ObjectID)
+ quote do
+ unquote do
+ import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
+ message_fields()
+ activity_fields()
+ end
+ end
+
field(:context, :string)
field(:content, :string)
- field(:to, ObjectValidators.Recipients, default: [])
- field(:cc, ObjectValidators.Recipients, default: [])
end
def cast_and_validate(data) do
@@ -31,6 +33,10 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator do
end
def cast_data(data) do
+ data =
+ data
+ |> fix()
+
%__MODULE__{}
|> changeset(data)
end
@@ -38,28 +44,42 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator do
def changeset(struct, data) do
struct
|> cast(data, __schema__(:fields))
- |> fix_after_cast()
end
- def fix_after_cast(cng) do
- cng
- |> fix_context()
+ defp fix(data) do
+ data =
+ data
+ |> fix_emoji_qualification()
+ |> CommonFixes.fix_actor()
+ |> CommonFixes.fix_activity_addressing()
+
+ with %Object{} = object <- Object.normalize(data["object"]) do
+ data
+ |> CommonFixes.fix_activity_context(object)
+ |> CommonFixes.fix_object_action_recipients(object)
+ else
+ _ -> data
+ end
end
- def fix_context(cng) do
- object = get_field(cng, :object)
+ defp fix_emoji_qualification(%{"content" => emoji} = data) do
+ new_emoji = Pleroma.Emoji.fully_qualify_emoji(emoji)
- with nil <- get_field(cng, :context),
- %Object{data: %{"context" => context}} <- Object.get_cached_by_ap_id(object) do
- cng
- |> put_change(:context, context)
- else
- _ ->
- cng
+ cond do
+ Pleroma.Emoji.is_unicode_emoji?(emoji) ->
+ data
+
+ Pleroma.Emoji.is_unicode_emoji?(new_emoji) ->
+ data |> Map.put("content", new_emoji)
+
+ true ->
+ data
end
end
- def validate_emoji(cng) do
+ defp fix_emoji_qualification(data), do: data
+
+ defp validate_emoji(cng) do
content = get_field(cng, :content)
if Pleroma.Emoji.is_unicode_emoji?(content) do
@@ -70,7 +90,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])
diff --git a/lib/pleroma/web/activity_pub/object_validators/event_validator.ex b/lib/pleroma/web/activity_pub/object_validators/event_validator.ex
index 0b4c99dc0..ab204f69a 100644
--- a/lib/pleroma/web/activity_pub/object_validators/event_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/event_validator.ex
@@ -1,12 +1,10 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.ObjectValidators.EventValidator do
use Ecto.Schema
- alias Pleroma.EctoType.ActivityPub.ObjectValidators
- alias Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
alias Pleroma.Web.ActivityPub.Transmogrifier
@@ -18,39 +16,14 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EventValidator do
# Extends from NoteValidator
embedded_schema do
- field(:id, ObjectValidators.ObjectID, primary_key: true)
- field(:to, ObjectValidators.Recipients, default: [])
- field(:cc, ObjectValidators.Recipients, default: [])
- field(:bto, ObjectValidators.Recipients, default: [])
- field(:bcc, ObjectValidators.Recipients, default: [])
- # TODO: Write type
- field(:tag, {:array, :map}, default: [])
- field(:type, :string)
-
- field(:name, :string)
- field(:summary, :string)
- field(:content, :string)
-
- field(:context, :string)
- # short identifier for PleromaFE to group statuses by context
- field(:context_id, :integer)
-
- # TODO: Remove actor on objects
- field(:actor, ObjectValidators.ObjectID)
-
- field(:attributedTo, ObjectValidators.ObjectID)
- field(:published, ObjectValidators.DateTime)
- field(:emoji, ObjectValidators.Emoji, default: %{})
- field(:sensitive, :boolean, default: false)
- embeds_many(:attachment, AttachmentValidator)
- field(:replies_count, :integer, default: 0)
- field(:like_count, :integer, default: 0)
- field(:announcement_count, :integer, default: 0)
- field(:inReplyTo, ObjectValidators.ObjectID)
- field(:url, ObjectValidators.Uri)
-
- field(:likes, {:array, ObjectValidators.ObjectID}, default: [])
- field(:announcements, {:array, ObjectValidators.ObjectID}, default: [])
+ quote do
+ unquote do
+ import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
+ message_fields()
+ object_fields()
+ status_object_fields()
+ end
+ end
end
def cast_and_apply(data) do
@@ -72,8 +45,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,14 +54,15 @@ 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])
+ |> validate_required([:id, :actor, :attributedTo, :type, :context])
|> CommonValidations.validate_any_presence([:cc, :to])
|> CommonValidations.validate_fields_match([:actor, :attributedTo])
|> CommonValidations.validate_actor_presence()
diff --git a/lib/pleroma/web/activity_pub/object_validators/follow_validator.ex b/lib/pleroma/web/activity_pub/object_validators/follow_validator.ex
index ca2724616..b3ca5b691 100644
--- a/lib/pleroma/web/activity_pub/object_validators/follow_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/follow_validator.ex
@@ -1,24 +1,24 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.ObjectValidators.FollowValidator do
use Ecto.Schema
- alias Pleroma.EctoType.ActivityPub.ObjectValidators
-
import Ecto.Changeset
import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
@primary_key false
embedded_schema do
- field(:id, ObjectValidators.ObjectID, primary_key: true)
- field(:type, :string)
- field(:actor, ObjectValidators.ObjectID)
- field(:to, ObjectValidators.Recipients, default: [])
- field(:cc, ObjectValidators.Recipients, default: [])
- field(:object, ObjectValidators.ObjectID)
+ quote do
+ unquote do
+ import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
+ message_fields()
+ activity_fields()
+ end
+ end
+
field(:state, :string, default: "pending")
end
@@ -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"])
diff --git a/lib/pleroma/web/activity_pub/object_validators/like_validator.ex b/lib/pleroma/web/activity_pub/object_validators/like_validator.ex
index 493e4c247..bdc4d7181 100644
--- a/lib/pleroma/web/activity_pub/object_validators/like_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/like_validator.ex
@@ -1,12 +1,12 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator do
use Ecto.Schema
- alias Pleroma.EctoType.ActivityPub.ObjectValidators
alias Pleroma.Object
+ alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
alias Pleroma.Web.ActivityPub.Utils
import Ecto.Changeset
@@ -15,13 +15,15 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator do
@primary_key false
embedded_schema do
- field(:id, ObjectValidators.ObjectID, primary_key: true)
- field(:type, :string)
- field(:object, ObjectValidators.ObjectID)
- field(:actor, ObjectValidators.ObjectID)
+ quote do
+ unquote do
+ import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
+ message_fields()
+ activity_fields()
+ end
+ end
+
field(:context, :string)
- field(:to, ObjectValidators.Recipients, default: [])
- field(:cc, ObjectValidators.Recipients, default: [])
end
def cast_and_validate(data) do
@@ -31,6 +33,10 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator do
end
def cast_data(data) do
+ data =
+ data
+ |> fix()
+
%__MODULE__{}
|> changeset(data)
end
@@ -38,45 +44,24 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator do
def changeset(struct, data) do
struct
|> cast(data, __schema__(:fields))
- |> fix_after_cast()
- end
-
- def fix_after_cast(cng) do
- cng
- |> fix_recipients()
- |> fix_context()
- end
-
- def fix_context(cng) do
- object = get_field(cng, :object)
-
- with nil <- get_field(cng, :context),
- %Object{data: %{"context" => context}} <- Object.get_cached_by_ap_id(object) do
- cng
- |> put_change(:context, context)
- else
- _ ->
- cng
- end
end
- def fix_recipients(cng) do
- to = get_field(cng, :to)
- cc = get_field(cng, :cc)
- object = get_field(cng, :object)
+ defp fix(data) do
+ data =
+ data
+ |> CommonFixes.fix_actor()
+ |> CommonFixes.fix_activity_addressing()
- with {[], []} <- {to, cc},
- %Object{data: %{"actor" => actor}} <- Object.get_cached_by_ap_id(object),
- {:ok, actor} <- ObjectValidators.ObjectID.cast(actor) do
- cng
- |> put_change(:to, [actor])
+ with %Object{} = object <- Object.normalize(data["object"]) do
+ data
+ |> CommonFixes.fix_activity_context(object)
+ |> CommonFixes.fix_object_action_recipients(object)
else
- _ ->
- cng
+ _ -> data
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])
@@ -85,7 +70,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator do
|> validate_existing_like()
end
- def validate_existing_like(%{changes: %{actor: actor, object: object}} = cng) do
+ defp validate_existing_like(%{changes: %{actor: actor, object: object}} = cng) do
if Utils.get_existing_like(actor, %{data: %{"id" => object}}) do
cng
|> add_error(:actor, "already liked this object")
@@ -95,5 +80,5 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator do
end
end
- def validate_existing_like(cng), do: cng
+ defp validate_existing_like(cng), do: cng
end
diff --git a/lib/pleroma/web/activity_pub/object_validators/question_options_validator.ex b/lib/pleroma/web/activity_pub/object_validators/question_options_validator.ex
index 478b3b5cf..541945fa4 100644
--- a/lib/pleroma/web/activity_pub/object_validators/question_options_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/question_options_validator.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.ObjectValidators.QuestionOptionsValidator do
diff --git a/lib/pleroma/web/activity_pub/object_validators/question_validator.ex b/lib/pleroma/web/activity_pub/object_validators/question_validator.ex
index 9310485dc..ce3305142 100644
--- a/lib/pleroma/web/activity_pub/object_validators/question_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/question_validator.ex
@@ -1,12 +1,11 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.ObjectValidators.QuestionValidator do
use Ecto.Schema
alias Pleroma.EctoType.ActivityPub.ObjectValidators
- alias Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
alias Pleroma.Web.ActivityPub.ObjectValidators.QuestionOptionsValidator
@@ -19,36 +18,14 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.QuestionValidator do
# Extends from NoteValidator
embedded_schema do
- field(:id, ObjectValidators.ObjectID, primary_key: true)
- field(:to, ObjectValidators.Recipients, default: [])
- field(:cc, ObjectValidators.Recipients, default: [])
- field(:bto, ObjectValidators.Recipients, default: [])
- field(:bcc, ObjectValidators.Recipients, default: [])
- # TODO: Write type
- field(:tag, {:array, :map}, default: [])
- field(:type, :string)
- field(:content, :string)
- field(:context, :string)
-
- # TODO: Remove actor on objects
- field(:actor, ObjectValidators.ObjectID)
-
- field(:attributedTo, ObjectValidators.ObjectID)
- field(:summary, :string)
- field(:published, ObjectValidators.DateTime)
- field(:emoji, ObjectValidators.Emoji, default: %{})
- field(:sensitive, :boolean, default: false)
- embeds_many(:attachment, AttachmentValidator)
- field(:replies_count, :integer, default: 0)
- field(:like_count, :integer, default: 0)
- field(:announcement_count, :integer, default: 0)
- field(:inReplyTo, ObjectValidators.ObjectID)
- field(:url, ObjectValidators.Uri)
- # short identifier for PleromaFE to group statuses by context
- field(:context_id, :integer)
-
- field(:likes, {:array, ObjectValidators.ObjectID}, default: [])
- field(:announcements, {:array, ObjectValidators.ObjectID}, default: [])
+ quote do
+ unquote do
+ import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
+ message_fields()
+ object_fields()
+ status_object_fields()
+ end
+ end
field(:closed, ObjectValidators.DateTime)
field(:voters, {:array, ObjectValidators.ObjectID}, default: [])
@@ -83,8 +60,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,16 +70,17 @@ 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])
+ |> validate_required([:id, :actor, :attributedTo, :type, :context])
|> CommonValidations.validate_any_presence([:cc, :to])
|> CommonValidations.validate_fields_match([:actor, :attributedTo])
|> CommonValidations.validate_actor_presence()
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
index 000000000..9f15f1981
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/object_validators/tag_validator.ex
@@ -0,0 +1,77 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 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
diff --git a/lib/pleroma/web/activity_pub/object_validators/undo_validator.ex b/lib/pleroma/web/activity_pub/object_validators/undo_validator.ex
index 8cae94467..f03051491 100644
--- a/lib/pleroma/web/activity_pub/object_validators/undo_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/undo_validator.ex
@@ -1,12 +1,12 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.ObjectValidators.UndoValidator do
use Ecto.Schema
alias Pleroma.Activity
- alias Pleroma.EctoType.ActivityPub.ObjectValidators
+ alias Pleroma.User
import Ecto.Changeset
import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
@@ -14,12 +14,13 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.UndoValidator do
@primary_key false
embedded_schema do
- field(:id, ObjectValidators.ObjectID, primary_key: true)
- field(:type, :string)
- field(:object, ObjectValidators.ObjectID)
- field(:actor, ObjectValidators.ObjectID)
- field(:to, ObjectValidators.Recipients, default: [])
- field(:cc, ObjectValidators.Recipients, default: [])
+ quote do
+ unquote do
+ import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
+ message_fields()
+ activity_fields()
+ end
+ end
end
def cast_and_validate(data) do
@@ -38,11 +39,11 @@ 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])
- |> validate_actor_presence()
+ |> validate_undo_actor(:actor)
|> validate_object_presence()
|> validate_undo_rights()
end
@@ -59,4 +60,13 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.UndoValidator do
_ -> cng
end
end
+
+ defp validate_undo_actor(cng, field_name) do
+ validate_change(cng, field_name, fn field_name, actor ->
+ case User.get_cached_by_ap_id(actor) do
+ %User{} -> []
+ _ -> [{field_name, "can't find user"}]
+ end
+ end)
+ end
end
diff --git a/lib/pleroma/web/activity_pub/object_validators/update_validator.ex b/lib/pleroma/web/activity_pub/object_validators/update_validator.ex
index b4ba5ede0..1e940a400 100644
--- a/lib/pleroma/web/activity_pub/object_validators/update_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/update_validator.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.ObjectValidators.UpdateValidator do
@@ -13,11 +13,14 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.UpdateValidator do
@primary_key false
embedded_schema do
- field(:id, ObjectValidators.ObjectID, primary_key: true)
- field(:type, :string)
+ quote do
+ unquote do
+ import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
+ message_fields()
+ end
+ end
+
field(:actor, ObjectValidators.ObjectID)
- field(:to, ObjectValidators.Recipients, default: [])
- field(:cc, ObjectValidators.Recipients, default: [])
# In this case, we save the full object in this activity instead of just a
# reference, so we can always see what was actually changed by this.
field(:object, :map)
@@ -28,7 +31,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"])
@@ -48,7 +51,9 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.UpdateValidator do
with actor = get_field(cng, :actor),
object = get_field(cng, :object),
{:ok, object_id} <- ObjectValidators.ObjectID.cast(object),
- true <- actor == object_id do
+ actor_uri <- URI.parse(actor),
+ object_uri <- URI.parse(object_id),
+ true <- actor_uri.host == object_uri.host do
cng
else
_e ->
diff --git a/lib/pleroma/web/activity_pub/pipeline.ex b/lib/pleroma/web/activity_pub/pipeline.ex
index 2715b94d4..ca8653ab1 100644
--- a/lib/pleroma/web/activity_pub/pipeline.ex
+++ b/lib/pleroma/web/activity_pub/pipeline.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.Pipeline do
@@ -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}
diff --git a/lib/pleroma/web/activity_pub/publisher.ex b/lib/pleroma/web/activity_pub/publisher.ex
index 5ab3562bf..6c1ba76a3 100644
--- a/lib/pleroma/web/activity_pub/publisher.ex
+++ b/lib/pleroma/web/activity_pub/publisher.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.Publisher do
@@ -63,18 +63,17 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
date: date
})
- with {:ok, %{status: code}} when code in 200..299 <-
- result =
- HTTP.post(
- inbox,
- json,
- [
- {"Content-Type", "application/activity+json"},
- {"Date", date},
- {"signature", signature},
- {"digest", digest}
- ]
- ) do
+ with {:ok, %{status: code}} = result when code in 200..299 <-
+ HTTP.post(
+ inbox,
+ json,
+ [
+ {"Content-Type", "application/activity+json"},
+ {"Date", date},
+ {"signature", signature},
+ {"digest", digest}
+ ]
+ ) do
if not Map.has_key?(params, :unreachable_since) || params[:unreachable_since] do
Instances.set_reachable(inbox)
end
@@ -112,6 +111,7 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
quarantined_instances =
Config.get([:instance, :quarantined_instances], [])
+ |> Pleroma.Web.ActivityPub.MRF.instance_list_from_tuples()
|> Pleroma.Web.ActivityPub.MRF.subdomains_regex()
!Pleroma.Web.ActivityPub.MRF.subdomain_match?(quarantined_instances, host)
@@ -129,7 +129,7 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
fetchers =
with %Activity{data: %{"type" => "Delete"}} <- activity,
- %Object{id: object_id} <- Object.normalize(activity),
+ %Object{id: object_id} <- Object.normalize(activity, fetch: false),
fetchers <- User.get_delivered_users_by_object_id(object_id),
_ <- Delivery.delete_all_by_object_id(object_id) do
fetchers
@@ -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
diff --git a/lib/pleroma/web/activity_pub/relay.ex b/lib/pleroma/web/activity_pub/relay.ex
index 6606e1780..2010351d1 100644
--- a/lib/pleroma/web/activity_pub/relay.ex
+++ b/lib/pleroma/web/activity_pub/relay.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.Relay do
diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex
index e37caf6a0..a2152b945 100644
--- a/lib/pleroma/web/activity_pub/side_effects.ex
+++ b/lib/pleroma/web/activity_pub/side_effects.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.SideEffects do
@@ -10,7 +10,6 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
collection, and so on.
"""
alias Pleroma.Activity
- alias Pleroma.Activity.Ir.Topics
alias Pleroma.Chat
alias Pleroma.Chat.MessageReference
alias Pleroma.FollowingRelationship
@@ -24,15 +23,18 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.Push
alias Pleroma.Web.Streamer
+ alias Pleroma.Workers.PollWorker
+ require Pleroma.Constants
require Logger
@cachex Pleroma.Config.get([:cachex, :provider], Cachex)
- @ap_streamer Pleroma.Config.get([:side_effects, :ap_streamer], ActivityPub)
@logger Pleroma.Config.get([:side_effects, :logger], Logger)
@behaviour Pleroma.Web.ActivityPub.SideEffects.Handling
+ defp ap_streamer, do: Pleroma.Config.get([:side_effects, :ap_streamer], ActivityPub)
+
@impl true
def handle(object, meta \\ [])
@@ -152,23 +154,26 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
# Tasks this handles:
# - Update the user
+ # - Update a non-user object (Note, Question, etc.)
#
# For a local user, we also get a changeset with the full information, so we
# can update non-federating, non-activitypub settings as well.
@impl true
def handle(%{data: %{"type" => "Update", "object" => updated_object}} = object, meta) do
- if changeset = Keyword.get(meta, :user_update_changeset) do
- changeset
- |> User.update_and_set_cache()
+ updated_object_id = updated_object["id"]
+
+ with {_, true} <- {:has_id, is_binary(updated_object_id)},
+ %{"type" => type} <- updated_object,
+ {_, is_user} <- {:is_user, type in Pleroma.Constants.actor_types()} do
+ if is_user do
+ handle_update_user(object, meta)
+ else
+ handle_update_object(object, meta)
+ end
else
- {:ok, new_user_data} = ActivityPub.user_data_from_user_object(updated_object)
-
- User.get_by_ap_id(updated_object["id"])
- |> User.remote_user_changeset(new_user_data)
- |> User.update_and_set_cache()
+ _ ->
+ {:ok, object, meta}
end
-
- {:ok, object, meta}
end
# Tasks this handles:
@@ -194,15 +199,29 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
# - Set up notifications
@impl true
def handle(%{data: %{"type" => "Create"}} = activity, meta) do
- with {:ok, object, meta} <- handle_object_creation(meta[:object_data], meta),
+ with {:ok, object, meta} <- handle_object_creation(meta[:object_data], activity, meta),
%User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
{:ok, notifications} = Notification.create_notifications(activity, do_send: false)
{:ok, _user} = ActivityPub.increase_note_count_if_public(user, object)
+ {:ok, _user} = ActivityPub.update_last_status_at_if_public(user, object)
- if in_reply_to = object.data["inReplyTo"] && object.data["type"] != "Answer" do
+ if in_reply_to = object.data["type"] != "Answer" && object.data["inReplyTo"] do
Object.increase_replies_count(in_reply_to)
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)
@@ -211,6 +230,8 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
meta
|> add_notifications(notifications)
+ ap_streamer().stream_out(activity)
+
{:ok, activity, meta}
else
e -> Repo.rollback(e)
@@ -231,9 +252,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
if !User.is_internal_user?(user) do
Notification.create_notifications(object)
- object
- |> Topics.get_activity_topics()
- |> Streamer.stream(object)
+ ap_streamer().stream_out(object)
end
{:ok, object, meta}
@@ -263,23 +282,22 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
# Tasks this handles:
# - Delete and unpins the create activity
# - Replace object with Tombstone
- # - Set up notification
# - Reduce the user note count
# - Reduce the reply count
# - Stream out the activity
@impl true
def handle(%{data: %{"type" => "Delete", "object" => deleted_object}} = object, meta) do
deleted_object =
- Object.normalize(deleted_object, false) ||
+ Object.normalize(deleted_object, fetch: false) ||
User.get_cached_by_ap_id(deleted_object)
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)
@@ -289,8 +307,8 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
MessageReference.delete_for_object(deleted_object)
- @ap_streamer.stream_out(object)
- @ap_streamer.stream_out_participations(deleted_object, user)
+ ap_streamer().stream_out(object)
+ ap_streamer().stream_out_participations(deleted_object, user)
:ok
else
{:actor, _} ->
@@ -305,20 +323,149 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
end
if result == :ok do
- Notification.create_notifications(object)
{:ok, object, meta}
else
{:error, result}
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
{:ok, object, meta}
end
- def handle_object_creation(%{"type" => "ChatMessage"} = object, meta) do
+ defp handle_update_user(
+ %{data: %{"type" => "Update", "object" => updated_object}} = object,
+ meta
+ ) do
+ if changeset = Keyword.get(meta, :user_update_changeset) do
+ changeset
+ |> User.update_and_set_cache()
+ else
+ {:ok, new_user_data} = ActivityPub.user_data_from_user_object(updated_object)
+
+ User.get_by_ap_id(updated_object["id"])
+ |> User.remote_user_changeset(new_user_data)
+ |> User.update_and_set_cache()
+ end
+
+ {:ok, object, meta}
+ end
+
+ defp handle_update_object(
+ %{data: %{"type" => "Update", "object" => updated_object}} = object,
+ meta
+ ) do
+ orig_object_ap_id = updated_object["id"]
+ orig_object = Object.get_by_ap_id(orig_object_ap_id)
+ orig_object_data = orig_object.data
+
+ updated_object =
+ if meta[:local] do
+ # If this is a local Update, we don't process it by transmogrifier,
+ # so we use the embedded object as-is.
+ updated_object
+ else
+ meta[:object_data]
+ end
+
+ if orig_object_data["type"] in Pleroma.Constants.updatable_object_types() do
+ %{
+ updated_data: updated_object_data,
+ updated: updated,
+ used_history_in_new_object?: used_history_in_new_object?
+ } = Object.Updater.make_new_object_data_from_update_object(orig_object_data, updated_object)
+
+ changeset =
+ orig_object
+ |> Repo.preload(:hashtags)
+ |> Object.change(%{data: updated_object_data})
+
+ with {:ok, new_object} <- Repo.update(changeset),
+ {:ok, _} <- Object.invalid_object_cache(new_object),
+ {:ok, _} <- Object.set_cache(new_object),
+ # The metadata/utils.ex uses the object id for the cache.
+ {:ok, _} <- Pleroma.Activity.HTML.invalidate_cache_for(new_object.id) do
+ if used_history_in_new_object? do
+ with create_activity when not is_nil(create_activity) <-
+ Pleroma.Activity.get_create_by_object_ap_id(orig_object_ap_id),
+ {:ok, _} <- Pleroma.Activity.HTML.invalidate_cache_for(create_activity.id) do
+ nil
+ else
+ _ -> nil
+ end
+ end
+
+ if updated do
+ object
+ |> Activity.normalize()
+ |> ActivityPub.notify_and_stream()
+ end
+ end
+ end
+
+ {:ok, object, meta}
+ end
+
+ def handle_object_creation(%{"type" => "ChatMessage"} = object, _activity, meta) do
with {:ok, object, meta} <- Pipeline.common_pipeline(object, meta) do
actor = User.get_cached_by_ap_id(object.data["actor"])
recipient = User.get_cached_by_ap_id(hd(object.data["to"]))
@@ -353,7 +500,14 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
end
end
- def handle_object_creation(%{"type" => "Answer"} = object_map, meta) do
+ def handle_object_creation(%{"type" => "Question"} = object, activity, meta) do
+ with {:ok, object, meta} <- Pipeline.common_pipeline(object, meta) do
+ PollWorker.schedule_poll_end(activity)
+ {:ok, object, meta}
+ end
+ end
+
+ def handle_object_creation(%{"type" => "Answer"} = object_map, _activity, meta) do
with {:ok, object, meta} <- Pipeline.common_pipeline(object_map, meta) do
Object.increase_vote_count(
object.data["inReplyTo"],
@@ -365,15 +519,15 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
end
end
- def handle_object_creation(%{"type" => objtype} = object, meta)
- when objtype in ~w[Audio Video Question Event Article] do
+ def handle_object_creation(%{"type" => objtype} = object, _activity, meta)
+ when objtype in ~w[Audio Video Event Article Note Page] do
with {:ok, object, meta} <- Pipeline.common_pipeline(object, meta) do
{:ok, object, meta}
end
end
# Nothing to do
- def handle_object_creation(object, meta) do
+ def handle_object_creation(object, _activity, meta) do
{:ok, object, meta}
end
diff --git a/lib/pleroma/web/activity_pub/side_effects/handling.ex b/lib/pleroma/web/activity_pub/side_effects/handling.ex
index 9d64c0e47..eb012f576 100644
--- a/lib/pleroma/web/activity_pub/side_effects/handling.ex
+++ b/lib/pleroma/web/activity_pub/side_effects/handling.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.SideEffects.Handling do
diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex
index 565d32433..e4c04da0d 100644
--- a/lib/pleroma/web/activity_pub/transmogrifier.ex
+++ b/lib/pleroma/web/activity_pub/transmogrifier.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.Transmogrifier do
@@ -32,19 +32,17 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
"""
def fix_object(object, options \\ []) do
object
- |> strip_internal_fields
- |> fix_actor
- |> fix_url
- |> fix_attachments
- |> fix_context
+ |> strip_internal_fields()
+ |> fix_actor()
+ |> fix_url()
+ |> fix_attachments()
+ |> fix_context()
|> fix_in_reply_to(options)
- |> fix_emoji
- |> fix_tag
- |> set_sensitive
- |> fix_content_map
- |> fix_addressing
- |> fix_summary
- |> fix_type(options)
+ |> fix_emoji()
+ |> fix_tag()
+ |> fix_content_map()
+ |> fix_addressing()
+ |> fix_summary()
end
def fix_summary(%{"summary" => nil} = object) do
@@ -73,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()
@@ -92,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
@@ -138,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
@@ -224,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) && url =~ Pleroma.Constants.mime_regex() ->
+ url["mediaType"]
+
+ is_bitstring(data["mediaType"]) && data["mediaType"] =~ Pleroma.Constants.mime_regex() ->
+ data["mediaType"]
+
+ is_bitstring(data["mimeType"]) && data["mimeType"] =~ Pleroma.Constants.mime_regex() ->
+ data["mimeType"]
+
+ true ->
+ nil
end
href =
@@ -245,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],
@@ -315,10 +303,9 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
tags =
tag
|> Enum.filter(fn data -> data["type"] == "Hashtag" and data["name"] end)
- |> Enum.map(fn %{"name" => name} ->
- name
- |> String.slice(1..-1)
- |> String.downcase()
+ |> Enum.map(fn
+ %{"name" => "#" <> hashtag} -> String.downcase(hashtag)
+ %{"name" => hashtag} -> String.downcase(hashtag)
end)
Map.put(object, "tag", tag ++ tags)
@@ -342,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
@@ -367,29 +353,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end)
end
- # Compatibility wrapper for Mastodon votes
- defp handle_create(%{"object" => %{"type" => "Answer"}} = data, _user) do
- handle_incoming(data)
- end
-
- defp handle_create(%{"object" => object} = data, user) do
- %{
- to: data["to"],
- object: object,
- actor: user,
- context: object["context"],
- local: false,
- published: data["published"],
- additional:
- Map.take(data, [
- "cc",
- "directMessage",
- "id"
- ])
- }
- |> ActivityPub.create()
- end
-
def handle_incoming(data, options \\ [])
# Flag objects are placed ahead of the ID check because Mastodon 2.8 and earlier send them
@@ -421,44 +384,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
def handle_incoming(%{"id" => id}, _options) when is_binary(id) and byte_size(id) < 8,
do: :error
- # TODO: validate those with a Ecto scheme
- # - tags
- # - emoji
- def handle_incoming(
- %{"type" => "Create", "object" => %{"type" => objtype} = object} = data,
- options
- )
- when objtype in ~w{Note Page} do
- actor = Containment.get_actor(data)
-
- with nil <- Activity.get_create_by_object_ap_id(object["id"]),
- {:ok, %User{} = user} <- User.get_or_fetch_by_ap_id(actor) do
- data =
- data
- |> Map.put("object", fix_object(object, options))
- |> Map.put("actor", actor)
- |> fix_addressing()
-
- with {:ok, created_activity} <- handle_create(data, user) do
- reply_depth = (options[:depth] || 0) + 1
-
- if Federator.allowed_thread_distance?(reply_depth) do
- for reply_id <- replies(object) do
- Pleroma.Workers.RemoteFetcherWorker.enqueue("fetch_remote", %{
- "id" => reply_id,
- "depth" => reply_depth
- })
- end
- end
-
- {:ok, created_activity}
- end
- else
- %Activity{} = activity -> {:ok, activity}
- _e -> :error
- end
- end
-
def handle_incoming(
%{"type" => "Listen", "object" => %{"type" => "Audio"} = object} = data,
options
@@ -520,14 +445,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 Page} 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}
@@ -536,7 +470,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
@@ -566,7 +500,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}} <-
@@ -653,7 +587,9 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
@spec get_obj_helper(String.t(), Keyword.t()) :: {:ok, Object.t()} | nil
def get_obj_helper(id, options \\ []) do
- case Object.normalize(id, true, options) do
+ options = Keyword.put(options, :fetch, true)
+
+ case Object.normalize(id, options) do
%Object{} = object -> {:ok, object}
_ -> nil
end
@@ -672,7 +608,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
"actor" => attributed_to,
"object" => data
}) do
- {:ok, Object.normalize(activity)}
+ {:ok, Object.normalize(activity, fetch: false)}
else
_ -> get_obj_helper(object_id)
end
@@ -740,7 +676,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
# Prepares the object of an outgoing create activity.
def prepare_object(object) do
object
- |> set_sensitive
|> add_hashtags
|> add_mention_tags
|> add_emoji_tags
@@ -752,6 +687,24 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|> strip_internal_fields
|> strip_internal_tags
|> set_type
+ |> maybe_process_history
+ end
+
+ defp maybe_process_history(%{"formerRepresentations" => %{"orderedItems" => history}} = object) do
+ processed_history =
+ Enum.map(
+ history,
+ fn
+ item when is_map(item) -> prepare_object(item)
+ item -> item
+ end
+ )
+
+ put_in(object, ["formerRepresentations", "orderedItems"], processed_history)
+ end
+
+ defp maybe_process_history(object) do
+ object
end
# @doc
@@ -763,7 +716,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
when activity_type in ["Create", "Listen"] do
object =
object_id
- |> Object.normalize()
+ |> Object.normalize(fetch: false)
|> Map.get(:data)
|> prepare_object
@@ -776,10 +729,25 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
{:ok, data}
end
+ def prepare_outgoing(%{"type" => "Update", "object" => %{"type" => objtype} = object} = data)
+ when objtype in Pleroma.Constants.updatable_object_types() do
+ object =
+ object
+ |> prepare_object
+
+ data =
+ data
+ |> Map.put("object", object)
+ |> Map.merge(Utils.make_json_ld_header())
+ |> Map.delete("bcc")
+
+ {:ok, data}
+ end
+
def prepare_outgoing(%{"type" => "Announce", "actor" => ap_id, "object" => object_id} = data) do
object =
object_id
- |> Object.normalize()
+ |> Object.normalize(fetch: false)
data =
if Visibility.is_private?(object) && object.data["actor"] == ap_id do
@@ -919,7 +887,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
defp build_emoji_tag({name, url}) do
%{
- "icon" => %{"url" => url, "type" => "Image"},
+ "icon" => %{"url" => "#{URI.encode(url)}", "type" => "Image"},
"name" => ":" <> name <> ":",
"type" => "Emoji",
"updated" => "1970-01-01T00:00:00Z",
@@ -931,15 +899,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
Map.put(object, "conversation", object["context"])
end
- def set_sensitive(%{"sensitive" => _} = object) do
- object
- end
-
- def set_sensitive(object) do
- tags = object["tag"] || []
- Map.put(object, "sensitive", "nsfw" in tags)
- end
-
def set_type(%{"type" => "Answer"} = object) do
Map.put(object, "type", "Note")
end
@@ -959,7 +918,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,
@@ -967,6 +926,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)
@@ -1010,6 +972,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
diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex
index ea1c3a04a..b898d6fe8 100644
--- a/lib/pleroma/web/activity_pub/utils.ex
+++ b/lib/pleroma/web/activity_pub/utils.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.Utils do
@@ -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
@@ -150,22 +154,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
Notification.get_notified_from_activity(%Activity{data: object}, false)
end
- def create_context(context) do
- context = context || generate_id("contexts")
-
- # Ecto has problems accessing the constraint inside the jsonb,
- # so we explicitly check for the existed object before insert
- object = Object.get_cached_by_ap_id(context)
-
- with true <- is_nil(object),
- changeset <- Object.context_mapping(context),
- {:ok, inserted_object} <- Repo.insert(changeset) do
- inserted_object
- else
- _ ->
- object
- end
- end
+ def maybe_create_context(context), do: context || generate_id("contexts")
@doc """
Enqueues an activity for federation if it's local
@@ -197,18 +186,16 @@ defmodule Pleroma.Web.ActivityPub.Utils do
|> Map.put_new("id", "pleroma:fakeid")
|> Map.put_new_lazy("published", &make_date/0)
|> Map.put_new("context", "pleroma:fakecontext")
- |> Map.put_new("context_id", -1)
|> lazy_put_object_defaults(true)
end
def lazy_put_activity_defaults(map, _fake?) do
- %{data: %{"id" => context}, id: context_id} = create_context(map["context"])
+ context = maybe_create_context(map["context"])
map
|> Map.put_new_lazy("id", &generate_activity_id/0)
|> Map.put_new_lazy("published", &make_date/0)
|> Map.put_new("context", context)
- |> Map.put_new("context_id", context_id)
|> lazy_put_object_defaults(false)
end
@@ -222,7 +209,6 @@ defmodule Pleroma.Web.ActivityPub.Utils do
|> Map.put_new("id", "pleroma:fake_object_id")
|> Map.put_new_lazy("published", &make_date/0)
|> Map.put_new("context", activity["context"])
- |> Map.put_new("context_id", activity["context_id"])
|> Map.put_new("fake", true)
%{activity | "object" => object}
@@ -235,7 +221,6 @@ defmodule Pleroma.Web.ActivityPub.Utils do
|> Map.put_new_lazy("id", &generate_object_id/0)
|> Map.put_new_lazy("published", &make_date/0)
|> Map.put_new("context", activity["context"])
- |> Map.put_new("context_id", activity["context_id"])
%{activity | "object" => object}
end
@@ -442,7 +427,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
|> Activity.Queries.by_type()
|> Activity.Queries.by_actor(actor)
|> Activity.Queries.by_object_id(object)
- |> where(fragment("data->>'state' = 'pending'"))
+ |> where(fragment("data->>'state' = 'pending'") or fragment("data->>'state' = 'accept'"))
|> update(set: [data: fragment("jsonb_set(data, '{state}', ?)", ^state)])
|> Repo.update_all([])
@@ -710,20 +695,24 @@ defmodule Pleroma.Web.ActivityPub.Utils do
Enum.map(statuses || [], &build_flag_object/1)
end
- defp build_flag_object(%Activity{data: %{"id" => id}, object: %{data: data}}) do
- activity_actor = User.get_by_ap_id(data["actor"])
+ defp build_flag_object(%Activity{} = activity) do
+ object = Object.normalize(activity, fetch: false)
- %{
- "type" => "Note",
- "id" => id,
- "content" => data["content"],
- "published" => data["published"],
- "actor" =>
- AccountView.render(
- "show.json",
- %{user: activity_actor, skip_visibility_check: true}
- )
- }
+ # Do not allow people to report Creates. Instead, report the Object that is Created.
+ if activity.data["type"] != "Create" do
+ build_flag_object_with_actor_and_id(
+ object,
+ User.get_by_ap_id(activity.data["actor"]),
+ activity.data["id"]
+ )
+ else
+ build_flag_object(object)
+ end
+ end
+
+ defp build_flag_object(%Object{} = object) do
+ actor = User.get_by_ap_id(object.data["actor"])
+ build_flag_object_with_actor_and_id(object, actor, object.data["id"])
end
defp build_flag_object(act) when is_map(act) or is_binary(act) do
@@ -735,12 +724,12 @@ defmodule Pleroma.Web.ActivityPub.Utils do
end
case Activity.get_by_ap_id_with_object(id) do
- %Activity{} = activity ->
- build_flag_object(activity)
+ %Activity{object: object} = _ ->
+ build_flag_object(object)
nil ->
- if activity = Activity.get_by_object_ap_id_with_object(id) do
- build_flag_object(activity)
+ if %Object{} = object = Object.get_by_ap_id(id) do
+ build_flag_object(object)
else
%{"id" => id, "deleted" => true}
end
@@ -749,6 +738,20 @@ defmodule Pleroma.Web.ActivityPub.Utils do
defp build_flag_object(_), do: []
+ defp build_flag_object_with_actor_and_id(%Object{data: data}, actor, id) do
+ %{
+ "type" => "Note",
+ "id" => id,
+ "content" => data["content"],
+ "published" => data["published"],
+ "actor" =>
+ AccountView.render(
+ "show.json",
+ %{user: actor, skip_visibility_check: true}
+ )
+ }
+ end
+
#### Report-related helpers
def get_reports(params, page, page_size) do
params =
@@ -763,22 +766,21 @@ defmodule Pleroma.Web.ActivityPub.Utils do
ActivityPub.fetch_activities([], params, :offset)
end
- def update_report_state(%Activity{} = activity, state)
- when state in @strip_status_report_states do
- {:ok, stripped_activity} = strip_report_status_data(activity)
+ defp maybe_strip_report_status(data, state) do
+ with true <- Config.get([:instance, :report_strip_status]),
+ true <- state in @strip_status_report_states,
+ {:ok, stripped_activity} = strip_report_status_data(%Activity{data: data}) do
+ data |> Map.put("object", stripped_activity.data["object"])
+ else
+ _ -> data
+ end
+ end
+ def update_report_state(%Activity{} = activity, state) when state in @supported_report_states do
new_data =
activity.data
|> Map.put("state", state)
- |> Map.put("object", stripped_activity.data["object"])
-
- activity
- |> Changeset.change(data: new_data)
- |> Repo.update()
- end
-
- def update_report_state(%Activity{} = activity, state) when state in @supported_report_states do
- new_data = Map.put(activity.data, "state", state)
+ |> maybe_strip_report_status(state)
activity
|> Changeset.change(data: new_data)
diff --git a/lib/pleroma/web/activity_pub/views/object_view.ex b/lib/pleroma/web/activity_pub/views/object_view.ex
index e555e9999..63caa915c 100644
--- a/lib/pleroma/web/activity_pub/views/object_view.ex
+++ b/lib/pleroma/web/activity_pub/views/object_view.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.ObjectView do
@@ -18,7 +18,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectView do
def render("object.json", %{object: %Activity{data: %{"type" => activity_type}} = activity})
when activity_type in ["Create", "Listen"] do
base = Pleroma.Web.ActivityPub.Utils.make_json_ld_header()
- object = Object.normalize(activity)
+ object = Object.normalize(activity, fetch: false)
additional =
Transmogrifier.prepare_object(activity.data)
@@ -29,11 +29,11 @@ defmodule Pleroma.Web.ActivityPub.ObjectView do
def render("object.json", %{object: %Activity{} = activity}) do
base = Pleroma.Web.ActivityPub.Utils.make_json_ld_header()
- object = Object.normalize(activity)
+ object_id = Object.normalize(activity, id_only: true)
additional =
Transmogrifier.prepare_object(activity.data)
- |> Map.put("object", object.data["id"])
+ |> Map.put("object", object_id)
Map.merge(base, additional)
end
diff --git a/lib/pleroma/web/activity_pub/views/user_view.ex b/lib/pleroma/web/activity_pub/views/user_view.ex
index 241224b57..f69fca075 100644
--- a/lib/pleroma/web/activity_pub/views/user_view.ex
+++ b/lib/pleroma/web/activity_pub/views/user_view.ex
@@ -1,13 +1,15 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
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
@@ -32,7 +34,6 @@ defmodule Pleroma.Web.ActivityPub.UserView do
def render("endpoints.json", _), do: %{}
def render("service.json", %{user: user}) do
- {:ok, user} = User.ensure_keys_present(user)
{:ok, _, public_key} = Keys.keys_from_pem(user.keys)
public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key)
public_key = :public_key.pem_encode([public_key])
@@ -69,7 +70,6 @@ defmodule Pleroma.Web.ActivityPub.UserView do
do: render("service.json", %{user: user}) |> Map.put("preferredUsername", user.nickname)
def render("user.json", %{user: user}) do
- {:ok, user} = User.ensure_keys_present(user)
{:ok, _, public_key} = Keys.keys_from_pem(user.keys)
public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key)
public_key = :public_key.pem_encode([public_key])
@@ -90,6 +90,11 @@ defmodule Pleroma.Web.ActivityPub.UserView do
%{}
end
+ birthday =
+ if user.show_birthday && user.birthday,
+ do: Date.to_iso8601(user.birthday),
+ else: nil
+
%{
"id" => user.ap_id,
"type" => user.actor_type,
@@ -97,6 +102,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,
@@ -113,7 +119,8 @@ defmodule Pleroma.Web.ActivityPub.UserView do
# Note: key name is indeed "discoverable" (not an error)
"discoverable" => user.is_discoverable,
"capabilities" => capabilities,
- "alsoKnownAs" => user.also_known_as
+ "alsoKnownAs" => user.also_known_as,
+ "vcard:bday" => birthday
}
|> Map.merge(maybe_make_image(&User.avatar_url/2, "icon", user))
|> Map.merge(maybe_make_image(&User.banner_url/2, "image", user))
@@ -245,6 +252,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
diff --git a/lib/pleroma/web/activity_pub/visibility.ex b/lib/pleroma/web/activity_pub/visibility.ex
index 2cb5a2bd0..7c57f88f9 100644
--- a/lib/pleroma/web/activity_pub/visibility.ex
+++ b/lib/pleroma/web/activity_pub/visibility.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.Visibility do
@@ -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
@@ -56,11 +56,11 @@ defmodule Pleroma.Web.ActivityPub.Visibility do
def is_list?(%{data: %{"listMessage" => _}}), do: true
def is_list?(_), do: false
- @spec visible_for_user?(Activity.t() | nil, User.t() | nil) :: boolean()
+ @spec visible_for_user?(Object.t() | Activity.t() | nil, User.t() | nil) :: boolean()
+ def visible_for_user?(%Object{data: %{"type" => "Tombstone"}}, _), do: false
def visible_for_user?(%Activity{actor: ap_id}, %User{ap_id: ap_id}), do: true
-
+ def visible_for_user?(%Object{data: %{"actor" => ap_id}}, %User{ap_id: ap_id}), do: true
def visible_for_user?(nil, _), do: false
-
def visible_for_user?(%Activity{data: %{"listMessage" => _}}, nil), do: false
def visible_for_user?(
@@ -73,16 +73,21 @@ defmodule Pleroma.Web.ActivityPub.Visibility do
|> Pleroma.List.member?(user)
end
- def visible_for_user?(%Activity{} = activity, nil) do
- if restrict_unauthenticated_access?(activity),
+ def visible_for_user?(%{__struct__: module} = message, nil)
+ when module in [Activity, Object] do
+ if restrict_unauthenticated_access?(message),
do: false,
- else: is_public?(activity)
+ else: is_public?(message) and not is_local_public?(message)
end
- def visible_for_user?(%Activity{} = activity, user) do
+ def visible_for_user?(%{__struct__: module} = message, user)
+ when module in [Activity, Object] do
x = [user.ap_id | User.following(user)]
- y = [activity.actor] ++ activity.data["to"] ++ (activity.data["cc"] || [])
- is_public?(activity) || Enum.any?(x, &(&1 in y))
+ y = [message.data["actor"]] ++ message.data["to"] ++ (message.data["cc"] || [])
+
+ user_is_local = user.local
+ federatable = not is_local_public?(message)
+ (is_public?(message) || Enum.any?(x, &(&1 in y))) and (user_is_local || federatable)
end
def entire_thread_visible_for_user?(%Activity{} = activity, %User{} = user) do
@@ -126,7 +131,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
diff --git a/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex b/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex
index 1c7c26d98..1894000ff 100644
--- a/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex
+++ b/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.AdminAPI.AdminAPIController do
@@ -25,13 +25,13 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
plug(
OAuthScopesPlug,
- %{scopes: ["read:accounts"], admin: true}
+ %{scopes: ["admin:read:accounts"]}
when action in [:right_get, :show_user_credentials, :create_backup]
)
plug(
OAuthScopesPlug,
- %{scopes: ["write:accounts"], admin: true}
+ %{scopes: ["admin:write:accounts"]}
when action in [
:get_password_reset,
:force_password_reset,
@@ -48,19 +48,19 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
plug(
OAuthScopesPlug,
- %{scopes: ["read:statuses"], admin: true}
- when action in [:list_user_statuses, :list_instance_statuses]
+ %{scopes: ["admin:read:statuses"]}
+ when action in [:list_user_statuses]
)
plug(
OAuthScopesPlug,
- %{scopes: ["read:chats"], admin: true}
+ %{scopes: ["admin:read:chats"]}
when action in [:list_user_chats]
)
plug(
OAuthScopesPlug,
- %{scopes: ["read"], admin: true}
+ %{scopes: ["admin:read"]}
when action in [
:list_log,
:stats,
@@ -70,7 +70,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
plug(
OAuthScopesPlug,
- %{scopes: ["write"], admin: true}
+ %{scopes: ["admin:write"]}
when action in [
:restart,
:resend_confirmation_email,
@@ -81,23 +81,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
action_fallback(AdminAPI.FallbackController)
- def list_instance_statuses(conn, %{"instance" => instance} = params) do
- with_reblogs = params["with_reblogs"] == "true" || params["with_reblogs"] == true
- {page, page_size} = page_params(params)
-
- activities =
- ActivityPub.fetch_statuses(nil, %{
- instance: instance,
- limit: page_size,
- offset: (page - 1) * page_size,
- exclude_reblogs: not with_reblogs
- })
-
- conn
- |> put_view(AdminAPI.StatusView)
- |> render("index.json", %{activities: activities, as: :activity})
- end
-
def list_user_statuses(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname} = params) do
with_reblogs = params["with_reblogs"] == "true" || params["with_reblogs"] == true
godmode = params["godmode"] == "true" || params["godmode"] == true
@@ -105,18 +88,19 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
with %User{} = user <- User.get_cached_by_nickname_or_id(nickname, for: admin) do
{page, page_size} = page_params(params)
- activities =
+ result =
ActivityPub.fetch_user_activities(user, nil, %{
limit: page_size,
offset: (page - 1) * page_size,
godmode: godmode,
exclude_reblogs: not with_reblogs,
- pagination_type: :offset
+ pagination_type: :offset,
+ total: true
})
conn
|> put_view(AdminAPI.StatusView)
- |> render("index.json", %{activities: activities, as: :activity})
+ |> render("index.json", %{total: result[:total], activities: result[:items], as: :activity})
else
_ -> {:error, :not_found}
end
@@ -404,7 +388,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
if Config.get(:configurable_from_database) do
:ok
else
- {:error, "To use this endpoint you need to enable configuration from database."}
+ {:error, "You must enable configurable_from_database in your config file."}
end
end
diff --git a/lib/pleroma/web/admin_api/controllers/announcement_controller.ex b/lib/pleroma/web/admin_api/controllers/announcement_controller.ex
new file mode 100644
index 000000000..6ad5fc12c
--- /dev/null
+++ b/lib/pleroma/web/admin_api/controllers/announcement_controller.ex
@@ -0,0 +1,83 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.AdminAPI.AnnouncementController do
+ use Pleroma.Web, :controller
+
+ alias Pleroma.Announcement
+ alias Pleroma.Web.ControllerHelper
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
+
+ plug(Pleroma.Web.ApiSpec.CastAndValidate)
+ plug(OAuthScopesPlug, %{scopes: ["admin:write"]} when action in [:create, :delete, :change])
+ plug(OAuthScopesPlug, %{scopes: ["admin:read"]} when action in [:index, :show])
+ action_fallback(Pleroma.Web.AdminAPI.FallbackController)
+
+ defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.Admin.AnnouncementOperation
+
+ defp default_limit, do: 20
+
+ def index(conn, params) do
+ limit = Map.get(params, :limit, default_limit())
+ offset = Map.get(params, :offset, 0)
+
+ announcements = Announcement.list_paginated(%{limit: limit, offset: offset})
+
+ render(conn, "index.json", announcements: announcements)
+ end
+
+ def show(conn, %{id: id} = _params) do
+ announcement = Announcement.get_by_id(id)
+
+ if is_nil(announcement) do
+ {:error, :not_found}
+ else
+ render(conn, "show.json", announcement: announcement)
+ end
+ end
+
+ def create(%{body_params: params} = conn, _params) do
+ with {:ok, announcement} <- Announcement.add(change_params(params)) do
+ render(conn, "show.json", announcement: announcement)
+ else
+ _ ->
+ {:error, 400}
+ end
+ end
+
+ def change_params(orig_params) do
+ data =
+ %{}
+ |> Pleroma.Maps.put_if_present("content", orig_params, &Map.fetch(&1, :content))
+ |> Pleroma.Maps.put_if_present("all_day", orig_params, &Map.fetch(&1, :all_day))
+
+ orig_params
+ |> Map.merge(%{data: data})
+ end
+
+ def change(%{body_params: params} = conn, %{id: id} = _params) do
+ with announcement <- Announcement.get_by_id(id),
+ {:exists, true} <- {:exists, not is_nil(announcement)},
+ {:ok, announcement} <- Announcement.update(announcement, change_params(params)) do
+ render(conn, "show.json", announcement: announcement)
+ else
+ {:exists, false} ->
+ {:error, :not_found}
+
+ _ ->
+ {:error, 400}
+ end
+ end
+
+ def delete(conn, %{id: id} = _params) do
+ case Announcement.delete_by_id(id) do
+ :ok ->
+ conn
+ |> ControllerHelper.json_response(:ok, %{})
+
+ _ ->
+ {:error, :not_found}
+ end
+ end
+end
diff --git a/lib/pleroma/web/admin_api/controllers/chat_controller.ex b/lib/pleroma/web/admin_api/controllers/chat_controller.ex
index af8ff8292..298543fcf 100644
--- a/lib/pleroma/web/admin_api/controllers/chat_controller.ex
+++ b/lib/pleroma/web/admin_api/controllers/chat_controller.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.AdminAPI.ChatController do
@@ -8,7 +8,6 @@ defmodule Pleroma.Web.AdminAPI.ChatController do
alias Pleroma.Activity
alias Pleroma.Chat
alias Pleroma.Chat.MessageReference
- alias Pleroma.ModerationLog
alias Pleroma.Pagination
alias Pleroma.Web.AdminAPI
alias Pleroma.Web.CommonAPI
@@ -21,12 +20,12 @@ defmodule Pleroma.Web.AdminAPI.ChatController do
plug(
OAuthScopesPlug,
- %{scopes: ["read:chats"], admin: true} when action in [:show, :messages]
+ %{scopes: ["admin:read:chats"]} when action in [:show, :messages]
)
plug(
OAuthScopesPlug,
- %{scopes: ["write:chats"], admin: true} when action in [:delete_message]
+ %{scopes: ["admin:write:chats"]} when action in [:delete_message]
)
action_fallback(Pleroma.Web.AdminAPI.FallbackController)
@@ -42,12 +41,6 @@ defmodule Pleroma.Web.AdminAPI.ChatController do
^chat_id <- to_string(cm_ref.chat_id),
%Activity{id: activity_id} <- Activity.get_create_by_object_ap_id(object_ap_id),
{:ok, _} <- CommonAPI.delete(activity_id, user) do
- ModerationLog.insert_log(%{
- action: "chat_message_delete",
- actor: user,
- subject_id: message_id
- })
-
conn
|> put_view(MessageReferenceView)
|> render("show.json", chat_message_reference: cm_ref)
diff --git a/lib/pleroma/web/admin_api/controllers/config_controller.ex b/lib/pleroma/web/admin_api/controllers/config_controller.ex
index 5d155af3d..a03318c0e 100644
--- a/lib/pleroma/web/admin_api/controllers/config_controller.ex
+++ b/lib/pleroma/web/admin_api/controllers/config_controller.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.AdminAPI.ConfigController do
@@ -10,11 +10,11 @@ defmodule Pleroma.Web.AdminAPI.ConfigController do
alias Pleroma.Web.Plugs.OAuthScopesPlug
plug(Pleroma.Web.ApiSpec.CastAndValidate)
- plug(OAuthScopesPlug, %{scopes: ["write"], admin: true} when action == :update)
+ plug(OAuthScopesPlug, %{scopes: ["admin:write"]} when action == :update)
plug(
OAuthScopesPlug,
- %{scopes: ["read"], admin: true}
+ %{scopes: ["admin:read"]}
when action in [:show, :descriptions]
)
@@ -22,10 +22,58 @@ defmodule Pleroma.Web.AdminAPI.ConfigController do
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.Admin.ConfigOperation
+ defp translate_descriptions(descriptions, path \\ []) do
+ Enum.map(descriptions, fn desc -> translate_item(desc, path) end)
+ end
+
+ defp translate_string(str, path, type) do
+ Gettext.dpgettext(
+ Pleroma.Web.Gettext,
+ "config_descriptions",
+ Pleroma.Docs.Translator.Compiler.msgctxt_for(path, type),
+ str
+ )
+ end
+
+ defp maybe_put_translated(item, key, path) do
+ if item[key] do
+ Map.put(
+ item,
+ key,
+ translate_string(
+ item[key],
+ path ++ [Pleroma.Docs.Translator.Compiler.key_for(item)],
+ to_string(key)
+ )
+ )
+ else
+ item
+ end
+ end
+
+ defp translate_item(item, path) do
+ item
+ |> maybe_put_translated(:label, path)
+ |> maybe_put_translated(:description, path)
+ |> translate_children(path)
+ end
+
+ defp translate_children(%{children: children} = item, path) when is_list(children) do
+ item
+ |> Map.put(
+ :children,
+ translate_descriptions(children, path ++ [Pleroma.Docs.Translator.Compiler.key_for(item)])
+ )
+ end
+
+ defp translate_children(item, _path) do
+ item
+ end
+
def descriptions(conn, _params) do
descriptions = Enum.filter(Pleroma.Docs.JSON.compiled_descriptions(), &whitelisted_config?/1)
- json(conn, descriptions)
+ json(conn, translate_descriptions(descriptions))
end
def show(conn, %{only_db: true}) do
@@ -122,7 +170,7 @@ defmodule Pleroma.Web.AdminAPI.ConfigController do
if Config.get(:configurable_from_database) do
:ok
else
- {:error, "To use this endpoint you need to enable configuration from database."}
+ {:error, "You must enable configurable_from_database in your config file."}
end
end
diff --git a/lib/pleroma/web/admin_api/controllers/fallback_controller.ex b/lib/pleroma/web/admin_api/controllers/fallback_controller.ex
index 34d90db07..e72f45c21 100644
--- a/lib/pleroma/web/admin_api/controllers/fallback_controller.ex
+++ b/lib/pleroma/web/admin_api/controllers/fallback_controller.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.AdminAPI.FallbackController do
diff --git a/lib/pleroma/web/admin_api/controllers/frontend_controller.ex b/lib/pleroma/web/admin_api/controllers/frontend_controller.ex
index fac3522b8..b4dbb82fe 100644
--- a/lib/pleroma/web/admin_api/controllers/frontend_controller.ex
+++ b/lib/pleroma/web/admin_api/controllers/frontend_controller.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.AdminAPI.FrontendController do
@@ -9,8 +9,8 @@ defmodule Pleroma.Web.AdminAPI.FrontendController do
alias Pleroma.Web.Plugs.OAuthScopesPlug
plug(Pleroma.Web.ApiSpec.CastAndValidate)
- plug(OAuthScopesPlug, %{scopes: ["write"], admin: true} when action == :install)
- plug(OAuthScopesPlug, %{scopes: ["read"], admin: true} when action == :index)
+ plug(OAuthScopesPlug, %{scopes: ["admin:write"]} when action == :install)
+ plug(OAuthScopesPlug, %{scopes: ["admin:read"]} when action == :index)
action_fallback(Pleroma.Web.AdminAPI.FallbackController)
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.Admin.FrontendOperation
@@ -35,6 +35,12 @@ defmodule Pleroma.Web.AdminAPI.FrontendController do
end
defp installed do
- File.ls!(Pleroma.Frontend.dir())
+ frontend_directory = Pleroma.Frontend.dir()
+
+ if File.exists?(frontend_directory) do
+ File.ls!(frontend_directory)
+ else
+ []
+ end
end
end
diff --git a/lib/pleroma/web/admin_api/controllers/instance_controller.ex b/lib/pleroma/web/admin_api/controllers/instance_controller.ex
new file mode 100644
index 000000000..117a72280
--- /dev/null
+++ b/lib/pleroma/web/admin_api/controllers/instance_controller.ex
@@ -0,0 +1,63 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.AdminAPI.InstanceController do
+ use Pleroma.Web, :controller
+
+ import Pleroma.Web.ControllerHelper, only: [fetch_integer_param: 3]
+
+ alias Pleroma.Instances.Instance
+ alias Pleroma.Web.ActivityPub.ActivityPub
+ alias Pleroma.Web.AdminAPI
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
+
+ require Logger
+
+ @default_page_size 50
+
+ plug(
+ OAuthScopesPlug,
+ %{scopes: ["admin:read:statuses"]}
+ when action in [:list_statuses]
+ )
+
+ plug(
+ OAuthScopesPlug,
+ %{scopes: ["admin:write:accounts", "admin:write:statuses"]}
+ when action in [:delete]
+ )
+
+ action_fallback(AdminAPI.FallbackController)
+
+ def list_statuses(conn, %{"instance" => instance} = params) do
+ with_reblogs = params["with_reblogs"] == "true" || params["with_reblogs"] == true
+ {page, page_size} = page_params(params)
+
+ result =
+ ActivityPub.fetch_statuses(nil, %{
+ instance: instance,
+ limit: page_size,
+ offset: (page - 1) * page_size,
+ exclude_reblogs: not with_reblogs,
+ total: true
+ })
+
+ conn
+ |> put_view(AdminAPI.StatusView)
+ |> render("index.json", %{total: result[:total], activities: result[:items], as: :activity})
+ end
+
+ def delete(conn, %{"instance" => instance}) do
+ with {:ok, _job} <- Instance.delete_users_and_activities(instance) do
+ json(conn, instance)
+ end
+ end
+
+ defp page_params(params) do
+ {
+ fetch_integer_param(params, "page", 1),
+ fetch_integer_param(params, "page_size", @default_page_size)
+ }
+ end
+end
diff --git a/lib/pleroma/web/admin_api/controllers/instance_document_controller.ex b/lib/pleroma/web/admin_api/controllers/instance_document_controller.ex
index 37dbfeb72..990a94313 100644
--- a/lib/pleroma/web/admin_api/controllers/instance_document_controller.ex
+++ b/lib/pleroma/web/admin_api/controllers/instance_document_controller.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.AdminAPI.InstanceDocumentController do
@@ -15,8 +15,8 @@ defmodule Pleroma.Web.AdminAPI.InstanceDocumentController do
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.Admin.InstanceDocumentOperation
- plug(OAuthScopesPlug, %{scopes: ["read"], admin: true} when action == :show)
- plug(OAuthScopesPlug, %{scopes: ["write"], admin: true} when action in [:update, :delete])
+ plug(OAuthScopesPlug, %{scopes: ["admin:read"]} when action == :show)
+ plug(OAuthScopesPlug, %{scopes: ["admin:write"]} when action in [:update, :delete])
def show(conn, %{name: document_name}) do
with {:ok, url} <- InstanceDocument.get(document_name),
diff --git a/lib/pleroma/web/admin_api/controllers/invite_controller.ex b/lib/pleroma/web/admin_api/controllers/invite_controller.ex
index 6a9b4038a..c5d759bb5 100644
--- a/lib/pleroma/web/admin_api/controllers/invite_controller.ex
+++ b/lib/pleroma/web/admin_api/controllers/invite_controller.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.AdminAPI.InviteController do
@@ -14,11 +14,11 @@ defmodule Pleroma.Web.AdminAPI.InviteController do
require Logger
plug(Pleroma.Web.ApiSpec.CastAndValidate)
- plug(OAuthScopesPlug, %{scopes: ["read:invites"], admin: true} when action == :index)
+ plug(OAuthScopesPlug, %{scopes: ["admin:read:invites"]} when action == :index)
plug(
OAuthScopesPlug,
- %{scopes: ["write:invites"], admin: true} when action in [:create, :revoke, :email]
+ %{scopes: ["admin:write:invites"]} when action in [:create, :revoke, :email]
)
action_fallback(Pleroma.Web.AdminAPI.FallbackController)
diff --git a/lib/pleroma/web/admin_api/controllers/media_proxy_cache_controller.ex b/lib/pleroma/web/admin_api/controllers/media_proxy_cache_controller.ex
index 2f712fb8c..4d53f5451 100644
--- a/lib/pleroma/web/admin_api/controllers/media_proxy_cache_controller.ex
+++ b/lib/pleroma/web/admin_api/controllers/media_proxy_cache_controller.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.AdminAPI.MediaProxyCacheController do
@@ -15,12 +15,12 @@ defmodule Pleroma.Web.AdminAPI.MediaProxyCacheController do
plug(
OAuthScopesPlug,
- %{scopes: ["read:media_proxy_caches"], admin: true} when action in [:index]
+ %{scopes: ["admin:read:media_proxy_caches"]} when action in [:index]
)
plug(
OAuthScopesPlug,
- %{scopes: ["write:media_proxy_caches"], admin: true} when action in [:purge, :delete]
+ %{scopes: ["admin:write:media_proxy_caches"]} when action in [:purge, :delete]
)
action_fallback(Pleroma.Web.AdminAPI.FallbackController)
diff --git a/lib/pleroma/web/admin_api/controllers/o_auth_app_controller.ex b/lib/pleroma/web/admin_api/controllers/o_auth_app_controller.ex
index 116a05a4d..879e8b2b4 100644
--- a/lib/pleroma/web/admin_api/controllers/o_auth_app_controller.ex
+++ b/lib/pleroma/web/admin_api/controllers/o_auth_app_controller.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.AdminAPI.OAuthAppController do
@@ -13,11 +13,10 @@ defmodule Pleroma.Web.AdminAPI.OAuthAppController do
require Logger
plug(Pleroma.Web.ApiSpec.CastAndValidate)
- plug(:put_view, Pleroma.Web.MastodonAPI.AppView)
plug(
OAuthScopesPlug,
- %{scopes: ["write"], admin: true}
+ %{scopes: ["admin:write"]}
when action in [:create, :index, :update, :delete]
)
diff --git a/lib/pleroma/web/admin_api/controllers/relay_controller.ex b/lib/pleroma/web/admin_api/controllers/relay_controller.ex
index 611388447..2e83fe139 100644
--- a/lib/pleroma/web/admin_api/controllers/relay_controller.ex
+++ b/lib/pleroma/web/admin_api/controllers/relay_controller.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.AdminAPI.RelayController do
@@ -15,11 +15,11 @@ defmodule Pleroma.Web.AdminAPI.RelayController do
plug(
OAuthScopesPlug,
- %{scopes: ["write:follows"], admin: true}
+ %{scopes: ["admin:write:follows"]}
when action in [:follow, :unfollow]
)
- plug(OAuthScopesPlug, %{scopes: ["read"], admin: true} when action == :index)
+ plug(OAuthScopesPlug, %{scopes: ["admin:read"]} when action == :index)
action_fallback(Pleroma.Web.AdminAPI.FallbackController)
diff --git a/lib/pleroma/web/admin_api/controllers/report_controller.ex b/lib/pleroma/web/admin_api/controllers/report_controller.ex
index cc77cbfdf..15cbbcc3e 100644
--- a/lib/pleroma/web/admin_api/controllers/report_controller.ex
+++ b/lib/pleroma/web/admin_api/controllers/report_controller.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.AdminAPI.ReportController do
@@ -19,11 +19,11 @@ defmodule Pleroma.Web.AdminAPI.ReportController do
require Logger
plug(Pleroma.Web.ApiSpec.CastAndValidate)
- plug(OAuthScopesPlug, %{scopes: ["read:reports"], admin: true} when action in [:index, :show])
+ plug(OAuthScopesPlug, %{scopes: ["admin:read:reports"]} when action in [:index, :show])
plug(
OAuthScopesPlug,
- %{scopes: ["write:reports"], admin: true}
+ %{scopes: ["admin:write:reports"]}
when action in [:update, :notes_create, :notes_delete]
)
diff --git a/lib/pleroma/web/admin_api/controllers/status_controller.ex b/lib/pleroma/web/admin_api/controllers/status_controller.ex
index 2bb437cfe..9a3d49b57 100644
--- a/lib/pleroma/web/admin_api/controllers/status_controller.ex
+++ b/lib/pleroma/web/admin_api/controllers/status_controller.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.AdminAPI.StatusController do
@@ -15,11 +15,11 @@ defmodule Pleroma.Web.AdminAPI.StatusController do
require Logger
plug(Pleroma.Web.ApiSpec.CastAndValidate)
- plug(OAuthScopesPlug, %{scopes: ["read:statuses"], admin: true} when action in [:index, :show])
+ plug(OAuthScopesPlug, %{scopes: ["admin:read:statuses"]} when action in [:index, :show])
plug(
OAuthScopesPlug,
- %{scopes: ["write:statuses"], admin: true} when action in [:update, :delete]
+ %{scopes: ["admin:write:statuses"]} when action in [:update, :delete]
)
action_fallback(Pleroma.Web.AdminAPI.FallbackController)
@@ -65,12 +65,6 @@ defmodule Pleroma.Web.AdminAPI.StatusController do
def delete(%{assigns: %{user: user}} = conn, %{id: id}) do
with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do
- ModerationLog.insert_log(%{
- action: "status_delete",
- actor: user,
- subject_id: id
- })
-
json(conn, %{})
end
end
diff --git a/lib/pleroma/web/admin_api/controllers/user_controller.ex b/lib/pleroma/web/admin_api/controllers/user_controller.ex
index a2a1c875d..7b4ee46a4 100644
--- a/lib/pleroma/web/admin_api/controllers/user_controller.ex
+++ b/lib/pleroma/web/admin_api/controllers/user_controller.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.AdminAPI.UserController do
@@ -13,44 +13,51 @@ defmodule Pleroma.Web.AdminAPI.UserController do
alias Pleroma.Web.ActivityPub.Builder
alias Pleroma.Web.ActivityPub.Pipeline
alias Pleroma.Web.AdminAPI
- alias Pleroma.Web.AdminAPI.AccountView
alias Pleroma.Web.AdminAPI.Search
alias Pleroma.Web.Plugs.OAuthScopesPlug
@users_page_size 50
+ plug(Pleroma.Web.ApiSpec.CastAndValidate)
+
plug(
OAuthScopesPlug,
- %{scopes: ["read:accounts"], admin: true}
- when action in [:list, :show]
+ %{scopes: ["admin:read:accounts"]}
+ when action in [:index, :show]
)
plug(
OAuthScopesPlug,
- %{scopes: ["write:accounts"], admin: true}
+ %{scopes: ["admin:write:accounts"]}
when action in [
:delete,
:create,
:toggle_activation,
:activate,
:deactivate,
- :approve
+ :approve,
+ :suggest,
+ :unsuggest
]
)
plug(
OAuthScopesPlug,
- %{scopes: ["write:follows"], admin: true}
+ %{scopes: ["admin:write:follows"]}
when action in [:follow, :unfollow]
)
action_fallback(AdminAPI.FallbackController)
- def delete(conn, %{"nickname" => nickname}) do
- delete(conn, %{"nicknames" => [nickname]})
+ defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.Admin.UserOperation
+
+ def delete(conn, %{nickname: nickname}) do
+ conn
+ |> Map.put(:body_params, %{nicknames: [nickname]})
+ |> delete(%{})
end
- def delete(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
+ def delete(%{assigns: %{user: admin}, body_params: %{nicknames: nicknames}} = conn, _) do
users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
Enum.each(users, fn user ->
@@ -67,10 +74,16 @@ defmodule Pleroma.Web.AdminAPI.UserController do
json(conn, nicknames)
end
- def follow(%{assigns: %{user: admin}} = conn, %{
- "follower" => follower_nick,
- "followed" => followed_nick
- }) do
+ def follow(
+ %{
+ assigns: %{user: admin},
+ body_params: %{
+ follower: follower_nick,
+ followed: followed_nick
+ }
+ } = conn,
+ _
+ ) do
with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
%User{} = followed <- User.get_cached_by_nickname(followed_nick) do
User.follow(follower, followed)
@@ -86,10 +99,16 @@ defmodule Pleroma.Web.AdminAPI.UserController do
json(conn, "ok")
end
- def unfollow(%{assigns: %{user: admin}} = conn, %{
- "follower" => follower_nick,
- "followed" => followed_nick
- }) do
+ def unfollow(
+ %{
+ assigns: %{user: admin},
+ body_params: %{
+ follower: follower_nick,
+ followed: followed_nick
+ }
+ } = conn,
+ _
+ ) do
with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
%User{} = followed <- User.get_cached_by_nickname(followed_nick) do
User.unfollow(follower, followed)
@@ -105,9 +124,10 @@ defmodule Pleroma.Web.AdminAPI.UserController do
json(conn, "ok")
end
- def create(%{assigns: %{user: admin}} = conn, %{"users" => users}) do
+ def create(%{assigns: %{user: admin}, body_params: %{users: users}} = conn, _) do
changesets =
- Enum.map(users, fn %{"nickname" => nickname, "email" => email, "password" => password} ->
+ users
+ |> Enum.map(fn %{nickname: nickname, email: email, password: password} ->
user_data = %{
nickname: nickname,
name: nickname,
@@ -124,57 +144,54 @@ defmodule Pleroma.Web.AdminAPI.UserController do
end)
case Pleroma.Repo.transaction(changesets) do
- {:ok, users} ->
- res =
- users
+ {:ok, users_map} ->
+ users =
+ users_map
|> Map.values()
|> Enum.map(fn user ->
{:ok, user} = User.post_register_action(user)
user
end)
- |> Enum.map(&AccountView.render("created.json", %{user: &1}))
ModerationLog.insert_log(%{
actor: admin,
- subjects: Map.values(users),
+ subjects: users,
action: "create"
})
- json(conn, res)
+ render(conn, "created_many.json", users: users)
{:error, id, changeset, _} ->
- res =
+ changesets =
Enum.map(changesets.operations, fn
- {current_id, {:changeset, _current_changeset, _}} when current_id == id ->
- AccountView.render("create-error.json", %{changeset: changeset})
+ {^id, {:changeset, _current_changeset, _}} ->
+ changeset
{_, {:changeset, current_changeset, _}} ->
- AccountView.render("create-error.json", %{changeset: current_changeset})
+ current_changeset
end)
conn
|> put_status(:conflict)
- |> json(res)
+ |> render("create_errors.json", changesets: changesets)
end
end
- def show(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
+ def show(%{assigns: %{user: admin}} = conn, %{nickname: nickname}) do
with %User{} = user <- User.get_cached_by_nickname_or_id(nickname, for: admin) do
- conn
- |> put_view(AccountView)
- |> render("show.json", %{user: user})
+ render(conn, "show.json", %{user: user})
else
_ -> {:error, :not_found}
end
end
- def toggle_activation(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
+ def toggle_activation(%{assigns: %{user: admin}} = conn, %{nickname: nickname}) do
user = User.get_cached_by_nickname(nickname)
- {:ok, updated_user} = User.deactivate(user, !user.deactivated)
+ {:ok, updated_user} = User.set_activation(user, !user.is_active)
- action = if user.deactivated, do: "activate", else: "deactivate"
+ action = if !user.is_active, do: "activate", else: "deactivate"
ModerationLog.insert_log(%{
actor: admin,
@@ -182,14 +199,12 @@ defmodule Pleroma.Web.AdminAPI.UserController do
action: action
})
- conn
- |> put_view(AccountView)
- |> render("show.json", %{user: updated_user})
+ render(conn, "show.json", user: updated_user)
end
- def activate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
+ def activate(%{assigns: %{user: admin}, body_params: %{nicknames: nicknames}} = conn, _) do
users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
- {:ok, updated_users} = User.deactivate(users, false)
+ {:ok, updated_users} = User.set_activation(users, true)
ModerationLog.insert_log(%{
actor: admin,
@@ -197,14 +212,12 @@ defmodule Pleroma.Web.AdminAPI.UserController do
action: "activate"
})
- conn
- |> put_view(AccountView)
- |> render("index.json", %{users: Keyword.values(updated_users)})
+ render(conn, "index.json", users: Keyword.values(updated_users))
end
- def deactivate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
+ def deactivate(%{assigns: %{user: admin}, body_params: %{nicknames: nicknames}} = conn, _) do
users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
- {:ok, updated_users} = User.deactivate(users, true)
+ {:ok, updated_users} = User.set_activation(users, false)
ModerationLog.insert_log(%{
actor: admin,
@@ -212,12 +225,10 @@ defmodule Pleroma.Web.AdminAPI.UserController do
action: "deactivate"
})
- conn
- |> put_view(AccountView)
- |> render("index.json", %{users: Keyword.values(updated_users)})
+ render(conn, "index.json", users: Keyword.values(updated_users))
end
- def approve(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
+ def approve(%{assigns: %{user: admin}, body_params: %{nicknames: nicknames}} = conn, _) do
users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
{:ok, updated_users} = User.approve(users)
@@ -227,36 +238,53 @@ defmodule Pleroma.Web.AdminAPI.UserController do
action: "approve"
})
- conn
- |> put_view(AccountView)
- |> render("index.json", %{users: updated_users})
+ render(conn, "index.json", users: updated_users)
+ end
+
+ def suggest(%{assigns: %{user: admin}, body_params: %{nicknames: nicknames}} = conn, _) do
+ users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
+ {:ok, updated_users} = User.set_suggestion(users, true)
+
+ ModerationLog.insert_log(%{
+ actor: admin,
+ subject: users,
+ action: "add_suggestion"
+ })
+
+ render(conn, "index.json", users: updated_users)
+ end
+
+ def unsuggest(%{assigns: %{user: admin}, body_params: %{nicknames: nicknames}} = conn, _) do
+ users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
+ {:ok, updated_users} = User.set_suggestion(users, false)
+
+ ModerationLog.insert_log(%{
+ actor: admin,
+ subject: users,
+ action: "remove_suggestion"
+ })
+
+ render(conn, "index.json", users: updated_users)
end
- def list(conn, params) do
+ def index(conn, params) do
{page, page_size} = page_params(params)
- filters = maybe_parse_filters(params["filters"])
+ filters = maybe_parse_filters(params[:filters])
search_params =
%{
- query: params["query"],
+ query: params[:query],
page: page,
page_size: page_size,
- tags: params["tags"],
- name: params["name"],
- email: params["email"],
- actor_types: params["actor_types"]
+ tags: params[:tags],
+ name: params[:name],
+ email: params[:email],
+ actor_types: params[:actor_types]
}
|> Map.merge(filters)
with {:ok, users, count} <- Search.user(search_params) do
- json(
- conn,
- AccountView.render("index.json",
- users: users,
- count: count,
- page_size: page_size
- )
- )
+ render(conn, "index.json", users: users, count: count, page_size: page_size)
end
end
@@ -274,8 +302,8 @@ defmodule Pleroma.Web.AdminAPI.UserController do
defp page_params(params) do
{
- fetch_integer_param(params, "page", 1),
- fetch_integer_param(params, "page_size", @users_page_size)
+ fetch_integer_param(params, :page, 1),
+ fetch_integer_param(params, :page_size, @users_page_size)
}
end
end
diff --git a/lib/pleroma/web/admin_api/report.ex b/lib/pleroma/web/admin_api/report.ex
index 8660d6520..c79bee27e 100644
--- a/lib/pleroma/web/admin_api/report.ex
+++ b/lib/pleroma/web/admin_api/report.ex
@@ -1,9 +1,10 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.AdminAPI.Report do
alias Pleroma.Activity
+ alias Pleroma.Object
alias Pleroma.User
def extract_report_info(
@@ -13,11 +14,47 @@ defmodule Pleroma.Web.AdminAPI.Report do
account = User.get_cached_by_ap_id(account_ap_id)
statuses =
- Enum.map(status_ap_ids, fn
- act when is_map(act) -> Activity.get_by_ap_id_with_object(act["id"])
- act when is_binary(act) -> Activity.get_by_ap_id_with_object(act)
+ status_ap_ids
+ |> Enum.reject(&is_nil(&1))
+ |> Enum.map(fn
+ act when is_map(act) ->
+ Activity.get_create_by_object_ap_id_with_object(act["id"]) ||
+ Activity.get_by_ap_id_with_object(act["id"]) || make_fake_activity(act, user)
+
+ act when is_binary(act) ->
+ Activity.get_create_by_object_ap_id_with_object(act) ||
+ Activity.get_by_ap_id_with_object(act)
end)
%{report: report, user: user, account: account, statuses: statuses}
end
+
+ defp make_fake_activity(act, user) do
+ %Activity{
+ id: "pleroma:fake",
+ data: %{
+ "actor" => user.ap_id,
+ "type" => "Create",
+ "to" => [],
+ "cc" => [],
+ "object" => act["id"],
+ "published" => act["published"],
+ "id" => act["id"],
+ "context" => "pleroma:fake"
+ },
+ recipients: [user.ap_id],
+ object: %Object{
+ data: %{
+ "actor" => user.ap_id,
+ "type" => "Note",
+ "content" => act["content"],
+ "published" => act["published"],
+ "to" => [],
+ "cc" => [],
+ "id" => act["id"],
+ "context" => "pleroma:fake"
+ }
+ }
+ }
+ end
end
diff --git a/lib/pleroma/web/admin_api/search.ex b/lib/pleroma/web/admin_api/search.ex
index 0bfb8f022..f5195acde 100644
--- a/lib/pleroma/web/admin_api/search.ex
+++ b/lib/pleroma/web/admin_api/search.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.AdminAPI.Search do
@@ -10,12 +10,6 @@ defmodule Pleroma.Web.AdminAPI.Search do
@page_size 50
- defmacro not_empty_string(string) do
- quote do
- is_binary(unquote(string)) and unquote(string) != ""
- end
- end
-
@spec user(map()) :: {:ok, [User.t()], pos_integer()}
def user(params \\ %{}) do
query =
@@ -23,7 +17,7 @@ defmodule Pleroma.Web.AdminAPI.Search do
|> Map.drop([:page, :page_size])
|> Map.put(:invisible, false)
|> User.Query.build()
- |> order_by([u], u.nickname)
+ |> order_by(desc: :id)
paginated_query =
User.Query.paginate(query, params[:page] || 1, params[:page_size] || @page_size)
diff --git a/lib/pleroma/web/admin_api/views/account_view.ex b/lib/pleroma/web/admin_api/views/account_view.ex
index 8bac24d3e..280152241 100644
--- a/lib/pleroma/web/admin_api/views/account_view.ex
+++ b/lib/pleroma/web/admin_api/views/account_view.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.AdminAPI.AccountView do
@@ -8,6 +8,7 @@ defmodule Pleroma.Web.AdminAPI.AccountView do
alias Pleroma.User
alias Pleroma.Web.AdminAPI
alias Pleroma.Web.AdminAPI.AccountView
+ alias Pleroma.Web.CommonAPI
alias Pleroma.Web.MastodonAPI
alias Pleroma.Web.MediaProxy
@@ -69,21 +70,28 @@ defmodule Pleroma.Web.AdminAPI.AccountView do
%{
"id" => user.id,
+ "email" => user.email,
"avatar" => avatar,
"nickname" => user.nickname,
"display_name" => display_name,
- "deactivated" => user.deactivated,
+ "is_active" => user.is_active,
"local" => user.local,
- "roles" => User.roles(user),
+ "roles" => roles(user),
"tags" => user.tags || [],
- "confirmation_pending" => user.confirmation_pending,
- "approval_pending" => user.approval_pending,
+ "is_confirmed" => user.is_confirmed,
+ "is_approved" => user.is_approved,
+ "is_suggested" => user.is_suggested,
"url" => user.uri || user.ap_id,
"registration_reason" => user.registration_reason,
- "actor_type" => user.actor_type
+ "actor_type" => user.actor_type,
+ "created_at" => CommonAPI.Utils.to_masto_date(user.inserted_at)
}
end
+ def render("created_many.json", %{users: users}) do
+ render_many(users, AccountView, "created.json", as: :user)
+ end
+
def render("created.json", %{user: user}) do
%{
type: "success",
@@ -95,7 +103,11 @@ defmodule Pleroma.Web.AdminAPI.AccountView do
}
end
- def render("create-error.json", %{changeset: %Ecto.Changeset{changes: changes, errors: errors}}) do
+ def render("create_errors.json", %{changesets: changesets}) do
+ render_many(changesets, AccountView, "create_error.json", as: :changeset)
+ end
+
+ def render("create_error.json", %{changeset: %Ecto.Changeset{changes: changes, errors: errors}}) do
%{
type: "error",
code: 409,
@@ -139,4 +151,11 @@ defmodule Pleroma.Web.AdminAPI.AccountView do
defp image_url(%{"url" => [%{"href" => href} | _]}), do: href
defp image_url(_), do: nil
+
+ defp roles(%{is_moderator: is_moderator, is_admin: is_admin}) do
+ %{
+ admin: is_admin,
+ moderator: is_moderator
+ }
+ end
end
diff --git a/lib/pleroma/web/admin_api/views/announcement_view.ex b/lib/pleroma/web/admin_api/views/announcement_view.ex
new file mode 100644
index 000000000..a35bd60cf
--- /dev/null
+++ b/lib/pleroma/web/admin_api/views/announcement_view.ex
@@ -0,0 +1,15 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.AdminAPI.AnnouncementView do
+ use Pleroma.Web, :view
+
+ def render("index.json", %{announcements: announcements}) do
+ render_many(announcements, __MODULE__, "show.json")
+ end
+
+ def render("show.json", %{announcement: announcement}) do
+ Pleroma.Announcement.render_json(announcement, admin: true)
+ end
+end
diff --git a/lib/pleroma/web/admin_api/views/chat_view.ex b/lib/pleroma/web/admin_api/views/chat_view.ex
index 847df1423..d58bf8eda 100644
--- a/lib/pleroma/web/admin_api/views/chat_view.ex
+++ b/lib/pleroma/web/admin_api/views/chat_view.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.AdminAPI.ChatView do
diff --git a/lib/pleroma/web/admin_api/views/config_view.ex b/lib/pleroma/web/admin_api/views/config_view.ex
index d2d8b5907..f582ad42e 100644
--- a/lib/pleroma/web/admin_api/views/config_view.ex
+++ b/lib/pleroma/web/admin_api/views/config_view.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.AdminAPI.ConfigView do
diff --git a/lib/pleroma/web/admin_api/views/frontend_view.ex b/lib/pleroma/web/admin_api/views/frontend_view.ex
index 374841d0b..0ca3d67cb 100644
--- a/lib/pleroma/web/admin_api/views/frontend_view.ex
+++ b/lib/pleroma/web/admin_api/views/frontend_view.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.AdminAPI.FrontendView do
diff --git a/lib/pleroma/web/admin_api/views/invite_view.ex b/lib/pleroma/web/admin_api/views/invite_view.ex
index f93cb6916..76cee3bc5 100644
--- a/lib/pleroma/web/admin_api/views/invite_view.ex
+++ b/lib/pleroma/web/admin_api/views/invite_view.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.AdminAPI.InviteView do
diff --git a/lib/pleroma/web/admin_api/views/media_proxy_cache_view.ex b/lib/pleroma/web/admin_api/views/media_proxy_cache_view.ex
index a803bda0b..b46f54efe 100644
--- a/lib/pleroma/web/admin_api/views/media_proxy_cache_view.ex
+++ b/lib/pleroma/web/admin_api/views/media_proxy_cache_view.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.AdminAPI.MediaProxyCacheView do
diff --git a/lib/pleroma/web/admin_api/views/moderation_log_view.ex b/lib/pleroma/web/admin_api/views/moderation_log_view.ex
index 3fa778b0a..1f25f194f 100644
--- a/lib/pleroma/web/admin_api/views/moderation_log_view.ex
+++ b/lib/pleroma/web/admin_api/views/moderation_log_view.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.AdminAPI.ModerationLogView do
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
index 000000000..d1aef0e10
--- /dev/null
+++ b/lib/pleroma/web/admin_api/views/o_auth_app_view.ex
@@ -0,0 +1,10 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 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
diff --git a/lib/pleroma/web/admin_api/views/report_view.ex b/lib/pleroma/web/admin_api/views/report_view.ex
index da949e306..b761dbb22 100644
--- a/lib/pleroma/web/admin_api/views/report_view.ex
+++ b/lib/pleroma/web/admin_api/views/report_view.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.AdminAPI.ReportView do
diff --git a/lib/pleroma/web/admin_api/views/status_view.ex b/lib/pleroma/web/admin_api/views/status_view.ex
index 6042a22b6..03b5c440a 100644
--- a/lib/pleroma/web/admin_api/views/status_view.ex
+++ b/lib/pleroma/web/admin_api/views/status_view.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.AdminAPI.StatusView do
@@ -13,6 +13,10 @@ defmodule Pleroma.Web.AdminAPI.StatusView do
defdelegate merge_account_views(user), to: AdminAPI.AccountView
+ def render("index.json", %{total: total} = opts) do
+ %{total: total, activities: safe_render_many(opts.activities, __MODULE__, "show.json", opts)}
+ end
+
def render("index.json", opts) do
safe_render_many(opts.activities, __MODULE__, "show.json", opts)
end
diff --git a/lib/pleroma/web/admin_api/views/user_view.ex b/lib/pleroma/web/admin_api/views/user_view.ex
new file mode 100644
index 000000000..f198921ae
--- /dev/null
+++ b/lib/pleroma/web/admin_api/views/user_view.ex
@@ -0,0 +1,10 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.AdminAPI.UserView do
+ use Pleroma.Web, :view
+ alias Pleroma.Web.AdminAPI
+
+ def render(view, opts), do: AdminAPI.AccountView.render(view, opts)
+end
diff --git a/lib/pleroma/web/api_spec.ex b/lib/pleroma/web/api_spec.ex
index 93a5273e3..cae4241ff 100644
--- a/lib/pleroma/web/api_spec.ex
+++ b/lib/pleroma/web/api_spec.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec do
@@ -11,10 +11,10 @@ defmodule Pleroma.Web.ApiSpec do
@behaviour OpenApi
@impl OpenApi
- def spec do
+ def spec(opts \\ []) do
%OpenApi{
servers:
- if Phoenix.Endpoint.server?(:pleroma, Endpoint) do
+ if opts[:server_specific] do
[
# Populate the Server info from a phoenix endpoint
OpenApiSpex.Server.from_endpoint(Endpoint)
@@ -23,9 +23,26 @@ defmodule Pleroma.Web.ApiSpec do
[]
end,
info: %OpenApiSpex.Info{
- title: "Pleroma",
- description: Application.spec(:pleroma, :description) |> to_string(),
- version: Application.spec(:pleroma, :vsn) |> to_string()
+ title: "Pleroma API",
+ description: """
+ This is documentation for client Pleroma API. Most of the endpoints and entities come
+ from Mastodon API and have custom extensions on top.
+
+ While this document aims to be a complete guide to the client API Pleroma exposes,
+ the details are still being worked out. Some endpoints may have incomplete or poorly worded documentation.
+ You might want to check the following resources if something is not clear:
+ - [Legacy Pleroma-specific endpoint documentation](https://docs-develop.pleroma.social/backend/development/API/pleroma_api/)
+ - [Mastodon API documentation](https://docs.joinmastodon.org/client/intro/)
+ - [Differences in Mastodon API responses from vanilla Mastodon](https://docs-develop.pleroma.social/backend/development/API/differences_in_mastoapi_responses/)
+
+ Please report such occurences on our [issue tracker](https://git.pleroma.social/pleroma/pleroma/-/issues). Feel free to submit API questions or proposals there too!
+ """,
+ # Strip environment from the version
+ version: Application.spec(:pleroma, :vsn) |> to_string() |> String.replace(~r/\+.*$/, ""),
+ extensions: %{
+ # Logo path should be picked so that the path exists both on Pleroma instances and on api.pleroma.social
+ "x-logo": %{"url" => "/static/logo.svg", "altText" => "Pleroma logo"}
+ }
},
# populate the paths from a phoenix router
paths: OpenApiSpex.Paths.from_router(Router),
@@ -45,15 +62,74 @@ defmodule Pleroma.Web.ApiSpec do
authorizationUrl: "/oauth/authorize",
tokenUrl: "/oauth/token",
scopes: %{
- "read" => "read",
- "write" => "write",
- "follow" => "follow",
- "push" => "push"
+ # TODO: Document granular scopes
+ "read" => "Read everything",
+ "write" => "Write everything",
+ "follow" => "Manage relationships",
+ "push" => "Web Push API subscriptions"
}
}
}
}
}
+ },
+ extensions: %{
+ # Redoc-specific extension, every time a new tag is added it should be reflected here,
+ # otherwise it won't be shown.
+ "x-tagGroups": [
+ %{
+ "name" => "Accounts",
+ "tags" => ["Account actions", "Retrieve account information", "Scrobbles"]
+ },
+ %{
+ "name" => "Administration",
+ "tags" => [
+ "Chat administration",
+ "Emoji pack administration",
+ "Frontend managment",
+ "Instance configuration",
+ "Instance documents",
+ "Invites",
+ "MediaProxy cache",
+ "OAuth application managment",
+ "Relays",
+ "Report managment",
+ "Status administration",
+ "User administration"
+ ]
+ },
+ %{"name" => "Applications", "tags" => ["Applications", "Push subscriptions"]},
+ %{
+ "name" => "Current account",
+ "tags" => [
+ "Account credentials",
+ "Backups",
+ "Blocks and mutes",
+ "Data import",
+ "Domain blocks",
+ "Follow requests",
+ "Mascot",
+ "Markers",
+ "Notifications"
+ ]
+ },
+ %{"name" => "Instance", "tags" => ["Custom emojis"]},
+ %{"name" => "Messaging", "tags" => ["Chats", "Conversations"]},
+ %{
+ "name" => "Statuses",
+ "tags" => [
+ "Emoji reactions",
+ "Lists",
+ "Polls",
+ "Timelines",
+ "Retrieve status information",
+ "Scheduled statuses",
+ "Search",
+ "Status actions"
+ ]
+ },
+ %{"name" => "Miscellaneous", "tags" => ["Emoji packs", "Reports", "Suggestions"]}
+ ]
}
}
# discover request/response schemas from path specs
diff --git a/lib/pleroma/web/api_spec/cast_and_validate.ex b/lib/pleroma/web/api_spec/cast_and_validate.ex
index 6d1a7ebbc..add59eb88 100644
--- a/lib/pleroma/web/api_spec/cast_and_validate.ex
+++ b/lib/pleroma/web/api_spec/cast_and_validate.ex
@@ -1,6 +1,6 @@
# Pleroma: A lightweight social networking server
# Copyright © 2019-2020 Moxley Stratton, Mike Buhot <https://github.com/open-api-spex/open_api_spex>, MPL-2.0
-# Copyright © 2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.CastAndValidate do
@@ -15,6 +15,7 @@ defmodule Pleroma.Web.ApiSpec.CastAndValidate do
@behaviour Plug
+ alias OpenApiSpex.Plug.PutApiSpec
alias Plug.Conn
@impl Plug
@@ -25,12 +26,10 @@ defmodule Pleroma.Web.ApiSpec.CastAndValidate do
end
@impl Plug
- def call(%{private: %{open_api_spex: private_data}} = conn, %{
- operation_id: operation_id,
- render_error: render_error
- }) do
- spec = private_data.spec
- operation = private_data.operation_lookup[operation_id]
+
+ def call(conn, %{operation_id: operation_id, render_error: render_error}) do
+ {spec, operation_lookup} = PutApiSpec.get_spec_and_operation_lookup(conn)
+ operation = operation_lookup[operation_id]
content_type =
case Conn.get_req_header(conn, "content-type") do
@@ -43,8 +42,7 @@ defmodule Pleroma.Web.ApiSpec.CastAndValidate do
"application/json"
end
- private_data = Map.put(private_data, :operation_id, operation_id)
- conn = Conn.put_private(conn, :open_api_spex, private_data)
+ conn = Conn.put_private(conn, :operation_id, operation_id)
case cast_and_validate(spec, operation, conn, content_type, strict?()) do
{:ok, conn} ->
@@ -64,25 +62,22 @@ defmodule Pleroma.Web.ApiSpec.CastAndValidate do
private: %{
phoenix_controller: controller,
phoenix_action: action,
- open_api_spex: private_data
+ open_api_spex: %{spec_module: spec_module}
}
} = conn,
opts
) do
+ {spec, operation_lookup} = PutApiSpec.get_spec_and_operation_lookup(conn)
+
operation =
- case private_data.operation_lookup[{controller, action}] do
+ case operation_lookup[{controller, action}] do
nil ->
operation_id = controller.open_api_operation(action).operationId
- operation = private_data.operation_lookup[operation_id]
+ operation = operation_lookup[operation_id]
- operation_lookup =
- private_data.operation_lookup
- |> Map.put({controller, action}, operation)
+ operation_lookup = Map.put(operation_lookup, {controller, action}, operation)
- OpenApiSpex.Plug.Cache.adapter().put(
- private_data.spec_module,
- {private_data.spec, operation_lookup}
- )
+ OpenApiSpex.Plug.Cache.adapter().put(spec_module, {spec, operation_lookup})
operation
diff --git a/lib/pleroma/web/api_spec/helpers.ex b/lib/pleroma/web/api_spec/helpers.ex
index 34de2ed57..f20a9163d 100644
--- a/lib/pleroma/web/api_spec/helpers.ex
+++ b/lib/pleroma/web/api_spec/helpers.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.Helpers do
@@ -63,7 +63,7 @@ defmodule Pleroma.Web.ApiSpec.Helpers do
:with_relationships,
:query,
BooleanLike,
- "Embed relationships into accounts."
+ "Embed relationships into accounts. **If this parameter is not set account's `pleroma.relationship` is going to be `null`.**"
)
end
diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex
index bd3a73c11..aabe988f7 100644
--- a/lib/pleroma/web/api_spec/operations/account_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/account_operation.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.AccountOperation do
@@ -26,7 +26,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
@spec create_operation() :: Operation.t()
def create_operation do
%Operation{
- tags: ["accounts"],
+ tags: ["Account credentials"],
summary: "Register an account",
description:
"Creates a user and account records. Returns an account access token for the app that initiated the request. The app should save this token for later, and should wait for the user to confirm their account by clicking a link in their email inbox.",
@@ -43,7 +43,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
def verify_credentials_operation do
%Operation{
- tags: ["accounts"],
+ tags: ["Account credentials"],
description: "Test to make sure that the user token works.",
summary: "Verify account credentials",
operationId: "AccountController.verify_credentials",
@@ -56,7 +56,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
def update_credentials_operation do
%Operation{
- tags: ["accounts"],
+ tags: ["Account credentials"],
summary: "Update account credentials",
description: "Update the user's display and preferences.",
operationId: "AccountController.update_credentials",
@@ -64,15 +64,16 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
requestBody: request_body("Parameters", update_credentials_request(), required: true),
responses: %{
200 => Operation.response("Account", "application/json", Account),
- 403 => Operation.response("Error", "application/json", ApiError)
+ 403 => Operation.response("Error", "application/json", ApiError),
+ 413 => Operation.response("Error", "application/json", ApiError)
}
}
end
def relationships_operation do
%Operation{
- tags: ["accounts"],
- summary: "Check relationships to other accounts",
+ tags: ["Retrieve account information"],
+ summary: "Relationship with current account",
operationId: "AccountController.relationships",
description: "Find out whether a given account is followed, blocked, muted, etc.",
security: [%{"oAuth" => ["read:follows"]}],
@@ -95,11 +96,14 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
def show_operation do
%Operation{
- tags: ["accounts"],
+ tags: ["Retrieve account information"],
summary: "Account",
operationId: "AccountController.show",
description: "View information about a profile.",
- parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
+ parameters: [
+ %Reference{"$ref": "#/components/parameters/accountIdOrNickname"},
+ with_relationships_param()
+ ],
responses: %{
200 => Operation.response("Account", "application/json", Account),
401 => Operation.response("Error", "application/json", ApiError),
@@ -110,8 +114,8 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
def statuses_operation do
%Operation{
- tags: ["accounts"],
summary: "Statuses",
+ tags: ["Retrieve account information"],
operationId: "AccountController.statuses",
description:
"Statuses posted to the given account. Public (for public statuses only), or user token + `read:statuses` (for private statuses the user is authorized to see)",
@@ -130,7 +134,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
:with_muted,
:query,
BooleanLike,
- "Include statuses from muted acccounts."
+ "Include statuses from muted accounts."
),
Operation.parameter(:exclude_reblogs, :query, BooleanLike, "Exclude reblogs"),
Operation.parameter(:exclude_replies, :query, BooleanLike, "Exclude replies"),
@@ -144,7 +148,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
:with_muted,
:query,
BooleanLike,
- "Include reactions from muted acccounts."
+ "Include reactions from muted accounts."
)
] ++ pagination_params(),
responses: %{
@@ -157,7 +161,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
def followers_operation do
%Operation{
- tags: ["accounts"],
+ tags: ["Retrieve account information"],
summary: "Followers",
operationId: "AccountController.followers",
security: [%{"oAuth" => ["read:accounts"]}],
@@ -176,7 +180,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
def following_operation do
%Operation{
- tags: ["accounts"],
+ tags: ["Retrieve account information"],
summary: "Following",
operationId: "AccountController.following",
security: [%{"oAuth" => ["read:accounts"]}],
@@ -193,7 +197,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
def lists_operation do
%Operation{
- tags: ["accounts"],
+ tags: ["Retrieve account information"],
summary: "Lists containing this account",
operationId: "AccountController.lists",
security: [%{"oAuth" => ["read:lists"]}],
@@ -205,7 +209,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
def follow_operation do
%Operation{
- tags: ["accounts"],
+ tags: ["Account actions"],
summary: "Follow",
operationId: "AccountController.follow",
security: [%{"oAuth" => ["follow", "write:follows"]}],
@@ -220,9 +224,15 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
type: :object,
properties: %{
reblogs: %Schema{
- type: :boolean,
+ allOf: [BooleanLike],
description: "Receive this account's reblogs in home timeline? Defaults to true.",
default: true
+ },
+ notify: %Schema{
+ allOf: [BooleanLike],
+ description:
+ "Receive notifications for all statuses posted by the account? Defaults to false.",
+ default: false
}
}
},
@@ -238,7 +248,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
def unfollow_operation do
%Operation{
- tags: ["accounts"],
+ tags: ["Account actions"],
summary: "Unfollow",
operationId: "AccountController.unfollow",
security: [%{"oAuth" => ["follow", "write:follows"]}],
@@ -254,7 +264,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
def mute_operation do
%Operation{
- tags: ["accounts"],
+ tags: ["Account actions"],
summary: "Mute",
operationId: "AccountController.mute",
security: [%{"oAuth" => ["follow", "write:mutes"]}],
@@ -270,10 +280,16 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
"Mute notifications in addition to statuses? Defaults to `true`."
),
Operation.parameter(
+ :duration,
+ :query,
+ %Schema{type: :integer},
+ "Expire the mute in `duration` seconds. Default 0 for infinity"
+ ),
+ Operation.parameter(
:expires_in,
:query,
%Schema{type: :integer, default: 0},
- "Expire the mute in `expires_in` seconds. Default 0 for infinity"
+ "Deprecated, use `duration` instead"
)
],
responses: %{
@@ -284,7 +300,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
def unmute_operation do
%Operation{
- tags: ["accounts"],
+ tags: ["Account actions"],
summary: "Unmute",
operationId: "AccountController.unmute",
security: [%{"oAuth" => ["follow", "write:mutes"]}],
@@ -298,7 +314,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
def block_operation do
%Operation{
- tags: ["accounts"],
+ tags: ["Account actions"],
summary: "Block",
operationId: "AccountController.block",
security: [%{"oAuth" => ["follow", "write:blocks"]}],
@@ -313,7 +329,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
def unblock_operation do
%Operation{
- tags: ["accounts"],
+ tags: ["Account actions"],
summary: "Unblock",
operationId: "AccountController.unblock",
security: [%{"oAuth" => ["follow", "write:blocks"]}],
@@ -325,9 +341,84 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
}
end
+ def endorse_operation do
+ %Operation{
+ tags: ["Account actions"],
+ summary: "Endorse",
+ operationId: "AccountController.endorse",
+ security: [%{"oAuth" => ["follow", "write:accounts"]}],
+ description: "Addds the given account to endorsed accounts list.",
+ parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
+ responses: %{
+ 200 => Operation.response("Relationship", "application/json", AccountRelationship),
+ 400 =>
+ Operation.response("Bad Request", "application/json", %Schema{
+ allOf: [ApiError],
+ title: "Unprocessable Entity",
+ example: %{
+ "error" => "You have already pinned the maximum number of users"
+ }
+ })
+ }
+ }
+ end
+
+ def unendorse_operation do
+ %Operation{
+ tags: ["Account actions"],
+ summary: "Unendorse",
+ operationId: "AccountController.unendorse",
+ security: [%{"oAuth" => ["follow", "write:accounts"]}],
+ description: "Removes the given account from endorsed accounts list.",
+ parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
+ responses: %{
+ 200 => Operation.response("Relationship", "application/json", AccountRelationship)
+ }
+ }
+ end
+
+ def remove_from_followers_operation do
+ %Operation{
+ tags: ["Account actions"],
+ summary: "Remove from followers",
+ operationId: "AccountController.remove_from_followers",
+ security: [%{"oAuth" => ["follow", "write:follows"]}],
+ description: "Remove the given account from followers",
+ parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
+ responses: %{
+ 200 => Operation.response("Relationship", "application/json", AccountRelationship),
+ 400 => Operation.response("Error", "application/json", ApiError),
+ 404 => Operation.response("Error", "application/json", ApiError)
+ }
+ }
+ end
+
+ def note_operation do
+ %Operation{
+ tags: ["Account actions"],
+ summary: "Set a private note about a user.",
+ operationId: "AccountController.note",
+ security: [%{"oAuth" => ["follow", "write:accounts"]}],
+ requestBody: request_body("Parameters", note_request()),
+ description: "Create a note for the given account.",
+ parameters: [
+ %Reference{"$ref": "#/components/parameters/accountIdOrNickname"},
+ Operation.parameter(
+ :comment,
+ :query,
+ %Schema{type: :string},
+ "Account note body"
+ )
+ ],
+ responses: %{
+ 200 => Operation.response("Relationship", "application/json", AccountRelationship)
+ }
+ }
+ end
+
def follow_by_uri_operation do
%Operation{
- tags: ["accounts"],
+ tags: ["Account actions"],
summary: "Follow by URI",
operationId: "AccountController.follows",
security: [%{"oAuth" => ["follow", "write:follows"]}],
@@ -342,12 +433,12 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
def mutes_operation do
%Operation{
- tags: ["accounts"],
- summary: "Muted accounts",
+ tags: ["Blocks and mutes"],
+ summary: "Retrieve list of mutes",
operationId: "AccountController.mutes",
description: "Accounts the user has muted.",
security: [%{"oAuth" => ["follow", "read:mutes"]}],
- parameters: pagination_params(),
+ parameters: [with_relationships_param() | pagination_params()],
responses: %{
200 => Operation.response("Accounts", "application/json", array_of_accounts())
}
@@ -356,8 +447,8 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
def blocks_operation do
%Operation{
- tags: ["accounts"],
- summary: "Blocked users",
+ tags: ["Blocks and mutes"],
+ summary: "Retrieve list of blocks",
operationId: "AccountController.blocks",
description: "View your blocks. See also accounts/:id/{block,unblock}",
security: [%{"oAuth" => ["read:blocks"]}],
@@ -368,22 +459,42 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
}
end
+ def lookup_operation do
+ %Operation{
+ tags: ["Account lookup"],
+ summary: "Find a user by nickname",
+ operationId: "AccountController.lookup",
+ parameters: [
+ Operation.parameter(
+ :acct,
+ :query,
+ :string,
+ "User nickname"
+ )
+ ],
+ responses: %{
+ 200 => Operation.response("Account", "application/json", Account),
+ 404 => Operation.response("Error", "application/json", ApiError)
+ }
+ }
+ end
+
def endorsements_operation do
%Operation{
- tags: ["accounts"],
+ tags: ["Retrieve account information"],
summary: "Endorsements",
operationId: "AccountController.endorsements",
- description: "Not implemented",
+ description: "Returns endorsed accounts",
security: [%{"oAuth" => ["read:accounts"]}],
responses: %{
- 200 => empty_array_response()
+ 200 => Operation.response("Array of Accounts", "application/json", array_of_accounts())
}
}
end
def identity_proofs_operation do
%Operation{
- tags: ["accounts"],
+ tags: ["Retrieve account information"],
summary: "Identity proofs",
operationId: "AccountController.identity_proofs",
# Validators complains about unused path params otherwise
@@ -455,6 +566,25 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
type: :string,
nullable: true,
description: "Invite token required when the registrations aren't public"
+ },
+ birthday: %Schema{
+ nullable: true,
+ description: "User's birthday",
+ anyOf: [
+ %Schema{
+ type: :string,
+ format: :date
+ },
+ %Schema{
+ type: :string,
+ maxLength: 0
+ }
+ ]
+ },
+ language: %Schema{
+ type: :string,
+ nullable: true,
+ description: "User's preferred language for emails"
}
},
example: %{
@@ -632,7 +762,26 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
description:
"Discovery (listing, indexing) of this account by external services (search bots etc.) is allowed."
},
- actor_type: ActorType
+ actor_type: ActorType,
+ birthday: %Schema{
+ nullable: true,
+ description: "User's birthday",
+ anyOf: [
+ %Schema{
+ type: :string,
+ format: :date
+ },
+ %Schema{
+ type: :string,
+ maxLength: 0
+ }
+ ]
+ },
+ show_birthday: %Schema{
+ allOf: [BooleanLike],
+ nullable: true,
+ description: "User's birthday will be visible"
+ }
},
example: %{
bot: false,
@@ -652,7 +801,9 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
allow_following_move: false,
also_known_as: ["https://foo.bar/users/foo"],
discoverable: false,
- actor_type: "Person"
+ actor_type: "Person",
+ show_birthday: false,
+ birthday: "2001-02-12"
}
}
end
@@ -682,9 +833,11 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
"blocked_by" => true,
"muting" => false,
"muting_notifications" => false,
+ "note" => "",
"requested" => false,
"domain_blocking" => false,
"subscribing" => false,
+ "notifying" => false,
"endorsed" => true
},
%{
@@ -696,9 +849,11 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
"blocked_by" => true,
"muting" => true,
"muting_notifications" => false,
+ "note" => "",
"requested" => true,
"domain_blocking" => false,
"subscribing" => false,
+ "notifying" => false,
"endorsed" => false
},
%{
@@ -710,9 +865,11 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
"blocked_by" => false,
"muting" => true,
"muting_notifications" => false,
+ "note" => "",
"requested" => false,
"domain_blocking" => true,
"subscribing" => true,
+ "notifying" => true,
"endorsed" => false
}
]
@@ -743,10 +900,15 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
description: "Mute notifications in addition to statuses? Defaults to true.",
default: true
},
+ duration: %Schema{
+ type: :integer,
+ nullable: true,
+ description: "Expire the mute in `expires_in` seconds. Default 0 for infinity"
+ },
expires_in: %Schema{
type: :integer,
nullable: true,
- description: "Expire the mute in `expires_in` seconds. Default 0 for infinity",
+ description: "Deprecated, use `duration` instead",
default: 0
}
},
@@ -757,6 +919,23 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
}
end
+ defp note_request do
+ %Schema{
+ title: "AccountNoteRequest",
+ description: "POST body for adding a note for an account",
+ type: :object,
+ properties: %{
+ comment: %Schema{
+ type: :string,
+ description: "Account note body"
+ }
+ },
+ example: %{
+ "comment" => "Example note"
+ }
+ }
+ end
+
defp array_of_lists do
%Schema{
title: "ArrayOfLists",
diff --git a/lib/pleroma/web/api_spec/operations/admin/announcement_operation.ex b/lib/pleroma/web/api_spec/operations/admin/announcement_operation.ex
new file mode 100644
index 000000000..58a039e72
--- /dev/null
+++ b/lib/pleroma/web/api_spec/operations/admin/announcement_operation.ex
@@ -0,0 +1,165 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ApiSpec.Admin.AnnouncementOperation do
+ alias OpenApiSpex.Operation
+ alias OpenApiSpex.Schema
+ alias Pleroma.Web.ApiSpec.Schemas.Announcement
+ alias Pleroma.Web.ApiSpec.Schemas.ApiError
+
+ import Pleroma.Web.ApiSpec.Helpers
+
+ def open_api_operation(action) do
+ operation = String.to_existing_atom("#{action}_operation")
+ apply(__MODULE__, operation, [])
+ end
+
+ def index_operation do
+ %Operation{
+ tags: ["Announcement managment"],
+ summary: "Retrieve a list of announcements",
+ operationId: "AdminAPI.AnnouncementController.index",
+ security: [%{"oAuth" => ["admin:read"]}],
+ parameters: [
+ Operation.parameter(
+ :limit,
+ :query,
+ %Schema{type: :integer, minimum: 1},
+ "the maximum number of announcements to return"
+ ),
+ Operation.parameter(
+ :offset,
+ :query,
+ %Schema{type: :integer, minimum: 0},
+ "the offset of the first announcement to return"
+ )
+ | admin_api_params()
+ ],
+ responses: %{
+ 200 => Operation.response("Response", "application/json", list_of_announcements()),
+ 400 => Operation.response("Forbidden", "application/json", ApiError),
+ 403 => Operation.response("Forbidden", "application/json", ApiError)
+ }
+ }
+ end
+
+ def show_operation do
+ %Operation{
+ tags: ["Announcement managment"],
+ summary: "Display one announcement",
+ operationId: "AdminAPI.AnnouncementController.show",
+ security: [%{"oAuth" => ["admin:read"]}],
+ parameters: [
+ Operation.parameter(
+ :id,
+ :path,
+ :string,
+ "announcement id"
+ )
+ | admin_api_params()
+ ],
+ responses: %{
+ 200 => Operation.response("Response", "application/json", Announcement),
+ 403 => Operation.response("Forbidden", "application/json", ApiError),
+ 404 => Operation.response("Not Found", "application/json", ApiError)
+ }
+ }
+ end
+
+ def delete_operation do
+ %Operation{
+ tags: ["Announcement managment"],
+ summary: "Delete one announcement",
+ operationId: "AdminAPI.AnnouncementController.delete",
+ security: [%{"oAuth" => ["admin:write"]}],
+ parameters: [
+ Operation.parameter(
+ :id,
+ :path,
+ :string,
+ "announcement id"
+ )
+ | admin_api_params()
+ ],
+ responses: %{
+ 200 => Operation.response("Response", "application/json", %Schema{type: :object}),
+ 403 => Operation.response("Forbidden", "application/json", ApiError),
+ 404 => Operation.response("Not Found", "application/json", ApiError)
+ }
+ }
+ end
+
+ def create_operation do
+ %Operation{
+ tags: ["Announcement managment"],
+ summary: "Create one announcement",
+ operationId: "AdminAPI.AnnouncementController.create",
+ security: [%{"oAuth" => ["admin:write"]}],
+ requestBody: request_body("Parameters", create_request(), required: true),
+ responses: %{
+ 200 => Operation.response("Response", "application/json", Announcement),
+ 400 => Operation.response("Bad Request", "application/json", ApiError),
+ 403 => Operation.response("Forbidden", "application/json", ApiError)
+ }
+ }
+ end
+
+ def change_operation do
+ %Operation{
+ tags: ["Announcement managment"],
+ summary: "Change one announcement",
+ operationId: "AdminAPI.AnnouncementController.change",
+ security: [%{"oAuth" => ["admin:write"]}],
+ parameters: [
+ Operation.parameter(
+ :id,
+ :path,
+ :string,
+ "announcement id"
+ )
+ | admin_api_params()
+ ],
+ requestBody: request_body("Parameters", change_request(), required: true),
+ responses: %{
+ 200 => Operation.response("Response", "application/json", Announcement),
+ 400 => Operation.response("Bad Request", "application/json", ApiError),
+ 403 => Operation.response("Forbidden", "application/json", ApiError),
+ 404 => Operation.response("Not Found", "application/json", ApiError)
+ }
+ }
+ end
+
+ defp create_or_change_props do
+ %{
+ content: %Schema{type: :string},
+ starts_at: %Schema{type: :string, format: "date-time", nullable: true},
+ ends_at: %Schema{type: :string, format: "date-time", nullable: true},
+ all_day: %Schema{type: :boolean}
+ }
+ end
+
+ def create_request do
+ %Schema{
+ title: "AnnouncementCreateRequest",
+ type: :object,
+ required: [:content],
+ properties: create_or_change_props()
+ }
+ end
+
+ def change_request do
+ %Schema{
+ title: "AnnouncementChangeRequest",
+ type: :object,
+ properties: create_or_change_props()
+ }
+ end
+
+ def list_of_announcements do
+ %Schema{
+ type: :array,
+ items: Announcement
+ }
+ end
+end
diff --git a/lib/pleroma/web/api_spec/operations/admin/chat_operation.ex b/lib/pleroma/web/api_spec/operations/admin/chat_operation.ex
index d3e5dfc1c..2a274e080 100644
--- a/lib/pleroma/web/api_spec/operations/admin/chat_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/admin/chat_operation.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.Admin.ChatOperation do
@@ -16,7 +16,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.ChatOperation do
def delete_message_operation do
%Operation{
- tags: ["admin", "chat"],
+ tags: ["Chat administration"],
summary: "Delete an individual chat message",
operationId: "AdminAPI.ChatController.delete_message",
parameters: [
@@ -33,7 +33,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.ChatOperation do
},
security: [
%{
- "oAuth" => ["write:chats"]
+ "oAuth" => ["admin:write:chats"]
}
]
}
@@ -41,8 +41,8 @@ defmodule Pleroma.Web.ApiSpec.Admin.ChatOperation do
def messages_operation do
%Operation{
- tags: ["admin", "chat"],
- summary: "Get the most recent messages of the chat",
+ tags: ["Chat administration"],
+ summary: "Get chat's messages",
operationId: "AdminAPI.ChatController.messages",
parameters:
[Operation.parameter(:id, :path, :string, "The ID of the Chat")] ++
@@ -57,7 +57,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.ChatOperation do
},
security: [
%{
- "oAuth" => ["read:chats"]
+ "oAuth" => ["admin:read:chats"]
}
]
}
@@ -65,7 +65,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.ChatOperation do
def show_operation do
%Operation{
- tags: ["chat"],
+ tags: ["Chat administration"],
summary: "Create a chat",
operationId: "AdminAPI.ChatController.show",
parameters: [
@@ -88,7 +88,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.ChatOperation do
},
security: [
%{
- "oAuth" => ["read"]
+ "oAuth" => ["admin:read"]
}
]
}
diff --git a/lib/pleroma/web/api_spec/operations/admin/config_operation.ex b/lib/pleroma/web/api_spec/operations/admin/config_operation.ex
index 3a8380797..487dd5cda 100644
--- a/lib/pleroma/web/api_spec/operations/admin/config_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/admin/config_operation.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.Admin.ConfigOperation do
@@ -16,8 +16,8 @@ defmodule Pleroma.Web.ApiSpec.Admin.ConfigOperation do
def show_operation do
%Operation{
- tags: ["Admin", "Config"],
- summary: "Get list of merged default settings with saved in database",
+ tags: ["Instance configuration"],
+ summary: "Retrieve instance configuration",
operationId: "AdminAPI.ConfigController.show",
parameters: [
Operation.parameter(
@@ -28,7 +28,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.ConfigOperation do
)
| admin_api_params()
],
- security: [%{"oAuth" => ["read"]}],
+ security: [%{"oAuth" => ["admin:read"]}],
responses: %{
200 => Operation.response("Config", "application/json", config_response()),
400 => Operation.response("Bad Request", "application/json", ApiError)
@@ -38,10 +38,10 @@ defmodule Pleroma.Web.ApiSpec.Admin.ConfigOperation do
def update_operation do
%Operation{
- tags: ["Admin", "Config"],
- summary: "Update config settings",
+ tags: ["Instance configuration"],
+ summary: "Update instance configuration",
operationId: "AdminAPI.ConfigController.update",
- security: [%{"oAuth" => ["write"]}],
+ security: [%{"oAuth" => ["admin:write"]}],
parameters: admin_api_params(),
requestBody:
request_body("Parameters", %Schema{
@@ -71,10 +71,10 @@ defmodule Pleroma.Web.ApiSpec.Admin.ConfigOperation do
def descriptions_operation do
%Operation{
- tags: ["Admin", "Config"],
- summary: "Get JSON with config descriptions.",
+ tags: ["Instance configuration"],
+ summary: "Retrieve config description",
operationId: "AdminAPI.ConfigController.descriptions",
- security: [%{"oAuth" => ["read"]}],
+ security: [%{"oAuth" => ["admin:read"]}],
parameters: admin_api_params(),
responses: %{
200 =>
diff --git a/lib/pleroma/web/api_spec/operations/admin/frontend_operation.ex b/lib/pleroma/web/api_spec/operations/admin/frontend_operation.ex
index 96d4cdee7..4bfe5ac5a 100644
--- a/lib/pleroma/web/api_spec/operations/admin/frontend_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/admin/frontend_operation.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.Admin.FrontendOperation do
@@ -16,10 +16,10 @@ defmodule Pleroma.Web.ApiSpec.Admin.FrontendOperation do
def index_operation do
%Operation{
- tags: ["Admin", "Reports"],
- summary: "Get a list of available frontends",
+ tags: ["Frontend managment"],
+ summary: "Retrieve a list of available frontends",
operationId: "AdminAPI.FrontendController.index",
- security: [%{"oAuth" => ["read"]}],
+ security: [%{"oAuth" => ["admin:read"]}],
responses: %{
200 => Operation.response("Response", "application/json", list_of_frontends()),
403 => Operation.response("Forbidden", "application/json", ApiError)
@@ -29,10 +29,10 @@ defmodule Pleroma.Web.ApiSpec.Admin.FrontendOperation do
def install_operation do
%Operation{
- tags: ["Admin", "Reports"],
+ tags: ["Frontend managment"],
summary: "Install a frontend",
operationId: "AdminAPI.FrontendController.install",
- security: [%{"oAuth" => ["read"]}],
+ security: [%{"oAuth" => ["admin:read"]}],
requestBody: request_body("Parameters", install_request(), required: true),
responses: %{
200 => Operation.response("Response", "application/json", list_of_frontends()),
diff --git a/lib/pleroma/web/api_spec/operations/admin/instance_document_operation.ex b/lib/pleroma/web/api_spec/operations/admin/instance_document_operation.ex
index a120ff4e8..fc0de499b 100644
--- a/lib/pleroma/web/api_spec/operations/admin/instance_document_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/admin/instance_document_operation.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.Admin.InstanceDocumentOperation do
@@ -15,10 +15,10 @@ defmodule Pleroma.Web.ApiSpec.Admin.InstanceDocumentOperation do
def show_operation do
%Operation{
- tags: ["Admin", "InstanceDocument"],
- summary: "Get the instance document",
+ tags: ["Instance documents"],
+ summary: "Retrieve an instance document",
operationId: "AdminAPI.InstanceDocumentController.show",
- security: [%{"oAuth" => ["read"]}],
+ security: [%{"oAuth" => ["admin:read"]}],
parameters: [
Operation.parameter(:name, :path, %Schema{type: :string}, "The document name",
required: true
@@ -36,10 +36,10 @@ defmodule Pleroma.Web.ApiSpec.Admin.InstanceDocumentOperation do
def update_operation do
%Operation{
- tags: ["Admin", "InstanceDocument"],
- summary: "Update the instance document",
+ tags: ["Instance documents"],
+ summary: "Update an instance document",
operationId: "AdminAPI.InstanceDocumentController.update",
- security: [%{"oAuth" => ["write"]}],
+ security: [%{"oAuth" => ["admin:write"]}],
requestBody: Helpers.request_body("Parameters", update_request()),
parameters: [
Operation.parameter(:name, :path, %Schema{type: :string}, "The document name",
@@ -74,10 +74,10 @@ defmodule Pleroma.Web.ApiSpec.Admin.InstanceDocumentOperation do
def delete_operation do
%Operation{
- tags: ["Admin", "InstanceDocument"],
- summary: "Get the instance document",
+ tags: ["Instance documents"],
+ summary: "Delete an instance document",
operationId: "AdminAPI.InstanceDocumentController.delete",
- security: [%{"oAuth" => ["write"]}],
+ security: [%{"oAuth" => ["admin:write"]}],
parameters: [
Operation.parameter(:name, :path, %Schema{type: :string}, "The document name",
required: true
diff --git a/lib/pleroma/web/api_spec/operations/admin/invite_operation.ex b/lib/pleroma/web/api_spec/operations/admin/invite_operation.ex
index 801024d75..e4a9ffaeb 100644
--- a/lib/pleroma/web/api_spec/operations/admin/invite_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/admin/invite_operation.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.Admin.InviteOperation do
@@ -16,10 +16,10 @@ defmodule Pleroma.Web.ApiSpec.Admin.InviteOperation do
def index_operation do
%Operation{
- tags: ["Admin", "Invites"],
+ tags: ["Invites"],
summary: "Get a list of generated invites",
operationId: "AdminAPI.InviteController.index",
- security: [%{"oAuth" => ["read:invites"]}],
+ security: [%{"oAuth" => ["admin:read:invites"]}],
parameters: admin_api_params(),
responses: %{
200 =>
@@ -48,10 +48,10 @@ defmodule Pleroma.Web.ApiSpec.Admin.InviteOperation do
def create_operation do
%Operation{
- tags: ["Admin", "Invites"],
+ tags: ["Invites"],
summary: "Create an account registration invite token",
operationId: "AdminAPI.InviteController.create",
- security: [%{"oAuth" => ["write:invites"]}],
+ security: [%{"oAuth" => ["admin:write:invites"]}],
parameters: admin_api_params(),
requestBody:
request_body("Parameters", %Schema{
@@ -69,10 +69,10 @@ defmodule Pleroma.Web.ApiSpec.Admin.InviteOperation do
def revoke_operation do
%Operation{
- tags: ["Admin", "Invites"],
+ tags: ["Invites"],
summary: "Revoke invite by token",
operationId: "AdminAPI.InviteController.revoke",
- security: [%{"oAuth" => ["write:invites"]}],
+ security: [%{"oAuth" => ["admin:write:invites"]}],
parameters: admin_api_params(),
requestBody:
request_body(
@@ -96,10 +96,10 @@ defmodule Pleroma.Web.ApiSpec.Admin.InviteOperation do
def email_operation do
%Operation{
- tags: ["Admin", "Invites"],
+ tags: ["Invites"],
summary: "Sends registration invite via email",
operationId: "AdminAPI.InviteController.email",
- security: [%{"oAuth" => ["write:invites"]}],
+ security: [%{"oAuth" => ["admin:write:invites"]}],
parameters: admin_api_params(),
requestBody:
request_body(
diff --git a/lib/pleroma/web/api_spec/operations/admin/media_proxy_cache_operation.ex b/lib/pleroma/web/api_spec/operations/admin/media_proxy_cache_operation.ex
index ab45d6633..0b1eb3946 100644
--- a/lib/pleroma/web/api_spec/operations/admin/media_proxy_cache_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/admin/media_proxy_cache_operation.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.Admin.MediaProxyCacheOperation do
@@ -16,10 +16,10 @@ defmodule Pleroma.Web.ApiSpec.Admin.MediaProxyCacheOperation do
def index_operation do
%Operation{
- tags: ["Admin", "MediaProxyCache"],
- summary: "Fetch a paginated list of all banned MediaProxy URLs in Cachex",
+ tags: ["MediaProxy cache"],
+ summary: "Retrieve a list of banned MediaProxy URLs",
operationId: "AdminAPI.MediaProxyCacheController.index",
- security: [%{"oAuth" => ["read:media_proxy_caches"]}],
+ security: [%{"oAuth" => ["admin:read:media_proxy_caches"]}],
parameters: [
Operation.parameter(
:query,
@@ -44,7 +44,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.MediaProxyCacheOperation do
responses: %{
200 =>
Operation.response(
- "Array of banned MediaProxy URLs in Cachex",
+ "Array of MediaProxy URLs",
"application/json",
%Schema{
type: :object,
@@ -68,10 +68,10 @@ defmodule Pleroma.Web.ApiSpec.Admin.MediaProxyCacheOperation do
def delete_operation do
%Operation{
- tags: ["Admin", "MediaProxyCache"],
- summary: "Remove a banned MediaProxy URL from Cachex",
+ tags: ["MediaProxy cache"],
+ summary: "Remove a banned MediaProxy URL",
operationId: "AdminAPI.MediaProxyCacheController.delete",
- security: [%{"oAuth" => ["write:media_proxy_caches"]}],
+ security: [%{"oAuth" => ["admin:write:media_proxy_caches"]}],
parameters: admin_api_params(),
requestBody:
request_body(
@@ -94,10 +94,10 @@ defmodule Pleroma.Web.ApiSpec.Admin.MediaProxyCacheOperation do
def purge_operation do
%Operation{
- tags: ["Admin", "MediaProxyCache"],
- summary: "Purge and optionally ban a MediaProxy URL",
+ tags: ["MediaProxy cache"],
+ summary: "Purge a URL from MediaProxy cache and optionally ban it",
operationId: "AdminAPI.MediaProxyCacheController.purge",
- security: [%{"oAuth" => ["write:media_proxy_caches"]}],
+ security: [%{"oAuth" => ["admin:write:media_proxy_caches"]}],
parameters: admin_api_params(),
requestBody:
request_body(
diff --git a/lib/pleroma/web/api_spec/operations/admin/o_auth_app_operation.ex b/lib/pleroma/web/api_spec/operations/admin/o_auth_app_operation.ex
index a75f3e622..1a05aff6a 100644
--- a/lib/pleroma/web/api_spec/operations/admin/o_auth_app_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/admin/o_auth_app_operation.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.Admin.OAuthAppOperation do
@@ -16,10 +16,10 @@ defmodule Pleroma.Web.ApiSpec.Admin.OAuthAppOperation do
def index_operation do
%Operation{
- summary: "List OAuth apps",
- tags: ["Admin", "oAuth Apps"],
+ summary: "Retrieve a list of OAuth applications",
+ tags: ["OAuth application managment"],
operationId: "AdminAPI.OAuthAppController.index",
- security: [%{"oAuth" => ["write"]}],
+ security: [%{"oAuth" => ["admin:write"]}],
parameters: [
Operation.parameter(:name, :query, %Schema{type: :string}, "App name"),
Operation.parameter(:client_id, :query, %Schema{type: :string}, "Client ID"),
@@ -69,12 +69,12 @@ defmodule Pleroma.Web.ApiSpec.Admin.OAuthAppOperation do
def create_operation do
%Operation{
- tags: ["Admin", "oAuth Apps"],
- summary: "Create OAuth App",
+ tags: ["OAuth application managment"],
+ summary: "Create an OAuth application",
operationId: "AdminAPI.OAuthAppController.create",
requestBody: request_body("Parameters", create_request()),
parameters: admin_api_params(),
- security: [%{"oAuth" => ["write"]}],
+ security: [%{"oAuth" => ["admin:write"]}],
responses: %{
200 => Operation.response("App", "application/json", oauth_app()),
400 => Operation.response("Bad Request", "application/json", ApiError)
@@ -84,11 +84,11 @@ defmodule Pleroma.Web.ApiSpec.Admin.OAuthAppOperation do
def update_operation do
%Operation{
- tags: ["Admin", "oAuth Apps"],
- summary: "Update OAuth App",
+ tags: ["OAuth application managment"],
+ summary: "Update OAuth application",
operationId: "AdminAPI.OAuthAppController.update",
parameters: [id_param() | admin_api_params()],
- security: [%{"oAuth" => ["write"]}],
+ security: [%{"oAuth" => ["admin:write"]}],
requestBody: request_body("Parameters", update_request()),
responses: %{
200 => Operation.response("App", "application/json", oauth_app()),
@@ -102,11 +102,11 @@ defmodule Pleroma.Web.ApiSpec.Admin.OAuthAppOperation do
def delete_operation do
%Operation{
- tags: ["Admin", "oAuth Apps"],
- summary: "Delete OAuth App",
+ tags: ["OAuth application managment"],
+ summary: "Delete OAuth application",
operationId: "AdminAPI.OAuthAppController.delete",
parameters: [id_param() | admin_api_params()],
- security: [%{"oAuth" => ["write"]}],
+ security: [%{"oAuth" => ["admin:write"]}],
responses: %{
204 => no_content_response(),
400 => no_content_response()
diff --git a/lib/pleroma/web/api_spec/operations/admin/relay_operation.ex b/lib/pleroma/web/api_spec/operations/admin/relay_operation.ex
index f754bb9f5..8b241bd49 100644
--- a/lib/pleroma/web/api_spec/operations/admin/relay_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/admin/relay_operation.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.Admin.RelayOperation do
@@ -15,10 +15,10 @@ defmodule Pleroma.Web.ApiSpec.Admin.RelayOperation do
def index_operation do
%Operation{
- tags: ["Admin", "Relays"],
- summary: "List Relays",
+ tags: ["Relays"],
+ summary: "Retrieve a list of relays",
operationId: "AdminAPI.RelayController.index",
- security: [%{"oAuth" => ["read"]}],
+ security: [%{"oAuth" => ["admin:read"]}],
parameters: admin_api_params(),
responses: %{
200 =>
@@ -37,10 +37,10 @@ defmodule Pleroma.Web.ApiSpec.Admin.RelayOperation do
def follow_operation do
%Operation{
- tags: ["Admin", "Relays"],
- summary: "Follow a Relay",
+ tags: ["Relays"],
+ summary: "Follow a relay",
operationId: "AdminAPI.RelayController.follow",
- security: [%{"oAuth" => ["write:follows"]}],
+ security: [%{"oAuth" => ["admin:write:follows"]}],
parameters: admin_api_params(),
requestBody: request_body("Parameters", relay_url()),
responses: %{
@@ -51,10 +51,10 @@ defmodule Pleroma.Web.ApiSpec.Admin.RelayOperation do
def unfollow_operation do
%Operation{
- tags: ["Admin", "Relays"],
- summary: "Unfollow a Relay",
+ tags: ["Relays"],
+ summary: "Unfollow a relay",
operationId: "AdminAPI.RelayController.unfollow",
- security: [%{"oAuth" => ["write:follows"]}],
+ security: [%{"oAuth" => ["admin:write:follows"]}],
parameters: admin_api_params(),
requestBody: request_body("Parameters", relay_unfollow()),
responses: %{
diff --git a/lib/pleroma/web/api_spec/operations/admin/report_operation.ex b/lib/pleroma/web/api_spec/operations/admin/report_operation.ex
index 3bb7ec49e..312e091a5 100644
--- a/lib/pleroma/web/api_spec/operations/admin/report_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/admin/report_operation.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.Admin.ReportOperation do
@@ -19,10 +19,10 @@ defmodule Pleroma.Web.ApiSpec.Admin.ReportOperation do
def index_operation do
%Operation{
- tags: ["Admin", "Reports"],
- summary: "Get a list of reports",
+ tags: ["Report managment"],
+ summary: "Retrieve a list of reports",
operationId: "AdminAPI.ReportController.index",
- security: [%{"oAuth" => ["read:reports"]}],
+ security: [%{"oAuth" => ["admin:read:reports"]}],
parameters: [
Operation.parameter(
:state,
@@ -69,11 +69,11 @@ defmodule Pleroma.Web.ApiSpec.Admin.ReportOperation do
def show_operation do
%Operation{
- tags: ["Admin", "Reports"],
- summary: "Get an individual report",
+ tags: ["Report managment"],
+ summary: "Retrieve a report",
operationId: "AdminAPI.ReportController.show",
parameters: [id_param() | admin_api_params()],
- security: [%{"oAuth" => ["read:reports"]}],
+ security: [%{"oAuth" => ["admin:read:reports"]}],
responses: %{
200 => Operation.response("Report", "application/json", report()),
404 => Operation.response("Not Found", "application/json", ApiError)
@@ -83,10 +83,10 @@ defmodule Pleroma.Web.ApiSpec.Admin.ReportOperation do
def update_operation do
%Operation{
- tags: ["Admin", "Reports"],
- summary: "Change the state of one or multiple reports",
+ tags: ["Report managment"],
+ summary: "Change state of specified reports",
operationId: "AdminAPI.ReportController.update",
- security: [%{"oAuth" => ["write:reports"]}],
+ security: [%{"oAuth" => ["admin:write:reports"]}],
parameters: admin_api_params(),
requestBody: request_body("Parameters", update_request(), required: true),
responses: %{
@@ -99,8 +99,8 @@ defmodule Pleroma.Web.ApiSpec.Admin.ReportOperation do
def notes_create_operation do
%Operation{
- tags: ["Admin", "Reports"],
- summary: "Create report note",
+ tags: ["Report managment"],
+ summary: "Add a note to the report",
operationId: "AdminAPI.ReportController.notes_create",
parameters: [id_param() | admin_api_params()],
requestBody:
@@ -110,7 +110,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.ReportOperation do
content: %Schema{type: :string, description: "The message"}
}
}),
- security: [%{"oAuth" => ["write:reports"]}],
+ security: [%{"oAuth" => ["admin:write:reports"]}],
responses: %{
204 => no_content_response(),
404 => Operation.response("Not Found", "application/json", ApiError)
@@ -120,15 +120,15 @@ defmodule Pleroma.Web.ApiSpec.Admin.ReportOperation do
def notes_delete_operation do
%Operation{
- tags: ["Admin", "Reports"],
- summary: "Delete report note",
+ tags: ["Report managment"],
+ summary: "Delete note attached to the report",
operationId: "AdminAPI.ReportController.notes_delete",
parameters: [
Operation.parameter(:report_id, :path, :string, "Report ID"),
Operation.parameter(:id, :path, :string, "Note ID")
| admin_api_params()
],
- security: [%{"oAuth" => ["write:reports"]}],
+ security: [%{"oAuth" => ["admin:write:reports"]}],
responses: %{
204 => no_content_response(),
404 => Operation.response("Not Found", "application/json", ApiError)
@@ -136,11 +136,11 @@ defmodule Pleroma.Web.ApiSpec.Admin.ReportOperation do
}
end
- defp report_state do
+ def report_state do
%Schema{type: :string, enum: ["open", "closed", "resolved"]}
end
- defp id_param do
+ def id_param do
Operation.parameter(:id, :path, FlakeID, "Report ID",
example: "9umDrYheeY451cQnEe",
required: true
@@ -182,7 +182,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.ReportOperation do
properties:
Map.merge(Account.schema().properties, %{
nickname: %Schema{type: :string},
- deactivated: %Schema{type: :boolean},
+ is_active: %Schema{type: :boolean},
local: %Schema{type: :boolean},
roles: %Schema{
type: :object,
@@ -191,7 +191,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.ReportOperation do
moderator: %Schema{type: :boolean}
}
},
- confirmation_pending: %Schema{type: :boolean}
+ is_confirmed: %Schema{type: :boolean}
})
}
end
diff --git a/lib/pleroma/web/api_spec/operations/admin/status_operation.ex b/lib/pleroma/web/api_spec/operations/admin/status_operation.ex
index c105838a4..229912dd7 100644
--- a/lib/pleroma/web/api_spec/operations/admin/status_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/admin/status_operation.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.Admin.StatusOperation do
@@ -21,9 +21,10 @@ defmodule Pleroma.Web.ApiSpec.Admin.StatusOperation do
def index_operation do
%Operation{
- tags: ["Admin", "Statuses"],
+ tags: ["Status administration"],
operationId: "AdminAPI.StatusController.index",
- security: [%{"oAuth" => ["read:statuses"]}],
+ summary: "Get all statuses",
+ security: [%{"oAuth" => ["admin:read:statuses"]}],
parameters: [
Operation.parameter(
:godmode,
@@ -69,11 +70,11 @@ defmodule Pleroma.Web.ApiSpec.Admin.StatusOperation do
def show_operation do
%Operation{
- tags: ["Admin", "Statuses"],
- summary: "Show Status",
+ tags: ["Status adminitration)"],
+ summary: "Get status",
operationId: "AdminAPI.StatusController.show",
parameters: [id_param() | admin_api_params()],
- security: [%{"oAuth" => ["read:statuses"]}],
+ security: [%{"oAuth" => ["admin:read:statuses"]}],
responses: %{
200 => Operation.response("Status", "application/json", status()),
404 => Operation.response("Not Found", "application/json", ApiError)
@@ -83,11 +84,11 @@ defmodule Pleroma.Web.ApiSpec.Admin.StatusOperation do
def update_operation do
%Operation{
- tags: ["Admin", "Statuses"],
- summary: "Change the scope of an individual reported status",
+ tags: ["Status adminitration)"],
+ summary: "Change the scope of a status",
operationId: "AdminAPI.StatusController.update",
parameters: [id_param() | admin_api_params()],
- security: [%{"oAuth" => ["write:statuses"]}],
+ security: [%{"oAuth" => ["admin:write:statuses"]}],
requestBody: request_body("Parameters", update_request(), required: true),
responses: %{
200 => Operation.response("Status", "application/json", Status),
@@ -98,11 +99,11 @@ defmodule Pleroma.Web.ApiSpec.Admin.StatusOperation do
def delete_operation do
%Operation{
- tags: ["Admin", "Statuses"],
- summary: "Delete an individual reported status",
+ tags: ["Status adminitration)"],
+ summary: "Delete status",
operationId: "AdminAPI.StatusController.delete",
parameters: [id_param() | admin_api_params()],
- security: [%{"oAuth" => ["write:statuses"]}],
+ security: [%{"oAuth" => ["admin:write:statuses"]}],
responses: %{
200 => empty_object_response(),
404 => Operation.response("Not Found", "application/json", ApiError)
@@ -132,7 +133,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.StatusOperation do
avatar: %Schema{type: :string},
nickname: %Schema{type: :string},
display_name: %Schema{type: :string},
- deactivated: %Schema{type: :boolean},
+ is_active: %Schema{type: :boolean},
local: %Schema{type: :boolean},
roles: %Schema{
type: :object,
@@ -142,7 +143,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.StatusOperation do
}
},
tags: %Schema{type: :string},
- confirmation_pending: %Schema{type: :string}
+ is_confirmed: %Schema{type: :string}
}
}
end
diff --git a/lib/pleroma/web/api_spec/operations/admin/user_operation.ex b/lib/pleroma/web/api_spec/operations/admin/user_operation.ex
new file mode 100644
index 000000000..a5179ac39
--- /dev/null
+++ b/lib/pleroma/web/api_spec/operations/admin/user_operation.ex
@@ -0,0 +1,453 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ApiSpec.Admin.UserOperation do
+ alias OpenApiSpex.Operation
+ alias OpenApiSpex.Schema
+ alias Pleroma.Web.ApiSpec.Schemas.ActorType
+ alias Pleroma.Web.ApiSpec.Schemas.ApiError
+
+ import Pleroma.Web.ApiSpec.Helpers
+
+ def open_api_operation(action) do
+ operation = String.to_existing_atom("#{action}_operation")
+ apply(__MODULE__, operation, [])
+ end
+
+ def index_operation do
+ %Operation{
+ tags: ["User administration"],
+ summary: "List users",
+ operationId: "AdminAPI.UserController.index",
+ security: [%{"oAuth" => ["admin:read:accounts"]}],
+ parameters: [
+ Operation.parameter(:filters, :query, :string, "Comma separated list of filters"),
+ Operation.parameter(:query, :query, :string, "Search users query"),
+ Operation.parameter(:name, :query, :string, "Search by display name"),
+ Operation.parameter(:email, :query, :string, "Search by email"),
+ Operation.parameter(:page, :query, :integer, "Page Number"),
+ Operation.parameter(:page_size, :query, :integer, "Number of users to return per page"),
+ Operation.parameter(
+ :actor_types,
+ :query,
+ %Schema{type: :array, items: ActorType},
+ "Filter by actor type"
+ ),
+ Operation.parameter(
+ :tags,
+ :query,
+ %Schema{type: :array, items: %Schema{type: :string}},
+ "Filter by tags"
+ )
+ | admin_api_params()
+ ],
+ responses: %{
+ 200 =>
+ Operation.response(
+ "Response",
+ "application/json",
+ %Schema{
+ type: :object,
+ properties: %{
+ users: %Schema{type: :array, items: user()},
+ count: %Schema{type: :integer},
+ page_size: %Schema{type: :integer}
+ }
+ }
+ ),
+ 403 => Operation.response("Forbidden", "application/json", ApiError)
+ }
+ }
+ end
+
+ def create_operation do
+ %Operation{
+ tags: ["User administration"],
+ summary: "Create a single or multiple users",
+ operationId: "AdminAPI.UserController.create",
+ security: [%{"oAuth" => ["admin:write:accounts"]}],
+ parameters: admin_api_params(),
+ requestBody:
+ request_body(
+ "Parameters",
+ %Schema{
+ description: "POST body for creating users",
+ type: :object,
+ properties: %{
+ users: %Schema{
+ type: :array,
+ items: %Schema{
+ type: :object,
+ properties: %{
+ nickname: %Schema{type: :string},
+ email: %Schema{type: :string},
+ password: %Schema{type: :string}
+ }
+ }
+ }
+ }
+ }
+ ),
+ responses: %{
+ 200 =>
+ Operation.response("Response", "application/json", %Schema{
+ type: :array,
+ items: %Schema{
+ type: :object,
+ properties: %{
+ code: %Schema{type: :integer},
+ type: %Schema{type: :string},
+ data: %Schema{
+ type: :object,
+ properties: %{
+ email: %Schema{type: :string, format: :email},
+ nickname: %Schema{type: :string}
+ }
+ }
+ }
+ }
+ }),
+ 403 => Operation.response("Forbidden", "application/json", ApiError),
+ 409 =>
+ Operation.response("Conflict", "application/json", %Schema{
+ type: :array,
+ items: %Schema{
+ type: :object,
+ properties: %{
+ code: %Schema{type: :integer},
+ error: %Schema{type: :string},
+ type: %Schema{type: :string},
+ data: %Schema{
+ type: :object,
+ properties: %{
+ email: %Schema{type: :string, format: :email},
+ nickname: %Schema{type: :string}
+ }
+ }
+ }
+ }
+ })
+ }
+ }
+ end
+
+ def show_operation do
+ %Operation{
+ tags: ["User administration"],
+ summary: "Show user",
+ operationId: "AdminAPI.UserController.show",
+ security: [%{"oAuth" => ["admin:read:accounts"]}],
+ parameters: [
+ Operation.parameter(
+ :nickname,
+ :path,
+ :string,
+ "User nickname or ID"
+ )
+ | admin_api_params()
+ ],
+ responses: %{
+ 200 => Operation.response("Response", "application/json", user()),
+ 403 => Operation.response("Forbidden", "application/json", ApiError),
+ 404 => Operation.response("Not Found", "application/json", ApiError)
+ }
+ }
+ end
+
+ def follow_operation do
+ %Operation{
+ tags: ["User administration"],
+ summary: "Follow",
+ operationId: "AdminAPI.UserController.follow",
+ security: [%{"oAuth" => ["admin:write:follows"]}],
+ parameters: admin_api_params(),
+ requestBody:
+ request_body(
+ "Parameters",
+ %Schema{
+ type: :object,
+ properties: %{
+ follower: %Schema{type: :string, description: "Follower nickname"},
+ followed: %Schema{type: :string, description: "Followed nickname"}
+ }
+ }
+ ),
+ responses: %{
+ 200 => Operation.response("Response", "application/json", %Schema{type: :string}),
+ 403 => Operation.response("Forbidden", "application/json", ApiError)
+ }
+ }
+ end
+
+ def unfollow_operation do
+ %Operation{
+ tags: ["User administration"],
+ summary: "Unfollow",
+ operationId: "AdminAPI.UserController.unfollow",
+ security: [%{"oAuth" => ["admin:write:follows"]}],
+ parameters: admin_api_params(),
+ requestBody:
+ request_body(
+ "Parameters",
+ %Schema{
+ type: :object,
+ properties: %{
+ follower: %Schema{type: :string, description: "Follower nickname"},
+ followed: %Schema{type: :string, description: "Followed nickname"}
+ }
+ }
+ ),
+ responses: %{
+ 200 => Operation.response("Response", "application/json", %Schema{type: :string}),
+ 403 => Operation.response("Forbidden", "application/json", ApiError)
+ }
+ }
+ end
+
+ def approve_operation do
+ %Operation{
+ tags: ["User administration"],
+ summary: "Approve multiple users",
+ operationId: "AdminAPI.UserController.approve",
+ security: [%{"oAuth" => ["admin:write:accounts"]}],
+ parameters: admin_api_params(),
+ requestBody:
+ request_body(
+ "Parameters",
+ %Schema{
+ description: "POST body for approving multiple users",
+ type: :object,
+ properties: %{
+ nicknames: %Schema{
+ type: :array,
+ items: %Schema{type: :string}
+ }
+ }
+ }
+ ),
+ responses: %{
+ 200 =>
+ Operation.response("Response", "application/json", %Schema{
+ type: :object,
+ properties: %{user: %Schema{type: :array, items: user()}}
+ }),
+ 403 => Operation.response("Forbidden", "application/json", ApiError)
+ }
+ }
+ end
+
+ def suggest_operation do
+ %Operation{
+ tags: ["User administration"],
+ summary: "Suggest multiple users",
+ operationId: "AdminAPI.UserController.suggest",
+ security: [%{"oAuth" => ["admin:write:accounts"]}],
+ parameters: admin_api_params(),
+ requestBody:
+ request_body(
+ "Parameters",
+ %Schema{
+ description: "POST body for adding multiple suggested users",
+ type: :object,
+ properties: %{
+ nicknames: %Schema{
+ type: :array,
+ items: %Schema{type: :string}
+ }
+ }
+ }
+ ),
+ responses: %{
+ 200 =>
+ Operation.response("Response", "application/json", %Schema{
+ type: :object,
+ properties: %{user: %Schema{type: :array, items: user()}}
+ }),
+ 403 => Operation.response("Forbidden", "application/json", ApiError)
+ }
+ }
+ end
+
+ def unsuggest_operation do
+ %Operation{
+ tags: ["User administration"],
+ summary: "Unsuggest multiple users",
+ operationId: "AdminAPI.UserController.unsuggest",
+ security: [%{"oAuth" => ["admin:write:accounts"]}],
+ parameters: admin_api_params(),
+ requestBody:
+ request_body(
+ "Parameters",
+ %Schema{
+ description: "POST body for removing multiple suggested users",
+ type: :object,
+ properties: %{
+ nicknames: %Schema{
+ type: :array,
+ items: %Schema{type: :string}
+ }
+ }
+ }
+ ),
+ responses: %{
+ 200 =>
+ Operation.response("Response", "application/json", %Schema{
+ type: :object,
+ properties: %{user: %Schema{type: :array, items: user()}}
+ }),
+ 403 => Operation.response("Forbidden", "application/json", ApiError)
+ }
+ }
+ end
+
+ def toggle_activation_operation do
+ %Operation{
+ tags: ["User administration"],
+ summary: "Toggle user activation",
+ operationId: "AdminAPI.UserController.toggle_activation",
+ security: [%{"oAuth" => ["admin:write:accounts"]}],
+ parameters: [
+ Operation.parameter(:nickname, :path, :string, "User nickname")
+ | admin_api_params()
+ ],
+ responses: %{
+ 200 => Operation.response("Response", "application/json", user()),
+ 403 => Operation.response("Forbidden", "application/json", ApiError)
+ }
+ }
+ end
+
+ def activate_operation do
+ %Operation{
+ tags: ["User administration"],
+ summary: "Activate multiple users",
+ operationId: "AdminAPI.UserController.activate",
+ security: [%{"oAuth" => ["admin:write:accounts"]}],
+ parameters: admin_api_params(),
+ requestBody:
+ request_body(
+ "Parameters",
+ %Schema{
+ description: "POST body for deleting multiple users",
+ type: :object,
+ properties: %{
+ nicknames: %Schema{
+ type: :array,
+ items: %Schema{type: :string}
+ }
+ }
+ }
+ ),
+ responses: %{
+ 200 =>
+ Operation.response("Response", "application/json", %Schema{
+ type: :object,
+ properties: %{user: %Schema{type: :array, items: user()}}
+ }),
+ 403 => Operation.response("Forbidden", "application/json", ApiError)
+ }
+ }
+ end
+
+ def deactivate_operation do
+ %Operation{
+ tags: ["User administration"],
+ summary: "Deactivates multiple users",
+ operationId: "AdminAPI.UserController.deactivate",
+ security: [%{"oAuth" => ["admin:write:accounts"]}],
+ parameters: admin_api_params(),
+ requestBody:
+ request_body(
+ "Parameters",
+ %Schema{
+ description: "POST body for deleting multiple users",
+ type: :object,
+ properties: %{
+ nicknames: %Schema{
+ type: :array,
+ items: %Schema{type: :string}
+ }
+ }
+ }
+ ),
+ responses: %{
+ 200 =>
+ Operation.response("Response", "application/json", %Schema{
+ type: :object,
+ properties: %{user: %Schema{type: :array, items: user()}}
+ }),
+ 403 => Operation.response("Forbidden", "application/json", ApiError)
+ }
+ }
+ end
+
+ def delete_operation do
+ %Operation{
+ tags: ["User administration"],
+ summary: "Removes a single or multiple users",
+ operationId: "AdminAPI.UserController.delete",
+ security: [%{"oAuth" => ["admin:write:accounts"]}],
+ parameters: [
+ Operation.parameter(
+ :nickname,
+ :query,
+ :string,
+ "User nickname"
+ )
+ | admin_api_params()
+ ],
+ requestBody:
+ request_body(
+ "Parameters",
+ %Schema{
+ description: "POST body for deleting multiple users",
+ type: :object,
+ properties: %{
+ nicknames: %Schema{
+ type: :array,
+ items: %Schema{type: :string}
+ }
+ }
+ }
+ ),
+ responses: %{
+ 200 =>
+ Operation.response("Response", "application/json", %Schema{
+ description: "Array of nicknames",
+ type: :array,
+ items: %Schema{type: :string}
+ }),
+ 403 => Operation.response("Forbidden", "application/json", ApiError)
+ }
+ }
+ end
+
+ defp user do
+ %Schema{
+ type: :object,
+ properties: %{
+ id: %Schema{type: :string},
+ email: %Schema{type: :string, format: :email},
+ avatar: %Schema{type: :string, format: :uri},
+ nickname: %Schema{type: :string},
+ display_name: %Schema{type: :string},
+ is_active: %Schema{type: :boolean},
+ local: %Schema{type: :boolean},
+ roles: %Schema{
+ type: :object,
+ properties: %{
+ admin: %Schema{type: :boolean},
+ moderator: %Schema{type: :boolean}
+ }
+ },
+ tags: %Schema{type: :array, items: %Schema{type: :string}},
+ is_confirmed: %Schema{type: :boolean},
+ is_approved: %Schema{type: :boolean},
+ url: %Schema{type: :string, format: :uri},
+ registration_reason: %Schema{type: :string, nullable: true},
+ actor_type: %Schema{type: :string}
+ }
+ }
+ end
+end
diff --git a/lib/pleroma/web/api_spec/operations/announcement_operation.ex b/lib/pleroma/web/api_spec/operations/announcement_operation.ex
new file mode 100644
index 000000000..71be0002a
--- /dev/null
+++ b/lib/pleroma/web/api_spec/operations/announcement_operation.ex
@@ -0,0 +1,57 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ApiSpec.AnnouncementOperation do
+ alias OpenApiSpex.Operation
+ alias OpenApiSpex.Schema
+ alias Pleroma.Web.ApiSpec.Schemas.Announcement
+ alias Pleroma.Web.ApiSpec.Schemas.ApiError
+
+ def open_api_operation(action) do
+ operation = String.to_existing_atom("#{action}_operation")
+ apply(__MODULE__, operation, [])
+ end
+
+ def index_operation do
+ %Operation{
+ tags: ["Announcement"],
+ summary: "Retrieve a list of announcements",
+ operationId: "MastodonAPI.AnnouncementController.index",
+ security: [%{"oAuth" => []}],
+ responses: %{
+ 200 => Operation.response("Response", "application/json", list_of_announcements()),
+ 403 => Operation.response("Forbidden", "application/json", ApiError)
+ }
+ }
+ end
+
+ def mark_read_operation do
+ %Operation{
+ tags: ["Announcement"],
+ summary: "Mark one announcement as read",
+ operationId: "MastodonAPI.AnnouncementController.mark_read",
+ security: [%{"oAuth" => ["write:accounts"]}],
+ parameters: [
+ Operation.parameter(
+ :id,
+ :path,
+ :string,
+ "announcement id"
+ )
+ ],
+ responses: %{
+ 200 => Operation.response("Response", "application/json", %Schema{type: :object}),
+ 403 => Operation.response("Forbidden", "application/json", ApiError),
+ 404 => Operation.response("Not Found", "application/json", ApiError)
+ }
+ }
+ end
+
+ def list_of_announcements do
+ %Schema{
+ type: :array,
+ items: Announcement
+ }
+ end
+end
diff --git a/lib/pleroma/web/api_spec/operations/app_operation.ex b/lib/pleroma/web/api_spec/operations/app_operation.ex
index ae01cbbec..dfa2237c0 100644
--- a/lib/pleroma/web/api_spec/operations/app_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/app_operation.ex
@@ -1,11 +1,12 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.AppOperation do
alias OpenApiSpex.Operation
alias OpenApiSpex.Schema
alias Pleroma.Web.ApiSpec.Helpers
+ alias Pleroma.Web.ApiSpec.Schemas.App
@spec open_api_operation(atom) :: Operation.t()
def open_api_operation(action) do
@@ -16,13 +17,13 @@ defmodule Pleroma.Web.ApiSpec.AppOperation do
@spec create_operation() :: Operation.t()
def create_operation do
%Operation{
- tags: ["apps"],
+ tags: ["Applications"],
summary: "Create an application",
description: "Create a new application to obtain OAuth2 credentials",
operationId: "AppController.create",
requestBody: Helpers.request_body("Parameters", create_request(), required: true),
responses: %{
- 200 => Operation.response("App", "application/json", create_response()),
+ 200 => Operation.response("App", "application/json", App),
422 =>
Operation.response(
"Unprocessable Entity",
@@ -45,8 +46,8 @@ defmodule Pleroma.Web.ApiSpec.AppOperation do
def verify_credentials_operation do
%Operation{
- tags: ["apps"],
- summary: "Verify your app works",
+ tags: ["Applications"],
+ summary: "Verify the application works",
description: "Confirm that the app's OAuth2 credentials work.",
operationId: "AppController.verify_credentials",
security: [%{"oAuth" => ["read"]}],
@@ -119,30 +120,4 @@ defmodule Pleroma.Web.ApiSpec.AppOperation do
}
}
end
-
- defp create_response do
- %Schema{
- title: "AppCreateResponse",
- description: "Response schema for an app",
- type: :object,
- properties: %{
- id: %Schema{type: :string},
- name: %Schema{type: :string},
- client_id: %Schema{type: :string},
- client_secret: %Schema{type: :string},
- redirect_uri: %Schema{type: :string},
- vapid_key: %Schema{type: :string},
- website: %Schema{type: :string, nullable: true}
- },
- example: %{
- "id" => "123",
- "name" => "My App",
- "client_id" => "TWhM-tNSuncnqN7DBJmoyeLnk6K3iJJ71KKXxgL1hPM",
- "client_secret" => "ZEaFUFmF0umgBX1qKJDjaU99Q31lDkOU8NutzTOoliw",
- "vapid_key" =>
- "BCk-QqERU0q-CfYZjcuB6lnyyOYfJ2AifKqfeGIm7Z-HiTU5T9eTG5GxVA0_OH5mMlI4UkkDTpaZwozy0TzdZ2M=",
- "website" => "https://myapp.com/"
- }
- }
- end
end
diff --git a/lib/pleroma/web/api_spec/operations/chat_operation.ex b/lib/pleroma/web/api_spec/operations/chat_operation.ex
index 560b81f17..cf6a055fc 100644
--- a/lib/pleroma/web/api_spec/operations/chat_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/chat_operation.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.ChatOperation do
@@ -20,7 +20,7 @@ defmodule Pleroma.Web.ApiSpec.ChatOperation do
def mark_as_read_operation do
%Operation{
- tags: ["chat"],
+ tags: ["Chats"],
summary: "Mark all messages in the chat as read",
operationId: "ChatController.mark_as_read",
parameters: [Operation.parameter(:id, :path, :string, "The ID of the Chat")],
@@ -43,8 +43,8 @@ defmodule Pleroma.Web.ApiSpec.ChatOperation do
def mark_message_as_read_operation do
%Operation{
- tags: ["chat"],
- summary: "Mark one message in the chat as read",
+ tags: ["Chats"],
+ summary: "Mark a message as read",
operationId: "ChatController.mark_message_as_read",
parameters: [
Operation.parameter(:id, :path, :string, "The ID of the Chat"),
@@ -68,8 +68,8 @@ defmodule Pleroma.Web.ApiSpec.ChatOperation do
def show_operation do
%Operation{
- tags: ["chat"],
- summary: "Create a chat",
+ tags: ["Chats"],
+ summary: "Retrieve a chat",
operationId: "ChatController.show",
parameters: [
Operation.parameter(
@@ -99,7 +99,7 @@ defmodule Pleroma.Web.ApiSpec.ChatOperation do
def create_operation do
%Operation{
- tags: ["chat"],
+ tags: ["Chats"],
summary: "Create a chat",
operationId: "ChatController.create",
parameters: [
@@ -130,11 +130,33 @@ defmodule Pleroma.Web.ApiSpec.ChatOperation do
def index_operation do
%Operation{
- tags: ["chat"],
- summary: "Get a list of chats that you participated in",
+ tags: ["Chats"],
+ summary: "Retrieve list of chats (unpaginated)",
+ deprecated: true,
+ description:
+ "Deprecated due to no support for pagination. Using [/api/v2/pleroma/chats](#operation/ChatController.index2) instead is recommended.",
operationId: "ChatController.index",
parameters: [
Operation.parameter(:with_muted, :query, BooleanLike, "Include chats from muted users")
+ ],
+ responses: %{
+ 200 => Operation.response("The chats of the user", "application/json", chats_response())
+ },
+ security: [
+ %{
+ "oAuth" => ["read:chats"]
+ }
+ ]
+ }
+ end
+
+ def index2_operation do
+ %Operation{
+ tags: ["Chats"],
+ summary: "Retrieve list of chats",
+ operationId: "ChatController.index2",
+ parameters: [
+ Operation.parameter(:with_muted, :query, BooleanLike, "Include chats from muted users")
| pagination_params()
],
responses: %{
@@ -150,8 +172,8 @@ defmodule Pleroma.Web.ApiSpec.ChatOperation do
def messages_operation do
%Operation{
- tags: ["chat"],
- summary: "Get the most recent messages of the chat",
+ tags: ["Chats"],
+ summary: "Retrieve chat's messages",
operationId: "ChatController.messages",
parameters:
[Operation.parameter(:id, :path, :string, "The ID of the Chat")] ++
@@ -175,7 +197,7 @@ defmodule Pleroma.Web.ApiSpec.ChatOperation do
def post_chat_message_operation do
%Operation{
- tags: ["chat"],
+ tags: ["Chats"],
summary: "Post a message to the chat",
operationId: "ChatController.post_chat_message",
parameters: [
@@ -202,8 +224,8 @@ defmodule Pleroma.Web.ApiSpec.ChatOperation do
def delete_message_operation do
%Operation{
- tags: ["chat"],
- summary: "delete_message",
+ tags: ["Chats"],
+ summary: "Delete message",
operationId: "ChatController.delete_message",
parameters: [
Operation.parameter(:id, :path, :string, "The ID of the Chat"),
@@ -236,7 +258,7 @@ defmodule Pleroma.Web.ApiSpec.ChatOperation do
"account" => %{
"pleroma" => %{
"is_admin" => false,
- "confirmation_pending" => false,
+ "is_confirmed" => true,
"hide_followers_count" => false,
"is_moderator" => false,
"hide_favorites" => true,
diff --git a/lib/pleroma/web/api_spec/operations/conversation_operation.ex b/lib/pleroma/web/api_spec/operations/conversation_operation.ex
index 475468893..82ccf41f9 100644
--- a/lib/pleroma/web/api_spec/operations/conversation_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/conversation_operation.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.ConversationOperation do
@@ -18,7 +18,7 @@ defmodule Pleroma.Web.ApiSpec.ConversationOperation do
def index_operation do
%Operation{
tags: ["Conversations"],
- summary: "Show conversation",
+ summary: "List of conversations",
security: [%{"oAuth" => ["read:statuses"]}],
operationId: "ConversationController.index",
parameters: [
@@ -44,18 +44,33 @@ defmodule Pleroma.Web.ApiSpec.ConversationOperation do
def mark_as_read_operation do
%Operation{
tags: ["Conversations"],
- summary: "Mark as read",
+ summary: "Mark conversation as read",
operationId: "ConversationController.mark_as_read",
- parameters: [
- Operation.parameter(:id, :path, :string, "Conversation ID",
- example: "123",
- required: true
- )
- ],
+ parameters: [id_param()],
security: [%{"oAuth" => ["write:conversations"]}],
responses: %{
200 => Operation.response("Conversation", "application/json", Conversation)
}
}
end
+
+ def delete_operation do
+ %Operation{
+ tags: ["Conversations"],
+ summary: "Remove conversation",
+ operationId: "ConversationController.delete",
+ parameters: [id_param()],
+ security: [%{"oAuth" => ["write:conversations"]}],
+ responses: %{
+ 200 => empty_object_response()
+ }
+ }
+ end
+
+ def id_param do
+ Operation.parameter(:id, :path, :string, "Conversation ID",
+ example: "123",
+ required: true
+ )
+ end
end
diff --git a/lib/pleroma/web/api_spec/operations/custom_emoji_operation.ex b/lib/pleroma/web/api_spec/operations/custom_emoji_operation.ex
index 5ff263ceb..77823f13e 100644
--- a/lib/pleroma/web/api_spec/operations/custom_emoji_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/custom_emoji_operation.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.CustomEmojiOperation do
@@ -14,8 +14,8 @@ defmodule Pleroma.Web.ApiSpec.CustomEmojiOperation do
def index_operation do
%Operation{
- tags: ["custom_emojis"],
- summary: "List custom custom emojis",
+ tags: ["Custom emojis"],
+ summary: "Retrieve a list of custom emojis",
description: "Returns custom emojis that are available on the server.",
operationId: "CustomEmojiController.index",
responses: %{
diff --git a/lib/pleroma/web/api_spec/operations/directory_operation.ex b/lib/pleroma/web/api_spec/operations/directory_operation.ex
new file mode 100644
index 000000000..55752fa62
--- /dev/null
+++ b/lib/pleroma/web/api_spec/operations/directory_operation.ex
@@ -0,0 +1,41 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ApiSpec.DirectoryOperation do
+ alias OpenApiSpex.Operation
+ alias Pleroma.Web.ApiSpec.AccountOperation
+ alias Pleroma.Web.ApiSpec.Schemas.ApiError
+ alias Pleroma.Web.ApiSpec.Schemas.BooleanLike
+
+ import Pleroma.Web.ApiSpec.Helpers
+
+ def open_api_operation(action) do
+ operation = String.to_existing_atom("#{action}_operation")
+ apply(__MODULE__, operation, [])
+ end
+
+ def index_operation do
+ %Operation{
+ tags: ["Directory"],
+ summary: "Profile directory",
+ operationId: "DirectoryController.index",
+ parameters:
+ [
+ Operation.parameter(
+ :order,
+ :query,
+ :string,
+ "Order by recent activity or account creation",
+ required: nil
+ ),
+ Operation.parameter(:local, :query, BooleanLike, "Include local users only")
+ ] ++ pagination_params(),
+ responses: %{
+ 200 =>
+ Operation.response("Accounts", "application/json", AccountOperation.array_of_accounts()),
+ 404 => Operation.response("Not Found", "application/json", ApiError)
+ }
+ }
+ end
+end
diff --git a/lib/pleroma/web/api_spec/operations/domain_block_operation.ex b/lib/pleroma/web/api_spec/operations/domain_block_operation.ex
index 1e0da8209..2340fd914 100644
--- a/lib/pleroma/web/api_spec/operations/domain_block_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/domain_block_operation.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.DomainBlockOperation do
@@ -14,9 +14,8 @@ defmodule Pleroma.Web.ApiSpec.DomainBlockOperation do
def index_operation do
%Operation{
- tags: ["domain_blocks"],
- summary: "Fetch domain blocks",
- description: "View domains the user has blocked.",
+ tags: ["Domain blocks"],
+ summary: "Retrieve a list of blocked domains",
security: [%{"oAuth" => ["follow", "read:blocks"]}],
operationId: "DomainBlockController.index",
responses: %{
@@ -34,7 +33,7 @@ defmodule Pleroma.Web.ApiSpec.DomainBlockOperation do
# Supporting domain query parameter is deprecated in Mastodon API
def create_operation do
%Operation{
- tags: ["domain_blocks"],
+ tags: ["Domain blocks"],
summary: "Block a domain",
description: """
Block a domain to:
@@ -55,7 +54,7 @@ defmodule Pleroma.Web.ApiSpec.DomainBlockOperation do
# Supporting domain query parameter is deprecated in Mastodon API
def delete_operation do
%Operation{
- tags: ["domain_blocks"],
+ tags: ["Domain blocks"],
summary: "Unblock a domain",
description: "Remove a domain block, if it exists in the user's array of blocked domains.",
operationId: "DomainBlockController.delete",
diff --git a/lib/pleroma/web/api_spec/operations/emoji_reaction_operation.ex b/lib/pleroma/web/api_spec/operations/emoji_reaction_operation.ex
index 9d0e39fc7..74341d64f 100644
--- a/lib/pleroma/web/api_spec/operations/emoji_reaction_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/emoji_reaction_operation.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.EmojiReactionOperation do
@@ -17,7 +17,7 @@ defmodule Pleroma.Web.ApiSpec.EmojiReactionOperation do
def index_operation do
%Operation{
- tags: ["Emoji Reactions"],
+ tags: ["Emoji reactions"],
summary:
"Get an object of emoji to account mappings with accounts that reacted to the post",
parameters: [
@@ -42,7 +42,7 @@ defmodule Pleroma.Web.ApiSpec.EmojiReactionOperation do
def create_operation do
%Operation{
- tags: ["Emoji Reactions"],
+ tags: ["Emoji reactions"],
summary: "React to a post with a unicode emoji",
parameters: [
Operation.parameter(:id, :path, FlakeID, "Status ID", required: true),
@@ -61,7 +61,7 @@ defmodule Pleroma.Web.ApiSpec.EmojiReactionOperation do
def delete_operation do
%Operation{
- tags: ["Emoji Reactions"],
+ tags: ["Emoji reactions"],
summary: "Remove a reaction to a post with a unicode emoji",
parameters: [
Operation.parameter(:id, :path, FlakeID, "Status ID", required: true),
@@ -78,7 +78,7 @@ defmodule Pleroma.Web.ApiSpec.EmojiReactionOperation do
end
defp array_of_reactions_response do
- Operation.response("Array of Emoji Reactions", "application/json", %Schema{
+ Operation.response("Array of Emoji reactions", "application/json", %Schema{
type: :array,
items: emoji_reaction(),
example: [emoji_reaction().example]
diff --git a/lib/pleroma/web/api_spec/operations/filter_operation.ex b/lib/pleroma/web/api_spec/operations/filter_operation.ex
index 31e576f99..a1700b7c9 100644
--- a/lib/pleroma/web/api_spec/operations/filter_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/filter_operation.ex
@@ -1,11 +1,12 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.FilterOperation do
alias OpenApiSpex.Operation
alias OpenApiSpex.Schema
alias Pleroma.Web.ApiSpec.Helpers
+ alias Pleroma.Web.ApiSpec.Schemas.ApiError
alias Pleroma.Web.ApiSpec.Schemas.BooleanLike
def open_api_operation(action) do
@@ -15,57 +16,64 @@ defmodule Pleroma.Web.ApiSpec.FilterOperation do
def index_operation do
%Operation{
- tags: ["apps"],
- summary: "View all filters",
+ tags: ["Filters"],
+ summary: "All filters",
operationId: "FilterController.index",
security: [%{"oAuth" => ["read:filters"]}],
responses: %{
- 200 => Operation.response("Filters", "application/json", array_of_filters())
+ 200 => Operation.response("Filters", "application/json", array_of_filters()),
+ 403 => Operation.response("Error", "application/json", ApiError)
}
}
end
def create_operation do
%Operation{
- tags: ["apps"],
+ tags: ["Filters"],
summary: "Create a filter",
operationId: "FilterController.create",
requestBody: Helpers.request_body("Parameters", create_request(), required: true),
security: [%{"oAuth" => ["write:filters"]}],
- responses: %{200 => Operation.response("Filter", "application/json", filter())}
+ responses: %{
+ 200 => Operation.response("Filter", "application/json", filter()),
+ 403 => Operation.response("Error", "application/json", ApiError)
+ }
}
end
def show_operation do
%Operation{
- tags: ["apps"],
- summary: "View all filters",
+ tags: ["Filters"],
+ summary: "Filter",
parameters: [id_param()],
operationId: "FilterController.show",
security: [%{"oAuth" => ["read:filters"]}],
responses: %{
- 200 => Operation.response("Filter", "application/json", filter())
+ 200 => Operation.response("Filter", "application/json", filter()),
+ 403 => Operation.response("Error", "application/json", ApiError),
+ 404 => Operation.response("Error", "application/json", ApiError)
}
}
end
def update_operation do
%Operation{
- tags: ["apps"],
+ tags: ["Filters"],
summary: "Update a filter",
parameters: [id_param()],
operationId: "FilterController.update",
requestBody: Helpers.request_body("Parameters", update_request(), required: true),
security: [%{"oAuth" => ["write:filters"]}],
responses: %{
- 200 => Operation.response("Filter", "application/json", filter())
+ 200 => Operation.response("Filter", "application/json", filter()),
+ 403 => Operation.response("Error", "application/json", ApiError)
}
}
end
def delete_operation do
%Operation{
- tags: ["apps"],
+ tags: ["Filters"],
summary: "Remove a filter",
parameters: [id_param()],
operationId: "FilterController.delete",
@@ -75,7 +83,8 @@ defmodule Pleroma.Web.ApiSpec.FilterOperation do
Operation.response("Filter", "application/json", %Schema{
type: :object,
description: "Empty object"
- })
+ }),
+ 403 => Operation.response("Error", "application/json", ApiError)
}
}
end
@@ -210,15 +219,13 @@ defmodule Pleroma.Web.ApiSpec.FilterOperation do
nullable: true,
description: "Consider word boundaries?",
default: true
+ },
+ expires_in: %Schema{
+ nullable: true,
+ type: :integer,
+ description:
+ "Number of seconds from now the filter should expire. Otherwise, null for a filter that doesn't expire."
}
- # TODO: probably should implement filter expiration
- # expires_in: %Schema{
- # type: :string,
- # format: :"date-time",
- # description:
- # "ISO 8601 Datetime for when the filter expires. Otherwise,
- # null for a filter that doesn't expire."
- # }
},
required: [:phrase, :context],
example: %{
diff --git a/lib/pleroma/web/api_spec/operations/follow_request_operation.ex b/lib/pleroma/web/api_spec/operations/follow_request_operation.ex
index ac4aee6da..72dc8b5fa 100644
--- a/lib/pleroma/web/api_spec/operations/follow_request_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/follow_request_operation.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.FollowRequestOperation do
@@ -15,8 +15,8 @@ defmodule Pleroma.Web.ApiSpec.FollowRequestOperation do
def index_operation do
%Operation{
- tags: ["Follow Requests"],
- summary: "Pending Follows",
+ tags: ["Follow requests"],
+ summary: "Retrieve follow requests",
security: [%{"oAuth" => ["read:follows", "follow"]}],
operationId: "FollowRequestController.index",
responses: %{
@@ -32,8 +32,8 @@ defmodule Pleroma.Web.ApiSpec.FollowRequestOperation do
def authorize_operation do
%Operation{
- tags: ["Follow Requests"],
- summary: "Accept Follow",
+ tags: ["Follow requests"],
+ summary: "Accept follow request",
operationId: "FollowRequestController.authorize",
parameters: [id_param()],
security: [%{"oAuth" => ["follow", "write:follows"]}],
@@ -45,8 +45,8 @@ defmodule Pleroma.Web.ApiSpec.FollowRequestOperation do
def reject_operation do
%Operation{
- tags: ["Follow Requests"],
- summary: "Reject Follow",
+ tags: ["Follow requests"],
+ summary: "Reject follow request",
operationId: "FollowRequestController.reject",
parameters: [id_param()],
security: [%{"oAuth" => ["follow", "write:follows"]}],
diff --git a/lib/pleroma/web/api_spec/operations/instance_operation.ex b/lib/pleroma/web/api_spec/operations/instance_operation.ex
index bf39ae643..3c4b504fe 100644
--- a/lib/pleroma/web/api_spec/operations/instance_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/instance_operation.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.InstanceOperation do
@@ -14,7 +14,7 @@ defmodule Pleroma.Web.ApiSpec.InstanceOperation do
def show_operation do
%Operation{
tags: ["Instance"],
- summary: "Fetch instance",
+ summary: "Retrieve instance information",
description: "Information about the server",
operationId: "InstanceController.show",
responses: %{
@@ -26,7 +26,7 @@ defmodule Pleroma.Web.ApiSpec.InstanceOperation do
def peers_operation do
%Operation{
tags: ["Instance"],
- summary: "List of known hosts",
+ summary: "Retrieve list of known instances",
operationId: "InstanceController.peers",
responses: %{
200 => Operation.response("Array of domains", "application/json", array_of_domains())
diff --git a/lib/pleroma/web/api_spec/operations/list_operation.ex b/lib/pleroma/web/api_spec/operations/list_operation.ex
index f6e73968a..7d876ae2d 100644
--- a/lib/pleroma/web/api_spec/operations/list_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/list_operation.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.ListOperation do
@@ -20,7 +20,7 @@ defmodule Pleroma.Web.ApiSpec.ListOperation do
def index_operation do
%Operation{
tags: ["Lists"],
- summary: "Show user's lists",
+ summary: "Retrieve a list of lists",
description: "Fetch all lists that the user owns",
security: [%{"oAuth" => ["read:lists"]}],
operationId: "ListController.index",
@@ -33,7 +33,7 @@ defmodule Pleroma.Web.ApiSpec.ListOperation do
def create_operation do
%Operation{
tags: ["Lists"],
- summary: "Create a list",
+ summary: "Create a list",
description: "Fetch the list with the given ID. Used for verifying the title of a list.",
operationId: "ListController.create",
requestBody: create_update_request(),
@@ -49,7 +49,7 @@ defmodule Pleroma.Web.ApiSpec.ListOperation do
def show_operation do
%Operation{
tags: ["Lists"],
- summary: "Show a single list",
+ summary: "Retrieve a list",
description: "Fetch the list with the given ID. Used for verifying the title of a list.",
operationId: "ListController.show",
parameters: [id_param()],
@@ -93,7 +93,7 @@ defmodule Pleroma.Web.ApiSpec.ListOperation do
def list_accounts_operation do
%Operation{
tags: ["Lists"],
- summary: "View accounts in list",
+ summary: "Retrieve accounts in list",
operationId: "ListController.list_accounts",
parameters: [id_param()],
security: [%{"oAuth" => ["read:lists"]}],
diff --git a/lib/pleroma/web/api_spec/operations/marker_operation.ex b/lib/pleroma/web/api_spec/operations/marker_operation.ex
index 714ef1f99..4dfdeb448 100644
--- a/lib/pleroma/web/api_spec/operations/marker_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/marker_operation.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.MarkerOperation do
diff --git a/lib/pleroma/web/api_spec/operations/media_operation.ex b/lib/pleroma/web/api_spec/operations/media_operation.ex
index d9c3c42db..e6df21246 100644
--- a/lib/pleroma/web/api_spec/operations/media_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/media_operation.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.MediaOperation do
@@ -16,7 +16,7 @@ defmodule Pleroma.Web.ApiSpec.MediaOperation do
def create_operation do
%Operation{
- tags: ["media"],
+ tags: ["Media attachments"],
summary: "Upload media as attachment",
description: "Creates an attachment to be used with a new status.",
operationId: "MediaController.create",
@@ -24,6 +24,7 @@ defmodule Pleroma.Web.ApiSpec.MediaOperation do
requestBody: Helpers.request_body("Parameters", create_request()),
responses: %{
200 => Operation.response("Media", "application/json", Attachment),
+ 400 => Operation.response("Media", "application/json", ApiError),
401 => Operation.response("Media", "application/json", ApiError),
422 => Operation.response("Media", "application/json", ApiError)
}
@@ -56,8 +57,8 @@ defmodule Pleroma.Web.ApiSpec.MediaOperation do
def update_operation do
%Operation{
- tags: ["media"],
- summary: "Upload media as attachment",
+ tags: ["Media attachments"],
+ summary: "Update attachment",
description: "Creates an attachment to be used with a new status.",
operationId: "MediaController.update",
security: [%{"oAuth" => ["write:media"]}],
@@ -97,14 +98,15 @@ defmodule Pleroma.Web.ApiSpec.MediaOperation do
def show_operation do
%Operation{
- tags: ["media"],
- summary: "Show Uploaded media attachment",
+ tags: ["Media attachments"],
+ summary: "Attachment",
operationId: "MediaController.show",
parameters: [id_param()],
security: [%{"oAuth" => ["read:media"]}],
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)
}
}
@@ -112,14 +114,15 @@ defmodule Pleroma.Web.ApiSpec.MediaOperation do
def create2_operation do
%Operation{
- tags: ["media"],
- summary: "Upload media as attachment",
+ tags: ["Media attachments"],
+ summary: "Upload media as attachment (v2)",
description: "Creates an attachment to be used with a new status.",
operationId: "MediaController.create2",
security: [%{"oAuth" => ["write:media"]}],
requestBody: Helpers.request_body("Parameters", create_request()),
responses: %{
202 => Operation.response("Media", "application/json", Attachment),
+ 400 => Operation.response("Media", "application/json", ApiError),
422 => Operation.response("Media", "application/json", ApiError),
500 => Operation.response("Media", "application/json", ApiError)
}
diff --git a/lib/pleroma/web/api_spec/operations/notification_operation.ex b/lib/pleroma/web/api_spec/operations/notification_operation.ex
index 264a530d2..56aa129d2 100644
--- a/lib/pleroma/web/api_spec/operations/notification_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/notification_operation.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.NotificationOperation do
@@ -22,7 +22,7 @@ defmodule Pleroma.Web.ApiSpec.NotificationOperation do
def index_operation do
%Operation{
tags: ["Notifications"],
- summary: "Get all notifications",
+ summary: "Retrieve a list of notifications",
description:
"Notifications concerning the user. This API returns Link headers containing links to the next/previous page. However, the links can also be constructed dynamically using query params and `id` values.",
operationId: "NotificationController.index",
@@ -51,6 +51,12 @@ defmodule Pleroma.Web.ApiSpec.NotificationOperation do
:include_types,
:query,
%Schema{type: :array, items: notification_type()},
+ "Deprecated, use `types` instead"
+ ),
+ Operation.parameter(
+ :types,
+ :query,
+ %Schema{type: :array, items: notification_type()},
"Include the notifications for activities with the given types"
),
Operation.parameter(
@@ -74,7 +80,7 @@ defmodule Pleroma.Web.ApiSpec.NotificationOperation do
def show_operation do
%Operation{
tags: ["Notifications"],
- summary: "Get a single notification",
+ summary: "Retrieve a notification",
description: "View information about a notification with a given ID.",
operationId: "NotificationController.show",
security: [%{"oAuth" => ["read:notifications"]}],
@@ -99,7 +105,7 @@ defmodule Pleroma.Web.ApiSpec.NotificationOperation do
def dismiss_operation do
%Operation{
tags: ["Notifications"],
- summary: "Dismiss a single notification",
+ summary: "Dismiss a notification",
description: "Clear a single notification from the server.",
operationId: "NotificationController.dismiss",
parameters: [id_param()],
@@ -195,7 +201,8 @@ defmodule Pleroma.Web.ApiSpec.NotificationOperation do
"pleroma:chat_mention",
"pleroma:report",
"move",
- "follow_request"
+ "follow_request",
+ "poll"
],
description: """
The type of event that resulted in the notification.
diff --git a/lib/pleroma/web/api_spec/operations/pleroma_account_operation.ex b/lib/pleroma/web/api_spec/operations/pleroma_account_operation.ex
index 97836b2eb..5375c5b15 100644
--- a/lib/pleroma/web/api_spec/operations/pleroma_account_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/pleroma_account_operation.ex
@@ -1,9 +1,11 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.PleromaAccountOperation do
alias OpenApiSpex.Operation
+ alias OpenApiSpex.Schema
+ alias Pleroma.Web.ApiSpec.AccountOperation
alias Pleroma.Web.ApiSpec.Schemas.AccountRelationship
alias Pleroma.Web.ApiSpec.Schemas.ApiError
alias Pleroma.Web.ApiSpec.Schemas.FlakeID
@@ -18,8 +20,9 @@ defmodule Pleroma.Web.ApiSpec.PleromaAccountOperation do
def confirmation_resend_operation do
%Operation{
- tags: ["Accounts"],
- summary: "Resend confirmation email. Expects `email` or `nickname`",
+ tags: ["Account credentials"],
+ summary: "Resend confirmation email",
+ description: "Expects `email` or `nickname`.",
operationId: "PleromaAPI.AccountController.confirmation_resend",
parameters: [
Operation.parameter(:email, :query, :string, "Email of that needs to be verified",
@@ -41,8 +44,10 @@ defmodule Pleroma.Web.ApiSpec.PleromaAccountOperation do
def favourites_operation do
%Operation{
- tags: ["Accounts"],
- summary: "Returns favorites timeline of any user",
+ tags: ["Retrieve account information"],
+ summary: "Favorites",
+ description:
+ "Only returns data if the user has opted into sharing it. See `hide_favorites` in [Update account credentials](#operation/AccountController.update_credentials).",
operationId: "PleromaAPI.AccountController.favourites",
parameters: [id_param() | pagination_params()],
security: [%{"oAuth" => ["read:favourites"]}],
@@ -59,10 +64,30 @@ defmodule Pleroma.Web.ApiSpec.PleromaAccountOperation do
}
end
+ def endorsements_operation do
+ %Operation{
+ tags: ["Retrieve account information"],
+ summary: "Endorsements",
+ description: "Returns endorsed accounts",
+ operationId: "PleromaAPI.AccountController.endorsements",
+ parameters: [with_relationships_param(), id_param()],
+ responses: %{
+ 200 =>
+ Operation.response(
+ "Array of Accounts",
+ "application/json",
+ AccountOperation.array_of_accounts()
+ ),
+ 404 => Operation.response("Not Found", "application/json", ApiError)
+ }
+ }
+ end
+
def subscribe_operation do
%Operation{
- tags: ["Accounts"],
- summary: "Subscribe to receive notifications for all statuses posted by a user",
+ tags: ["Account actions"],
+ summary: "Subscribe",
+ description: "Receive notifications for all statuses posted by the account.",
operationId: "PleromaAPI.AccountController.subscribe",
parameters: [id_param()],
security: [%{"oAuth" => ["follow", "write:follows"]}],
@@ -75,8 +100,9 @@ defmodule Pleroma.Web.ApiSpec.PleromaAccountOperation do
def unsubscribe_operation do
%Operation{
- tags: ["Accounts"],
- summary: "Unsubscribe to stop receiving notifications from user statuses",
+ tags: ["Account actions"],
+ summary: "Unsubscribe",
+ description: "Stop receiving notifications for all statuses posted by the account.",
operationId: "PleromaAPI.AccountController.unsubscribe",
parameters: [id_param()],
security: [%{"oAuth" => ["follow", "write:follows"]}],
@@ -87,6 +113,34 @@ defmodule Pleroma.Web.ApiSpec.PleromaAccountOperation do
}
end
+ def birthdays_operation do
+ %Operation{
+ tags: ["Retrieve account information"],
+ summary: "Birthday reminders",
+ description: "Birthday reminders about users you follow.",
+ operationId: "PleromaAPI.AccountController.birthdays",
+ parameters: [
+ Operation.parameter(
+ :day,
+ :query,
+ %Schema{type: :integer},
+ "Day of users' birthdays"
+ ),
+ Operation.parameter(
+ :month,
+ :query,
+ %Schema{type: :integer},
+ "Month of users' birthdays"
+ )
+ ],
+ security: [%{"oAuth" => ["read:accounts"]}],
+ responses: %{
+ 200 =>
+ Operation.response("Accounts", "application/json", AccountOperation.array_of_accounts())
+ }
+ }
+ end
+
defp id_param do
Operation.parameter(:id, :path, FlakeID, "Account ID",
example: "9umDrYheeY451cQnEe",
diff --git a/lib/pleroma/web/api_spec/operations/pleroma_app_operation.ex b/lib/pleroma/web/api_spec/operations/pleroma_app_operation.ex
new file mode 100644
index 000000000..9f8df6c5c
--- /dev/null
+++ b/lib/pleroma/web/api_spec/operations/pleroma_app_operation.ex
@@ -0,0 +1,31 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ApiSpec.PleromaAppOperation do
+ alias OpenApiSpex.Operation
+ alias OpenApiSpex.Schema
+ alias Pleroma.Web.ApiSpec.Schemas.App
+
+ def open_api_operation(action) do
+ operation = String.to_existing_atom("#{action}_operation")
+ apply(__MODULE__, operation, [])
+ end
+
+ @spec index_operation() :: Operation.t()
+ def index_operation do
+ %Operation{
+ tags: ["Applications"],
+ summary: "List applications",
+ description: "List the OAuth applications for the current user",
+ operationId: "AppController.index",
+ responses: %{
+ 200 => Operation.response("Array of App", "application/json", array_of_apps())
+ }
+ }
+ end
+
+ defp array_of_apps do
+ %Schema{type: :array, items: App, example: [App.schema().example]}
+ end
+end
diff --git a/lib/pleroma/web/api_spec/operations/pleroma_backup_operation.ex b/lib/pleroma/web/api_spec/operations/pleroma_backup_operation.ex
index 6993794db..45fa2b058 100644
--- a/lib/pleroma/web/api_spec/operations/pleroma_backup_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/pleroma_backup_operation.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.PleromaBackupOperation do
@@ -16,7 +16,7 @@ defmodule Pleroma.Web.ApiSpec.PleromaBackupOperation do
%Operation{
tags: ["Backups"],
summary: "List backups",
- security: [%{"oAuth" => ["read:account"]}],
+ security: [%{"oAuth" => ["read:backups"]}],
operationId: "PleromaAPI.BackupController.index",
responses: %{
200 =>
@@ -37,7 +37,7 @@ defmodule Pleroma.Web.ApiSpec.PleromaBackupOperation do
%Operation{
tags: ["Backups"],
summary: "Create a backup",
- security: [%{"oAuth" => ["read:account"]}],
+ security: [%{"oAuth" => ["read:backups"]}],
operationId: "PleromaAPI.BackupController.create",
responses: %{
200 =>
diff --git a/lib/pleroma/web/api_spec/operations/pleroma_conversation_operation.ex b/lib/pleroma/web/api_spec/operations/pleroma_conversation_operation.ex
index e885eab20..89f0e13e1 100644
--- a/lib/pleroma/web/api_spec/operations/pleroma_conversation_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/pleroma_conversation_operation.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.PleromaConversationOperation do
@@ -19,7 +19,7 @@ defmodule Pleroma.Web.ApiSpec.PleromaConversationOperation do
def show_operation do
%Operation{
tags: ["Conversations"],
- summary: "The conversation with the given ID",
+ summary: "Conversation",
parameters: [
Operation.parameter(:id, :path, :string, "Conversation ID",
example: "123",
@@ -37,7 +37,7 @@ defmodule Pleroma.Web.ApiSpec.PleromaConversationOperation do
def statuses_operation do
%Operation{
tags: ["Conversations"],
- summary: "Timeline for a given conversation",
+ summary: "Timeline for conversation",
parameters: [
Operation.parameter(:id, :path, :string, "Conversation ID",
example: "123",
@@ -61,7 +61,8 @@ defmodule Pleroma.Web.ApiSpec.PleromaConversationOperation do
def update_operation do
%Operation{
tags: ["Conversations"],
- summary: "Update a conversation. Used to change the set of recipients.",
+ summary: "Update conversation",
+ description: "Change set of recipients for the conversation.",
parameters: [
Operation.parameter(:id, :path, :string, "Conversation ID",
example: "123",
@@ -86,7 +87,7 @@ defmodule Pleroma.Web.ApiSpec.PleromaConversationOperation do
def mark_as_read_operation do
%Operation{
tags: ["Conversations"],
- summary: "Marks all user's conversations as read",
+ summary: "Marks all conversations as read",
security: [%{"oAuth" => ["write:conversations"]}],
operationId: "PleromaAPI.ConversationController.mark_as_read",
responses: %{
diff --git a/lib/pleroma/web/api_spec/operations/pleroma_emoji_file_operation.ex b/lib/pleroma/web/api_spec/operations/pleroma_emoji_file_operation.ex
index 747f17e7f..d09c1c10e 100644
--- a/lib/pleroma/web/api_spec/operations/pleroma_emoji_file_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/pleroma_emoji_file_operation.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.PleromaEmojiFileOperation do
@@ -16,10 +16,10 @@ defmodule Pleroma.Web.ApiSpec.PleromaEmojiFileOperation do
def create_operation do
%Operation{
- tags: ["Emoji Packs"],
+ tags: ["Emoji pack administration"],
summary: "Add new file to the pack",
operationId: "PleromaAPI.EmojiPackController.add_file",
- security: [%{"oAuth" => ["write"]}],
+ security: [%{"oAuth" => ["admin:write"]}],
requestBody: request_body("Parameters", create_request(), required: true),
parameters: [name_param()],
responses: %{
@@ -62,10 +62,10 @@ defmodule Pleroma.Web.ApiSpec.PleromaEmojiFileOperation do
def update_operation do
%Operation{
- tags: ["Emoji Packs"],
+ tags: ["Emoji pack administration"],
summary: "Add new file to the pack",
operationId: "PleromaAPI.EmojiPackController.update_file",
- security: [%{"oAuth" => ["write"]}],
+ security: [%{"oAuth" => ["admin:write"]}],
requestBody: request_body("Parameters", update_request(), required: true),
parameters: [name_param()],
responses: %{
@@ -106,10 +106,10 @@ defmodule Pleroma.Web.ApiSpec.PleromaEmojiFileOperation do
def delete_operation do
%Operation{
- tags: ["Emoji Packs"],
+ tags: ["Emoji pack administration"],
summary: "Delete emoji file from pack",
operationId: "PleromaAPI.EmojiPackController.delete_file",
- security: [%{"oAuth" => ["write"]}],
+ security: [%{"oAuth" => ["admin:write"]}],
parameters: [
name_param(),
Operation.parameter(:shortcode, :query, :string, "File shortcode",
diff --git a/lib/pleroma/web/api_spec/operations/pleroma_emoji_pack_operation.ex b/lib/pleroma/web/api_spec/operations/pleroma_emoji_pack_operation.ex
index e576ccbad..6add3ff33 100644
--- a/lib/pleroma/web/api_spec/operations/pleroma_emoji_pack_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/pleroma_emoji_pack_operation.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.PleromaEmojiPackOperation do
@@ -16,9 +16,9 @@ defmodule Pleroma.Web.ApiSpec.PleromaEmojiPackOperation do
def remote_operation do
%Operation{
- tags: ["Emoji Packs"],
+ tags: ["Emoji pack administration"],
summary: "Make request to another instance for emoji packs list",
- security: [%{"oAuth" => ["write"]}],
+ security: [%{"oAuth" => ["admin:write"]}],
parameters: [
url_param(),
Operation.parameter(
@@ -44,7 +44,7 @@ defmodule Pleroma.Web.ApiSpec.PleromaEmojiPackOperation do
def index_operation do
%Operation{
- tags: ["Emoji Packs"],
+ tags: ["Emoji packs"],
summary: "Lists local custom emoji packs",
operationId: "PleromaAPI.EmojiPackController.index",
parameters: [
@@ -69,7 +69,7 @@ defmodule Pleroma.Web.ApiSpec.PleromaEmojiPackOperation do
def show_operation do
%Operation{
- tags: ["Emoji Packs"],
+ tags: ["Emoji packs"],
summary: "Show emoji pack",
operationId: "PleromaAPI.EmojiPackController.show",
parameters: [
@@ -97,7 +97,7 @@ defmodule Pleroma.Web.ApiSpec.PleromaEmojiPackOperation do
def archive_operation do
%Operation{
- tags: ["Emoji Packs"],
+ tags: ["Emoji packs"],
summary: "Requests a local pack archive from the instance",
operationId: "PleromaAPI.EmojiPackController.archive",
parameters: [name_param()],
@@ -115,10 +115,10 @@ defmodule Pleroma.Web.ApiSpec.PleromaEmojiPackOperation do
def download_operation do
%Operation{
- tags: ["Emoji Packs"],
+ tags: ["Emoji pack administration"],
summary: "Download pack from another instance",
operationId: "PleromaAPI.EmojiPackController.download",
- security: [%{"oAuth" => ["write"]}],
+ security: [%{"oAuth" => ["admin:write"]}],
requestBody: request_body("Parameters", download_request(), required: true),
responses: %{
200 => ok_response(),
@@ -145,10 +145,10 @@ defmodule Pleroma.Web.ApiSpec.PleromaEmojiPackOperation do
def create_operation do
%Operation{
- tags: ["Emoji Packs"],
+ tags: ["Emoji pack administration"],
summary: "Create an empty pack",
operationId: "PleromaAPI.EmojiPackController.create",
- security: [%{"oAuth" => ["write"]}],
+ security: [%{"oAuth" => ["admin:write"]}],
parameters: [name_param()],
responses: %{
200 => ok_response(),
@@ -161,10 +161,10 @@ defmodule Pleroma.Web.ApiSpec.PleromaEmojiPackOperation do
def delete_operation do
%Operation{
- tags: ["Emoji Packs"],
+ tags: ["Emoji pack administration"],
summary: "Delete a custom emoji pack",
operationId: "PleromaAPI.EmojiPackController.delete",
- security: [%{"oAuth" => ["write"]}],
+ security: [%{"oAuth" => ["admin:write"]}],
parameters: [name_param()],
responses: %{
200 => ok_response(),
@@ -177,10 +177,10 @@ defmodule Pleroma.Web.ApiSpec.PleromaEmojiPackOperation do
def update_operation do
%Operation{
- tags: ["Emoji Packs"],
+ tags: ["Emoji pack administration"],
summary: "Updates (replaces) pack metadata",
operationId: "PleromaAPI.EmojiPackController.update",
- security: [%{"oAuth" => ["write"]}],
+ security: [%{"oAuth" => ["admin:write"]}],
requestBody: request_body("Parameters", update_request(), required: true),
parameters: [name_param()],
responses: %{
@@ -193,10 +193,10 @@ defmodule Pleroma.Web.ApiSpec.PleromaEmojiPackOperation do
def import_from_filesystem_operation do
%Operation{
- tags: ["Emoji Packs"],
+ tags: ["Emoji pack administration"],
summary: "Imports packs from filesystem",
operationId: "PleromaAPI.EmojiPackController.import",
- security: [%{"oAuth" => ["write"]}],
+ security: [%{"oAuth" => ["admin:write"]}],
responses: %{
200 =>
Operation.response("Array of imported pack names", "application/json", %Schema{
diff --git a/lib/pleroma/web/api_spec/operations/pleroma_instances_operation.ex b/lib/pleroma/web/api_spec/operations/pleroma_instances_operation.ex
index 2c455b0df..82db4e1a8 100644
--- a/lib/pleroma/web/api_spec/operations/pleroma_instances_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/pleroma_instances_operation.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.PleromaInstancesOperation do
@@ -13,8 +13,8 @@ defmodule Pleroma.Web.ApiSpec.PleromaInstancesOperation do
def show_operation do
%Operation{
- tags: ["PleromaInstances"],
- summary: "Instances federation status",
+ tags: ["Instance"],
+ summary: "Retrieve federation status",
description: "Information about instances deemed unreachable by the server",
operationId: "PleromaInstances.show",
responses: %{
diff --git a/lib/pleroma/web/api_spec/operations/pleroma_mascot_operation.ex b/lib/pleroma/web/api_spec/operations/pleroma_mascot_operation.ex
index 8c5f37ea6..775e27219 100644
--- a/lib/pleroma/web/api_spec/operations/pleroma_mascot_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/pleroma_mascot_operation.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.PleromaMascotOperation do
@@ -17,7 +17,7 @@ defmodule Pleroma.Web.ApiSpec.PleromaMascotOperation do
def show_operation do
%Operation{
tags: ["Mascot"],
- summary: "Gets user mascot image",
+ summary: "Retrieve mascot",
security: [%{"oAuth" => ["read:accounts"]}],
operationId: "PleromaAPI.MascotController.show",
responses: %{
@@ -29,7 +29,7 @@ defmodule Pleroma.Web.ApiSpec.PleromaMascotOperation do
def update_operation do
%Operation{
tags: ["Mascot"],
- summary: "Set/clear user avatar image",
+ summary: "Set or clear mascot",
description:
"Behaves exactly the same as `POST /api/v1/upload`. Can only accept images - any attempt to upload non-image files will be met with `HTTP 415 Unsupported Media Type`.",
operationId: "PleromaAPI.MascotController.update",
diff --git a/lib/pleroma/web/api_spec/operations/pleroma_notification_operation.ex b/lib/pleroma/web/api_spec/operations/pleroma_notification_operation.ex
index b0c8db863..a994345db 100644
--- a/lib/pleroma/web/api_spec/operations/pleroma_notification_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/pleroma_notification_operation.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.PleromaNotificationOperation do
@@ -18,7 +18,8 @@ defmodule Pleroma.Web.ApiSpec.PleromaNotificationOperation do
def mark_as_read_operation do
%Operation{
tags: ["Notifications"],
- summary: "Mark notifications as read. Query parameters are mutually exclusive.",
+ summary: "Mark notifications as read",
+ description: "Query parameters are mutually exclusive.",
requestBody:
request_body("Parameters", %Schema{
type: :object,
@@ -32,7 +33,7 @@ defmodule Pleroma.Web.ApiSpec.PleromaNotificationOperation do
responses: %{
200 =>
Operation.response(
- "A Notification or array of Motifications",
+ "A Notification or array of Notifications",
"application/json",
%Schema{
anyOf: [
diff --git a/lib/pleroma/web/api_spec/operations/pleroma_report_operation.ex b/lib/pleroma/web/api_spec/operations/pleroma_report_operation.ex
new file mode 100644
index 000000000..9bc1877b1
--- /dev/null
+++ b/lib/pleroma/web/api_spec/operations/pleroma_report_operation.ex
@@ -0,0 +1,97 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ApiSpec.PleromaReportOperation do
+ alias OpenApiSpex.Operation
+ alias OpenApiSpex.Schema
+ alias Pleroma.Web.ApiSpec.Admin.ReportOperation
+ alias Pleroma.Web.ApiSpec.Schemas.Account
+ alias Pleroma.Web.ApiSpec.Schemas.ApiError
+ alias Pleroma.Web.ApiSpec.Schemas.FlakeID
+ alias Pleroma.Web.ApiSpec.Schemas.Status
+
+ def open_api_operation(action) do
+ operation = String.to_existing_atom("#{action}_operation")
+ apply(__MODULE__, operation, [])
+ end
+
+ def index_operation do
+ %Operation{
+ tags: ["Reports"],
+ summary: "Get a list of your own reports",
+ operationId: "PleromaAPI.ReportController.index",
+ security: [%{"oAuth" => ["read:reports"]}],
+ parameters: [
+ Operation.parameter(
+ :state,
+ :query,
+ ReportOperation.report_state(),
+ "Filter by report state"
+ ),
+ Operation.parameter(
+ :limit,
+ :query,
+ %Schema{type: :integer},
+ "The number of records to retrieve"
+ ),
+ Operation.parameter(
+ :page,
+ :query,
+ %Schema{type: :integer, default: 1},
+ "Page number"
+ ),
+ Operation.parameter(
+ :page_size,
+ :query,
+ %Schema{type: :integer, default: 50},
+ "Number number of log entries per page"
+ )
+ ],
+ responses: %{
+ 200 =>
+ Operation.response("Response", "application/json", %Schema{
+ type: :object,
+ properties: %{
+ total: %Schema{type: :integer},
+ reports: %Schema{
+ type: :array,
+ items: report()
+ }
+ }
+ }),
+ 404 => Operation.response("Not Found", "application/json", ApiError)
+ }
+ }
+ end
+
+ def show_operation do
+ %Operation{
+ tags: ["Reports"],
+ summary: "Get an individual report",
+ operationId: "PleromaAPI.ReportController.show",
+ parameters: [ReportOperation.id_param()],
+ security: [%{"oAuth" => ["read:reports"]}],
+ responses: %{
+ 200 => Operation.response("Report", "application/json", report()),
+ 404 => Operation.response("Not Found", "application/json", ApiError)
+ }
+ }
+ end
+
+ # Copied from ReportOperation.report with removing notes
+ defp report do
+ %Schema{
+ type: :object,
+ properties: %{
+ id: FlakeID,
+ state: ReportOperation.report_state(),
+ account: Account,
+ actor: Account,
+ content: %Schema{type: :string},
+ created_at: %Schema{type: :string, format: :"date-time"},
+ statuses: %Schema{type: :array, items: Status}
+ }
+ }
+ end
+end
diff --git a/lib/pleroma/web/api_spec/operations/pleroma_scrobble_operation.ex b/lib/pleroma/web/api_spec/operations/pleroma_scrobble_operation.ex
index 85a22aa0b..b6273bfcf 100644
--- a/lib/pleroma/web/api_spec/operations/pleroma_scrobble_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/pleroma_scrobble_operation.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.PleromaScrobbleOperation do
diff --git a/lib/pleroma/web/api_spec/operations/pleroma_settings_operation.ex b/lib/pleroma/web/api_spec/operations/pleroma_settings_operation.ex
new file mode 100644
index 000000000..e2cef4f67
--- /dev/null
+++ b/lib/pleroma/web/api_spec/operations/pleroma_settings_operation.ex
@@ -0,0 +1,72 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ApiSpec.PleromaSettingsOperation do
+ alias OpenApiSpex.Operation
+ alias OpenApiSpex.Schema
+
+ import Pleroma.Web.ApiSpec.Helpers
+
+ def open_api_operation(action) do
+ operation = String.to_existing_atom("#{action}_operation")
+ apply(__MODULE__, operation, [])
+ end
+
+ def show_operation do
+ %Operation{
+ tags: ["Settings"],
+ summary: "Get settings for an application",
+ description: "Get synchronized settings for an application",
+ operationId: "SettingsController.show",
+ parameters: [app_name_param()],
+ security: [%{"oAuth" => ["read:accounts"]}],
+ responses: %{
+ 200 => Operation.response("object", "application/json", object())
+ }
+ }
+ end
+
+ def update_operation do
+ %Operation{
+ tags: ["Settings"],
+ summary: "Update settings for an application",
+ description: "Update synchronized settings for an application",
+ operationId: "SettingsController.update",
+ parameters: [app_name_param()],
+ security: [%{"oAuth" => ["write:accounts"]}],
+ requestBody: request_body("Parameters", update_request(), required: true),
+ responses: %{
+ 200 => Operation.response("object", "application/json", object())
+ }
+ }
+ end
+
+ def app_name_param do
+ Operation.parameter(:app, :path, %Schema{type: :string}, "Application name",
+ example: "pleroma-fe",
+ required: true
+ )
+ end
+
+ def object do
+ %Schema{
+ title: "Settings object",
+ description: "The object that contains settings for the application.",
+ type: :object
+ }
+ end
+
+ def update_request do
+ %Schema{
+ title: "SettingsUpdateRequest",
+ type: :object,
+ description:
+ "The settings object to be merged with the current settings. To remove a field, set it to null.",
+ example: %{
+ "config1" => true,
+ "config2_to_unset" => nil
+ }
+ }
+ end
+end
diff --git a/lib/pleroma/web/api_spec/operations/poll_operation.ex b/lib/pleroma/web/api_spec/operations/poll_operation.ex
index e15c7dc95..efd784f03 100644
--- a/lib/pleroma/web/api_spec/operations/poll_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/poll_operation.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.PollOperation do
diff --git a/lib/pleroma/web/api_spec/operations/report_operation.ex b/lib/pleroma/web/api_spec/operations/report_operation.ex
index b9b4c4f79..c74ac7d5f 100644
--- a/lib/pleroma/web/api_spec/operations/report_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/report_operation.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.ReportOperation do
@@ -16,7 +16,7 @@ defmodule Pleroma.Web.ApiSpec.ReportOperation do
def create_operation do
%Operation{
- tags: ["reports"],
+ tags: ["Reports"],
summary: "File a report",
description: "Report problematic users to your moderators",
operationId: "ReportController.create",
diff --git a/lib/pleroma/web/api_spec/operations/scheduled_activity_operation.ex b/lib/pleroma/web/api_spec/operations/scheduled_activity_operation.ex
index fe675a923..802d3b6dd 100644
--- a/lib/pleroma/web/api_spec/operations/scheduled_activity_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/scheduled_activity_operation.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.ScheduledActivityOperation do
@@ -18,7 +18,7 @@ defmodule Pleroma.Web.ApiSpec.ScheduledActivityOperation do
def index_operation do
%Operation{
- tags: ["Scheduled Statuses"],
+ tags: ["Scheduled statuses"],
summary: "View scheduled statuses",
security: [%{"oAuth" => ["read:statuses"]}],
parameters: pagination_params(),
@@ -35,7 +35,7 @@ defmodule Pleroma.Web.ApiSpec.ScheduledActivityOperation do
def show_operation do
%Operation{
- tags: ["Scheduled Statuses"],
+ tags: ["Scheduled statuses"],
summary: "View a single scheduled status",
security: [%{"oAuth" => ["read:statuses"]}],
parameters: [id_param()],
@@ -49,7 +49,7 @@ defmodule Pleroma.Web.ApiSpec.ScheduledActivityOperation do
def update_operation do
%Operation{
- tags: ["Scheduled Statuses"],
+ tags: ["Scheduled statuses"],
summary: "Schedule a status",
operationId: "ScheduledActivity.update",
security: [%{"oAuth" => ["write:statuses"]}],
@@ -75,7 +75,7 @@ defmodule Pleroma.Web.ApiSpec.ScheduledActivityOperation do
def delete_operation do
%Operation{
- tags: ["Scheduled Statuses"],
+ tags: ["Scheduled statuses"],
summary: "Cancel a scheduled status",
security: [%{"oAuth" => ["write:statuses"]}],
parameters: [id_param()],
diff --git a/lib/pleroma/web/api_spec/operations/search_operation.ex b/lib/pleroma/web/api_spec/operations/search_operation.ex
index 169c36d87..1a7e49be4 100644
--- a/lib/pleroma/web/api_spec/operations/search_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/search_operation.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.SearchOperation do
diff --git a/lib/pleroma/web/api_spec/operations/status_operation.ex b/lib/pleroma/web/api_spec/operations/status_operation.ex
index 4ab918d83..e921128c7 100644
--- a/lib/pleroma/web/api_spec/operations/status_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/status_operation.ex
@@ -1,14 +1,18 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.StatusOperation do
alias OpenApiSpex.Operation
alias OpenApiSpex.Schema
alias Pleroma.Web.ApiSpec.AccountOperation
+ alias Pleroma.Web.ApiSpec.Schemas.Account
alias Pleroma.Web.ApiSpec.Schemas.ApiError
+ alias Pleroma.Web.ApiSpec.Schemas.Attachment
alias Pleroma.Web.ApiSpec.Schemas.BooleanLike
+ alias Pleroma.Web.ApiSpec.Schemas.Emoji
alias Pleroma.Web.ApiSpec.Schemas.FlakeID
+ alias Pleroma.Web.ApiSpec.Schemas.Poll
alias Pleroma.Web.ApiSpec.Schemas.ScheduledStatus
alias Pleroma.Web.ApiSpec.Schemas.Status
alias Pleroma.Web.ApiSpec.Schemas.VisibilityScope
@@ -22,8 +26,8 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do
def index_operation do
%Operation{
- tags: ["Statuses"],
- summary: "Get multiple statuses by IDs",
+ tags: ["Retrieve status information"],
+ summary: "Multiple statuses",
security: [%{"oAuth" => ["read:statuses"]}],
parameters: [
Operation.parameter(
@@ -48,7 +52,7 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do
def create_operation do
%Operation{
- tags: ["Statuses"],
+ tags: ["Status actions"],
summary: "Publish new status",
security: [%{"oAuth" => ["write:statuses"]}],
description: "Post a new status",
@@ -59,7 +63,7 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do
Operation.response(
"Status. When `scheduled_at` is present, ScheduledStatus is returned instead",
"application/json",
- %Schema{oneOf: [Status, ScheduledStatus]}
+ %Schema{anyOf: [Status, ScheduledStatus]}
),
422 => Operation.response("Bad Request / MRF Rejection", "application/json", ApiError)
}
@@ -68,8 +72,8 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do
def show_operation do
%Operation{
- tags: ["Statuses"],
- summary: "View specific status",
+ tags: ["Retrieve status information"],
+ summary: "Status",
description: "View information about a status",
operationId: "StatusController.show",
security: [%{"oAuth" => ["read:statuses"]}],
@@ -91,8 +95,8 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do
def delete_operation do
%Operation{
- tags: ["Statuses"],
- summary: "Delete status",
+ tags: ["Status actions"],
+ summary: "Delete",
security: [%{"oAuth" => ["write:statuses"]}],
description: "Delete one of your own statuses",
operationId: "StatusController.delete",
@@ -107,8 +111,8 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do
def reblog_operation do
%Operation{
- tags: ["Statuses"],
- summary: "Boost",
+ tags: ["Status actions"],
+ summary: "Reblog",
security: [%{"oAuth" => ["write:statuses"]}],
description: "Share a status",
operationId: "StatusController.reblog",
@@ -117,7 +121,7 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do
request_body("Parameters", %Schema{
type: :object,
properties: %{
- visibility: %Schema{allOf: [VisibilityScope], default: "public"}
+ visibility: %Schema{allOf: [VisibilityScope]}
}
}),
responses: %{
@@ -129,8 +133,8 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do
def unreblog_operation do
%Operation{
- tags: ["Statuses"],
- summary: "Undo boost",
+ tags: ["Status actions"],
+ summary: "Undo reblog",
security: [%{"oAuth" => ["write:statuses"]}],
description: "Undo a reshare of a status",
operationId: "StatusController.unreblog",
@@ -144,7 +148,7 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do
def favourite_operation do
%Operation{
- tags: ["Statuses"],
+ tags: ["Status actions"],
summary: "Favourite",
security: [%{"oAuth" => ["write:favourites"]}],
description: "Add a status to your favourites list",
@@ -159,7 +163,7 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do
def unfavourite_operation do
%Operation{
- tags: ["Statuses"],
+ tags: ["Status actions"],
summary: "Undo favourite",
security: [%{"oAuth" => ["write:favourites"]}],
description: "Remove a status from your favourites list",
@@ -174,7 +178,7 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do
def pin_operation do
%Operation{
- tags: ["Statuses"],
+ tags: ["Status actions"],
summary: "Pin to profile",
security: [%{"oAuth" => ["write:accounts"]}],
description: "Feature one of your own public statuses at the top of your profile",
@@ -182,29 +186,71 @@ 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
def unpin_operation do
%Operation{
- tags: ["Statuses"],
- summary: "Unpin to profile",
+ tags: ["Status actions"],
+ summary: "Unpin from profile",
security: [%{"oAuth" => ["write:accounts"]}],
description: "Unfeature a status from the top of your profile",
operationId: "StatusController.unpin",
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
def bookmark_operation do
%Operation{
- tags: ["Statuses"],
+ tags: ["Status actions"],
summary: "Bookmark",
security: [%{"oAuth" => ["write:bookmarks"]}],
description: "Privately bookmark a status",
@@ -218,7 +264,7 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do
def unbookmark_operation do
%Operation{
- tags: ["Statuses"],
+ tags: ["Status actions"],
summary: "Undo bookmark",
security: [%{"oAuth" => ["write:bookmarks"]}],
description: "Remove a status from your private bookmarks",
@@ -232,7 +278,7 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do
def mute_conversation_operation do
%Operation{
- tags: ["Statuses"],
+ tags: ["Status actions"],
summary: "Mute conversation",
security: [%{"oAuth" => ["write:mutes"]}],
description: "Do not receive notifications for the thread that this status is part of.",
@@ -267,7 +313,7 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do
def unmute_conversation_operation do
%Operation{
- tags: ["Statuses"],
+ tags: ["Status actions"],
summary: "Unmute conversation",
security: [%{"oAuth" => ["write:mutes"]}],
description:
@@ -283,7 +329,7 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do
def card_operation do
%Operation{
- tags: ["Statuses"],
+ tags: ["Retrieve status information"],
deprecated: true,
summary: "Preview card",
description: "Deprecated in favor of card property inlined on Status entity",
@@ -311,7 +357,7 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do
def favourited_by_operation do
%Operation{
- tags: ["Statuses"],
+ tags: ["Retrieve status information"],
summary: "Favourited by",
description: "View who favourited a given status",
operationId: "StatusController.favourited_by",
@@ -331,9 +377,9 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do
def reblogged_by_operation do
%Operation{
- tags: ["Statuses"],
- summary: "Boosted by",
- description: "View who boosted a given status",
+ tags: ["Retrieve status information"],
+ summary: "Reblogged by",
+ description: "View who reblogged a given status",
operationId: "StatusController.reblogged_by",
security: [%{"oAuth" => ["read:accounts"]}],
parameters: [id_param()],
@@ -351,7 +397,7 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do
def context_operation do
%Operation{
- tags: ["Statuses"],
+ tags: ["Retrieve status information"],
summary: "Parent and child statuses",
description: "View statuses above and below this status in the thread",
operationId: "StatusController.context",
@@ -365,7 +411,7 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do
def favourites_operation do
%Operation{
- tags: ["Statuses"],
+ tags: ["Timelines"],
summary: "Favourited statuses",
description:
"Statuses the user has favourited. Please note that you have to use the link headers to paginate this. You can not build the query parameters yourself.",
@@ -380,7 +426,7 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do
def bookmarks_operation do
%Operation{
- tags: ["Statuses"],
+ tags: ["Timelines"],
summary: "Bookmarked statuses",
description: "Statuses the user has bookmarked",
operationId: "StatusController.bookmarks",
@@ -392,6 +438,59 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do
}
end
+ def show_history_operation do
+ %Operation{
+ tags: ["Retrieve status history"],
+ summary: "Status history",
+ description: "View history of a status",
+ operationId: "StatusController.show_history",
+ security: [%{"oAuth" => ["read:statuses"]}],
+ parameters: [
+ id_param()
+ ],
+ responses: %{
+ 200 => status_history_response(),
+ 404 => Operation.response("Not Found", "application/json", ApiError)
+ }
+ }
+ end
+
+ def show_source_operation do
+ %Operation{
+ tags: ["Retrieve status source"],
+ summary: "Status source",
+ description: "View source of a status",
+ operationId: "StatusController.show_source",
+ security: [%{"oAuth" => ["read:statuses"]}],
+ parameters: [
+ id_param()
+ ],
+ responses: %{
+ 200 => status_source_response(),
+ 404 => Operation.response("Not Found", "application/json", ApiError)
+ }
+ }
+ end
+
+ def update_operation do
+ %Operation{
+ tags: ["Update status"],
+ summary: "Update status",
+ description: "Change the content of a status",
+ operationId: "StatusController.update",
+ security: [%{"oAuth" => ["write:statuses"]}],
+ parameters: [
+ id_param()
+ ],
+ requestBody: request_body("Parameters", update_request(), required: true),
+ responses: %{
+ 200 => status_response(),
+ 403 => Operation.response("Forbidden", "application/json", ApiError),
+ 404 => Operation.response("Not Found", "application/json", ApiError)
+ }
+ }
+ end
+
def array_of_statuses do
%Schema{type: :array, items: Status, example: [Status.schema().example]}
end
@@ -413,34 +512,7 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do
items: %Schema{type: :string},
description: "Array of Attachment ids to be attached as media."
},
- poll: %Schema{
- nullable: true,
- type: :object,
- required: [:options],
- properties: %{
- options: %Schema{
- type: :array,
- items: %Schema{type: :string},
- description: "Array of possible answers. Must be provided with `poll[expires_in]`."
- },
- expires_in: %Schema{
- type: :integer,
- nullable: true,
- description:
- "Duration the poll should be open, in seconds. Must be provided with `poll[options]`"
- },
- multiple: %Schema{
- allOf: [BooleanLike],
- nullable: true,
- description: "Allow multiple choices?"
- },
- hide_totals: %Schema{
- allOf: [BooleanLike],
- nullable: true,
- description: "Hide vote counts until the poll ends?"
- }
- }
- },
+ poll: poll_params(),
in_reply_to_id: %Schema{
nullable: true,
allOf: [FlakeID],
@@ -522,6 +594,91 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do
}
end
+ defp update_request do
+ %Schema{
+ title: "StatusUpdateRequest",
+ type: :object,
+ properties: %{
+ status: %Schema{
+ type: :string,
+ nullable: true,
+ description:
+ "Text content of the status. If `media_ids` is provided, this becomes optional. Attaching a `poll` is optional while `status` is provided."
+ },
+ media_ids: %Schema{
+ nullable: true,
+ type: :array,
+ items: %Schema{type: :string},
+ description: "Array of Attachment ids to be attached as media."
+ },
+ poll: poll_params(),
+ sensitive: %Schema{
+ allOf: [BooleanLike],
+ nullable: true,
+ description: "Mark status and attached media as sensitive?"
+ },
+ spoiler_text: %Schema{
+ type: :string,
+ nullable: true,
+ description:
+ "Text to be shown as a warning or subject before the actual content. Statuses are generally collapsed behind this field."
+ },
+ content_type: %Schema{
+ type: :string,
+ nullable: true,
+ description:
+ "The MIME type of the status, it is transformed into HTML by the backend. You can get the list of the supported MIME types with the nodeinfo endpoint."
+ },
+ to: %Schema{
+ type: :array,
+ nullable: true,
+ items: %Schema{type: :string},
+ description:
+ "A list of nicknames (like `lain@soykaf.club` or `lain` on the local server) that will be used to determine who is going to be addressed by this post. Using this will disable the implicit addressing by mentioned names in the `status` body, only the people in the `to` list will be addressed. The normal rules for for post visibility are not affected by this and will still apply"
+ }
+ },
+ example: %{
+ "status" => "What time is it?",
+ "sensitive" => "false",
+ "poll" => %{
+ "options" => ["Cofe", "Adventure"],
+ "expires_in" => 420
+ }
+ }
+ }
+ end
+
+ def poll_params do
+ %Schema{
+ nullable: true,
+ type: :object,
+ required: [:options, :expires_in],
+ properties: %{
+ options: %Schema{
+ type: :array,
+ items: %Schema{type: :string},
+ description: "Array of possible answers. Must be provided with `poll[expires_in]`."
+ },
+ expires_in: %Schema{
+ type: :integer,
+ nullable: true,
+ description:
+ "Duration the poll should be open, in seconds. Must be provided with `poll[options]`"
+ },
+ multiple: %Schema{
+ allOf: [BooleanLike],
+ nullable: true,
+ description: "Allow multiple choices?"
+ },
+ hide_totals: %Schema{
+ allOf: [BooleanLike],
+ nullable: true,
+ description: "Hide vote counts until the poll ends?"
+ }
+ }
+ }
+ end
+
def id_param do
Operation.parameter(:id, :path, FlakeID, "Status ID",
example: "9umDrYheeY451cQnEe",
@@ -533,6 +690,87 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do
Operation.response("Status", "application/json", Status)
end
+ defp status_history_response do
+ Operation.response(
+ "Status History",
+ "application/json",
+ %Schema{
+ title: "Status history",
+ description: "Response schema for history of a status",
+ type: :array,
+ items: %Schema{
+ type: :object,
+ properties: %{
+ account: %Schema{
+ allOf: [Account],
+ description: "The account that authored this status"
+ },
+ content: %Schema{
+ type: :string,
+ format: :html,
+ description: "HTML-encoded status content"
+ },
+ sensitive: %Schema{
+ type: :boolean,
+ description: "Is this status marked as sensitive content?"
+ },
+ spoiler_text: %Schema{
+ type: :string,
+ description:
+ "Subject or summary line, below which status content is collapsed until expanded"
+ },
+ created_at: %Schema{
+ type: :string,
+ format: "date-time",
+ description: "The date when this status was created"
+ },
+ media_attachments: %Schema{
+ type: :array,
+ items: Attachment,
+ description: "Media that is attached to this status"
+ },
+ emojis: %Schema{
+ type: :array,
+ items: Emoji,
+ description: "Custom emoji to be used when rendering status content"
+ },
+ poll: %Schema{
+ allOf: [Poll],
+ nullable: true,
+ description: "The poll attached to the status"
+ }
+ }
+ }
+ }
+ )
+ end
+
+ defp status_source_response do
+ Operation.response(
+ "Status Source",
+ "application/json",
+ %Schema{
+ type: :object,
+ properties: %{
+ id: FlakeID,
+ text: %Schema{
+ type: :string,
+ description: "Raw source of status content"
+ },
+ spoiler_text: %Schema{
+ type: :string,
+ description:
+ "Subject or summary line, below which status content is collapsed until expanded"
+ },
+ content_type: %Schema{
+ type: :string,
+ description: "The content type of the source"
+ }
+ }
+ }
+ )
+ end
+
defp context do
%Schema{
title: "StatusContext",
diff --git a/lib/pleroma/web/api_spec/operations/subscription_operation.ex b/lib/pleroma/web/api_spec/operations/subscription_operation.ex
index 67c7ea8f3..c53ec2956 100644
--- a/lib/pleroma/web/api_spec/operations/subscription_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/subscription_operation.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.SubscriptionOperation do
@@ -17,7 +17,7 @@ defmodule Pleroma.Web.ApiSpec.SubscriptionOperation do
def create_operation do
%Operation{
- tags: ["Push Subscriptions"],
+ tags: ["Push subscriptions"],
summary: "Subscribe to push notifications",
description:
"Add a Web Push API subscription to receive notifications. Each access token can have one push subscription. If you create a new subscription, the old subscription is deleted.",
@@ -25,7 +25,7 @@ defmodule Pleroma.Web.ApiSpec.SubscriptionOperation do
security: [%{"oAuth" => ["push"]}],
requestBody: Helpers.request_body("Parameters", create_request(), required: true),
responses: %{
- 200 => Operation.response("Push Subscription", "application/json", PushSubscription),
+ 200 => Operation.response("Push subscription", "application/json", PushSubscription),
400 => Operation.response("Error", "application/json", ApiError),
403 => Operation.response("Error", "application/json", ApiError)
}
@@ -34,13 +34,13 @@ defmodule Pleroma.Web.ApiSpec.SubscriptionOperation do
def show_operation do
%Operation{
- tags: ["Push Subscriptions"],
+ tags: ["Push subscriptions"],
summary: "Get current subscription",
description: "View the PushSubscription currently associated with this access token.",
operationId: "SubscriptionController.show",
security: [%{"oAuth" => ["push"]}],
responses: %{
- 200 => Operation.response("Push Subscription", "application/json", PushSubscription),
+ 200 => Operation.response("Push subscription", "application/json", PushSubscription),
403 => Operation.response("Error", "application/json", ApiError),
404 => Operation.response("Error", "application/json", ApiError)
}
@@ -49,7 +49,7 @@ defmodule Pleroma.Web.ApiSpec.SubscriptionOperation do
def update_operation do
%Operation{
- tags: ["Push Subscriptions"],
+ tags: ["Push subscriptions"],
summary: "Change types of notifications",
description:
"Updates the current push subscription. Only the data part can be updated. To change fundamentals, a new subscription must be created instead.",
@@ -57,7 +57,7 @@ defmodule Pleroma.Web.ApiSpec.SubscriptionOperation do
security: [%{"oAuth" => ["push"]}],
requestBody: Helpers.request_body("Parameters", update_request(), required: true),
responses: %{
- 200 => Operation.response("Push Subscription", "application/json", PushSubscription),
+ 200 => Operation.response("Push subscription", "application/json", PushSubscription),
403 => Operation.response("Error", "application/json", ApiError)
}
}
@@ -65,7 +65,7 @@ defmodule Pleroma.Web.ApiSpec.SubscriptionOperation do
def delete_operation do
%Operation{
- tags: ["Push Subscriptions"],
+ tags: ["Push subscriptions"],
summary: "Remove current subscription",
description: "Removes the current Web Push API subscription.",
operationId: "SubscriptionController.delete",
diff --git a/lib/pleroma/web/api_spec/operations/timeline_operation.ex b/lib/pleroma/web/api_spec/operations/timeline_operation.ex
index 95720df9f..fbe3f763a 100644
--- a/lib/pleroma/web/api_spec/operations/timeline_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/timeline_operation.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.TimelineOperation do
@@ -25,6 +25,8 @@ defmodule Pleroma.Web.ApiSpec.TimelineOperation do
security: [%{"oAuth" => ["read:statuses"]}],
parameters: [
local_param(),
+ remote_param(),
+ only_media_param(),
with_muted_param(),
exclude_visibilities_param(),
reply_visibility_param() | pagination_params()
@@ -41,8 +43,7 @@ defmodule Pleroma.Web.ApiSpec.TimelineOperation do
tags: ["Timelines"],
summary: "Direct timeline",
description:
- "View statuses with a “direct” privacy, from your account or in your notifications",
- deprecated: true,
+ "View statuses with a “direct” scope addressed to the account. Using this endpoint is discouraged, please use [conversations](#tag/Conversations) or [chats](#tag/Chats).",
parameters: [with_muted_param() | pagination_params()],
security: [%{"oAuth" => ["read:statuses"]}],
operationId: "TimelineController.direct",
@@ -61,6 +62,7 @@ defmodule Pleroma.Web.ApiSpec.TimelineOperation do
local_param(),
instance_param(),
only_media_param(),
+ remote_param(),
with_muted_param(),
exclude_visibilities_param(),
reply_visibility_param() | pagination_params()
@@ -107,12 +109,14 @@ defmodule Pleroma.Web.ApiSpec.TimelineOperation do
),
local_param(),
only_media_param(),
+ remote_param(),
with_muted_param(),
exclude_visibilities_param() | pagination_params()
],
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
@@ -132,6 +136,9 @@ defmodule Pleroma.Web.ApiSpec.TimelineOperation do
required: true
),
with_muted_param(),
+ local_param(),
+ remote_param(),
+ only_media_param(),
exclude_visibilities_param() | pagination_params()
],
operationId: "TimelineController.list",
@@ -198,4 +205,13 @@ defmodule Pleroma.Web.ApiSpec.TimelineOperation do
"Show only statuses with media attached?"
)
end
+
+ defp remote_param do
+ Operation.parameter(
+ :remote,
+ :query,
+ %Schema{allOf: [BooleanLike], default: false},
+ "Show only remote statuses?"
+ )
+ end
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
index 000000000..29df03e34
--- /dev/null
+++ b/lib/pleroma/web/api_spec/operations/twitter_util_operation.ex
@@ -0,0 +1,435 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 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
+
+ import Pleroma.Web.ApiSpec.Helpers
+
+ 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",
+ requestBody: request_body("Parameters", change_password_request(), 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
+
+ defp change_password_request do
+ %Schema{
+ title: "ChangePasswordRequest",
+ description: "POST body for changing the account's passowrd",
+ type: :object,
+ required: [:password, :new_password, :new_password_confirmation],
+ properties: %{
+ password: %Schema{type: :string, description: "Current password"},
+ new_password: %Schema{type: :string, description: "New password"},
+ new_password_confirmation: %Schema{
+ type: :string,
+ description: "New password, confirmation"
+ }
+ }
+ }
+ end
+
+ def change_email_operation do
+ %Operation{
+ tags: ["Account credentials"],
+ summary: "Change account email",
+ security: [%{"oAuth" => ["write:accounts"]}],
+ operationId: "UtilController.change_email",
+ requestBody: request_body("Parameters", change_email_request(), 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
+
+ defp change_email_request do
+ %Schema{
+ title: "ChangeEmailRequest",
+ description: "POST body for changing the account's email",
+ type: :object,
+ required: [:email, :password],
+ properties: %{
+ email: %Schema{
+ type: :string,
+ description: "New email. Set to blank to remove the user's email."
+ },
+ password: %Schema{type: :string, description: "Current password"}
+ }
+ }
+ end
+
+ def update_notificaton_settings_operation do
+ %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")
+ ],
+ requestBody: request_body("Parameters", delete_account_request(), required: false),
+ 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 move_account_operation do
+ %Operation{
+ tags: ["Account credentials"],
+ summary: "Move account",
+ security: [%{"oAuth" => ["write:accounts"]}],
+ operationId: "UtilController.move_account",
+ requestBody: request_body("Parameters", move_account_request(), 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),
+ 404 => Operation.response("Error", "application/json", ApiError)
+ }
+ }
+ end
+
+ defp move_account_request do
+ %Schema{
+ title: "MoveAccountRequest",
+ description: "POST body for moving the account",
+ type: :object,
+ required: [:password, :target_account],
+ properties: %{
+ password: %Schema{type: :string, description: "Current password"},
+ target_account: %Schema{
+ type: :string,
+ description: "The nickname of the target account to move to"
+ }
+ }
+ }
+ end
+
+ def list_aliases_operation do
+ %Operation{
+ tags: ["Account credentials"],
+ summary: "List account aliases",
+ security: [%{"oAuth" => ["read:accounts"]}],
+ operationId: "UtilController.list_aliases",
+ responses: %{
+ 200 =>
+ Operation.response("Success", "application/json", %Schema{
+ type: :object,
+ properties: %{
+ aliases: %Schema{
+ type: :array,
+ items: %Schema{type: :string},
+ example: ["foo@example.org"]
+ }
+ }
+ }),
+ 400 => Operation.response("Error", "application/json", ApiError),
+ 403 => Operation.response("Error", "application/json", ApiError)
+ }
+ }
+ end
+
+ def add_alias_operation do
+ %Operation{
+ tags: ["Account credentials"],
+ summary: "Add an alias to this account",
+ security: [%{"oAuth" => ["write:accounts"]}],
+ operationId: "UtilController.add_alias",
+ requestBody: request_body("Parameters", add_alias_request(), 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),
+ 404 => Operation.response("Error", "application/json", ApiError)
+ }
+ }
+ end
+
+ defp add_alias_request do
+ %Schema{
+ title: "AddAliasRequest",
+ description: "PUT body for adding aliases",
+ type: :object,
+ required: [:alias],
+ properties: %{
+ alias: %Schema{
+ type: :string,
+ description: "The nickname of the account to add to aliases"
+ }
+ }
+ }
+ end
+
+ def delete_alias_operation do
+ %Operation{
+ tags: ["Account credentials"],
+ summary: "Delete an alias from this account",
+ security: [%{"oAuth" => ["write:accounts"]}],
+ operationId: "UtilController.delete_alias",
+ requestBody: request_body("Parameters", delete_alias_request(), 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),
+ 404 => Operation.response("Error", "application/json", ApiError)
+ }
+ }
+ end
+
+ defp delete_alias_request do
+ %Schema{
+ title: "DeleteAliasRequest",
+ description: "PUT body for deleting aliases",
+ type: :object,
+ required: [:alias],
+ properties: %{
+ alias: %Schema{
+ type: :string,
+ description: "The nickname of the account to delete from aliases"
+ }
+ }
+ }
+ 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
+
+ def remote_interaction_operation do
+ %Operation{
+ tags: ["Accounts"],
+ summary: "Remote interaction",
+ operationId: "UtilController.remote_interaction",
+ requestBody: request_body("Parameters", remote_interaction_request(), required: true),
+ responses: %{
+ 200 =>
+ Operation.response("Remote interaction URL", "application/json", %Schema{type: :object})
+ }
+ }
+ end
+
+ defp remote_interaction_request do
+ %Schema{
+ title: "RemoteInteractionRequest",
+ description: "POST body for remote interaction",
+ type: :object,
+ required: [:ap_id, :profile],
+ properties: %{
+ ap_id: %Schema{type: :string, description: "Profile or status ActivityPub ID"},
+ profile: %Schema{type: :string, description: "Remote profile webfinger"}
+ }
+ }
+ end
+
+ def show_subscribe_form_operation do
+ %Operation{
+ tags: ["Accounts"],
+ summary: "Show remote subscribe form",
+ operationId: "UtilController.show_subscribe_form",
+ parameters: [],
+ responses: %{200 => Operation.response("Web Page", "test/html", %Schema{type: :string})}
+ }
+ end
+
+ defp delete_account_request do
+ %Schema{
+ title: "AccountDeleteRequest",
+ description: "POST body for deleting one's own account",
+ type: :object,
+ properties: %{
+ password: %Schema{
+ type: :string,
+ description: "The user's own password for confirmation.",
+ format: :password
+ }
+ },
+ example: %{
+ "password" => "prettyp0ony1313"
+ }
+ }
+ end
+end
diff --git a/lib/pleroma/web/api_spec/operations/user_import_operation.ex b/lib/pleroma/web/api_spec/operations/user_import_operation.ex
index a50314fb7..e99e6e648 100644
--- a/lib/pleroma/web/api_spec/operations/user_import_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/user_import_operation.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.UserImportOperation do
@@ -17,12 +17,13 @@ defmodule Pleroma.Web.ApiSpec.UserImportOperation do
def follow_operation do
%Operation{
- tags: ["follow_import"],
- summary: "Imports your follows.",
+ tags: ["Data import"],
+ summary: "Import follows",
operationId: "UserImportController.follow",
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"]}]
@@ -31,8 +32,8 @@ defmodule Pleroma.Web.ApiSpec.UserImportOperation do
def blocks_operation do
%Operation{
- tags: ["blocks_import"],
- summary: "Imports your blocks.",
+ tags: ["Data import"],
+ summary: "Import blocks",
operationId: "UserImportController.blocks",
requestBody: request_body("Parameters", import_request(), required: true),
responses: %{
@@ -45,8 +46,8 @@ defmodule Pleroma.Web.ApiSpec.UserImportOperation do
def mutes_operation do
%Operation{
- tags: ["mutes_import"],
- summary: "Imports your mutes.",
+ tags: ["Data import"],
+ summary: "Import mutes",
operationId: "UserImportController.mutes",
requestBody: request_body("Parameters", import_request(), required: true),
responses: %{
diff --git a/lib/pleroma/web/api_spec/render_error.ex b/lib/pleroma/web/api_spec/render_error.ex
index d476b8ef3..3539af6e4 100644
--- a/lib/pleroma/web/api_spec/render_error.ex
+++ b/lib/pleroma/web/api_spec/render_error.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.RenderError do
diff --git a/lib/pleroma/web/api_spec/schemas/account.ex b/lib/pleroma/web/api_spec/schemas/account.ex
index 70437003c..8aeb821a8 100644
--- a/lib/pleroma/web/api_spec/schemas/account.ex
+++ b/lib/pleroma/web/api_spec/schemas/account.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.Schemas.Account do
@@ -33,6 +33,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
header: %Schema{type: :string, format: :uri},
id: FlakeID,
locked: %Schema{type: :boolean},
+ mute_expires_at: %Schema{type: :string, format: "date-time", nullable: true},
note: %Schema{type: :string, format: :html},
statuses_count: %Schema{type: :integer},
url: %Schema{type: :string, format: :uri},
@@ -47,12 +48,14 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
description: "whether the user allows automatically follow moved following accounts"
},
background_image: %Schema{type: :string, nullable: true, format: :uri},
+ birthday: %Schema{type: :string, nullable: true, format: :date},
chat_token: %Schema{type: :string},
- confirmation_pending: %Schema{
+ is_confirmed: %Schema{
type: :boolean,
description:
"whether the user account is waiting on email confirmation to be activated"
},
+ show_birthday: %Schema{type: :boolean, nullable: true},
hide_favorites: %Schema{type: :boolean},
hide_followers_count: %Schema{
type: :boolean,
@@ -96,7 +99,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
hide_notification_contents: %Schema{type: :boolean}
}
},
- relationship: AccountRelationship,
+ relationship: %Schema{allOf: [AccountRelationship], nullable: true},
settings_store: %Schema{
type: :object,
description:
@@ -166,7 +169,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
"pleroma" => %{
"allow_following_move" => true,
"background_image" => nil,
- "confirmation_pending" => true,
+ "is_confirmed" => false,
"hide_favorites" => true,
"hide_followers" => false,
"hide_followers_count" => false,
@@ -194,13 +197,16 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
"id" => "9tKi3esbG7OQgZ2920",
"muting" => false,
"muting_notifications" => false,
+ "note" => "",
"requested" => false,
"showing_reblogs" => true,
- "subscribing" => false
+ "subscribing" => false,
+ "notifying" => false
},
"settings_store" => %{
"pleroma-fe" => %{}
- }
+ },
+ "birthday" => "2001-02-12"
},
"source" => %{
"fields" => [],
diff --git a/lib/pleroma/web/api_spec/schemas/account_field.ex b/lib/pleroma/web/api_spec/schemas/account_field.ex
index fa97073a0..93ba1b5a5 100644
--- a/lib/pleroma/web/api_spec/schemas/account_field.ex
+++ b/lib/pleroma/web/api_spec/schemas/account_field.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.Schemas.AccountField do
diff --git a/lib/pleroma/web/api_spec/schemas/account_relationship.ex b/lib/pleroma/web/api_spec/schemas/account_relationship.ex
index 8b982669e..68219a099 100644
--- a/lib/pleroma/web/api_spec/schemas/account_relationship.ex
+++ b/lib/pleroma/web/api_spec/schemas/account_relationship.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.Schemas.AccountRelationship do
@@ -10,7 +10,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.AccountRelationship do
OpenApiSpex.schema(%{
title: "AccountRelationship",
- description: "Response schema for relationship",
+ description: "Relationship between current account and requested account",
type: :object,
properties: %{
blocked_by: %Schema{type: :boolean},
@@ -22,9 +22,11 @@ defmodule Pleroma.Web.ApiSpec.Schemas.AccountRelationship do
id: FlakeID,
muting: %Schema{type: :boolean},
muting_notifications: %Schema{type: :boolean},
+ note: %Schema{type: :string},
requested: %Schema{type: :boolean},
showing_reblogs: %Schema{type: :boolean},
- subscribing: %Schema{type: :boolean}
+ subscribing: %Schema{type: :boolean},
+ notifying: %Schema{type: :boolean}
},
example: %{
"blocked_by" => false,
@@ -36,9 +38,11 @@ defmodule Pleroma.Web.ApiSpec.Schemas.AccountRelationship do
"id" => "9tKi3esbG7OQgZ2920",
"muting" => false,
"muting_notifications" => false,
+ "note" => "",
"requested" => false,
"showing_reblogs" => true,
- "subscribing" => false
+ "subscribing" => false,
+ "notifying" => false
}
})
end
diff --git a/lib/pleroma/web/api_spec/schemas/actor_type.ex b/lib/pleroma/web/api_spec/schemas/actor_type.ex
index ac9b46678..13b6b476b 100644
--- a/lib/pleroma/web/api_spec/schemas/actor_type.ex
+++ b/lib/pleroma/web/api_spec/schemas/actor_type.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.Schemas.ActorType do
diff --git a/lib/pleroma/web/api_spec/schemas/announcement.ex b/lib/pleroma/web/api_spec/schemas/announcement.ex
new file mode 100644
index 000000000..67d129ef6
--- /dev/null
+++ b/lib/pleroma/web/api_spec/schemas/announcement.ex
@@ -0,0 +1,45 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ApiSpec.Schemas.Announcement do
+ alias OpenApiSpex.Schema
+ alias Pleroma.Web.ApiSpec.Schemas.FlakeID
+
+ require OpenApiSpex
+
+ OpenApiSpex.schema(%{
+ title: "Announcement",
+ description: "Response schema for an announcement",
+ type: :object,
+ properties: %{
+ id: FlakeID,
+ content: %Schema{type: :string},
+ starts_at: %Schema{
+ type: :string,
+ format: "date-time",
+ nullable: true
+ },
+ ends_at: %Schema{
+ type: :string,
+ format: "date-time",
+ nullable: true
+ },
+ all_day: %Schema{type: :boolean},
+ published_at: %Schema{type: :string, format: "date-time"},
+ updated_at: %Schema{type: :string, format: "date-time"},
+ read: %Schema{type: :boolean},
+ mentions: %Schema{type: :array},
+ statuses: %Schema{type: :array},
+ tags: %Schema{type: :array},
+ emojis: %Schema{type: :array},
+ reactions: %Schema{type: :array},
+ pleroma: %Schema{
+ type: :object,
+ properties: %{
+ raw_content: %Schema{type: :string}
+ }
+ }
+ }
+ })
+end
diff --git a/lib/pleroma/web/api_spec/schemas/api_error.ex b/lib/pleroma/web/api_spec/schemas/api_error.ex
index 5815df94c..58a710761 100644
--- a/lib/pleroma/web/api_spec/schemas/api_error.ex
+++ b/lib/pleroma/web/api_spec/schemas/api_error.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.Schemas.ApiError do
diff --git a/lib/pleroma/web/api_spec/schemas/app.ex b/lib/pleroma/web/api_spec/schemas/app.ex
new file mode 100644
index 000000000..742413b33
--- /dev/null
+++ b/lib/pleroma/web/api_spec/schemas/app.ex
@@ -0,0 +1,33 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ApiSpec.Schemas.App do
+ alias OpenApiSpex.Schema
+
+ require OpenApiSpex
+
+ OpenApiSpex.schema(%{
+ title: "App",
+ description: "Response schema for an app",
+ type: :object,
+ properties: %{
+ id: %Schema{type: :string},
+ name: %Schema{type: :string},
+ client_id: %Schema{type: :string},
+ client_secret: %Schema{type: :string},
+ redirect_uri: %Schema{type: :string},
+ vapid_key: %Schema{type: :string},
+ website: %Schema{type: :string, nullable: true}
+ },
+ example: %{
+ "id" => "123",
+ "name" => "My App",
+ "client_id" => "TWhM-tNSuncnqN7DBJmoyeLnk6K3iJJ71KKXxgL1hPM",
+ "client_secret" => "ZEaFUFmF0umgBX1qKJDjaU99Q31lDkOU8NutzTOoliw",
+ "vapid_key" =>
+ "BCk-QqERU0q-CfYZjcuB6lnyyOYfJ2AifKqfeGIm7Z-HiTU5T9eTG5GxVA0_OH5mMlI4UkkDTpaZwozy0TzdZ2M=",
+ "website" => "https://myapp.com/"
+ }
+ })
+end
diff --git a/lib/pleroma/web/api_spec/schemas/attachment.ex b/lib/pleroma/web/api_spec/schemas/attachment.ex
index c6edf6d36..48634a14f 100644
--- a/lib/pleroma/web/api_spec/schemas/attachment.ex
+++ b/lib/pleroma/web/api_spec/schemas/attachment.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.Schemas.Attachment do
diff --git a/lib/pleroma/web/api_spec/schemas/boolean_like.ex b/lib/pleroma/web/api_spec/schemas/boolean_like.ex
index f3bfb74da..14f728eef 100644
--- a/lib/pleroma/web/api_spec/schemas/boolean_like.ex
+++ b/lib/pleroma/web/api_spec/schemas/boolean_like.ex
@@ -1,8 +1,9 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.Schemas.BooleanLike do
+ alias OpenApiSpex.Cast
alias OpenApiSpex.Schema
require OpenApiSpex
@@ -27,10 +28,13 @@ defmodule Pleroma.Web.ApiSpec.Schemas.BooleanLike do
%Schema{type: :boolean},
%Schema{type: :string},
%Schema{type: :integer}
- ]
+ ],
+ "x-validate": __MODULE__
})
- def after_cast(value, _schmea) do
- {:ok, Pleroma.Web.ControllerHelper.truthy_param?(value)}
+ def cast(%Cast{value: value} = context) do
+ context
+ |> Map.put(:value, Pleroma.Web.Utils.Params.truthy_param?(value))
+ |> Cast.ok()
end
end
diff --git a/lib/pleroma/web/api_spec/schemas/chat.ex b/lib/pleroma/web/api_spec/schemas/chat.ex
index 65f908e33..a07d12865 100644
--- a/lib/pleroma/web/api_spec/schemas/chat.ex
+++ b/lib/pleroma/web/api_spec/schemas/chat.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.Schemas.Chat do
@@ -23,7 +23,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Chat do
"account" => %{
"pleroma" => %{
"is_admin" => false,
- "confirmation_pending" => false,
+ "is_confirmed" => true,
"hide_followers_count" => false,
"is_moderator" => false,
"hide_favorites" => true,
diff --git a/lib/pleroma/web/api_spec/schemas/chat_message.ex b/lib/pleroma/web/api_spec/schemas/chat_message.ex
index 9d2799618..57f7890e5 100644
--- a/lib/pleroma/web/api_spec/schemas/chat_message.ex
+++ b/lib/pleroma/web/api_spec/schemas/chat_message.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.Schemas.ChatMessage do
@@ -52,7 +52,8 @@ defmodule Pleroma.Web.ApiSpec.Schemas.ChatMessage do
title: %Schema{type: :string, description: "Title of linked resource"},
description: %Schema{type: :string, description: "Description of preview"}
}
- }
+ },
+ unread: %Schema{type: :boolean, description: "Whether a message has been marked as read."}
},
example: %{
"account_id" => "someflakeid",
@@ -69,7 +70,8 @@ defmodule Pleroma.Web.ApiSpec.Schemas.ChatMessage do
}
],
"id" => "14",
- "attachment" => nil
+ "attachment" => nil,
+ "unread" => false
}
})
end
diff --git a/lib/pleroma/web/api_spec/schemas/conversation.ex b/lib/pleroma/web/api_spec/schemas/conversation.ex
index d8ff5ba26..f00a9733f 100644
--- a/lib/pleroma/web/api_spec/schemas/conversation.ex
+++ b/lib/pleroma/web/api_spec/schemas/conversation.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.Schemas.Conversation do
diff --git a/lib/pleroma/web/api_spec/schemas/emoji.ex b/lib/pleroma/web/api_spec/schemas/emoji.ex
index 26f35e648..936bbe1da 100644
--- a/lib/pleroma/web/api_spec/schemas/emoji.ex
+++ b/lib/pleroma/web/api_spec/schemas/emoji.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.Schemas.Emoji do
diff --git a/lib/pleroma/web/api_spec/schemas/flake_id.ex b/lib/pleroma/web/api_spec/schemas/flake_id.ex
index 3b5f6477a..4c3ec01e7 100644
--- a/lib/pleroma/web/api_spec/schemas/flake_id.ex
+++ b/lib/pleroma/web/api_spec/schemas/flake_id.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.Schemas.FlakeID do
diff --git a/lib/pleroma/web/api_spec/schemas/list.ex b/lib/pleroma/web/api_spec/schemas/list.ex
index b7d1685c9..e57de7917 100644
--- a/lib/pleroma/web/api_spec/schemas/list.ex
+++ b/lib/pleroma/web/api_spec/schemas/list.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.Schemas.List do
diff --git a/lib/pleroma/web/api_spec/schemas/poll.ex b/lib/pleroma/web/api_spec/schemas/poll.ex
index 0dfa60b97..91570582b 100644
--- a/lib/pleroma/web/api_spec/schemas/poll.ex
+++ b/lib/pleroma/web/api_spec/schemas/poll.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.Schemas.Poll do
diff --git a/lib/pleroma/web/api_spec/schemas/push_subscription.ex b/lib/pleroma/web/api_spec/schemas/push_subscription.ex
index cc91b95b8..a5466630b 100644
--- a/lib/pleroma/web/api_spec/schemas/push_subscription.ex
+++ b/lib/pleroma/web/api_spec/schemas/push_subscription.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.Schemas.PushSubscription do
diff --git a/lib/pleroma/web/api_spec/schemas/scheduled_status.ex b/lib/pleroma/web/api_spec/schemas/scheduled_status.ex
index addefa9d3..a1acda1e8 100644
--- a/lib/pleroma/web/api_spec/schemas/scheduled_status.ex
+++ b/lib/pleroma/web/api_spec/schemas/scheduled_status.ex
@@ -1,12 +1,12 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.Schemas.ScheduledStatus do
alias OpenApiSpex.Schema
alias Pleroma.Web.ApiSpec.Schemas.Attachment
- alias Pleroma.Web.ApiSpec.Schemas.Poll
alias Pleroma.Web.ApiSpec.Schemas.VisibilityScope
+ alias Pleroma.Web.ApiSpec.StatusOperation
require OpenApiSpex
@@ -29,8 +29,9 @@ defmodule Pleroma.Web.ApiSpec.Schemas.ScheduledStatus do
spoiler_text: %Schema{type: :string, nullable: true},
visibility: %Schema{allOf: [VisibilityScope], nullable: true},
scheduled_at: %Schema{type: :string, format: :"date-time", nullable: true},
- poll: %Schema{allOf: [Poll], nullable: true},
- in_reply_to_id: %Schema{type: :string, nullable: true}
+ poll: StatusOperation.poll_params(),
+ in_reply_to_id: %Schema{type: :string, nullable: true},
+ expires_in: %Schema{type: :integer, nullable: true}
}
}
},
@@ -46,7 +47,8 @@ defmodule Pleroma.Web.ApiSpec.Schemas.ScheduledStatus do
scheduled_at: nil,
poll: nil,
idempotency: nil,
- in_reply_to_id: nil
+ in_reply_to_id: nil,
+ expires_in: nil
},
media_attachments: [Attachment.schema().example]
}
diff --git a/lib/pleroma/web/api_spec/schemas/status.ex b/lib/pleroma/web/api_spec/schemas/status.ex
index e6890df2d..698f11794 100644
--- a/lib/pleroma/web/api_spec/schemas/status.ex
+++ b/lib/pleroma/web/api_spec/schemas/status.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.Schemas.Status do
@@ -23,9 +23,10 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do
application: %Schema{
description: "The application used to post this status",
type: :object,
+ nullable: true,
properties: %{
name: %Schema{type: :string},
- website: %Schema{type: :string, nullable: true, format: :uri}
+ website: %Schema{type: :string, format: :uri}
}
},
bookmarked: %Schema{type: :boolean, description: "Have you bookmarked this status?"},
@@ -72,6 +73,12 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do
format: "date-time",
description: "The date when this status was created"
},
+ edited_at: %Schema{
+ type: :string,
+ format: "date-time",
+ nullable: true,
+ description: "The date when this status was last edited"
+ },
emojis: %Schema{
type: :array,
items: Emoji,
@@ -141,9 +148,15 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do
description:
"A map consisting of alternate representations of the `content` property with the key being it's mimetype. Currently the only alternate representation supported is `text/plain`"
},
+ context: %Schema{
+ type: :string,
+ description: "The thread identifier the status is associated with"
+ },
conversation_id: %Schema{
type: :integer,
- description: "The ID of the AP context the status is associated with (if any)"
+ deprecated: true,
+ description:
+ "The ID of the AP context the status is associated with (if any); deprecated, please use `context` instead"
},
direct_conversation_id: %Schema{
type: :integer,
@@ -193,6 +206,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"
}
}
},
@@ -256,7 +276,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do
"note" => "Tester Number 6",
"pleroma" => %{
"background_image" => nil,
- "confirmation_pending" => false,
+ "is_confirmed" => true,
"hide_favorites" => true,
"hide_followers" => false,
"hide_followers_count" => false,
@@ -274,9 +294,11 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do
"id" => "9toJCsKN7SmSf3aj5c",
"muting" => false,
"muting_notifications" => false,
+ "note" => "",
"requested" => false,
"showing_reblogs" => true,
- "subscribing" => false
+ "subscribing" => false,
+ "notifying" => false
},
"skip_thread_containment" => false,
"tags" => []
@@ -291,7 +313,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do
"url" => "http://localhost:4001/users/nick6",
"username" => "nick6"
},
- "application" => %{"name" => "Web", "website" => nil},
+ "application" => nil,
"bookmarked" => false,
"card" => nil,
"content" => "foobar",
@@ -309,6 +331,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do
"pinned" => false,
"pleroma" => %{
"content" => %{"text/plain" => "foobar"},
+ "context" => "http://localhost:4001/objects/8b4c0c80-6a37-4d2a-b1b9-05a19e3875aa",
"conversation_id" => 345_972,
"direct_conversation_id" => nil,
"emoji_reactions" => [],
diff --git a/lib/pleroma/web/api_spec/schemas/tag.ex b/lib/pleroma/web/api_spec/schemas/tag.ex
index e693fb83e..66bf0ca71 100644
--- a/lib/pleroma/web/api_spec/schemas/tag.ex
+++ b/lib/pleroma/web/api_spec/schemas/tag.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.Schemas.Tag do
diff --git a/lib/pleroma/web/api_spec/schemas/visibility_scope.ex b/lib/pleroma/web/api_spec/schemas/visibility_scope.ex
index 633269a92..ecd247ba4 100644
--- a/lib/pleroma/web/api_spec/schemas/visibility_scope.ex
+++ b/lib/pleroma/web/api_spec/schemas/visibility_scope.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.Schemas.VisibilityScope do
diff --git a/lib/pleroma/web/auth/authenticator.ex b/lib/pleroma/web/auth/authenticator.ex
index b4db312fb..a0bd154db 100644
--- a/lib/pleroma/web/auth/authenticator.ex
+++ b/lib/pleroma/web/auth/authenticator.ex
@@ -1,70 +1,13 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Auth.Authenticator do
- alias Pleroma.Registration
- alias Pleroma.User
-
- def implementation do
- Pleroma.Config.get(
- Pleroma.Web.Auth.Authenticator,
- Pleroma.Web.Auth.PleromaAuthenticator
- )
- end
-
- @callback get_user(Plug.Conn.t()) :: {:ok, User.t()} | {:error, any()}
- def get_user(plug), do: implementation().get_user(plug)
-
- @callback create_from_registration(Plug.Conn.t(), Registration.t()) ::
+ @callback get_user(Plug.Conn.t()) :: {:ok, user :: struct()} | {:error, any()}
+ @callback create_from_registration(Plug.Conn.t(), registration :: struct()) ::
{:ok, User.t()} | {:error, any()}
- def create_from_registration(plug, registration),
- do: implementation().create_from_registration(plug, registration)
-
- @callback get_registration(Plug.Conn.t()) :: {:ok, Registration.t()} | {:error, any()}
- def get_registration(plug), do: implementation().get_registration(plug)
-
+ @callback get_registration(Plug.Conn.t()) :: {:ok, registration :: struct()} | {:error, any()}
@callback handle_error(Plug.Conn.t(), any()) :: any()
- def handle_error(plug, error),
- do: implementation().handle_error(plug, error)
-
@callback auth_template() :: String.t() | nil
- def auth_template do
- # Note: `config :pleroma, :auth_template, "..."` support is deprecated
- implementation().auth_template() ||
- Pleroma.Config.get([:auth, :auth_template], Pleroma.Config.get(:auth_template)) ||
- "show.html"
- end
-
@callback oauth_consumer_template() :: String.t() | nil
- def oauth_consumer_template do
- implementation().oauth_consumer_template() ||
- Pleroma.Config.get([:auth, :oauth_consumer_template], "consumer.html")
- end
-
- @doc "Gets user by nickname or email for auth."
- @spec fetch_user(String.t()) :: User.t() | nil
- def fetch_user(name) do
- User.get_by_nickname_or_email(name)
- end
-
- # Gets name and password from conn
- #
- @spec fetch_credentials(Plug.Conn.t() | map()) ::
- {:ok, {name :: any, password :: any}} | {:error, :invalid_credentials}
- def fetch_credentials(%Plug.Conn{params: params} = _),
- do: fetch_credentials(params)
-
- def fetch_credentials(params) do
- case params do
- %{"authorization" => %{"name" => name, "password" => password}} ->
- {:ok, {name, password}}
-
- %{"grant_type" => "password", "username" => name, "password" => password} ->
- {:ok, {name, password}}
-
- _ ->
- {:error, :invalid_credentials}
- end
- end
end
diff --git a/lib/pleroma/web/auth/helpers.ex b/lib/pleroma/web/auth/helpers.ex
new file mode 100644
index 000000000..02e1f39ab
--- /dev/null
+++ b/lib/pleroma/web/auth/helpers.ex
@@ -0,0 +1,33 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Auth.Helpers do
+ alias Pleroma.User
+
+ @doc "Gets user by nickname or email for auth."
+ @spec fetch_user(String.t()) :: User.t() | nil
+ def fetch_user(name) do
+ User.get_by_nickname_or_email(name)
+ end
+
+ # Gets name and password from conn
+ #
+ @spec fetch_credentials(Plug.Conn.t() | map()) ::
+ {:ok, {name :: any, password :: any}} | {:error, :invalid_credentials}
+ def fetch_credentials(%Plug.Conn{params: params} = _),
+ do: fetch_credentials(params)
+
+ def fetch_credentials(params) do
+ case params do
+ %{"authorization" => %{"name" => name, "password" => password}} ->
+ {:ok, {name, password}}
+
+ %{"grant_type" => "password", "username" => name, "password" => password} ->
+ {:ok, {name, password}}
+
+ _ ->
+ {:error, :invalid_credentials}
+ end
+ end
+end
diff --git a/lib/pleroma/web/auth/ldap_authenticator.ex b/lib/pleroma/web/auth/ldap_authenticator.ex
index 402ab428b..e8cd4491c 100644
--- a/lib/pleroma/web/auth/ldap_authenticator.ex
+++ b/lib/pleroma/web/auth/ldap_authenticator.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Auth.LDAPAuthenticator do
@@ -7,8 +7,7 @@ defmodule Pleroma.Web.Auth.LDAPAuthenticator do
require Logger
- import Pleroma.Web.Auth.Authenticator,
- only: [fetch_credentials: 1, fetch_user: 1]
+ import Pleroma.Web.Auth.Helpers, only: [fetch_credentials: 1, fetch_user: 1]
@behaviour Pleroma.Web.Auth.Authenticator
@base Pleroma.Web.Auth.PleromaAuthenticator
diff --git a/lib/pleroma/web/auth/pleroma_authenticator.ex b/lib/pleroma/web/auth/pleroma_authenticator.ex
index d6d2a8d06..09a58eb66 100644
--- a/lib/pleroma/web/auth/pleroma_authenticator.ex
+++ b/lib/pleroma/web/auth/pleroma_authenticator.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Auth.PleromaAuthenticator do
@@ -8,8 +8,7 @@ defmodule Pleroma.Web.Auth.PleromaAuthenticator do
alias Pleroma.User
alias Pleroma.Web.Plugs.AuthenticationPlug
- import Pleroma.Web.Auth.Authenticator,
- only: [fetch_credentials: 1, fetch_user: 1]
+ import Pleroma.Web.Auth.Helpers, only: [fetch_credentials: 1, fetch_user: 1]
@behaviour Pleroma.Web.Auth.Authenticator
@@ -84,7 +83,7 @@ defmodule Pleroma.Web.Auth.PleromaAuthenticator do
password_confirmation: random_password
},
external: true,
- need_confirmation: false
+ confirmed: true
)
|> Repo.insert(),
{:ok, _} <-
diff --git a/lib/pleroma/web/auth/totp_authenticator.ex b/lib/pleroma/web/auth/totp_authenticator.ex
index edc9871ea..4be3641fd 100644
--- a/lib/pleroma/web/auth/totp_authenticator.ex
+++ b/lib/pleroma/web/auth/totp_authenticator.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Auth.TOTPAuthenticator do
diff --git a/lib/pleroma/web/auth/wrapper_authenticator.ex b/lib/pleroma/web/auth/wrapper_authenticator.ex
new file mode 100644
index 000000000..a077cfa41
--- /dev/null
+++ b/lib/pleroma/web/auth/wrapper_authenticator.ex
@@ -0,0 +1,42 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Auth.WrapperAuthenticator do
+ @behaviour Pleroma.Web.Auth.Authenticator
+
+ defp implementation do
+ Pleroma.Config.get(
+ Pleroma.Web.Auth.Authenticator,
+ Pleroma.Web.Auth.PleromaAuthenticator
+ )
+ end
+
+ @impl true
+ def get_user(plug), do: implementation().get_user(plug)
+
+ @impl true
+ def create_from_registration(plug, registration),
+ do: implementation().create_from_registration(plug, registration)
+
+ @impl true
+ def get_registration(plug), do: implementation().get_registration(plug)
+
+ @impl true
+ def handle_error(plug, error),
+ do: implementation().handle_error(plug, error)
+
+ @impl true
+ def auth_template do
+ # Note: `config :pleroma, :auth_template, "..."` support is deprecated
+ implementation().auth_template() ||
+ Pleroma.Config.get([:auth, :auth_template], Pleroma.Config.get(:auth_template)) ||
+ "show.html"
+ end
+
+ @impl true
+ def oauth_consumer_template do
+ implementation().oauth_consumer_template() ||
+ Pleroma.Config.get([:auth, :oauth_consumer_template], "consumer.html")
+ end
+end
diff --git a/lib/pleroma/web/channels/user_socket.ex b/lib/pleroma/web/channels/user_socket.ex
index 306ef1916..0f61b80c3 100644
--- a/lib/pleroma/web/channels/user_socket.ex
+++ b/lib/pleroma/web/channels/user_socket.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.UserSocket do
@@ -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)}
diff --git a/lib/pleroma/web/common_api.ex b/lib/pleroma/web/common_api.ex
index e59254791..62ab6b69c 100644
--- a/lib/pleroma/web/common_api.ex
+++ b/lib/pleroma/web/common_api.ex
@@ -1,11 +1,12 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.CommonAPI do
alias Pleroma.Activity
alias Pleroma.Conversation.Participation
alias Pleroma.Formatter
+ alias Pleroma.ModerationLog
alias Pleroma.Object
alias Pleroma.ThreadMute
alias Pleroma.User
@@ -117,7 +118,8 @@ defmodule Pleroma.Web.CommonAPI do
def unfollow(follower, unfollowed) do
with {:ok, follower, _follow_activity} <- User.unfollow(follower, unfollowed),
{:ok, _activity} <- ActivityPub.unfollow(follower, unfollowed),
- {:ok, _subscription} <- User.unsubscribe(follower, unfollowed) do
+ {:ok, _subscription} <- User.unsubscribe(follower, unfollowed),
+ {:ok, _endorsement} <- User.unendorse(follower, unfollowed) do
{:ok, follower}
end
end
@@ -142,10 +144,25 @@ defmodule Pleroma.Web.CommonAPI do
with {_, %Activity{data: %{"object" => _, "type" => "Create"}} = activity} <-
{:find_activity, Activity.get_by_id(activity_id)},
{_, %Object{} = object, _} <-
- {:find_object, Object.normalize(activity, false), activity},
+ {:find_object, Object.normalize(activity, fetch: false), activity},
true <- User.superuser?(user) || user.ap_id == object.data["actor"],
{:ok, delete_data, _} <- Builder.delete(user, object.data["id"]),
{:ok, delete, _} <- Pipeline.common_pipeline(delete_data, local: true) do
+ if User.superuser?(user) and user.ap_id != object.data["actor"] do
+ action =
+ if object.data["type"] == "ChatMessage" do
+ "chat_message_delete"
+ else
+ "status_delete"
+ end
+
+ ModerationLog.insert_log(%{
+ action: action,
+ actor: user,
+ subject_id: activity_id
+ })
+ end
+
{:ok, delete}
else
{:find_activity, _} ->
@@ -173,7 +190,7 @@ defmodule Pleroma.Web.CommonAPI do
def repeat(id, user, params \\ %{}) do
with %Activity{data: %{"type" => "Create"}} = activity <- Activity.get_by_id(id),
- object = %Object{} <- Object.normalize(activity, false),
+ object = %Object{} <- Object.normalize(activity, fetch: false),
{_, nil} <- {:existing_announce, Utils.get_existing_announce(user.ap_id, object)},
public = public_announce?(object, params),
{:ok, announce, _} <- Builder.announce(user, object, public: public),
@@ -191,7 +208,7 @@ defmodule Pleroma.Web.CommonAPI do
def unrepeat(id, user) do
with {_, %Activity{data: %{"type" => "Create"}} = activity} <-
{:find_activity, Activity.get_by_id(id)},
- %Object{} = note <- Object.normalize(activity, false),
+ %Object{} = note <- Object.normalize(activity, fetch: false),
%Activity{} = announce <- Utils.get_existing_announce(user.ap_id, note),
{:ok, undo, _} <- Builder.undo(user, announce),
{:ok, activity, _} <- Pipeline.common_pipeline(undo, local: true) do
@@ -228,17 +245,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
@@ -253,7 +260,7 @@ defmodule Pleroma.Web.CommonAPI do
def unfavorite(id, user) do
with {_, %Activity{data: %{"type" => "Create"}} = activity} <-
{:find_activity, Activity.get_by_id(id)},
- %Object{} = note <- Object.normalize(activity, false),
+ %Object{} = note <- Object.normalize(activity, fetch: false),
%Activity{} = like <- Utils.get_existing_like(user.ap_id, note),
{:ok, undo, _} <- Builder.undo(user, like),
{:ok, activity, _} <- Pipeline.common_pipeline(undo, local: true) do
@@ -266,7 +273,7 @@ defmodule Pleroma.Web.CommonAPI do
def react_with_emoji(id, user, emoji) do
with %Activity{} = activity <- Activity.get_by_id(id),
- object <- Object.normalize(activity),
+ object <- Object.normalize(activity, fetch: false),
{:ok, emoji_react, _} <- Builder.emoji_react(user, object, emoji),
{:ok, activity, _} <- Pipeline.common_pipeline(emoji_react, local: true) do
{:ok, activity}
@@ -377,7 +384,7 @@ defmodule Pleroma.Web.CommonAPI do
def get_replied_to_visibility(nil), do: nil
def get_replied_to_visibility(activity) do
- with %Object{} = object <- Object.normalize(activity) do
+ with %Object{} = object <- Object.normalize(activity, fetch: false) do
Visibility.get_visibility(object)
end
end
@@ -411,29 +418,93 @@ 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
+ def update(user, orig_activity, changes) do
+ with orig_object <- Object.normalize(orig_activity),
+ {:ok, new_object} <- make_update_data(user, orig_object, changes),
+ {:ok, update_data, _} <- Builder.update(user, new_object),
+ {:ok, update, _} <- Pipeline.common_pipeline(update_data, local: true) do
+ {:ok, update}
+ else
+ _ -> {:error, nil}
+ end
+ end
+
+ defp make_update_data(user, orig_object, changes) do
+ kept_params = %{
+ visibility: Visibility.get_visibility(orig_object),
+ in_reply_to_id:
+ with replied_id when is_binary(replied_id) <- orig_object.data["inReplyTo"],
+ %Activity{id: activity_id} <- Activity.get_create_by_object_ap_id(replied_id) do
+ activity_id
+ else
+ _ -> nil
+ end
+ }
+
+ params = Map.merge(changes, kept_params)
+
+ with {:ok, draft} <- ActivityDraft.create(user, params) do
+ change =
+ Object.Updater.make_update_object_data(orig_object.data, draft.object, Utils.make_date())
+
+ {:ok, change}
+ else
+ _ -> {:error, nil}
+ end
+ end
+
+ @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
@@ -468,9 +539,7 @@ defmodule Pleroma.Web.CommonAPI do
else
{what, result} = error ->
Logger.warn(
- "CommonAPI.remove_mute/2 failed. #{what}: #{result}, user_id: #{user_id}, activity_id: #{
- activity_id
- }"
+ "CommonAPI.remove_mute/2 failed. #{what}: #{result}, user_id: #{user_id}, activity_id: #{activity_id}"
)
{:error, error}
diff --git a/lib/pleroma/web/common_api/activity_draft.ex b/lib/pleroma/web/common_api/activity_draft.ex
index aa2616d9e..9af635da8 100644
--- a/lib/pleroma/web/common_api/activity_draft.ex
+++ b/lib/pleroma/web/common_api/activity_draft.ex
@@ -1,10 +1,12 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.CommonAPI.ActivityDraft do
alias Pleroma.Activity
alias Pleroma.Conversation.Participation
+ alias Pleroma.Object
+ alias Pleroma.Web.ActivityPub.Builder
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.CommonAPI.Utils
@@ -110,7 +112,12 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do
defp attachments(%{params: params} = draft) do
attachments = Utils.attachments_from_ids(params)
- %__MODULE__{draft | attachments: attachments}
+ draft = %__MODULE__{draft | attachments: attachments}
+
+ case Utils.validate_attachments_count(attachments) do
+ :ok -> draft
+ {:error, message} -> add_error(draft, message)
+ end
end
defp in_reply_to(%{params: %{in_reply_to_status_id: ""}} = draft), do: draft
@@ -179,23 +186,55 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do
end
defp sensitive(draft) do
- sensitive = draft.params[:sensitive] || Enum.member?(draft.tags, {"#nsfw", "nsfw"})
+ sensitive = draft.params[:sensitive]
%__MODULE__{draft | sensitive: sensitive}
end
defp object(draft) do
emoji = Map.merge(Pleroma.Emoji.Formatter.get_emoji_map(draft.full_payload), draft.emoji)
+ # Sometimes people create posts with subject containing emoji,
+ # since subjects are usually copied this will result in a broken
+ # subject when someone replies from an instance that does not have
+ # the emoji or has it under different shortcode. This is an attempt
+ # to mitigate this by copying emoji from inReplyTo if they are present
+ # in the subject.
+ summary_emoji =
+ with %Activity{} <- draft.in_reply_to,
+ %Object{data: %{"tag" => [_ | _] = tag}} <- Object.normalize(draft.in_reply_to) do
+ Enum.reduce(tag, %{}, fn
+ %{"type" => "Emoji", "name" => name, "icon" => %{"url" => url}}, acc ->
+ if String.contains?(draft.summary, name) do
+ Map.put(acc, name, url)
+ else
+ acc
+ end
+
+ _, acc ->
+ acc
+ end)
+ else
+ _ -> %{}
+ end
+
+ emoji = Map.merge(emoji, summary_emoji)
+
+ {:ok, note_data, _meta} = Builder.note(draft)
+
object =
- Utils.make_note_data(draft)
+ note_data
|> Map.put("emoji", emoji)
- |> Map.put("source", draft.status)
+ |> Map.put("source", %{
+ "content" => draft.status,
+ "mediaType" => Utils.get_content_type(draft.params[:content_type])
+ })
+ |> Map.put("generator", draft.params[:generator])
%__MODULE__{draft | object: object}
end
defp preview?(draft) do
- preview? = Pleroma.Web.ControllerHelper.truthy_param?(draft.params[:preview])
+ preview? = Pleroma.Web.Utils.Params.truthy_param?(draft.params[:preview])
%__MODULE__{draft | preview?: preview?}
end
diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex
index 1c74ea787..ff0814329 100644
--- a/lib/pleroma/web/common_api/utils.ex
+++ b/lib/pleroma/web/common_api/utils.ex
@@ -1,10 +1,9 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.CommonAPI.Utils do
import Pleroma.Web.Gettext
- import Pleroma.Web.ControllerHelper, only: [truthy_param?: 1]
alias Calendar.Strftime
alias Pleroma.Activity
@@ -19,6 +18,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do
alias Pleroma.Web.CommonAPI.ActivityDraft
alias Pleroma.Web.MediaProxy
alias Pleroma.Web.Plugs.AuthenticationPlug
+ alias Pleroma.Web.Utils.Params
require Logger
require Pleroma.Constants
@@ -37,7 +37,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do
def attachments_from_ids_no_descs(ids) do
Enum.map(ids, fn media_id ->
- case Repo.get(Object, media_id) do
+ case get_attachment(media_id) do
%Object{data: data} -> data
_ -> nil
end
@@ -51,13 +51,17 @@ defmodule Pleroma.Web.CommonAPI.Utils do
{_, descs} = Jason.decode(descs_str)
Enum.map(ids, fn media_id ->
- with %Object{data: data} <- Repo.get(Object, media_id) do
+ with %Object{data: data} <- get_attachment(media_id) do
Map.put(data, "name", descs[media_id])
end
end)
|> Enum.reject(&is_nil/1)
end
+ defp get_attachment(media_id) do
+ Repo.get(Object, media_id)
+ end
+
@spec get_to_and_cc(ActivityDraft.t()) :: {list(String.t()), list(String.t())}
def get_to_and_cc(%{in_reply_to_conversation: %Participation{} = participation}) do
@@ -69,7 +73,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]
@@ -160,7 +164,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do
|> DateTime.add(expires_in)
|> DateTime.to_iso8601()
- key = if truthy_param?(data.poll[:multiple]), do: "anyOf", else: "oneOf"
+ key = if Params.truthy_param?(data.poll[:multiple]), do: "anyOf", else: "oneOf"
poll = %{"type" => "Question", key => option_notes, "closed" => end_time}
{:ok, {poll, emoji}}
@@ -203,7 +207,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do
attachment_links =
draft.params
|> Map.get("attachment_links", Config.get([:instance, :attachment_links]))
- |> truthy_param?()
+ |> Params.truthy_param?()
content_type = get_content_type(draft.params[:content_type])
@@ -217,10 +221,9 @@ defmodule Pleroma.Web.CommonAPI.Utils do
draft.status
|> format_input(content_type, options)
|> maybe_add_attachments(draft.attachments, attachment_links)
- |> maybe_add_nsfw_tag(draft.params)
end
- defp get_content_type(content_type) do
+ def get_content_type(content_type) do
if Enum.member?(Config.get([:instance, :allowed_post_formats]), content_type) do
content_type
else
@@ -228,13 +231,6 @@ defmodule Pleroma.Web.CommonAPI.Utils do
end
end
- defp maybe_add_nsfw_tag({text, mentions, tags}, %{"sensitive" => sensitive})
- when sensitive in [true, "True", "true", "1"] do
- {text, mentions, [{"#nsfw", "nsfw"} | tags]}
- end
-
- defp maybe_add_nsfw_tag(data, _), do: data
-
def make_context(_, %Participation{} = participation) do
Repo.preload(participation, :conversation).conversation.ap_id
end
@@ -294,38 +290,11 @@ 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
- def make_note_data(%ActivityDraft{} = draft) do
- %{
- "type" => "Note",
- "to" => draft.to,
- "cc" => draft.cc,
- "content" => draft.content_html,
- "summary" => draft.summary,
- "sensitive" => draft.sensitive,
- "context" => draft.context,
- "attachment" => draft.attachments,
- "actor" => draft.user.ap_id,
- "tag" => Keyword.values(draft.tags) |> Enum.uniq()
- }
- |> add_in_reply_to(draft.in_reply_to)
- |> Map.merge(draft.extra)
- end
-
- defp add_in_reply_to(object, nil), do: object
-
- defp add_in_reply_to(object, in_reply_to) do
- with %Object{} = in_reply_to_object <- Object.normalize(in_reply_to) do
- Map.put(object, "inReplyTo", in_reply_to_object.data["id"])
- else
- _ -> object
- end
- end
-
def format_naive_asctime(date) do
date |> DateTime.from_naive!("Etc/UTC") |> format_asctime
end
@@ -399,7 +368,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do
%Activity{data: %{"to" => _to, "type" => type} = data} = activity
)
when type == "Create" do
- object = Object.normalize(activity, false)
+ object = Object.normalize(activity, fetch: false)
object_data =
cond do
@@ -420,19 +389,14 @@ defmodule Pleroma.Web.CommonAPI.Utils do
def maybe_notify_mentioned_recipients(recipients, _), do: recipients
- # Do not notify subscribers if author is making a reply
- def maybe_notify_subscribers(recipients, %Activity{
- object: %Object{data: %{"inReplyTo" => _ap_id}}
- }) do
- recipients
- end
-
def maybe_notify_subscribers(
recipients,
- %Activity{data: %{"actor" => actor, "type" => type}} = activity
- )
- when type == "Create" do
- with %User{} = user <- User.get_cached_by_ap_id(actor) do
+ %Activity{data: %{"actor" => actor, "type" => "Create"}} = activity
+ ) do
+ # Do not notify subscribers if author is making a reply
+ with %Object{data: object} <- Object.normalize(activity, fetch: false),
+ nil <- object["inReplyTo"],
+ %User{} = user <- User.get_cached_by_ap_id(actor) do
subscriber_ids =
user
|> User.subscriber_users()
@@ -489,35 +453,6 @@ defmodule Pleroma.Web.CommonAPI.Utils do
def get_report_statuses(_, _), do: {:ok, nil}
- # DEPRECATED mostly, context objects are now created at insertion time.
- def context_to_conversation_id(context) do
- with %Object{id: id} <- Object.get_cached_by_ap_id(context) do
- id
- else
- _e ->
- changeset = Object.context_mapping(context)
-
- case Repo.insert(changeset) do
- {:ok, %{id: id}} ->
- id
-
- # This should be solved by an upsert, but it seems ecto
- # has problems accessing the constraint inside the jsonb.
- {:error, _} ->
- Object.get_cached_by_ap_id(context).id
- end
- end
- end
-
- def conversation_id_to_context(id) do
- with %Object{data: %{"id" => context}} <- Repo.get(Object, id) do
- context
- else
- _e ->
- {:error, dgettext("errors", "No such conversation")}
- end
- end
-
def validate_character_limit("" = _full_payload, [] = _attachments) do
{:error, dgettext("errors", "Cannot post an empty status without attachments")}
end
@@ -532,4 +467,19 @@ defmodule Pleroma.Web.CommonAPI.Utils do
{:error, dgettext("errors", "The status is over the character limit")}
end
end
+
+ def validate_attachments_count([] = _attachments) do
+ :ok
+ end
+
+ def validate_attachments_count(attachments) do
+ limit = Config.get([:instance, :max_media_attachments])
+ count = length(attachments)
+
+ if count <= limit do
+ :ok
+ else
+ {:error, dgettext("errors", "Too many attachments")}
+ end
+ end
end
diff --git a/lib/pleroma/web/controller_helper.ex b/lib/pleroma/web/controller_helper.ex
index 69188a882..0c7fc17f4 100644
--- a/lib/pleroma/web/controller_helper.ex
+++ b/lib/pleroma/web/controller_helper.ex
@@ -1,22 +1,12 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ControllerHelper do
use Pleroma.Web, :controller
alias Pleroma.Pagination
-
- # As in Mastodon API, per https://api.rubyonrails.org/classes/ActiveModel/Type/Boolean.html
- @falsy_param_values [false, 0, "0", "f", "F", "false", "False", "FALSE", "off", "OFF"]
-
- def explicitly_falsy_param?(value), do: value in @falsy_param_values
-
- # Note: `nil` and `""` are considered falsy values in Pleroma
- def falsy_param?(value),
- do: explicitly_falsy_param?(value) or value in [nil, ""]
-
- def truthy_param?(value), do: not falsy_param?(value)
+ alias Pleroma.Web.Utils.Params
def json_response(conn, status, _) when status in [204, :no_content] do
conn
@@ -67,7 +57,7 @@ defmodule Pleroma.Web.ControllerHelper do
defp build_pagination_fields(conn, min_id, max_id, extra_params) do
params =
conn.params
- |> Map.drop(Map.keys(conn.path_params))
+ |> Map.drop(Map.keys(conn.path_params) |> Enum.map(&String.to_existing_atom/1))
|> Map.merge(extra_params)
|> Map.drop(@id_keys)
@@ -123,6 +113,6 @@ defmodule Pleroma.Web.ControllerHelper do
# To do once OpenAPI transition mess is over: just `truthy_param?(params[:with_relationships])`
params
|> Map.get(:with_relationships, params["with_relationships"])
- |> truthy_param?()
+ |> Params.truthy_param?()
end
end
diff --git a/lib/pleroma/web/embed_controller.ex b/lib/pleroma/web/embed_controller.ex
index f6b8a5ee1..8b9f0a051 100644
--- a/lib/pleroma/web/embed_controller.ex
+++ b/lib/pleroma/web/embed_controller.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.EmbedController do
@@ -31,7 +31,7 @@ defmodule Pleroma.Web.EmbedController do
end
defp get_counts(%Activity{} = activity) do
- %Object{data: data} = Object.normalize(activity)
+ %Object{data: data} = Object.normalize(activity, fetch: false)
%{
likes: Map.get(data, "like_count", 0),
diff --git a/lib/pleroma/web/endpoint.ex b/lib/pleroma/web/endpoint.ex
index f26542e88..d8d40cceb 100644
--- a/lib/pleroma/web/endpoint.ex
+++ b/lib/pleroma/web/endpoint.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Endpoint do
@@ -10,6 +10,7 @@ defmodule Pleroma.Web.Endpoint do
alias Pleroma.Config
socket("/socket", Pleroma.Web.UserSocket)
+ socket("/live", Phoenix.LiveView.Socket)
plug(Plug.Telemetry, event_prefix: [:phoenix, :endpoint])
@@ -23,6 +24,18 @@ defmodule Pleroma.Web.Endpoint do
# InstanceStatic needs to be before Plug.Static to be able to override shipped-static files
# If you're adding new paths to `only:` you'll need to configure them in InstanceStatic as well
# Cache-control headers are duplicated in case we turn off etags in the future
+ plug(
+ Pleroma.Web.Plugs.InstanceStatic,
+ at: "/",
+ from: :pleroma,
+ only: ["emoji", "images"],
+ gzip: true,
+ cache_control_for_etags: "public, max-age=1209600",
+ headers: %{
+ "cache-control" => "public, max-age=1209600"
+ }
+ )
+
plug(Pleroma.Web.Plugs.InstanceStatic,
at: "/",
gzip: true,
diff --git a/lib/pleroma/web/fallback/legacy_pleroma_api_rerouter_plug.ex b/lib/pleroma/web/fallback/legacy_pleroma_api_rerouter_plug.ex
new file mode 100644
index 000000000..6176f3d90
--- /dev/null
+++ b/lib/pleroma/web/fallback/legacy_pleroma_api_rerouter_plug.ex
@@ -0,0 +1,26 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Fallback.LegacyPleromaApiRerouterPlug do
+ alias Pleroma.Web.Endpoint
+ alias Pleroma.Web.Fallback.RedirectController
+
+ def init(opts), do: opts
+
+ def call(%{path_info: ["api", "pleroma" | path_info_rest]} = conn, _opts) do
+ new_path_info = ["api", "v1", "pleroma" | path_info_rest]
+ new_request_path = Enum.join(new_path_info, "/")
+
+ conn
+ |> Map.merge(%{
+ path_info: new_path_info,
+ request_path: new_request_path
+ })
+ |> Endpoint.call(conn.params)
+ end
+
+ def call(conn, _opts) do
+ RedirectController.api_not_implemented(conn, %{})
+ end
+end
diff --git a/lib/pleroma/web/fallback/redirect_controller.ex b/lib/pleroma/web/fallback/redirect_controller.ex
index 1ac1319f8..1a86f7a53 100644
--- a/lib/pleroma/web/fallback/redirect_controller.ex
+++ b/lib/pleroma/web/fallback/redirect_controller.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Fallback.RedirectController do
diff --git a/lib/pleroma/web/federator.ex b/lib/pleroma/web/federator.ex
index 658d20954..318b6cb11 100644
--- a/lib/pleroma/web/federator.ex
+++ b/lib/pleroma/web/federator.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Federator do
@@ -47,10 +47,15 @@ defmodule Pleroma.Web.Federator do
end
@impl true
- def publish(activity) do
- PublisherWorker.enqueue("publish", %{"activity_id" => activity.id})
+ def publish(%Pleroma.Activity{data: %{"type" => type}} = activity) do
+ PublisherWorker.enqueue("publish", %{"activity_id" => activity.id},
+ priority: publish_priority(type)
+ )
end
+ defp publish_priority("Delete"), do: 3
+ defp publish_priority(_), do: 0
+
# Job Worker Callbacks
@spec perform(atom(), module(), any()) :: {:ok, any()} | {:error, any()}
@@ -61,10 +66,8 @@ defmodule Pleroma.Web.Federator do
def perform(:publish, activity) do
Logger.debug(fn -> "Running publish for #{activity.data["id"]}" end)
- with %User{} = actor <- User.get_cached_by_ap_id(activity.data["actor"]),
- {:ok, actor} <- User.ensure_keys_present(actor) do
- Publisher.publish(actor, activity)
- end
+ %User{} = actor = User.get_cached_by_ap_id(activity.data["actor"])
+ Publisher.publish(actor, activity)
end
def perform(:incoming_ap_doc, params) do
@@ -96,6 +99,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)
diff --git a/lib/pleroma/web/federator/publisher.ex b/lib/pleroma/web/federator/publisher.ex
index ad0201361..a45796e9d 100644
--- a/lib/pleroma/web/federator/publisher.ex
+++ b/lib/pleroma/web/federator/publisher.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Federator.Publisher do
diff --git a/lib/pleroma/web/federator/publishing.ex b/lib/pleroma/web/federator/publishing.ex
index d6fba8f24..3a242b8dc 100644
--- a/lib/pleroma/web/federator/publishing.ex
+++ b/lib/pleroma/web/federator/publishing.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Federator.Publishing do
diff --git a/lib/pleroma/web/feed/feed_view.ex b/lib/pleroma/web/feed/feed_view.ex
index 30e0a2a55..449659f4b 100644
--- a/lib/pleroma/web/feed/feed_view.ex
+++ b/lib/pleroma/web/feed/feed_view.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Feed.FeedView do
@@ -9,21 +9,16 @@ defmodule Pleroma.Web.Feed.FeedView do
alias Pleroma.Formatter
alias Pleroma.Object
alias Pleroma.User
+ alias Pleroma.Web.Gettext
alias Pleroma.Web.MediaProxy
require Pleroma.Constants
- @spec pub_date(String.t() | DateTime.t()) :: String.t()
- def pub_date(date) when is_binary(date) do
- date
- |> Timex.parse!("{ISO:Extended}")
- |> pub_date
- end
-
- def pub_date(%DateTime{} = date), do: Timex.format!(date, "{RFC822}")
+ @days ~w(Mon Tue Wed Thu Fri Sat Sun)
+ @months ~w(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec)
def prepare_activity(activity, opts \\ []) do
- object = Object.normalize(activity)
+ object = Object.normalize(activity, fetch: false)
actor =
if opts[:actor] do
@@ -32,6 +27,7 @@ defmodule Pleroma.Web.Feed.FeedView do
%{
activity: activity,
+ object: object,
data: Map.get(object, :data),
actor: actor
}
@@ -39,26 +35,35 @@ defmodule Pleroma.Web.Feed.FeedView do
def most_recent_update(activities) do
with %{updated_at: updated_at} <- List.first(activities) do
- NaiveDateTime.to_iso8601(updated_at)
+ to_rfc3339(updated_at)
end
end
- def most_recent_update(activities, user) do
+ def most_recent_update(activities, user, :atom) do
(List.first(activities) || user).updated_at
- |> NaiveDateTime.to_iso8601()
+ |> to_rfc3339()
+ end
+
+ def most_recent_update(activities, user, :rss) do
+ (List.first(activities) || user).updated_at
+ |> to_rfc2822()
end
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
+ def email(user) do
+ user.nickname <> "@" <> Pleroma.Web.Endpoint.host()
+ end
+
def logo(user) do
user
|> User.avatar_url()
@@ -67,18 +72,34 @@ defmodule Pleroma.Web.Feed.FeedView do
def last_activity(activities), do: List.last(activities)
- def activity_title(%{"content" => content}, opts \\ %{}) do
- content
+ def activity_title(%{"content" => content, "summary" => summary} = data, opts \\ %{}) do
+ title =
+ cond do
+ summary != "" -> summary
+ content != "" -> activity_content(data)
+ true -> "a post"
+ end
+
+ title
|> Pleroma.Web.Metadata.Utils.scrub_html()
|> Pleroma.Emoji.Formatter.demojify()
|> Formatter.truncate(opts[:max_length], opts[:omission])
- |> escape()
+ end
+
+ def activity_description(data) do
+ content = activity_content(data)
+ summary = data["summary"]
+
+ cond do
+ content != "" -> escape(content)
+ summary != "" -> escape(summary)
+ true -> escape(data["type"])
+ end
end
def activity_content(%{"content" => content}) do
content
|> String.replace(~r/[\n\r]/, "")
- |> escape()
end
def activity_content(_), do: ""
@@ -110,4 +131,60 @@ defmodule Pleroma.Web.Feed.FeedView do
|> html_escape()
|> safe_to_string()
end
+
+ @spec to_rfc3339(String.t() | NativeDateTime.t()) :: String.t()
+ def to_rfc3339(date) when is_binary(date) do
+ date
+ |> Timex.parse!("{ISO:Extended}")
+ |> to_rfc3339()
+ end
+
+ def to_rfc3339(nd) do
+ nd
+ |> Timex.to_datetime()
+ |> Timex.format!("{RFC3339}")
+ end
+
+ @spec to_rfc2822(String.t() | DateTime.t() | NativeDateTime.t()) :: String.t()
+ def to_rfc2822(datestr) when is_binary(datestr) do
+ datestr
+ |> Timex.parse!("{ISO:Extended}")
+ |> to_rfc2822()
+ end
+
+ def to_rfc2822(%DateTime{} = date) do
+ date
+ |> DateTime.to_naive()
+ |> NaiveDateTime.to_erl()
+ |> rfc2822_from_erl()
+ end
+
+ def to_rfc2822(nd) do
+ nd
+ |> Timex.to_datetime()
+ |> DateTime.to_naive()
+ |> NaiveDateTime.to_erl()
+ |> rfc2822_from_erl()
+ end
+
+ @doc """
+ Builds a RFC2822 timestamp from an Erlang timestamp
+ [RFC2822 3.3 - Date and Time Specification](https://tools.ietf.org/html/rfc2822#section-3.3)
+ This function always assumes the Erlang timestamp is in Universal time, not Local time
+ """
+ def rfc2822_from_erl({{year, month, day} = date, {hour, minute, second}}) do
+ day_name = Enum.at(@days, :calendar.day_of_the_week(date) - 1)
+ month_name = Enum.at(@months, month - 1)
+
+ date_part = "#{day_name}, #{day} #{month_name} #{year}"
+ time_part = "#{pad(hour)}:#{pad(minute)}:#{pad(second)}"
+
+ date_part <> " " <> time_part <> " +0000"
+ end
+
+ defp pad(num) do
+ num
+ |> Integer.to_string()
+ |> String.pad_leading(2, "0")
+ end
end
diff --git a/lib/pleroma/web/feed/tag_controller.ex b/lib/pleroma/web/feed/tag_controller.ex
index 218cdbdf3..e60767327 100644
--- a/lib/pleroma/web/feed/tag_controller.ex
+++ b/lib/pleroma/web/feed/tag_controller.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Feed.TagController do
diff --git a/lib/pleroma/web/feed/user_controller.ex b/lib/pleroma/web/feed/user_controller.ex
index a5013d2c0..6657c2b3e 100644
--- a/lib/pleroma/web/feed/user_controller.ex
+++ b/lib/pleroma/web/feed/user_controller.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Feed.UserController do
@@ -18,6 +18,8 @@ defmodule Pleroma.Web.Feed.UserController do
def feed_redirect(%{assigns: %{format: "html"}} = conn, %{"nickname" => nickname}) do
with {_, %User{} = user} <- {:fetch_user, User.get_cached_by_nickname_or_id(nickname)} do
Pleroma.Web.Fallback.RedirectController.redirector_with_meta(conn, %{user: user})
+ else
+ _ -> Pleroma.Web.Fallback.RedirectController.redirector(conn, nil)
end
end
@@ -28,7 +30,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/gettext.ex b/lib/pleroma/web/gettext.ex
index 0adf428ec..5ef49d841 100644
--- a/lib/pleroma/web/gettext.ex
+++ b/lib/pleroma/web/gettext.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Gettext do
@@ -25,4 +25,196 @@ defmodule Pleroma.Web.Gettext do
See the [Gettext Docs](https://hexdocs.pm/gettext) for detailed usage.
"""
use Gettext, otp_app: :pleroma
+
+ def language_tag do
+ # Naive implementation: HTML lang attribute uses BCP 47, which
+ # uses - as a separator.
+ # https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/lang
+
+ Gettext.get_locale()
+ |> String.replace("_", "-", global: true)
+ end
+
+ def normalize_locale(locale) do
+ if is_binary(locale) do
+ String.replace(locale, "-", "_", global: true)
+ else
+ nil
+ end
+ end
+
+ def supports_locale?(locale) do
+ Pleroma.Web.Gettext
+ |> Gettext.known_locales()
+ |> Enum.member?(locale)
+ end
+
+ def variant?(locale), do: String.contains?(locale, "_")
+
+ def language_for_variant(locale) do
+ Enum.at(String.split(locale, "_"), 0)
+ end
+
+ def ensure_fallbacks(locales) do
+ locales
+ |> Enum.flat_map(fn locale ->
+ others =
+ other_supported_variants_of_locale(locale)
+ |> Enum.filter(fn l -> not Enum.member?(locales, l) end)
+
+ [locale] ++ others
+ end)
+ end
+
+ def other_supported_variants_of_locale(locale) do
+ cond do
+ supports_locale?(locale) ->
+ []
+
+ variant?(locale) ->
+ lang = language_for_variant(locale)
+ if supports_locale?(lang), do: [lang], else: []
+
+ true ->
+ Gettext.known_locales(Pleroma.Web.Gettext)
+ |> Enum.filter(fn l -> String.starts_with?(l, locale <> "_") end)
+ end
+ end
+
+ def get_locales do
+ Process.get({Pleroma.Web.Gettext, :locales}, [])
+ end
+
+ def is_locale_list(locales) do
+ Enum.all?(locales, &is_binary/1)
+ end
+
+ def put_locales(locales) do
+ if is_locale_list(locales) do
+ Process.put({Pleroma.Web.Gettext, :locales}, Enum.uniq(locales))
+ Gettext.put_locale(Enum.at(locales, 0, Gettext.get_locale()))
+ :ok
+ else
+ {:error, :not_locale_list}
+ end
+ end
+
+ def locale_or_default(locale) do
+ if supports_locale?(locale) do
+ locale
+ else
+ Gettext.get_locale()
+ end
+ end
+
+ def with_locales_func(locales, fun) do
+ prev_locales = Process.get({Pleroma.Web.Gettext, :locales})
+ put_locales(locales)
+
+ try do
+ fun.()
+ after
+ if prev_locales do
+ put_locales(prev_locales)
+ else
+ Process.delete({Pleroma.Web.Gettext, :locales})
+ Process.delete(Gettext)
+ end
+ end
+ end
+
+ defmacro with_locales(locales, do: fun) do
+ quote do
+ Pleroma.Web.Gettext.with_locales_func(unquote(locales), fn ->
+ unquote(fun)
+ end)
+ end
+ end
+
+ def to_locale_list(locale) when is_binary(locale) do
+ locale
+ |> String.split(",")
+ |> Enum.filter(&supports_locale?/1)
+ end
+
+ def to_locale_list(_), do: []
+
+ defmacro with_locale_or_default(locale, do: fun) do
+ quote do
+ Pleroma.Web.Gettext.with_locales_func(
+ Pleroma.Web.Gettext.to_locale_list(unquote(locale))
+ |> Enum.concat(Pleroma.Web.Gettext.get_locales()),
+ fn ->
+ unquote(fun)
+ end
+ )
+ end
+ end
+
+ defp next_locale(locale, list) do
+ index = Enum.find_index(list, fn item -> item == locale end)
+
+ if not is_nil(index) do
+ Enum.at(list, index + 1)
+ else
+ nil
+ end
+ end
+
+ # We do not yet have a proper English translation. The "English"
+ # version is currently but the fallback msgid. However, this
+ # will not work if the user puts English as the first language,
+ # and at the same time specifies other languages, as gettext will
+ # think the English translation is missing, and call
+ # handle_missing_translation functions. This may result in
+ # text in other languages being shown even if English is preferred
+ # by the user.
+ #
+ # To prevent this, we do not allow fallbacking when the current
+ # locale missing a translation is English.
+ defp should_fallback?(locale) do
+ locale != "en"
+ end
+
+ def handle_missing_translation(locale, domain, msgctxt, msgid, bindings) do
+ next = next_locale(locale, get_locales())
+
+ if is_nil(next) or not should_fallback?(locale) do
+ super(locale, domain, msgctxt, msgid, bindings)
+ else
+ {:ok,
+ Gettext.with_locale(next, fn ->
+ Gettext.dpgettext(Pleroma.Web.Gettext, domain, msgctxt, msgid, bindings)
+ end)}
+ end
+ end
+
+ def handle_missing_plural_translation(
+ locale,
+ domain,
+ msgctxt,
+ msgid,
+ msgid_plural,
+ n,
+ bindings
+ ) do
+ next = next_locale(locale, get_locales())
+
+ if is_nil(next) or not should_fallback?(locale) do
+ super(locale, domain, msgctxt, msgid, msgid_plural, n, bindings)
+ else
+ {:ok,
+ Gettext.with_locale(next, fn ->
+ Gettext.dpngettext(
+ Pleroma.Web.Gettext,
+ domain,
+ msgctxt,
+ msgid,
+ msgid_plural,
+ n,
+ bindings
+ )
+ end)}
+ end
+ end
end
diff --git a/lib/pleroma/web/instance_document.ex b/lib/pleroma/web/instance_document.ex
index df5caebf0..9da3c5008 100644
--- a/lib/pleroma/web/instance_document.ex
+++ b/lib/pleroma/web/instance_document.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.InstanceDocument do
diff --git a/lib/pleroma/web/mailer/subscription_controller.ex b/lib/pleroma/web/mailer/subscription_controller.ex
index ace44afd1..f2fc8fbc8 100644
--- a/lib/pleroma/web/mailer/subscription_controller.ex
+++ b/lib/pleroma/web/mailer/subscription_controller.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Mailer.SubscriptionController do
diff --git a/lib/pleroma/web/manifest_controller.ex b/lib/pleroma/web/manifest_controller.ex
new file mode 100644
index 000000000..3b02e4bc0
--- /dev/null
+++ b/lib/pleroma/web/manifest_controller.ex
@@ -0,0 +1,14 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ManifestController do
+ use Pleroma.Web, :controller
+
+ plug(:skip_auth when action == :show)
+
+ @doc "GET /manifest.json"
+ def show(conn, _params) do
+ render(conn, "manifest.json")
+ end
+end
diff --git a/lib/pleroma/web/masto_fe_controller.ex b/lib/pleroma/web/masto_fe_controller.ex
deleted file mode 100644
index 20279ff45..000000000
--- a/lib/pleroma/web/masto_fe_controller.ex
+++ /dev/null
@@ -1,65 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.MastoFEController do
- use Pleroma.Web, :controller
-
- alias Pleroma.User
- alias Pleroma.Web.MastodonAPI.AuthController
- alias Pleroma.Web.OAuth.Token
- alias Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug
- alias Pleroma.Web.Plugs.OAuthScopesPlug
-
- plug(OAuthScopesPlug, %{scopes: ["write:accounts"]} when action == :put_settings)
-
- # Note: :index action handles attempt of unauthenticated access to private instance with redirect
- plug(:skip_plug, EnsurePublicOrAuthenticatedPlug when action == :index)
-
- plug(
- OAuthScopesPlug,
- %{scopes: ["read"], fallback: :proceed_unauthenticated}
- when action == :index
- )
-
- plug(
- :skip_plug,
- [OAuthScopesPlug, EnsurePublicOrAuthenticatedPlug] when action == :manifest
- )
-
- @doc "GET /web/*path"
- def index(conn, _params) do
- with %{assigns: %{user: %User{} = user, token: %Token{app_id: token_app_id} = token}} <- conn,
- {:ok, %{id: ^token_app_id}} <- AuthController.local_mastofe_app() do
- conn
- |> put_layout(false)
- |> render("index.html",
- token: token.token,
- user: user,
- custom_emojis: Pleroma.Emoji.get_all()
- )
- else
- _ ->
- conn
- |> put_session(:return_to, conn.request_path)
- |> redirect(to: "/web/login")
- end
- end
-
- @doc "GET /web/manifest.json"
- def manifest(conn, _params) do
- render(conn, "manifest.json")
- end
-
- @doc "PUT /api/web/settings: Backend-obscure settings blob for MastoFE, don't parse/reuse elsewhere"
- def put_settings(%{assigns: %{user: user}} = conn, %{"data" => settings} = _params) do
- with {:ok, _} <- User.mastodon_settings_update(user, settings) do
- json(conn, %{})
- else
- e ->
- conn
- |> put_status(:internal_server_error)
- |> json(%{error: inspect(e)})
- end
- end
-end
diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
index 3951d10ac..ea6e593d9 100644
--- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.AccountController do
@@ -8,7 +8,6 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
import Pleroma.Web.ControllerHelper,
only: [
add_link_headers: 2,
- truthy_param?: 1,
assign_account_by_id: 2,
embed_relationships?: 1,
json_response: 3
@@ -16,6 +15,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
alias Pleroma.Maps
alias Pleroma.User
+ alias Pleroma.UserNote
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Builder
alias Pleroma.Web.ActivityPub.Pipeline
@@ -25,16 +25,16 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
alias Pleroma.Web.MastodonAPI.MastodonAPIController
alias Pleroma.Web.MastodonAPI.StatusView
alias Pleroma.Web.OAuth.OAuthController
- alias Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug
alias Pleroma.Web.Plugs.OAuthScopesPlug
alias Pleroma.Web.Plugs.RateLimiter
alias Pleroma.Web.TwitterAPI.TwitterAPI
+ alias Pleroma.Web.Utils.Params
plug(Pleroma.Web.ApiSpec.CastAndValidate)
- plug(:skip_plug, [OAuthScopesPlug, EnsurePublicOrAuthenticatedPlug] when action == :create)
+ plug(:skip_auth when action in [:create, :lookup])
- plug(:skip_plug, EnsurePublicOrAuthenticatedPlug when action in [:show, :statuses])
+ plug(:skip_public_check when action in [:show, :statuses])
plug(
OAuthScopesPlug,
@@ -54,7 +54,11 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
when action in [:verify_credentials, :endorsements, :identity_proofs]
)
- plug(OAuthScopesPlug, %{scopes: ["write:accounts"]} when action == :update_credentials)
+ plug(
+ OAuthScopesPlug,
+ %{scopes: ["write:accounts"]}
+ when action in [:update_credentials, :note, :endorse, :unendorse]
+ )
plug(OAuthScopesPlug, %{scopes: ["read:lists"]} when action == :lists)
@@ -72,15 +76,19 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
plug(
OAuthScopesPlug,
- %{scopes: ["follow", "write:follows"]} when action in [:follow_by_uri, :follow, :unfollow]
+ %{scopes: ["follow", "write:follows"]}
+ when action in [:follow_by_uri, :follow, :unfollow, :remove_from_followers]
)
plug(OAuthScopesPlug, %{scopes: ["follow", "read:mutes"]} when action == :mutes)
plug(OAuthScopesPlug, %{scopes: ["follow", "write:mutes"]} when action in [:mute, :unmute])
- @relationship_actions [:follow, :unfollow]
- @needs_account ~W(followers following lists follow unfollow mute unmute block unblock)a
+ @relationship_actions [:follow, :unfollow, :remove_from_followers]
+ @needs_account ~W(
+ followers following lists follow unfollow mute unmute block unblock
+ note endorse unendorse remove_from_followers
+ )a
plug(
RateLimiter,
@@ -185,10 +193,11 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
:skip_thread_containment,
:allow_following_move,
:also_known_as,
- :accepts_chat_messages
+ :accepts_chat_messages,
+ :show_birthday
]
|> Enum.reduce(%{}, fn key, acc ->
- Maps.put_if_present(acc, key, params[key], &{:ok, truthy_param?(&1)})
+ Maps.put_if_present(acc, key, params[key], &{:ok, Params.truthy_param?(&1)})
end)
|> Maps.put_if_present(:name, params[:display_name])
|> Maps.put_if_present(:bio, params[:note])
@@ -213,6 +222,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
|> Maps.put_if_present(:is_locked, params[:locked])
# Note: param name is indeed :discoverable (not an error)
|> Maps.put_if_present(:is_discoverable, params[:discoverable])
+ |> Maps.put_if_present(:birthday, params[:birthday])
+ |> Maps.put_if_present(:language, Pleroma.Web.Gettext.normalize_locale(params[:language]))
# What happens here:
#
@@ -243,7 +254,17 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
with_pleroma_settings: true
)
else
- _e -> render_error(conn, :forbidden, "Invalid request")
+ {:error, %Ecto.Changeset{errors: [avatar: {"file is too large", _}]}} ->
+ render_error(conn, :request_entity_too_large, "File is too large")
+
+ {:error, %Ecto.Changeset{errors: [banner: {"file is too large", _}]}} ->
+ render_error(conn, :request_entity_too_large, "File is too large")
+
+ {:error, %Ecto.Changeset{errors: [background: {"file is too large", _}]}} ->
+ render_error(conn, :request_entity_too_large, "File is too large")
+
+ _e ->
+ render_error(conn, :forbidden, "Invalid request")
end
end
@@ -269,10 +290,14 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
def relationships(%{assigns: %{user: _user}} = conn, _), do: json(conn, [])
@doc "GET /api/v1/accounts/:id"
- def show(%{assigns: %{user: for_user}} = conn, %{id: nickname_or_id}) do
+ def show(%{assigns: %{user: for_user}} = conn, %{id: nickname_or_id} = params) do
with %User{} = user <- User.get_cached_by_nickname_or_id(nickname_or_id, for: for_user),
:visible <- User.visible_for(user, for_user) do
- render(conn, "show.json", user: user, for: for_user)
+ render(conn, "show.json",
+ user: user,
+ for: for_user,
+ embed_relationships: embed_relationships?(params)
+ )
else
error -> user_visibility_error(conn, error)
end
@@ -398,6 +423,10 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
@doc "POST /api/v1/accounts/:id/mute"
def mute(%{assigns: %{user: muter, account: muted}, body_params: params} = conn, _params) do
+ params =
+ params
+ |> Map.put_new(:duration, Map.get(params, :expires_in, 0))
+
with {:ok, _user_relationships} <- User.mute(muter, muted, params) do
render(conn, "relationship.json", user: muter, target: muted)
else
@@ -432,6 +461,48 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
end
end
+ @doc "POST /api/v1/accounts/:id/note"
+ def note(
+ %{assigns: %{user: noter, account: target}, body_params: %{comment: comment}} = conn,
+ _params
+ ) do
+ with {:ok, _user_note} <- UserNote.create(noter, target, comment) do
+ render(conn, "relationship.json", user: noter, target: target)
+ end
+ end
+
+ @doc "POST /api/v1/accounts/:id/pin"
+ def endorse(%{assigns: %{user: endorser, account: endorsed}} = conn, _params) do
+ with {:ok, _user_relationships} <- User.endorse(endorser, endorsed) do
+ render(conn, "relationship.json", user: endorser, target: endorsed)
+ else
+ {:error, message} -> json_response(conn, :bad_request, %{error: message})
+ end
+ end
+
+ @doc "POST /api/v1/accounts/:id/unpin"
+ def unendorse(%{assigns: %{user: endorser, account: endorsed}} = conn, _params) do
+ with {:ok, _user_relationships} <- User.unendorse(endorser, endorsed) do
+ render(conn, "relationship.json", user: endorser, target: endorsed)
+ else
+ {:error, message} -> json_response(conn, :forbidden, %{error: message})
+ end
+ end
+
+ @doc "POST /api/v1/accounts/:id/remove_from_followers"
+ def remove_from_followers(%{assigns: %{user: %{id: id}, account: %{id: id}}}, _params) do
+ {:error, "Can not unfollow yourself"}
+ end
+
+ def remove_from_followers(%{assigns: %{user: followed, account: follower}} = conn, _params) do
+ with {:ok, follower} <- CommonAPI.reject_follow_request(follower, followed) do
+ render(conn, "relationship.json", user: followed, target: follower)
+ else
+ nil ->
+ render_error(conn, :not_found, "Record not found")
+ end
+ end
+
@doc "POST /api/v1/follows"
def follow_by_uri(%{body_params: %{uri: uri}} = conn, _) do
case User.get_cached_by_nickname(uri) do
@@ -450,11 +521,17 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
users =
user
|> User.muted_users_relation(_restrict_deactivated = true)
- |> Pleroma.Pagination.fetch_paginated(Map.put(params, :skip_order, true))
+ |> Pleroma.Pagination.fetch_paginated(params)
conn
|> add_link_headers(users)
- |> render("index.json", users: users, for: user, as: :user)
+ |> render("index.json",
+ users: users,
+ for: user,
+ as: :user,
+ embed_relationships: embed_relationships?(params),
+ mutes: true
+ )
end
@doc "GET /api/v1/blocks"
@@ -462,15 +539,40 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
users =
user
|> User.blocked_users_relation(_restrict_deactivated = true)
- |> Pleroma.Pagination.fetch_paginated(Map.put(params, :skip_order, true))
+ |> Pleroma.Pagination.fetch_paginated(params)
conn
|> add_link_headers(users)
|> render("index.json", users: users, for: user, as: :user)
end
+ @doc "GET /api/v1/accounts/lookup"
+ def lookup(conn, %{acct: nickname} = _params) do
+ with %User{} = user <- User.get_by_nickname(nickname) do
+ render(conn, "show.json",
+ user: user,
+ skip_visibility_check: true
+ )
+ else
+ error -> user_visibility_error(conn, error)
+ end
+ end
+
@doc "GET /api/v1/endorsements"
- def endorsements(conn, params), do: MastodonAPIController.empty_array(conn, params)
+ def endorsements(%{assigns: %{user: user}} = conn, params) do
+ users =
+ user
+ |> User.endorsed_users_relation(_restrict_deactivated = true)
+ |> Pleroma.Repo.all()
+
+ conn
+ |> render("index.json",
+ users: users,
+ for: user,
+ as: :user,
+ embed_relationships: embed_relationships?(params)
+ )
+ end
@doc "GET /api/v1/identity_proofs"
def identity_proofs(conn, params), do: MastodonAPIController.empty_array(conn, params)
diff --git a/lib/pleroma/web/mastodon_api/controllers/announcement_controller.ex b/lib/pleroma/web/mastodon_api/controllers/announcement_controller.ex
new file mode 100644
index 000000000..080af96d5
--- /dev/null
+++ b/lib/pleroma/web/mastodon_api/controllers/announcement_controller.ex
@@ -0,0 +1,60 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.MastodonAPI.AnnouncementController do
+ use Pleroma.Web, :controller
+
+ import Pleroma.Web.ControllerHelper,
+ only: [
+ json_response: 3
+ ]
+
+ alias Pleroma.Announcement
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
+
+ plug(Pleroma.Web.ApiSpec.CastAndValidate)
+
+ # Mastodon docs say this only requires a user token, no scopes needed
+ # As the op `|` requires at least one scope to be present, we use `&` here.
+ plug(
+ OAuthScopesPlug,
+ %{scopes: [], op: :&}
+ when action in [:index]
+ )
+
+ # Same as in MastodonAPI specs
+ plug(
+ OAuthScopesPlug,
+ %{scopes: ["write:accounts"]}
+ when action in [:mark_read]
+ )
+
+ action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
+
+ defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.AnnouncementOperation
+
+ @doc "GET /api/v1/announcements"
+ def index(%{assigns: %{user: user}} = conn, _params) do
+ render(conn, "index.json", announcements: all_visible(), user: user)
+ end
+
+ def index(conn, _params) do
+ render(conn, "index.json", announcements: all_visible(), user: nil)
+ end
+
+ defp all_visible do
+ Announcement.list_all_visible()
+ end
+
+ @doc "POST /api/v1/announcements/:id/dismiss"
+ def mark_read(%{assigns: %{user: user}} = conn, %{id: id} = _params) do
+ with announcement when not is_nil(announcement) <- Announcement.get_by_id(id),
+ {:ok, _} <- Announcement.mark_read_by(announcement, user) do
+ json_response(conn, :ok, %{})
+ else
+ _ ->
+ {:error, :not_found}
+ end
+ end
+end
diff --git a/lib/pleroma/web/mastodon_api/controllers/app_controller.ex b/lib/pleroma/web/mastodon_api/controllers/app_controller.ex
index 143dcf80c..844673ae0 100644
--- a/lib/pleroma/web/mastodon_api/controllers/app_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/app_controller.ex
@@ -1,53 +1,57 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.AppController do
+ @moduledoc """
+ Controller for supporting app-related actions.
+ If authentication is an option, app tokens (user-unbound) must be supported.
+ """
+
use Pleroma.Web, :controller
+ alias Pleroma.Maps
alias Pleroma.Repo
+ alias Pleroma.User
alias Pleroma.Web.OAuth.App
alias Pleroma.Web.OAuth.Scopes
alias Pleroma.Web.OAuth.Token
- alias Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug
- alias Pleroma.Web.Plugs.OAuthScopesPlug
action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
- plug(
- :skip_plug,
- [OAuthScopesPlug, EnsurePublicOrAuthenticatedPlug]
- when action == :create
- )
-
- plug(OAuthScopesPlug, %{scopes: ["read"]} when action == :verify_credentials)
+ plug(:skip_auth when action in [:create, :verify_credentials])
plug(Pleroma.Web.ApiSpec.CastAndValidate)
- @local_mastodon_name "Mastodon-Local"
-
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.AppOperation
@doc "POST /api/v1/apps"
def create(%{body_params: params} = conn, _params) do
scopes = Scopes.fetch_scopes(params, ["read"])
+ user_id = get_user_id(conn)
app_attrs =
params
|> Map.take([:client_name, :redirect_uris, :website])
|> Map.put(:scopes, scopes)
+ |> Maps.put_if_present(:user_id, user_id)
with cs <- App.register_changeset(%App{}, app_attrs),
- false <- cs.changes[:client_name] == @local_mastodon_name,
{:ok, app} <- Repo.insert(cs) do
render(conn, "show.json", app: app)
end
end
- @doc "GET /api/v1/apps/verify_credentials"
- def verify_credentials(%{assigns: %{user: _user, token: token}} = conn, _) do
- with %Token{app: %App{} = app} <- Repo.preload(token, :app) do
- render(conn, "short.json", app: app)
+ defp get_user_id(%{assigns: %{user: %User{id: user_id}}}), do: user_id
+ defp get_user_id(_conn), do: nil
+
+ @doc """
+ GET /api/v1/apps/verify_credentials
+ Gets compact non-secret representation of the app. Supports app tokens and user tokens.
+ """
+ def verify_credentials(%{assigns: %{token: %Token{} = token}} = conn, _) do
+ with %{app: %App{} = app} <- Repo.preload(token, :app) do
+ render(conn, "compact_non_secret.json", app: app)
end
end
end
diff --git a/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex b/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex
index 93d057a79..fbb54a171 100644
--- a/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.AuthController do
@@ -7,77 +7,12 @@ defmodule Pleroma.Web.MastodonAPI.AuthController do
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 =
- 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"]
@@ -86,23 +21,4 @@ defmodule Pleroma.Web.MastodonAPI.AuthController do
json_response(conn, :no_content, "")
end
-
- defp local_mastodon_post_login_path(conn) do
- case get_session(conn, :return_to) do
- nil ->
- 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
diff --git a/lib/pleroma/web/mastodon_api/controllers/conversation_controller.ex b/lib/pleroma/web/mastodon_api/controllers/conversation_controller.ex
index 61347d8db..9cc6225c6 100644
--- a/lib/pleroma/web/mastodon_api/controllers/conversation_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/conversation_controller.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.ConversationController do
@@ -36,4 +36,13 @@ defmodule Pleroma.Web.MastodonAPI.ConversationController do
render(conn, "participation.json", participation: participation, for: user)
end
end
+
+ @doc "DELETE /api/v1/conversations/:id"
+ def delete(%{assigns: %{user: user}} = conn, %{id: participation_id}) do
+ with %Participation{} = participation <-
+ Repo.get_by(Participation, id: participation_id, user_id: user.id),
+ {:ok, _} <- Participation.delete(participation) do
+ json(conn, %{})
+ end
+ end
end
diff --git a/lib/pleroma/web/mastodon_api/controllers/custom_emoji_controller.ex b/lib/pleroma/web/mastodon_api/controllers/custom_emoji_controller.ex
index 872cb1f4d..8b27b0b02 100644
--- a/lib/pleroma/web/mastodon_api/controllers/custom_emoji_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/custom_emoji_controller.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.CustomEmojiController do
@@ -7,11 +7,7 @@ defmodule Pleroma.Web.MastodonAPI.CustomEmojiController do
plug(Pleroma.Web.ApiSpec.CastAndValidate)
- plug(
- :skip_plug,
- [Pleroma.Web.Plugs.OAuthScopesPlug, Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug]
- when action == :index
- )
+ plug(:skip_auth when action == :index)
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.CustomEmojiOperation
diff --git a/lib/pleroma/web/mastodon_api/controllers/directory_controller.ex b/lib/pleroma/web/mastodon_api/controllers/directory_controller.ex
new file mode 100644
index 000000000..253f06cfb
--- /dev/null
+++ b/lib/pleroma/web/mastodon_api/controllers/directory_controller.ex
@@ -0,0 +1,82 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.MastodonAPI.DirectoryController do
+ use Pleroma.Web, :controller
+
+ import Ecto.Query
+ alias Pleroma.Pagination
+ alias Pleroma.User
+ alias Pleroma.UserRelationship
+ alias Pleroma.Web.MastodonAPI.AccountView
+
+ require Logger
+
+ plug(Pleroma.Web.ApiSpec.CastAndValidate)
+
+ plug(:skip_auth when action == "index")
+
+ defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.DirectoryOperation
+
+ @doc "GET /api/v1/directory"
+ def index(%{assigns: %{user: user}} = conn, params) do
+ with true <- Pleroma.Config.get([:instance, :profile_directory]) do
+ limit = Map.get(params, :limit, 20) |> min(80)
+
+ users =
+ User.Query.build(%{is_discoverable: true, invisible: false, limit: limit})
+ |> order_by_creation_date(params)
+ |> exclude_remote(params)
+ |> exclude_user(user)
+ |> exclude_relationships(user, [:block, :mute])
+ |> Pagination.fetch_paginated(params, :offset)
+
+ conn
+ |> put_view(AccountView)
+ |> render("index.json", for: user, users: users, as: :user)
+ else
+ _ -> json(conn, [])
+ end
+ end
+
+ defp order_by_creation_date(query, %{order: "new"}) do
+ query
+ end
+
+ defp order_by_creation_date(query, _params) do
+ query
+ |> order_by([u], desc_nulls_last: u.last_status_at)
+ end
+
+ defp exclude_remote(query, %{local: true}) do
+ where(query, [u], u.local == true)
+ end
+
+ defp exclude_remote(query, _params) do
+ query
+ end
+
+ defp exclude_user(query, %User{id: user_id}) do
+ where(query, [u], u.id != ^user_id)
+ end
+
+ defp exclude_user(query, _user) do
+ query
+ end
+
+ defp exclude_relationships(query, %User{id: user_id}, relationship_types) do
+ query
+ |> join(:left, [u], r in UserRelationship,
+ as: :user_relationships,
+ on:
+ r.target_id == u.id and r.source_id == ^user_id and
+ r.relationship_type in ^relationship_types
+ )
+ |> where([user_relationships: r], is_nil(r.target_id))
+ end
+
+ defp exclude_relationships(query, _user, _relationship_types) do
+ query
+ end
+end
diff --git a/lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex b/lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex
index 503bd7d5f..b2e347ed9 100644
--- a/lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.DomainBlockController do
diff --git a/lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex b/lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex
index 8af557b61..1c650eb21 100644
--- a/lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.FallbackController do
@@ -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)
diff --git a/lib/pleroma/web/mastodon_api/controllers/filter_controller.ex b/lib/pleroma/web/mastodon_api/controllers/filter_controller.ex
index c71a34b15..0959b4bcc 100644
--- a/lib/pleroma/web/mastodon_api/controllers/filter_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/filter_controller.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.FilterController do
@@ -20,6 +20,8 @@ defmodule Pleroma.Web.MastodonAPI.FilterController do
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.FilterOperation
+ action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
+
@doc "GET /api/v1/filters"
def index(%{assigns: %{user: user}} = conn, _) do
filters = Filter.get_filters(user)
@@ -29,25 +31,23 @@ defmodule Pleroma.Web.MastodonAPI.FilterController do
@doc "POST /api/v1/filters"
def create(%{assigns: %{user: user}, body_params: params} = conn, _) do
- query = %Filter{
- user_id: user.id,
- phrase: params.phrase,
- context: params.context,
- hide: params.irreversible,
- whole_word: params.whole_word
- # TODO: support `expires_in` parameter (as in Mastodon API)
- }
-
- {:ok, response} = Filter.create(query)
-
- render(conn, "show.json", filter: response)
+ with {:ok, response} <-
+ params
+ |> Map.put(:user_id, user.id)
+ |> Map.put(:hide, params[:irreversible])
+ |> Map.delete(:irreversible)
+ |> Filter.create() do
+ render(conn, "show.json", filter: response)
+ end
end
@doc "GET /api/v1/filters/:id"
def show(%{assigns: %{user: user}} = conn, %{id: filter_id}) do
- filter = Filter.get(filter_id, user)
-
- render(conn, "show.json", filter: filter)
+ with %Filter{} = filter <- Filter.get(filter_id, user) do
+ render(conn, "show.json", filter: filter)
+ else
+ nil -> {:error, :not_found}
+ end
end
@doc "PUT /api/v1/filters/:id"
@@ -56,28 +56,31 @@ defmodule Pleroma.Web.MastodonAPI.FilterController do
%{id: filter_id}
) do
params =
- params
- |> Map.delete(:irreversible)
- |> Map.put(:hide, params[:irreversible])
- |> Enum.reject(fn {_key, value} -> is_nil(value) end)
- |> Map.new()
-
- # TODO: support `expires_in` parameter (as in Mastodon API)
+ if is_boolean(params[:irreversible]) do
+ params
+ |> Map.put(:hide, params[:irreversible])
+ |> Map.delete(:irreversible)
+ else
+ params
+ end
with %Filter{} = filter <- Filter.get(filter_id, user),
{:ok, %Filter{} = filter} <- Filter.update(filter, params) do
render(conn, "show.json", filter: filter)
+ else
+ nil -> {:error, :not_found}
+ error -> error
end
end
@doc "DELETE /api/v1/filters/:id"
def delete(%{assigns: %{user: user}} = conn, %{id: filter_id}) do
- query = %Filter{
- user_id: user.id,
- filter_id: filter_id
- }
-
- {:ok, _} = Filter.delete(query)
- json(conn, %{})
+ with %Filter{} = filter <- Filter.get(filter_id, user),
+ {:ok, _} <- Filter.delete(filter) do
+ json(conn, %{})
+ else
+ nil -> {:error, :not_found}
+ error -> error
+ end
end
end
diff --git a/lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex b/lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex
index f8cd7fa9f..ba6d074cc 100644
--- a/lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.FollowRequestController do
@@ -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)
diff --git a/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex b/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex
index 07a32491a..6410e872c 100644
--- a/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex
@@ -1,17 +1,13 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.InstanceController do
use Pleroma.Web, :controller
- plug(OpenApiSpex.Plug.CastAndValidate)
+ plug(Pleroma.Web.ApiSpec.CastAndValidate)
- plug(
- :skip_plug,
- [Pleroma.Web.Plugs.OAuthScopesPlug, Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug]
- when action in [:show, :peers]
- )
+ plug(:skip_auth when action in [:show, :peers])
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.InstanceOperation
diff --git a/lib/pleroma/web/mastodon_api/controllers/list_controller.ex b/lib/pleroma/web/mastodon_api/controllers/list_controller.ex
index f6b51bf02..2117aae3a 100644
--- a/lib/pleroma/web/mastodon_api/controllers/list_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/list_controller.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.ListController do
diff --git a/lib/pleroma/web/mastodon_api/controllers/marker_controller.ex b/lib/pleroma/web/mastodon_api/controllers/marker_controller.ex
index 0628b2b49..4ad30f330 100644
--- a/lib/pleroma/web/mastodon_api/controllers/marker_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/marker_controller.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.MarkerController do
diff --git a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex
index 9cf682c7b..0aa7b379f 100644
--- a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
@@ -15,11 +15,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
require Logger
- plug(
- :skip_plug,
- [Pleroma.Web.Plugs.OAuthScopesPlug, Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug]
- when action in [:empty_array, :empty_object]
- )
+ plug(:skip_auth when action in [:empty_array, :empty_object])
action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
diff --git a/lib/pleroma/web/mastodon_api/controllers/media_controller.ex b/lib/pleroma/web/mastodon_api/controllers/media_controller.ex
index 161193134..7d9a63cf4 100644
--- a/lib/pleroma/web/mastodon_api/controllers/media_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/media_controller.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.MediaController do
@@ -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)
diff --git a/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex b/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex
index c3c8606f2..a490e8319 100644
--- a/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.NotificationController do
@@ -50,11 +50,13 @@ defmodule Pleroma.Web.MastodonAPI.NotificationController do
favourite
move
pleroma:emoji_reaction
+ poll
+ update
}
def index(%{assigns: %{user: user}} = conn, params) do
params =
Map.new(params, fn {k, v} -> {to_string(k), v} end)
- |> Map.put_new("include_types", @default_notification_types)
+ |> Map.put_new("types", Map.get(params, :include_types, @default_notification_types))
notifications = MastodonAPI.get_notifications(user, params)
diff --git a/lib/pleroma/web/mastodon_api/controllers/poll_controller.ex b/lib/pleroma/web/mastodon_api/controllers/poll_controller.ex
index e26ec7136..002c210d2 100644
--- a/lib/pleroma/web/mastodon_api/controllers/poll_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/poll_controller.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.PollController do
diff --git a/lib/pleroma/web/mastodon_api/controllers/report_controller.ex b/lib/pleroma/web/mastodon_api/controllers/report_controller.ex
index 156544f40..3db80d728 100644
--- a/lib/pleroma/web/mastodon_api/controllers/report_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/report_controller.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.ReportController do
diff --git a/lib/pleroma/web/mastodon_api/controllers/scheduled_activity_controller.ex b/lib/pleroma/web/mastodon_api/controllers/scheduled_activity_controller.ex
index 322a46497..0392fcef1 100644
--- a/lib/pleroma/web/mastodon_api/controllers/scheduled_activity_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/scheduled_activity_controller.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.ScheduledActivityController do
diff --git a/lib/pleroma/web/mastodon_api/controllers/search_controller.ex b/lib/pleroma/web/mastodon_api/controllers/search_controller.ex
index 0043c3a56..5e6e04734 100644
--- a/lib/pleroma/web/mastodon_api/controllers/search_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/search_controller.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.SearchController do
@@ -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
@@ -17,6 +17,8 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do
require Logger
+ @search_limit 40
+
plug(Pleroma.Web.ApiSpec.CastAndValidate)
# Note: Mastodon doesn't allow unauthenticated access (requires read:accounts / read:search)
@@ -77,7 +79,7 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do
[
resolve: params[:resolve],
following: params[:following],
- limit: params[:limit],
+ limit: min(params[:limit], @search_limit),
offset: params[:offset],
type: params[:type],
author: get_author(params),
@@ -108,7 +110,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)
diff --git a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex
index 9e3a584f0..e594ea491 100644
--- a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.StatusController do
@@ -21,15 +21,13 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.MastodonAPI.AccountView
alias Pleroma.Web.MastodonAPI.ScheduledActivityView
+ alias Pleroma.Web.OAuth.Token
alias Pleroma.Web.Plugs.OAuthScopesPlug
alias Pleroma.Web.Plugs.RateLimiter
plug(Pleroma.Web.ApiSpec.CastAndValidate)
- plug(
- :skip_plug,
- Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug when action in [:index, :show]
- )
+ plug(:skip_public_check when action in [:index, :show])
@unauthenticated_access %{fallback: :proceed_unauthenticated, scopes: []}
@@ -40,7 +38,9 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
:index,
:show,
:card,
- :context
+ :context,
+ :show_history,
+ :show_source
]
)
@@ -51,7 +51,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
:create,
:delete,
:reblog,
- :unreblog
+ :unreblog,
+ :update
]
)
@@ -138,7 +139,9 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
_
)
when not is_nil(scheduled_at) do
- params = Map.put(params, :in_reply_to_status_id, params[:in_reply_to_id])
+ params =
+ Map.put(params, :in_reply_to_status_id, params[:in_reply_to_id])
+ |> put_application(conn)
attrs = %{
params: Map.new(params, fn {key, value} -> {to_string(key), value} end),
@@ -162,7 +165,9 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
# Creates a regular status
def create(%{assigns: %{user: user}, body_params: %{status: _} = params} = conn, _) do
- params = Map.put(params, :in_reply_to_status_id, params[:in_reply_to_id])
+ params =
+ Map.put(params, :in_reply_to_status_id, params[:in_reply_to_id])
+ |> put_application(conn)
with {:ok, activity} <- CommonAPI.post(user, params) do
try_render(conn, "show.json",
@@ -189,6 +194,59 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
create(%Plug.Conn{conn | body_params: params}, %{})
end
+ @doc "GET /api/v1/statuses/:id/history"
+ def show_history(%{assigns: assigns} = conn, %{id: id} = params) do
+ with user = assigns[:user],
+ %Activity{} = activity <- Activity.get_by_id_with_object(id),
+ true <- Visibility.visible_for_user?(activity, user) do
+ try_render(conn, "history.json",
+ activity: activity,
+ for: user,
+ with_direct_conversation_id: true,
+ with_muted: Map.get(params, :with_muted, false)
+ )
+ else
+ _ -> {:error, :not_found}
+ end
+ end
+
+ @doc "GET /api/v1/statuses/:id/source"
+ def show_source(%{assigns: assigns} = conn, %{id: id} = _params) do
+ with user = assigns[:user],
+ %Activity{} = activity <- Activity.get_by_id_with_object(id),
+ true <- Visibility.visible_for_user?(activity, user) do
+ try_render(conn, "source.json",
+ activity: activity,
+ for: user
+ )
+ else
+ _ -> {:error, :not_found}
+ end
+ end
+
+ @doc "PUT /api/v1/statuses/:id"
+ def update(%{assigns: %{user: user}, body_params: body_params} = conn, %{id: id} = params) do
+ with {_, %Activity{}} = {_, activity} <- {:activity, Activity.get_by_id_with_object(id)},
+ {_, true} <- {:visible, Visibility.visible_for_user?(activity, user)},
+ {_, true} <- {:is_create, activity.data["type"] == "Create"},
+ actor <- Activity.user_actor(activity),
+ {_, true} <- {:own_status, actor.id == user.id},
+ changes <- body_params |> put_application(conn),
+ {_, {:ok, _update_activity}} <- {:pipeline, CommonAPI.update(user, activity, changes)},
+ {_, %Activity{}} = {_, activity} <- {:refetched, Activity.get_by_id_with_object(id)} do
+ try_render(conn, "show.json",
+ activity: activity,
+ for: user,
+ with_direct_conversation_id: true,
+ with_muted: Map.get(params, :with_muted, false)
+ )
+ else
+ {:own_status, _} -> {:error, :forbidden}
+ {:pipeline, _} -> {:error, :internal_server_error}
+ _ -> {:error, :not_found}
+ end
+ end
+
@doc "GET /api/v1/statuses/:id"
def show(%{assigns: %{user: user}} = conn, %{id: id} = params) do
with %Activity{} = activity <- Activity.get_by_id_with_object(id),
@@ -255,6 +313,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
@@ -318,7 +388,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
with true <- Pleroma.Config.get([:instance, :show_reactions]),
%Activity{} = activity <- Activity.get_by_id_with_object(id),
{:visible, true} <- {:visible, Visibility.visible_for_user?(activity, user)},
- %Object{data: %{"likes" => likes}} <- Object.normalize(activity) do
+ %Object{data: %{"likes" => likes}} <- Object.normalize(activity, fetch: false) do
users =
User
|> Ecto.Query.where([u], u.ap_id in ^likes)
@@ -339,7 +409,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
with %Activity{} = activity <- Activity.get_by_id_with_object(id),
{:visible, true} <- {:visible, Visibility.visible_for_user?(activity, user)},
%Object{data: %{"announcements" => announces, "id" => ap_id}} <-
- Object.normalize(activity) do
+ Object.normalize(activity, fetch: false) do
announces =
"Announce"
|> Activity.Queries.by_type()
@@ -414,4 +484,15 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
as: :activity
)
end
+
+ defp put_application(params, %{assigns: %{token: %Token{user: %User{} = user} = token}} = _conn) do
+ if user.disclose_client do
+ %{client_name: client_name, website: website} = Repo.preload(token, :app).app
+ Map.put(params, :generator, %{type: "Application", name: client_name, url: website})
+ else
+ Map.put(params, :generator, nil)
+ end
+ end
+
+ defp put_application(params, _), do: Map.put(params, :generator, nil)
end
diff --git a/lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex b/lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex
index 20138908c..9cc0071f6 100644
--- a/lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.SubscriptionController do
diff --git a/lib/pleroma/web/mastodon_api/controllers/suggestion_controller.ex b/lib/pleroma/web/mastodon_api/controllers/suggestion_controller.ex
index 5765271cf..69ae70ad4 100644
--- a/lib/pleroma/web/mastodon_api/controllers/suggestion_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/suggestion_controller.ex
@@ -1,14 +1,19 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.SuggestionController do
use Pleroma.Web, :controller
+ import Ecto.Query
+ alias Pleroma.FollowingRelationship
+ alias Pleroma.User
+ alias Pleroma.UserRelationship
require Logger
plug(Pleroma.Web.ApiSpec.CastAndValidate)
- plug(Pleroma.Web.Plugs.OAuthScopesPlug, %{scopes: ["read"]} when action == :index)
+ plug(Pleroma.Web.Plugs.OAuthScopesPlug, %{scopes: ["read"]} when action in [:index, :index2])
+ plug(Pleroma.Web.Plugs.OAuthScopesPlug, %{scopes: ["write"]} when action in [:dismiss])
def open_api_operation(action) do
operation = String.to_existing_atom("#{action}_operation")
@@ -26,7 +31,90 @@ defmodule Pleroma.Web.MastodonAPI.SuggestionController do
}
end
+ def index2_operation do
+ %OpenApiSpex.Operation{
+ tags: ["Suggestions"],
+ summary: "Follow suggestions",
+ operationId: "SuggestionController.index2",
+ responses: %{
+ 200 => Pleroma.Web.ApiSpec.Helpers.empty_array_response()
+ }
+ }
+ end
+
+ def dismiss_operation do
+ %OpenApiSpex.Operation{
+ tags: ["Suggestions"],
+ summary: "Remove a suggestion",
+ operationId: "SuggestionController.dismiss",
+ parameters: [
+ OpenApiSpex.Operation.parameter(
+ :account_id,
+ :path,
+ %OpenApiSpex.Schema{type: :string},
+ "Account to dismiss",
+ required: true
+ )
+ ],
+ responses: %{
+ 200 => Pleroma.Web.ApiSpec.Helpers.empty_object_response()
+ }
+ }
+ end
+
@doc "GET /api/v1/suggestions"
def index(conn, params),
do: Pleroma.Web.MastodonAPI.MastodonAPIController.empty_array(conn, params)
+
+ @doc "GET /api/v2/suggestions"
+ def index2(%{assigns: %{user: user}} = conn, params) do
+ limit = Map.get(params, :limit, 40) |> min(80)
+
+ users =
+ %{is_suggested: true, invisible: false, limit: limit}
+ |> User.Query.build()
+ |> exclude_user(user)
+ |> exclude_relationships(user, [:block, :mute, :suggestion_dismiss])
+ |> exclude_following(user)
+ |> Pleroma.Repo.all()
+
+ render(conn, "index.json", %{
+ users: users,
+ source: :staff,
+ for: user,
+ skip_visibility_check: true
+ })
+ end
+
+ defp exclude_user(query, %User{id: user_id}) do
+ where(query, [u], u.id != ^user_id)
+ end
+
+ defp exclude_relationships(query, %User{id: user_id}, relationship_types) do
+ query
+ |> join(:left, [u], r in UserRelationship,
+ as: :user_relationships,
+ on:
+ r.target_id == u.id and r.source_id == ^user_id and
+ r.relationship_type in ^relationship_types
+ )
+ |> where([user_relationships: r], is_nil(r.target_id))
+ end
+
+ defp exclude_following(query, %User{id: user_id}) do
+ query
+ |> join(:left, [u], r in FollowingRelationship,
+ as: :following_relationships,
+ on: r.following_id == u.id and r.follower_id == ^user_id and r.state == :follow_accept
+ )
+ |> where([following_relationships: r], is_nil(r.following_id))
+ end
+
+ @doc "DELETE /api/v1/suggestions/:account_id"
+ def dismiss(%{assigns: %{user: source}} = conn, %{account_id: user_id}) do
+ with %User{} = target <- User.get_cached_by_id(user_id),
+ {:ok, _} <- UserRelationship.create(:suggestion_dismiss, source, target) do
+ json(conn, %{})
+ end
+ end
end
diff --git a/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex b/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex
index 852bd0695..293c61b41 100644
--- a/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.TimelineController do
@@ -12,12 +12,11 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
alias Pleroma.Pagination
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
- alias Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug
alias Pleroma.Web.Plugs.OAuthScopesPlug
alias Pleroma.Web.Plugs.RateLimiter
plug(Pleroma.Web.ApiSpec.CastAndValidate)
- plug(:skip_plug, EnsurePublicOrAuthenticatedPlug when action in [:public, :hashtag])
+ plug(:skip_public_check when action in [:public, :hashtag])
# TODO: Replace with a macro when there is a Phoenix release with the following commit in it:
# https://github.com/phoenixframework/phoenix/commit/2e8c63c01fec4dde5467dbbbf9705ff9e780735e
@@ -37,8 +36,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
@@ -51,6 +48,8 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
|> Map.put(:reply_filtering_user, user)
|> Map.put(:announce_filtering_user, user)
|> Map.put(:user, user)
+ |> Map.put(:local_only, params[:local])
+ |> Map.delete(:local)
activities =
[user.ap_id | User.following(user)]
@@ -113,6 +112,8 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
|> Map.put(:muting_user, user)
|> Map.put(:reply_filtering_user, user)
|> Map.put(:instance, params[:instance])
+ # Restricts unfederated content to authenticated users
+ |> Map.put(:includes_local_public, not is_nil(user))
|> ActivityPub.fetch_public_activities()
conn
@@ -131,34 +132,25 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
end
defp hashtag_fetching(params, user, local_only) do
- tags =
+ # Note: not sanitizing tag options at this stage (may be mix-cased, have duplicates etc.)
+ tags_any =
[params[:tag], params[:any]]
|> List.flatten()
- |> Enum.uniq()
- |> Enum.reject(&is_nil/1)
- |> Enum.map(&String.downcase/1)
-
- tag_all =
- params
- |> Map.get(:all, [])
- |> Enum.map(&String.downcase/1)
-
- tag_reject =
- params
- |> Map.get(:none, [])
- |> Enum.map(&String.downcase/1)
-
- _activities =
- params
- |> Map.put(:type, "Create")
- |> Map.put(:local_only, local_only)
- |> Map.put(:blocking_user, user)
- |> Map.put(:muting_user, user)
- |> Map.put(:user, user)
- |> Map.put(:tag, tags)
- |> Map.put(:tag_all, tag_all)
- |> Map.put(:tag_reject, tag_reject)
- |> ActivityPub.fetch_public_activities()
+ |> Enum.filter(& &1)
+
+ tag_all = Map.get(params, :all, [])
+ tag_reject = Map.get(params, :none, [])
+
+ params
+ |> Map.put(:type, "Create")
+ |> Map.put(:local_only, local_only)
+ |> Map.put(:blocking_user, user)
+ |> Map.put(:muting_user, user)
+ |> Map.put(:user, user)
+ |> Map.put(:tag, tags_any)
+ |> Map.put(:tag_all, tag_all)
+ |> Map.put(:tag_reject, tag_reject)
+ |> ActivityPub.fetch_public_activities()
end
# GET /api/v1/timelines/tag/:tag
@@ -190,6 +182,7 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
|> Map.put(:blocking_user, user)
|> Map.put(:user, user)
|> Map.put(:muting_user, user)
+ |> Map.put(:local_only, params[:local])
# we must filter the following list for the user to avoid leaking statuses the user
# does not actually have permission to see (for more info, peruse security issue #270).
@@ -202,7 +195,9 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
|> ActivityPub.fetch_activities_bounded(following, params)
|> Enum.reverse()
- render(conn, "index.json",
+ conn
+ |> add_link_headers(activities)
+ |> render("index.json",
activities: activities,
for: user,
as: :activity,
diff --git a/lib/pleroma/web/mastodon_api/mastodon_api.ex b/lib/pleroma/web/mastodon_api/mastodon_api.ex
index 694bf5ca8..b4d092eed 100644
--- a/lib/pleroma/web/mastodon_api/mastodon_api.ex
+++ b/lib/pleroma/web/mastodon_api/mastodon_api.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.MastodonAPI do
@@ -24,6 +24,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPI do
with {:ok, follower, _followed, _} <- result do
options = cast_params(params)
set_reblogs_visibility(options[:reblogs], result)
+ set_subscription(options[:notify], result)
{:ok, follower}
end
end
@@ -36,6 +37,16 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPI do
CommonAPI.show_reblogs(follower, followed)
end
+ defp set_subscription(true, {:ok, follower, followed, _}) do
+ User.subscribe(follower, followed)
+ end
+
+ defp set_subscription(false, {:ok, follower, followed, _}) do
+ User.unsubscribe(follower, followed)
+ end
+
+ defp set_subscription(_, _), do: {:ok, nil}
+
@spec get_followers(User.t(), map()) :: list(User.t())
def get_followers(user, params \\ %{}) do
user
@@ -54,7 +65,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPI do
user
|> Notification.for_user_query(options)
- |> restrict(:include_types, options)
+ |> restrict(:types, options)
|> restrict(:exclude_types, options)
|> restrict(:account_ap_id, options)
|> Pagination.fetch_paginated(params)
@@ -69,18 +80,19 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPI do
defp cast_params(params) do
param_types = %{
exclude_types: {:array, :string},
- include_types: {:array, :string},
+ types: {:array, :string},
exclude_visibilities: {:array, :string},
reblogs: :boolean,
with_muted: :boolean,
- account_ap_id: :string
+ account_ap_id: :string,
+ notify: :boolean
}
changeset = cast({%{}, param_types}, params, Map.keys(param_types))
changeset.changes
end
- defp restrict(query, :include_types, %{include_types: mastodon_types = [_ | _]}) do
+ defp restrict(query, :types, %{types: mastodon_types = [_ | _]}) do
where(query, [n], n.type in ^mastodon_types)
end
diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex
index 948a05a6d..2260bf5da 100644
--- a/lib/pleroma/web/mastodon_api/views/account_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/account_view.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.AccountView do
@@ -7,6 +7,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
alias Pleroma.FollowingRelationship
alias Pleroma.User
+ alias Pleroma.UserNote
alias Pleroma.UserRelationship
alias Pleroma.Web.CommonAPI.Utils
alias Pleroma.Web.MastodonAPI.AccountView
@@ -101,6 +102,15 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
User.following?(target, reading_user)
end
+ subscribing =
+ UserRelationship.exists?(
+ user_relationships,
+ :inverse_subscription,
+ target,
+ reading_user,
+ &User.subscribed_to?(&2, &1)
+ )
+
# NOTE: adjust UserRelationship.view_relationships_option/2 on new relation-related flags
%{
id: to_string(target.id),
@@ -138,14 +148,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
target,
&User.muted_notifications?(&1, &2)
),
- subscribing:
- UserRelationship.exists?(
- user_relationships,
- :inverse_subscription,
- target,
- reading_user,
- &User.subscribed_to?(&2, &1)
- ),
+ subscribing: subscribing,
+ notifying: subscribing,
requested: follow_state == :follow_pending,
domain_blocking: User.blocks_domain?(reading_user, target),
showing_reblogs:
@@ -156,7 +160,19 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
target,
&User.muting_reblogs?(&1, &2)
),
- endorsed: false
+ note:
+ UserNote.show(
+ reading_user,
+ target
+ ),
+ endorsed:
+ UserRelationship.exists?(
+ user_relationships,
+ :endorsement,
+ target,
+ reading_user,
+ &User.endorses?(&2, &1)
+ )
}
end
@@ -261,12 +277,16 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
actor_type: user.actor_type
}
},
+ last_status_at: user.last_status_at,
- # Pleroma extension
+ # Pleroma extensions
+ # Note: it's insecure to output :email but fully-qualified nickname may serve as safe stub
+ fqn: User.full_nickname(user),
pleroma: %{
ap_id: user.ap_id,
also_known_as: user.also_known_as,
- confirmation_pending: user.confirmation_pending,
+ is_confirmed: user.is_confirmed,
+ is_suggested: user.is_suggested,
tags: user.tags,
hide_followers_count: user.hide_followers_count,
hide_follows_count: user.hide_follows_count,
@@ -290,6 +310,9 @@ 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])
+ |> maybe_put_mute_expires_at(user, opts[:for], opts)
+ |> maybe_show_birthday(user, opts[:for])
end
defp username_from_nickname(string) when is_binary(string) do
@@ -323,6 +346,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
|> Kernel.put_in([:source, :privacy], user.default_scope)
|> Kernel.put_in([:source, :pleroma, :show_role], user.show_role)
|> Kernel.put_in([:source, :pleroma, :no_rich_text], user.no_rich_text)
+ |> Kernel.put_in([:source, :pleroma, :show_birthday], user.show_birthday)
end
defp maybe_put_settings(data, _, _, _), do: data
@@ -376,7 +400,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
defp maybe_put_allow_following_move(data, _, _), do: data
defp maybe_put_activation_status(data, user, %User{is_admin: true}) do
- Kernel.put_in(data, [:pleroma, :deactivated], user.deactivated)
+ Kernel.put_in(data, [:pleroma, :deactivated], !user.is_active)
end
defp maybe_put_activation_status(data, _, _), do: data
@@ -401,6 +425,40 @@ 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 maybe_put_mute_expires_at(data, %User{} = user, target, %{mutes: true}) do
+ Map.put(
+ data,
+ :mute_expires_at,
+ UserRelationship.get_mute_expire_date(target, user)
+ )
+ end
+
+ defp maybe_put_mute_expires_at(data, _, _, _), do: data
+
+ defp maybe_show_birthday(data, %User{id: user_id} = user, %User{id: user_id}) do
+ data
+ |> Kernel.put_in([:pleroma, :birthday], user.birthday)
+ end
+
+ defp maybe_show_birthday(data, %User{show_birthday: true} = user, _) do
+ data
+ |> Kernel.put_in([:pleroma, :birthday], user.birthday)
+ end
+
+ defp maybe_show_birthday(data, _, _) do
+ data
+ end
+
defp image_url(%{"url" => [%{"href" => href} | _]}), do: href
defp image_url(_), do: nil
end
diff --git a/lib/pleroma/web/mastodon_api/views/announcement_view.ex b/lib/pleroma/web/mastodon_api/views/announcement_view.ex
new file mode 100644
index 000000000..93fdfb1f1
--- /dev/null
+++ b/lib/pleroma/web/mastodon_api/views/announcement_view.ex
@@ -0,0 +1,15 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.MastodonAPI.AnnouncementView do
+ use Pleroma.Web, :view
+
+ def render("index.json", %{announcements: announcements, user: user}) do
+ render_many(announcements, __MODULE__, "show.json", user: user)
+ end
+
+ def render("show.json", %{announcement: announcement, user: user}) do
+ Pleroma.Announcement.render_json(announcement, for: user)
+ end
+end
diff --git a/lib/pleroma/web/mastodon_api/views/app_view.ex b/lib/pleroma/web/mastodon_api/views/app_view.ex
index e44272c6f..92cccd452 100644
--- a/lib/pleroma/web/mastodon_api/views/app_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/app_view.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.AppView do
@@ -34,10 +34,10 @@ defmodule Pleroma.Web.MastodonAPI.AppView do
|> with_vapid_key()
end
- def render("short.json", %{app: %App{website: webiste, client_name: name}}) do
+ def render("compact_non_secret.json", %{app: %App{website: website, client_name: name}}) do
%{
name: name,
- website: webiste
+ website: website
}
|> with_vapid_key()
end
diff --git a/lib/pleroma/web/mastodon_api/views/conversation_view.ex b/lib/pleroma/web/mastodon_api/views/conversation_view.ex
index 82fcff062..f6577cd2f 100644
--- a/lib/pleroma/web/mastodon_api/views/conversation_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/conversation_view.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.ConversationView do
diff --git a/lib/pleroma/web/mastodon_api/views/custom_emoji_view.ex b/lib/pleroma/web/mastodon_api/views/custom_emoji_view.ex
index 47a242b8e..cd59ab946 100644
--- a/lib/pleroma/web/mastodon_api/views/custom_emoji_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/custom_emoji_view.ex
@@ -1,19 +1,19 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
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/filter_view.ex b/lib/pleroma/web/mastodon_api/views/filter_view.ex
index c37f624e0..0c9706166 100644
--- a/lib/pleroma/web/mastodon_api/views/filter_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/filter_view.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.FilterView do
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
index 000000000..5e50bc2b6
--- /dev/null
+++ b/lib/pleroma/web/mastodon_api/views/follow_request_view.ex
@@ -0,0 +1,10 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 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
diff --git a/lib/pleroma/web/mastodon_api/views/instance_view.ex b/lib/pleroma/web/mastodon_api/views/instance_view.ex
index c5aca5506..1cc230316 100644
--- a/lib/pleroma/web/mastodon_api/views/instance_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/instance_view.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.InstanceView do
@@ -14,28 +14,32 @@ 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),
+ short_description: Keyword.get(instance, :short_description),
version: "#{@mastodon_api_level} (compatible; #{Pleroma.Application.named_version()})",
email: Keyword.get(instance, :email),
urls: %{
streaming_api: Pleroma.Web.Endpoint.websocket_url()
},
stats: Pleroma.Stats.get_stats(),
- thumbnail: Pleroma.Web.base_url() <> Keyword.get(instance, :instance_thumbnail),
+ thumbnail:
+ 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),
# Extra (not present in Mastodon):
max_toot_chars: Keyword.get(instance, :limit),
+ max_media_attachments: Keyword.get(instance, :max_media_attachments),
poll_limits: Keyword.get(instance, :poll_limits),
upload_limit: Keyword.get(instance, :upload_limit),
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: %{
@@ -43,8 +47,12 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do
features: features(),
federation: federation(),
fields_limits: fields_limits(),
- post_formats: Config.get([:instance, :allowed_post_formats])
+ post_formats: Config.get([:instance, :allowed_post_formats]),
+ privileged_staff: Config.get([:instance, :privileged_staff]),
+ birthday_required: Config.get([:instance, :birthday_required]),
+ birthday_min_age: Config.get([:instance, :birthday_min_age])
},
+ stats: %{mau: Pleroma.User.active_user_count()},
vapid_public_key: Keyword.get(Pleroma.Web.Push.vapid_config(), :public_key)
}
}
@@ -56,19 +64,28 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do
"mastodon_api",
"mastodon_api_streaming",
"polls",
+ "v2_suggestions",
"pleroma_explicit_addressing",
"shareable_emoji_packs",
"multifetch",
"pleroma:api/v1/notifications:include_types_filter",
+ "editing",
+ if Config.get([:activitypub, :blockers_visible]) do
+ "blockers_visible"
+ end,
if Config.get([:media_proxy, :enabled]) do
"media_proxy"
end,
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,
@@ -76,7 +93,14 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do
"safe_dm_mentions"
end,
"pleroma_emoji_reactions",
- "pleroma_chat_messages"
+ "pleroma_chat_messages",
+ if Config.get([:instance, :show_reactions]) do
+ "exposable_reactions"
+ end,
+ if Config.get([:instance, :profile_directory]) do
+ "profile_directory"
+ end,
+ "pleroma:get:main/ostatus"
]
|> Enum.filter(& &1)
end
@@ -88,7 +112,20 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do
{:ok, data} = MRF.describe()
data
- |> Map.merge(%{quarantined_instances: quarantined})
+ |> Map.put(
+ :quarantined_instances,
+ Enum.map(quarantined, fn {instance, _reason} -> instance end)
+ )
+ # This is for backwards compatibility. We originally didn't sent
+ # extra info like a reason why an instance was rejected/quarantined/etc.
+ # Because we didn't want to break backwards compatibility it was decided
+ # to add an extra "info" key.
+ |> Map.put(:quarantined_instances_info, %{
+ "quarantined_instances" =>
+ quarantined
+ |> Enum.map(fn {instance, reason} -> {instance, %{"reason" => reason}} end)
+ |> Map.new()
+ })
else
%{}
end
diff --git a/lib/pleroma/web/mastodon_api/views/list_view.ex b/lib/pleroma/web/mastodon_api/views/list_view.ex
index 580596b64..a7ae7c5f7 100644
--- a/lib/pleroma/web/mastodon_api/views/list_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/list_view.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.ListView do
diff --git a/lib/pleroma/web/mastodon_api/views/marker_view.ex b/lib/pleroma/web/mastodon_api/views/marker_view.ex
index 21d535d54..944769b1a 100644
--- a/lib/pleroma/web/mastodon_api/views/marker_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/marker_view.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.MarkerView do
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
index 000000000..4db72de8f
--- /dev/null
+++ b/lib/pleroma/web/mastodon_api/views/media_view.ex
@@ -0,0 +1,10 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 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
diff --git a/lib/pleroma/web/mastodon_api/views/notification_view.ex b/lib/pleroma/web/mastodon_api/views/notification_view.ex
index 5b06a6b51..b5b5b2376 100644
--- a/lib/pleroma/web/mastodon_api/views/notification_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/notification_view.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.NotificationView do
@@ -19,7 +19,11 @@ defmodule Pleroma.Web.MastodonAPI.NotificationView do
alias Pleroma.Web.MastodonAPI.StatusView
alias Pleroma.Web.PleromaAPI.Chat.MessageReferenceView
- @parent_types ~w{Like Announce EmojiReact}
+ defp object_id_for(%{data: %{"object" => %{"id" => id}}}) when is_binary(id), do: id
+
+ defp object_id_for(%{data: %{"object" => id}}) when is_binary(id), do: id
+
+ @parent_types ~w{Like Announce EmojiReact Update}
def render("index.json", %{notifications: notifications, for: reading_user} = opts) do
activities = Enum.map(notifications, & &1.activity)
@@ -30,7 +34,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationView do
%{data: %{"type" => type}} ->
type in @parent_types
end)
- |> Enum.map(& &1.data["object"])
+ |> Enum.map(&object_id_for/1)
|> Activity.create_by_object_ap_id()
|> Activity.with_preloaded_object(:left)
|> Pleroma.Repo.all()
@@ -78,9 +82,9 @@ defmodule Pleroma.Web.MastodonAPI.NotificationView do
parent_activity_fn = fn ->
if opts[:parent_activities] do
- Activity.Queries.find_by_object_ap_id(opts[:parent_activities], activity.data["object"])
+ Activity.Queries.find_by_object_ap_id(opts[:parent_activities], object_id_for(activity))
else
- Activity.get_create_by_object_ap_id(activity.data["object"])
+ Activity.get_create_by_object_ap_id(object_id_for(activity))
end
end
@@ -109,9 +113,15 @@ defmodule Pleroma.Web.MastodonAPI.NotificationView do
"reblog" ->
put_status(response, parent_activity_fn.(), reading_user, status_render_opts)
+ "update" ->
+ put_status(response, parent_activity_fn.(), reading_user, status_render_opts)
+
"move" ->
put_target(response, activity, reading_user, %{})
+ "poll" ->
+ put_status(response, activity, reading_user, status_render_opts)
+
"pleroma:emoji_reaction" ->
response
|> put_status(parent_activity_fn.(), reading_user, status_render_opts)
@@ -139,7 +149,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationView do
end
defp put_chat_message(response, activity, reading_user, opts) do
- object = Object.normalize(activity)
+ object = Object.normalize(activity, fetch: false)
author = User.get_cached_by_ap_id(object.data["actor"])
chat = Pleroma.Chat.get(reading_user.id, author.ap_id)
cm_ref = MessageReference.for_chat_and_object(chat, object)
diff --git a/lib/pleroma/web/mastodon_api/views/poll_view.ex b/lib/pleroma/web/mastodon_api/views/poll_view.ex
index 4101f21d0..34e23873e 100644
--- a/lib/pleroma/web/mastodon_api/views/poll_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/poll_view.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.PollView do
@@ -11,7 +11,7 @@ defmodule Pleroma.Web.MastodonAPI.PollView do
{end_time, expired} = end_time_and_expired(object)
{options, votes_count} = options_and_votes_count(options)
- %{
+ poll = %{
# Mastodon uses separate ids for polls, but an object can't have
# more than one poll embedded so object id is fine
id: to_string(object.id),
@@ -21,9 +21,16 @@ defmodule Pleroma.Web.MastodonAPI.PollView do
votes_count: votes_count,
voters_count: voters_count(object),
options: options,
- voted: voted?(params),
emojis: Pleroma.Web.MastodonAPI.StatusView.build_emojis(object.data["emoji"])
}
+
+ if params[:for] do
+ # when unauthenticated Mastodon doesn't include `voted` & `own_votes` keys in response
+ {voted, own_votes} = voted_and_own_votes(params, options)
+ Map.merge(poll, %{voted: voted, own_votes: own_votes})
+ else
+ poll
+ end
end
def render("show.json", %{object: object} = params) do
@@ -67,12 +74,29 @@ defmodule Pleroma.Web.MastodonAPI.PollView do
defp voters_count(_), do: 0
- defp voted?(%{object: object} = opts) do
- if opts[:for] do
- existing_votes = Pleroma.Web.ActivityPub.Utils.get_existing_votes(opts[:for].ap_id, object)
- existing_votes != [] or opts[:for].ap_id == object.data["actor"]
+ defp voted_and_own_votes(%{object: object} = params, options) do
+ if params[:for] do
+ existing_votes =
+ Pleroma.Web.ActivityPub.Utils.get_existing_votes(params[:for].ap_id, object)
+
+ voted = existing_votes != [] or params[:for].ap_id == object.data["actor"]
+
+ own_votes =
+ if voted do
+ titles = Enum.map(options, & &1[:title])
+
+ Enum.reduce(existing_votes, [], fn vote, acc ->
+ data = vote |> Map.get(:object) |> Map.get(:data)
+ index = Enum.find_index(titles, &(&1 == data["name"]))
+ [index | acc]
+ end)
+ else
+ []
+ end
+
+ {voted, own_votes}
else
- false
+ {false, []}
end
end
end
diff --git a/lib/pleroma/web/mastodon_api/views/report_view.ex b/lib/pleroma/web/mastodon_api/views/report_view.ex
index 98cb581ef..983f7bd24 100644
--- a/lib/pleroma/web/mastodon_api/views/report_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/report_view.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.ReportView do
diff --git a/lib/pleroma/web/mastodon_api/views/scheduled_activity_view.ex b/lib/pleroma/web/mastodon_api/views/scheduled_activity_view.ex
index 5b896bf3b..772d22f0c 100644
--- a/lib/pleroma/web/mastodon_api/views/scheduled_activity_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/scheduled_activity_view.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.ScheduledActivityView do
@@ -37,7 +37,8 @@ defmodule Pleroma.Web.MastodonAPI.ScheduledActivityView do
visibility: params["visibility"],
scheduled_at: params["scheduled_at"],
poll: params["poll"],
- in_reply_to_id: params["in_reply_to_id"]
+ in_reply_to_id: params["in_reply_to_id"],
+ expires_in: params["expires_in"]
}
|> Pleroma.Maps.put_if_present(:media_ids, params["media_ids"])
end
diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex
index 2301e21cf..0a8c98b44 100644
--- a/lib/pleroma/web/mastodon_api/views/status_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/status_view.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.StatusView do
@@ -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
@@ -41,7 +42,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
activities
|> Enum.map(fn
%{data: %{"type" => "Create"}} = activity ->
- object = Object.normalize(activity)
+ object = Object.normalize(activity, fetch: false)
object && object.data["inReplyTo"] != "" && object.data["inReplyTo"]
_ ->
@@ -51,24 +52,40 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
|> Activity.create_by_object_ap_id_with_object()
|> Repo.all()
|> Enum.reduce(%{}, fn activity, acc ->
- object = Object.normalize(activity)
+ object = Object.normalize(activity, fetch: false)
if object, do: Map.put(acc, object.data["id"], activity), else: acc
end)
end
- defp get_context_id(%{data: %{"context_id" => context_id}}) when not is_nil(context_id),
- do: context_id
-
- defp get_context_id(%{data: %{"context" => context}}) when is_binary(context),
- do: Utils.context_to_conversation_id(context)
+ # DEPRECATED This field seems to be a left-over from the StatusNet era.
+ # If your application uses `pleroma.conversation_id`: this field is deprecated.
+ # It is currently stubbed instead by doing a CRC32 of the context, and
+ # clearing the MSB to avoid overflow exceptions with signed integers on the
+ # different clients using this field (Java/Kotlin code, mostly; see Husky.)
+ # This should be removed in a future version of Pleroma. Pleroma-FE currently
+ # depends on this field, as well.
+ defp get_context_id(%{data: %{"context" => context}}) when is_binary(context) do
+ import Bitwise
+
+ :erlang.crc32(context)
+ |> band(bnot(0x8000_0000))
+ end
defp get_context_id(_), do: nil
- defp reblogged?(activity, user) do
- object = Object.normalize(activity) || %{}
- present?(user && user.ap_id in (object.data["announcements"] || []))
+ # Check if the user reblogged this status
+ defp reblogged?(activity, %User{ap_id: ap_id}) do
+ with %Object{data: %{"announcements" => announcements}} when is_list(announcements) <-
+ Object.normalize(activity, fetch: false) do
+ ap_id in announcements
+ else
+ _ -> false
+ end
end
+ # False if the user is logged out
+ defp reblogged?(_activity, _user), do: false
+
def render("index.json", opts) do
reading_user = opts[:for]
@@ -84,7 +101,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
parent_activities =
activities
|> Enum.filter(&(&1.data["type"] == "Announce" && &1.data["object"]))
- |> Enum.map(&Object.normalize(&1).data["id"])
+ |> Enum.map(&Object.normalize(&1, fetch: false).data["id"])
|> Activity.create_by_object_ap_id()
|> Activity.with_preloaded_object(:left)
|> Activity.with_preloaded_bookmark(reading_user)
@@ -124,16 +141,16 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
) do
user = CommonAPI.get_user(activity.data["actor"])
created_at = Utils.to_masto_date(activity.data["published"])
- activity_object = Object.normalize(activity)
+ object = Object.normalize(activity, fetch: false)
reblogged_parent_activity =
if opts[:parent_activities] do
Activity.Queries.find_by_object_ap_id(
opts[:parent_activities],
- activity_object.data["id"]
+ object.data["id"]
)
else
- Activity.create_by_object_ap_id(activity_object.data["id"])
+ Activity.create_by_object_ap_id(object.data["id"])
|> Activity.with_preloaded_bookmark(opts[:for])
|> Activity.with_set_thread_muted_field(opts[:for])
|> Repo.one()
@@ -142,7 +159,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
reblog_rendering_opts = Map.put(opts, :activity, reblogged_parent_activity)
reblogged = render("show.json", reblog_rendering_opts)
- favorited = opts[:for] && opts[:for].ap_id in (activity_object.data["likes"] || [])
+ favorited = opts[:for] && opts[:for].ap_id in (object.data["likes"] || [])
bookmarked = Activity.get_bookmark(reblogged_parent_activity, opts[:for]) != nil
@@ -152,10 +169,12 @@ 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: activity_object.data["id"],
- url: activity_object.data["id"],
+ uri: object.data["id"],
+ url: object.data["id"],
account:
AccountView.render("show.json", %{
user: user,
@@ -173,27 +192,25 @@ 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),
media_attachments: reblogged[:media_attachments] || [],
mentions: mentions,
tags: reblogged[:tags] || [],
- application: %{
- name: "Web",
- website: nil
- },
+ application: build_application(object.data["generator"]),
language: nil,
emojis: [],
pleroma: %{
- local: activity.local
+ local: activity.local,
+ pinned_at: pinned_at
}
}
end
def render("show.json", %{activity: %{data: %{"object" => _object}} = activity} = opts) do
- object = Object.normalize(activity)
+ object = Object.normalize(activity, fetch: false)
user = CommonAPI.get_user(activity.data["actor"])
user_follower_address = user.follower_address
@@ -201,8 +218,10 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
like_count = object.data["like_count"] || 0
announcement_count = object.data["announcement_count"] || 0
- tags = object.data["tag"] || []
- sensitive = object.data["sensitive"] || Enum.member?(tags, "nsfw")
+ hashtags = Object.hashtags(object)
+ sensitive = object.data["sensitive"] || Enum.member?(hashtags, "nsfw")
+
+ tags = Object.tags(object)
tag_mentions =
tags
@@ -247,27 +266,47 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
created_at = Utils.to_masto_date(object.data["published"])
+ edited_at =
+ with %{"updated" => updated} <- object.data,
+ date <- Utils.to_masto_date(updated),
+ true <- date != "" do
+ date
+ else
+ _ ->
+ nil
+ end
+
reply_to = get_reply_to(activity, opts)
reply_to_user = reply_to && CommonAPI.get_user(reply_to.data["actor"])
+ history_len =
+ 1 +
+ (Object.Updater.history_for(object.data)
+ |> Map.get("orderedItems")
+ |> length())
+
+ # See render("history.json", ...) for more details
+ # Here the implicit index of the current content is 0
+ chrono_order = history_len - 1
+
content =
object
|> render_content()
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"
+ "mastoapi:content:#{chrono_order}"
)
content_plaintext =
content
- |> HTML.get_cached_stripped_html_for_activity(
+ |> Activity.HTML.get_cached_stripped_html_for_activity(
activity,
- "mastoapi:content"
+ "mastoapi:content:#{chrono_order}"
)
summary = object.data["summary"] || ""
@@ -317,6 +356,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"],
@@ -331,8 +372,9 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
reblog: nil,
card: card,
content: content_html,
- text: opts[:with_source] && object.data["source"],
+ text: opts[:with_source] && get_source_text(object.data["source"]),
created_at: created_at,
+ edited_at: edited_at,
reblogs_count: announcement_count,
replies_count: object.data["repliesCount"] || 0,
favourites_count: like_count,
@@ -340,7 +382,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),
@@ -348,15 +390,13 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
poll: render(PollView, "show.json", object: object, for: opts[:for]),
mentions: mentions,
tags: build_tags(tags),
- application: %{
- name: "Web",
- website: nil
- },
+ application: build_application(object.data["generator"]),
language: nil,
emojis: build_emojis(object.data["emoji"]),
pleroma: %{
local: activity.local,
conversation_id: get_context_id(activity),
+ context: object.data["context"],
in_reply_to_account_acct: reply_to_user && reply_to_user.nickname,
content: %{"text/plain" => content_plaintext},
spoiler_text: %{"text/plain" => summary},
@@ -364,7 +404,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
@@ -373,6 +414,100 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
nil
end
+ def render("history.json", %{activity: %{data: %{"object" => _object}} = activity} = opts) do
+ object = Object.normalize(activity, fetch: false)
+
+ hashtags = Object.hashtags(object)
+
+ user = CommonAPI.get_user(activity.data["actor"])
+
+ past_history =
+ Object.Updater.history_for(object.data)
+ |> Map.get("orderedItems")
+ |> Enum.map(&Map.put(&1, "id", object.data["id"]))
+ |> Enum.map(&%Object{data: &1, id: object.id})
+
+ history =
+ [object | past_history]
+ # Mastodon expects the original to be at the first
+ |> Enum.reverse()
+ |> Enum.with_index()
+ |> Enum.map(fn {object, chrono_order} ->
+ %{
+ # The history is prepended every time there is a new edit.
+ # In chrono_order, the oldest item is always at 0, and so on.
+ # The chrono_order is an invariant kept between edits.
+ chrono_order: chrono_order,
+ object: object
+ }
+ end)
+
+ individual_opts =
+ opts
+ |> Map.put(:as, :item)
+ |> Map.put(:user, user)
+ |> Map.put(:hashtags, hashtags)
+
+ render_many(history, StatusView, "history_item.json", individual_opts)
+ end
+
+ def render(
+ "history_item.json",
+ %{
+ activity: activity,
+ user: user,
+ item: %{object: object, chrono_order: chrono_order},
+ hashtags: hashtags
+ } = opts
+ ) do
+ sensitive = object.data["sensitive"] || Enum.member?(hashtags, "nsfw")
+
+ attachment_data = object.data["attachment"] || []
+ attachments = render_many(attachment_data, StatusView, "attachment.json", as: :attachment)
+
+ created_at = Utils.to_masto_date(object.data["updated"] || object.data["published"])
+
+ content =
+ object
+ |> render_content()
+
+ content_html =
+ content
+ |> Activity.HTML.get_cached_scrubbed_html_for_activity(
+ User.html_filter_policy(opts[:for]),
+ activity,
+ "mastoapi:content:#{chrono_order}"
+ )
+
+ summary = object.data["summary"] || ""
+
+ %{
+ account:
+ AccountView.render("show.json", %{
+ user: user,
+ for: opts[:for]
+ }),
+ content: content_html,
+ sensitive: sensitive,
+ spoiler_text: summary,
+ created_at: created_at,
+ media_attachments: attachments,
+ emojis: build_emojis(object.data["emoji"]),
+ poll: render(PollView, "show.json", object: object, for: opts[:for])
+ }
+ end
+
+ def render("source.json", %{activity: %{data: %{"object" => _object}} = activity} = _opts) do
+ object = Object.normalize(activity, fetch: false)
+
+ %{
+ id: activity.id,
+ text: get_source_text(Map.get(object.data, "source", "")),
+ spoiler_text: Map.get(object.data, "summary", ""),
+ content_type: get_source_content_type(object.data["source"])
+ }
+ end
+
def render("card.json", %{rich_media: rich_media, page_url: page_url}) do
page_url_data = URI.parse(page_url)
@@ -385,12 +520,15 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
page_url = page_url_data |> to_string
- image_url =
+ image_url_data =
if is_binary(rich_media["image"]) do
- URI.merge(page_url_data, URI.parse(rich_media["image"]))
- |> to_string
+ URI.parse(rich_media["image"])
+ else
+ nil
end
+ image_url = build_image_url(image_url_data, page_url_data)
+
%{
type: "link",
provider_name: page_url_data.host,
@@ -412,6 +550,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
@@ -421,10 +560,19 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
true -> "unknown"
end
- <<hash_id::signed-32, _rest::binary>> = :crypto.hash(:md5, href)
+ attachment_id =
+ with {_, ap_id} when is_binary(ap_id) <- {:ap_id, attachment["id"]},
+ {_, %Object{data: _object_data, id: object_id}} <-
+ {:object, Object.get_by_ap_id(ap_id)} do
+ to_string(object_id)
+ else
+ _ ->
+ <<hash_id::signed-32, _rest::binary>> = :crypto.hash(:md5, href)
+ to_string(attachment["id"] || hash_id)
+ end
%{
- id: to_string(attachment["id"] || hash_id),
+ id: attachment_id,
url: href,
remote_url: href,
preview_url: href_preview,
@@ -434,8 +582,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
@@ -451,7 +615,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
end
def get_reply_to(activity, %{replied_to_activities: replied_to_activities}) do
- object = Object.normalize(activity)
+ object = Object.normalize(activity, fetch: false)
with nil <- replied_to_activities[object.data["inReplyTo"]] do
# If user didn't participate in the thread
@@ -460,7 +624,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
end
def get_reply_to(%{data: %{"object" => _object}} = activity, _) do
- object = Object.normalize(activity)
+ object = Object.normalize(activity, fetch: false)
if object.data["inReplyTo"] && object.data["inReplyTo"] != "" do
Activity.get_create_by_object_ap_id(object.data["inReplyTo"])
@@ -491,7 +655,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: "/tag/#{URI.encode(&1)}"})
+ |> Enum.map(&%{name: &1, url: "#{Pleroma.Web.Endpoint.url()}/tag/#{URI.encode(&1)}"})
end
def build_tags(_), do: []
@@ -530,8 +694,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
%{
@@ -540,4 +709,49 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
me: !!(current_user && current_user.ap_id in users)
}
end
+
+ @spec build_application(map() | nil) :: map() | nil
+ defp build_application(%{"type" => _type, "name" => name, "url" => url}),
+ do: %{name: name, website: url}
+
+ defp build_application(_), do: nil
+
+ # Workaround for Elixir issue #10771
+ # Avoid applying URI.merge unless necessary
+ # TODO: revert to always attempting URI.merge(image_url_data, page_url_data)
+ # when Elixir 1.12 is the minimum supported version
+ @spec build_image_url(struct() | nil, struct()) :: String.t() | nil
+ defp build_image_url(
+ %URI{scheme: image_scheme, host: image_host} = image_url_data,
+ %URI{} = _page_url_data
+ )
+ when not is_nil(image_scheme) and not is_nil(image_host) do
+ image_url_data |> to_string
+ end
+
+ defp build_image_url(%URI{} = image_url_data, %URI{} = page_url_data) do
+ URI.merge(page_url_data, image_url_data) |> to_string
+ end
+
+ defp build_image_url(_, _), do: nil
+
+ defp get_source_text(%{"content" => content} = _source) do
+ content
+ end
+
+ defp get_source_text(source) when is_binary(source) do
+ source
+ end
+
+ defp get_source_text(_) do
+ ""
+ end
+
+ defp get_source_content_type(%{"mediaType" => type} = _source) do
+ type
+ end
+
+ defp get_source_content_type(_source) do
+ Utils.get_content_type(nil)
+ end
end
diff --git a/lib/pleroma/web/mastodon_api/views/subscription_view.ex b/lib/pleroma/web/mastodon_api/views/subscription_view.ex
index 7c67cc924..baa1e03bd 100644
--- a/lib/pleroma/web/mastodon_api/views/subscription_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/subscription_view.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.SubscriptionView do
diff --git a/lib/pleroma/web/mastodon_api/views/suggestion_view.ex b/lib/pleroma/web/mastodon_api/views/suggestion_view.ex
new file mode 100644
index 000000000..d3df4ef3f
--- /dev/null
+++ b/lib/pleroma/web/mastodon_api/views/suggestion_view.ex
@@ -0,0 +1,28 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.MastodonAPI.SuggestionView do
+ use Pleroma.Web, :view
+ alias Pleroma.Web.MastodonAPI.AccountView
+
+ @source_types [:staff, :global, :past_interactions]
+
+ def render("index.json", %{users: users} = opts) do
+ Enum.map(users, fn user ->
+ opts =
+ opts
+ |> Map.put(:user, user)
+ |> Map.delete(:users)
+
+ render("show.json", opts)
+ end)
+ end
+
+ def render("show.json", %{source: source, user: _user} = opts) when source in @source_types do
+ %{
+ source: source,
+ account: AccountView.render("show.json", opts)
+ }
+ end
+end
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
index 000000000..702eb7eeb
--- /dev/null
+++ b/lib/pleroma/web/mastodon_api/views/timeline_view.ex
@@ -0,0 +1,10 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 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
diff --git a/lib/pleroma/web/mastodon_api/websocket_handler.ex b/lib/pleroma/web/mastodon_api/websocket_handler.ex
index 439cdd716..88444106d 100644
--- a/lib/pleroma/web/mastodon_api/websocket_handler.ex
+++ b/lib/pleroma/web/mastodon_api/websocket_handler.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do
@@ -32,7 +32,8 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do
req
end
- {:cowboy_websocket, req, %{user: user, topic: topic, count: 0, timer: nil},
+ {:cowboy_websocket, req,
+ %{user: user, topic: topic, oauth_token: oauth_token, count: 0, timer: nil},
%{idle_timeout: @timeout}}
else
{:error, :bad_topic} ->
@@ -49,12 +50,10 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do
def websocket_init(state) do
Logger.debug(
- "#{__MODULE__} accepted websocket connection for user #{
- (state.user || %{id: "anonymous"}).id
- }, topic #{state.topic}"
+ "#{__MODULE__} accepted websocket connection for user #{(state.user || %{id: "anonymous"}).id}, topic #{state.topic}"
)
- Streamer.add_socket(state.topic, state.user)
+ Streamer.add_socket(state.topic, state.oauth_token)
{:ok, %{state | timer: timer()}}
end
@@ -100,15 +99,17 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do
{:reply, :ping, %{state | timer: nil, count: 0}, :hibernate}
end
+ def websocket_info(:close, state) do
+ {:stop, state}
+ end
+
# State can be `[]` only in case we terminate before switching to websocket,
# we already log errors for these cases in `init/1`, so just do nothing here
def terminate(_reason, _req, []), do: :ok
def terminate(reason, _req, state) do
Logger.debug(
- "#{__MODULE__} terminating websocket connection for user #{
- (state.user || %{id: "anonymous"}).id
- }, topic #{state.topic || "?"}: #{inspect(reason)}"
+ "#{__MODULE__} terminating websocket connection for user #{(state.user || %{id: "anonymous"}).id}, topic #{state.topic || "?"}: #{inspect(reason)}"
)
Streamer.remove_socket(state.topic)
diff --git a/lib/pleroma/web/media_proxy.ex b/lib/pleroma/web/media_proxy.ex
index 2793cabc1..d64760fc2 100644
--- a/lib/pleroma/web/media_proxy.ex
+++ b/lib/pleroma/web/media_proxy.ex
@@ -1,12 +1,12 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
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, Pleroma.Web.base_url())
+ def local?(url), do: String.starts_with?(url, Endpoint.url())
def whitelisted?(url) do
%{host: domain} = URI.parse(url)
@@ -77,17 +77,10 @@ defmodule Pleroma.Web.MediaProxy do
mediaproxy_whitelist_domains =
[:media_proxy, :whitelist]
|> Config.get()
+ |> Kernel.++(["#{Upload.base_url()}"])
|> Enum.map(&maybe_get_domain_from_url/1)
- whitelist_domains =
- if base_url = Config.get([Upload, :base_url]) do
- %{host: base_domain} = URI.parse(base_url)
- [base_domain | mediaproxy_whitelist_domains]
- else
- mediaproxy_whitelist_domains
- end
-
- domain in whitelist_domains
+ domain in mediaproxy_whitelist_domains
end
defp maybe_get_domain_from_url("http" <> _ = url) do
@@ -128,8 +121,13 @@ defmodule Pleroma.Web.MediaProxy do
end
end
+ def decode_url(encoded) do
+ [_, "proxy", sig, base64 | _] = URI.parse(encoded).path |> String.split("/")
+ decode_url(sig, base64)
+ 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
@@ -137,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
diff --git a/lib/pleroma/web/media_proxy/invalidation.ex b/lib/pleroma/web/media_proxy/invalidation.ex
index 4f4340478..ea927fe41 100644
--- a/lib/pleroma/web/media_proxy/invalidation.ex
+++ b/lib/pleroma/web/media_proxy/invalidation.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MediaProxy.Invalidation do
diff --git a/lib/pleroma/web/media_proxy/invalidation/http.ex b/lib/pleroma/web/media_proxy/invalidation/http.ex
index 0b0cde68c..28ea74991 100644
--- a/lib/pleroma/web/media_proxy/invalidation/http.ex
+++ b/lib/pleroma/web/media_proxy/invalidation/http.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MediaProxy.Invalidation.Http do
diff --git a/lib/pleroma/web/media_proxy/invalidation/script.ex b/lib/pleroma/web/media_proxy/invalidation/script.ex
index d32ffc50b..784178f1c 100644
--- a/lib/pleroma/web/media_proxy/invalidation/script.ex
+++ b/lib/pleroma/web/media_proxy/invalidation/script.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MediaProxy.Invalidation.Script do
@@ -13,6 +13,7 @@ defmodule Pleroma.Web.MediaProxy.Invalidation.Script do
def purge(urls, opts \\ []) do
args =
urls
+ |> maybe_format_urls(Keyword.get(opts, :url_format))
|> List.wrap()
|> Enum.uniq()
|> Enum.join(" ")
@@ -40,4 +41,22 @@ defmodule Pleroma.Web.MediaProxy.Invalidation.Script do
Logger.error("Error while cache purge: #{inspect(error)}")
{:error, inspect(error)}
end
+
+ def maybe_format_urls(urls, :htcacheclean) do
+ urls
+ |> Enum.map(fn url ->
+ uri = URI.parse(url)
+
+ query =
+ if !is_nil(uri.query) do
+ "?" <> uri.query
+ else
+ "?"
+ end
+
+ uri.scheme <> "://" <> uri.host <> ":#{inspect(uri.port)}" <> uri.path <> query
+ end)
+ end
+
+ def maybe_format_urls(urls, _), do: urls
end
diff --git a/lib/pleroma/web/media_proxy/media_proxy_controller.ex b/lib/pleroma/web/media_proxy/media_proxy_controller.ex
index 90651ed9b..d2ad62c13 100644
--- a/lib/pleroma/web/media_proxy/media_proxy_controller.ex
+++ b/lib/pleroma/web/media_proxy/media_proxy_controller.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MediaProxy.MediaProxyController do
@@ -54,7 +54,7 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do
media_proxy_url = MediaProxy.url(url)
with {:ok, %{status: status} = head_response} when status in 200..299 <-
- Pleroma.HTTP.request("head", media_proxy_url, [], [], pool: :media) do
+ Pleroma.HTTP.request("HEAD", media_proxy_url, [], [], pool: :media) do
content_type = Tesla.get_header(head_response, "content-type")
content_length = Tesla.get_header(head_response, "content-length")
content_length = content_length && String.to_integer(content_length)
diff --git a/lib/pleroma/web/metadata.ex b/lib/pleroma/web/metadata.ex
index 0f2d8d1e7..59d018730 100644
--- a/lib/pleroma/web/metadata.ex
+++ b/lib/pleroma/web/metadata.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Metadata do
diff --git a/lib/pleroma/web/metadata/player_view.ex b/lib/pleroma/web/metadata/player_view.ex
index 5a918532a..59c56a236 100644
--- a/lib/pleroma/web/metadata/player_view.ex
+++ b/lib/pleroma/web/metadata/player_view.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Metadata.PlayerView do
diff --git a/lib/pleroma/web/metadata/providers/feed.ex b/lib/pleroma/web/metadata/providers/feed.ex
index bd1459a17..e97d6a54f 100644
--- a/lib/pleroma/web/metadata/providers/feed.ex
+++ b/lib/pleroma/web/metadata/providers/feed.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Metadata.Providers.Feed do
diff --git a/lib/pleroma/web/metadata/providers/open_graph.ex b/lib/pleroma/web/metadata/providers/open_graph.ex
index bb1b23208..97d3865ed 100644
--- a/lib/pleroma/web/metadata/providers/open_graph.ex
+++ b/lib/pleroma/web/metadata/providers/open_graph.ex
@@ -1,9 +1,10 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Metadata.Providers.OpenGraph do
alias Pleroma.User
+ alias Pleroma.Web.MediaProxy
alias Pleroma.Web.Metadata
alias Pleroma.Web.Metadata.Providers.Provider
alias Pleroma.Web.Metadata.Utils
@@ -19,37 +20,24 @@ defmodule Pleroma.Web.Metadata.Providers.OpenGraph do
}) do
attachments = build_attachments(object)
scrubbed_content = Utils.scrub_html_and_truncate(object)
- # Zero width space
- content =
- if scrubbed_content != "" and scrubbed_content != "\u200B" do
- ": “" <> scrubbed_content <> "”"
- else
- ""
- end
- # Most previews only show og:title which is inconvenient. Instagram
- # hacks this by putting the description in the title and making the
- # description longer prefixed by how many likes and shares the post
- # has. Here we use the descriptive nickname in the title, and expand
- # the full account & nickname in the description. We also use the cute^Wevil
- # smart quotes around the status text like Instagram, too.
[
{:meta,
[
property: "og:title",
- content: "#{user.name}" <> content
+ content: Utils.user_name_string(user)
], []},
{:meta, [property: "og:url", content: url], []},
{:meta,
[
property: "og:description",
- content: "#{Utils.user_name_string(user)}" <> content
+ content: scrubbed_content
], []},
- {:meta, [property: "og:type", content: "website"], []}
+ {:meta, [property: "og:type", content: "article"], []}
] ++
if attachments == [] or Metadata.activity_nsfw?(object) do
[
- {:meta, [property: "og:image", content: Utils.attachment_url(User.avatar_url(user))],
+ {:meta, [property: "og:image", content: MediaProxy.preview_url(User.avatar_url(user))],
[]},
{:meta, [property: "og:image:width", content: 150], []},
{:meta, [property: "og:image:height", content: 150], []}
@@ -70,8 +58,9 @@ defmodule Pleroma.Web.Metadata.Providers.OpenGraph do
], []},
{:meta, [property: "og:url", content: user.uri || user.ap_id], []},
{:meta, [property: "og:description", content: truncated_bio], []},
- {:meta, [property: "og:type", content: "website"], []},
- {:meta, [property: "og:image", content: Utils.attachment_url(User.avatar_url(user))], []},
+ {:meta, [property: "og:type", content: "article"], []},
+ {:meta, [property: "og:image", content: MediaProxy.preview_url(User.avatar_url(user))],
+ []},
{:meta, [property: "og:image:width", content: 150], []},
{:meta, [property: "og:image:height", content: 150], []}
]
@@ -82,29 +71,35 @@ defmodule Pleroma.Web.Metadata.Providers.OpenGraph do
Enum.reduce(attachments, [], fn attachment, acc ->
rendered_tags =
Enum.reduce(attachment["url"], [], fn url, acc ->
- # TODO: Add additional properties to objects when we have the data available.
- # Also, Whatsapp only wants JPEG or PNGs. It seems that if we add a second og:image
+ # TODO: Whatsapp only wants JPEG or PNGs. It seems that if we add a second og:image
# object when a Video or GIF is attached it will display that in Whatsapp Rich Preview.
case Utils.fetch_media_type(@media_types, url["mediaType"]) do
"audio" ->
[
- {:meta, [property: "og:audio", content: Utils.attachment_url(url["href"])], []}
+ {:meta, [property: "og:audio", content: MediaProxy.url(url["href"])], []}
| acc
]
+ # Not using preview_url for this. It saves bandwidth, but the image dimensions will
+ # be wrong. We generate it on the fly and have no way to capture or analyze the
+ # image to get the dimensions. This can be an issue for apps/FEs rendering images
+ # in timelines too, but you can get clever with the aspect ratio metadata as a
+ # workaround.
"image" ->
[
- {:meta, [property: "og:image", content: Utils.attachment_url(url["href"])], []},
- {:meta, [property: "og:image:width", content: 150], []},
- {:meta, [property: "og:image:height", content: 150], []}
+ {:meta, [property: "og:image", content: MediaProxy.url(url["href"])], []},
+ {:meta, [property: "og:image:alt", content: attachment["name"]], []}
| acc
]
+ |> maybe_add_dimensions(url)
"video" ->
[
- {:meta, [property: "og:video", content: Utils.attachment_url(url["href"])], []}
+ {:meta, [property: "og:video", content: MediaProxy.url(url["href"])], []}
| acc
]
+ |> maybe_add_dimensions(url)
+ |> maybe_add_video_thumbnail(url)
_ ->
acc
@@ -116,4 +111,38 @@ defmodule Pleroma.Web.Metadata.Providers.OpenGraph do
end
defp build_attachments(_), do: []
+
+ # We can use url["mediaType"] to dynamically fill the metadata
+ defp maybe_add_dimensions(metadata, url) do
+ type = url["mediaType"] |> String.split("/") |> List.first()
+
+ cond do
+ !is_nil(url["height"]) && !is_nil(url["width"]) ->
+ metadata ++
+ [
+ {:meta, [property: "og:#{type}:width", content: "#{url["width"]}"], []},
+ {:meta, [property: "og:#{type}:height", content: "#{url["height"]}"], []}
+ ]
+
+ true ->
+ metadata
+ end
+ end
+
+ # Media Preview Proxy makes thumbnails of videos without resizing, so we can trust the
+ # width and height of the source video.
+ defp maybe_add_video_thumbnail(metadata, url) do
+ cond do
+ Pleroma.Config.get([:media_preview_proxy, :enabled], false) ->
+ metadata ++
+ [
+ {:meta, [property: "og:image:width", content: "#{url["width"]}"], []},
+ {:meta, [property: "og:image:height", content: "#{url["height"]}"], []},
+ {:meta, [property: "og:image", content: MediaProxy.preview_url(url["href"])], []}
+ ]
+
+ true ->
+ metadata
+ end
+ end
end
diff --git a/lib/pleroma/web/metadata/providers/provider.ex b/lib/pleroma/web/metadata/providers/provider.ex
index 767288f9c..bb31c4d83 100644
--- a/lib/pleroma/web/metadata/providers/provider.ex
+++ b/lib/pleroma/web/metadata/providers/provider.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Metadata.Providers.Provider do
diff --git a/lib/pleroma/web/metadata/providers/rel_me.ex b/lib/pleroma/web/metadata/providers/rel_me.ex
index 8905c9c72..f0bee85c8 100644
--- a/lib/pleroma/web/metadata/providers/rel_me.ex
+++ b/lib/pleroma/web/metadata/providers/rel_me.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Metadata.Providers.RelMe do
diff --git a/lib/pleroma/web/metadata/providers/restrict_indexing.ex b/lib/pleroma/web/metadata/providers/restrict_indexing.ex
index a08a04b4a..a43a7c92e 100644
--- a/lib/pleroma/web/metadata/providers/restrict_indexing.ex
+++ b/lib/pleroma/web/metadata/providers/restrict_indexing.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Metadata.Providers.RestrictIndexing do
diff --git a/lib/pleroma/web/metadata/providers/twitter_card.ex b/lib/pleroma/web/metadata/providers/twitter_card.ex
index df34b033f..2dac22ee2 100644
--- a/lib/pleroma/web/metadata/providers/twitter_card.ex
+++ b/lib/pleroma/web/metadata/providers/twitter_card.ex
@@ -1,10 +1,11 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Metadata.Providers.TwitterCard do
alias Pleroma.User
+ alias Pleroma.Web.MediaProxy
alias Pleroma.Web.Metadata
alias Pleroma.Web.Metadata.Providers.Provider
alias Pleroma.Web.Metadata.Utils
@@ -16,22 +17,15 @@ defmodule Pleroma.Web.Metadata.Providers.TwitterCard do
def build_tags(%{activity_id: id, object: object, user: user}) do
attachments = build_attachments(id, object)
scrubbed_content = Utils.scrub_html_and_truncate(object)
- # Zero width space
- content =
- if scrubbed_content != "" and scrubbed_content != "\u200B" do
- "“" <> scrubbed_content <> "”"
- else
- ""
- end
[
title_tag(user),
- {:meta, [property: "twitter:description", content: content], []}
+ {:meta, [name: "twitter:description", content: scrubbed_content], []}
] ++
if attachments == [] or Metadata.activity_nsfw?(object) do
[
image_tag(user),
- {:meta, [property: "twitter:card", content: "summary"], []}
+ {:meta, [name: "twitter:card", content: "summary"], []}
]
else
attachments
@@ -43,55 +37,66 @@ defmodule Pleroma.Web.Metadata.Providers.TwitterCard do
with truncated_bio = Utils.scrub_html_and_truncate(user.bio) do
[
title_tag(user),
- {:meta, [property: "twitter:description", content: truncated_bio], []},
+ {:meta, [name: "twitter:description", content: truncated_bio], []},
image_tag(user),
- {:meta, [property: "twitter:card", content: "summary"], []}
+ {:meta, [name: "twitter:card", content: "summary"], []}
]
end
end
defp title_tag(user) do
- {:meta, [property: "twitter:title", content: Utils.user_name_string(user)], []}
+ {:meta, [name: "twitter:title", content: Utils.user_name_string(user)], []}
end
def image_tag(user) do
- {:meta, [property: "twitter:image", content: Utils.attachment_url(User.avatar_url(user))], []}
+ {:meta, [name: "twitter:image", content: MediaProxy.preview_url(User.avatar_url(user))], []}
end
defp build_attachments(id, %{data: %{"attachment" => attachments}}) do
Enum.reduce(attachments, [], fn attachment, acc ->
rendered_tags =
Enum.reduce(attachment["url"], [], fn url, acc ->
- # TODO: Add additional properties to objects when we have the data available.
case Utils.fetch_media_type(@media_types, url["mediaType"]) do
"audio" ->
[
- {:meta, [property: "twitter:card", content: "player"], []},
- {:meta, [property: "twitter:player:width", content: "480"], []},
- {:meta, [property: "twitter:player:height", content: "80"], []},
- {:meta, [property: "twitter:player", content: player_url(id)], []}
+ {:meta, [name: "twitter:card", content: "player"], []},
+ {:meta, [name: "twitter:player:width", content: "480"], []},
+ {:meta, [name: "twitter:player:height", content: "80"], []},
+ {:meta, [name: "twitter:player", content: player_url(id)], []}
| acc
]
+ # Not using preview_url for this. It saves bandwidth, but the image dimensions will
+ # be wrong. We generate it on the fly and have no way to capture or analyze the
+ # image to get the dimensions. This can be an issue for apps/FEs rendering images
+ # in timelines too, but you can get clever with the aspect ratio metadata as a
+ # workaround.
"image" ->
[
- {:meta, [property: "twitter:card", content: "summary_large_image"], []},
+ {:meta, [name: "twitter:card", content: "summary_large_image"], []},
{:meta,
[
- property: "twitter:player",
- content: Utils.attachment_url(url["href"])
+ name: "twitter:player",
+ content: MediaProxy.url(url["href"])
], []}
| acc
]
+ |> maybe_add_dimensions(url)
- # TODO: Need the true width and height values here or Twitter renders an iFrame with
- # a bad aspect ratio
"video" ->
+ # fallback to old placeholder values
+ height = url["height"] || 480
+ width = url["width"] || 480
+
[
- {:meta, [property: "twitter:card", content: "player"], []},
- {:meta, [property: "twitter:player", content: player_url(id)], []},
- {:meta, [property: "twitter:player:width", content: "480"], []},
- {:meta, [property: "twitter:player:height", content: "480"], []}
+ {:meta, [name: "twitter:card", content: "player"], []},
+ {:meta, [name: "twitter:player", content: player_url(id)], []},
+ {:meta, [name: "twitter:player:width", content: "#{width}"], []},
+ {:meta, [name: "twitter:player:height", content: "#{height}"], []},
+ {:meta, [name: "twitter:player:stream", content: MediaProxy.url(url["href"])],
+ []},
+ {:meta, [name: "twitter:player:stream:content_type", content: url["mediaType"]],
+ []}
| acc
]
@@ -109,4 +114,20 @@ defmodule Pleroma.Web.Metadata.Providers.TwitterCard do
defp player_url(id) do
Pleroma.Web.Router.Helpers.o_status_url(Pleroma.Web.Endpoint, :notice_player, id)
end
+
+ # Videos have problems without dimensions, but we used to not provide WxH for images.
+ # A default (read: incorrect) fallback for images is likely to cause rendering bugs.
+ defp maybe_add_dimensions(metadata, url) do
+ cond do
+ !is_nil(url["height"]) && !is_nil(url["width"]) ->
+ metadata ++
+ [
+ {:meta, [name: "twitter:player:width", content: "#{url["width"]}"], []},
+ {:meta, [name: "twitter:player:height", content: "#{url["height"]}"], []}
+ ]
+
+ true ->
+ metadata
+ end
+ end
end
diff --git a/lib/pleroma/web/metadata/utils.ex b/lib/pleroma/web/metadata/utils.ex
index 8a206e019..15414a988 100644
--- a/lib/pleroma/web/metadata/utils.ex
+++ b/lib/pleroma/web/metadata/utils.ex
@@ -1,24 +1,35 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# 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
- alias Pleroma.Web.MediaProxy
- def scrub_html_and_truncate(%{data: %{"content" => content}} = object) do
- content
+ defp scrub_html_and_truncate_object_field(field, object) do
+ field
# 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()
end
+ def scrub_html_and_truncate(%{data: %{"summary" => summary}} = object)
+ when is_binary(summary) and summary != "" do
+ summary
+ |> scrub_html_and_truncate_object_field(object)
+ end
+
+ def scrub_html_and_truncate(%{data: %{"content" => content}} = object) do
+ content
+ |> scrub_html_and_truncate_object_field(object)
+ end
+
def scrub_html_and_truncate(content, max_length \\ 200) when is_binary(content) do
content
|> scrub_html
@@ -37,10 +48,6 @@ defmodule Pleroma.Web.Metadata.Utils do
def scrub_html(content), do: content
- def attachment_url(url) do
- MediaProxy.preview_url(url)
- end
-
def user_name_string(user) do
"#{user.name} " <>
if user.local do
diff --git a/lib/pleroma/web/mongoose_im/mongoose_im_controller.ex b/lib/pleroma/web/mongoose_im/mongoose_im_controller.ex
index 2a5c7c356..0945ebb12 100644
--- a/lib/pleroma/web/mongoose_im/mongoose_im_controller.ex
+++ b/lib/pleroma/web/mongoose_im/mongoose_im_controller.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MongooseIM.MongooseIMController do
@@ -14,7 +14,7 @@ defmodule Pleroma.Web.MongooseIM.MongooseIMController do
plug(RateLimiter, [name: :authentication, params: ["user"]] when action == :check_password)
def user_exists(conn, %{"user" => username}) do
- with %User{} <- Repo.get_by(User, nickname: username, local: true, deactivated: false) do
+ with %User{} <- Repo.get_by(User, nickname: username, local: true, is_active: true) do
conn
|> json(true)
else
@@ -26,7 +26,7 @@ defmodule Pleroma.Web.MongooseIM.MongooseIMController do
end
def check_password(conn, %{"user" => username, "pass" => password}) do
- with %User{password_hash: password_hash, deactivated: false} <-
+ with %User{password_hash: password_hash, is_active: true} <-
Repo.get_by(User, nickname: username, local: true),
true <- AuthenticationPlug.checkpw(password, password_hash) do
conn
diff --git a/lib/pleroma/web/nodeinfo/nodeinfo.ex b/lib/pleroma/web/nodeinfo/nodeinfo.ex
index 47fa46376..62d445f34 100644
--- a/lib/pleroma/web/nodeinfo/nodeinfo.ex
+++ b/lib/pleroma/web/nodeinfo/nodeinfo.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Nodeinfo.Nodeinfo do
@@ -35,7 +35,9 @@ defmodule Pleroma.Web.Nodeinfo.Nodeinfo do
openRegistrations: Config.get([:instance, :registrations_open]),
usage: %{
users: %{
- total: Map.get(stats, :user_count, 0)
+ total: Map.get(stats, :user_count, 0),
+ activeMonth: Pleroma.User.active_user_count(30),
+ activeHalfyear: Pleroma.User.active_user_count(180)
},
localPosts: Map.get(stats, :status_count, 0)
},
@@ -67,7 +69,8 @@ defmodule Pleroma.Web.Nodeinfo.Nodeinfo do
mailerEnabled: Config.get([Pleroma.Emails.Mailer, :enabled], false),
features: features,
restrictedNicknames: Config.get([Pleroma.User, :restricted_nicknames]),
- skipThreadContainment: Config.get([:instance, :skip_thread_containment], false)
+ skipThreadContainment: Config.get([:instance, :skip_thread_containment], false),
+ privilegedStaff: Config.get([:instance, :privileged_staff])
}
}
end
diff --git a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex
index 8c7a9e565..85c2393ff 100644
--- a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex
+++ b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex
@@ -1,11 +1,11 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
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"
}
]
}
diff --git a/lib/pleroma/web/o_auth.ex b/lib/pleroma/web/o_auth.ex
index 2f1b8708d..d8c68df66 100644
--- a/lib/pleroma/web/o_auth.ex
+++ b/lib/pleroma/web/o_auth.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.OAuth do
diff --git a/lib/pleroma/web/o_auth/app.ex b/lib/pleroma/web/o_auth/app.ex
index df99472e1..0aa655381 100644
--- a/lib/pleroma/web/o_auth/app.ex
+++ b/lib/pleroma/web/o_auth/app.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.OAuth.App do
@@ -7,6 +7,7 @@ defmodule Pleroma.Web.OAuth.App do
import Ecto.Changeset
import Ecto.Query
alias Pleroma.Repo
+ alias Pleroma.User
@type t :: %__MODULE__{}
@@ -19,6 +20,8 @@ defmodule Pleroma.Web.OAuth.App do
field(:client_secret, :string)
field(:trusted, :boolean, default: false)
+ belongs_to(:user, User, type: FlakeId.Ecto.CompatType)
+
has_many(:oauth_authorizations, Pleroma.Web.OAuth.Authorization, on_delete: :delete_all)
has_many(:oauth_tokens, Pleroma.Web.OAuth.Token, on_delete: :delete_all)
@@ -27,7 +30,7 @@ defmodule Pleroma.Web.OAuth.App do
@spec changeset(t(), map()) :: Ecto.Changeset.t()
def changeset(struct, params) do
- cast(struct, params, [:client_name, :redirect_uris, :scopes, :website, :trusted])
+ cast(struct, params, [:client_name, :redirect_uris, :scopes, :website, :trusted, :user_id])
end
@spec register_changeset(t(), map()) :: Ecto.Changeset.t()
@@ -129,6 +132,12 @@ defmodule Pleroma.Web.OAuth.App do
{:ok, Repo.all(query), count}
end
+ @spec get_user_apps(User.t()) :: {:ok, [t()], non_neg_integer()}
+ def get_user_apps(%User{id: user_id}) do
+ from(a in __MODULE__, where: a.user_id == ^user_id)
+ |> Repo.all()
+ end
+
@spec destroy(pos_integer()) :: {:ok, t()} | {:error, Ecto.Changeset.t()}
def destroy(id) do
with %__MODULE__{} = app <- Repo.get(__MODULE__, id) do
diff --git a/lib/pleroma/web/o_auth/authorization.ex b/lib/pleroma/web/o_auth/authorization.ex
index e766dcada..593d2d66f 100644
--- a/lib/pleroma/web/o_auth/authorization.ex
+++ b/lib/pleroma/web/o_auth/authorization.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.OAuth.Authorization do
diff --git a/lib/pleroma/web/o_auth/fallback_controller.ex b/lib/pleroma/web/o_auth/fallback_controller.ex
index a89ced886..684a52c81 100644
--- a/lib/pleroma/web/o_auth/fallback_controller.ex
+++ b/lib/pleroma/web/o_auth/fallback_controller.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.OAuth.FallbackController do
diff --git a/lib/pleroma/web/o_auth/mfa_controller.ex b/lib/pleroma/web/o_auth/mfa_controller.ex
index 5d5ec286a..c4bc4a879 100644
--- a/lib/pleroma/web/o_auth/mfa_controller.ex
+++ b/lib/pleroma/web/o_auth/mfa_controller.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.OAuth.MFAController do
diff --git a/lib/pleroma/web/o_auth/mfa_view.ex b/lib/pleroma/web/o_auth/mfa_view.ex
index 5d87db268..bcadafe57 100644
--- a/lib/pleroma/web/o_auth/mfa_view.ex
+++ b/lib/pleroma/web/o_auth/mfa_view.ex
@@ -1,11 +1,12 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.OAuth.MFAView do
use Pleroma.Web, :view
import Phoenix.HTML.Form
alias Pleroma.MFA
+ alias Pleroma.Web.Gettext
def render("mfa_response.json", %{token: token, user: user}) do
%{
diff --git a/lib/pleroma/web/o_auth/o_auth_controller.ex b/lib/pleroma/web/o_auth/o_auth_controller.ex
index 6e3c7e1a1..c1fb4f378 100644
--- a/lib/pleroma/web/o_auth/o_auth_controller.ex
+++ b/lib/pleroma/web/o_auth/o_auth_controller.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.OAuth.OAuthController do
@@ -12,8 +12,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
alias Pleroma.Registration
alias Pleroma.Repo
alias Pleroma.User
- alias Pleroma.Web.Auth.Authenticator
- alias Pleroma.Web.ControllerHelper
+ alias Pleroma.Web.Auth.WrapperAuthenticator, as: Authenticator
alias Pleroma.Web.OAuth.App
alias Pleroma.Web.OAuth.Authorization
alias Pleroma.Web.OAuth.MFAController
@@ -24,6 +23,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
alias Pleroma.Web.OAuth.Token.Strategy.RefreshToken
alias Pleroma.Web.OAuth.Token.Strategy.Revoke, as: RevokeToken
alias Pleroma.Web.Plugs.RateLimiter
+ alias Pleroma.Web.Utils.Params
require Logger
@@ -32,10 +32,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
plug(:fetch_session)
plug(:fetch_flash)
- plug(:skip_plug, [
- Pleroma.Web.Plugs.OAuthScopesPlug,
- Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug
- ])
+ plug(:skip_auth)
plug(RateLimiter, [name: :authentication] when action == :create_authorization)
@@ -50,7 +47,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
end
def authorize(%Plug.Conn{assigns: %{token: %Token{}}} = conn, %{"force_login" => _} = params) do
- if ControllerHelper.truthy_param?(params["force_login"]) do
+ if Params.truthy_param?(params["force_login"]) do
do_authorize(conn, params)
else
handle_existing_authorization(conn, params)
@@ -427,7 +424,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,9 +597,6 @@ defmodule Pleroma.Web.OAuth.OAuthController do
end
end
- # Special case: Local MastodonFE
- defp redirect_uri(%Plug.Conn{} = conn, "."), do: auth_url(conn, :login)
-
defp redirect_uri(%Plug.Conn{}, redirect_uri), do: redirect_uri
defp get_session_registration_id(%Plug.Conn{} = conn), do: get_session(conn, :registration_id)
diff --git a/lib/pleroma/web/o_auth/o_auth_view.ex b/lib/pleroma/web/o_auth/o_auth_view.ex
index d22b2f7fe..108102ce2 100644
--- a/lib/pleroma/web/o_auth/o_auth_view.ex
+++ b/lib/pleroma/web/o_auth/o_auth_view.ex
@@ -1,15 +1,18 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.OAuth.OAuthView do
use Pleroma.Web, :view
import Phoenix.HTML.Form
+ import Phoenix.HTML
+ alias Pleroma.Web.Gettext
alias Pleroma.Web.OAuth.Token.Utils
def render("token.json", %{token: token} = opts) do
response = %{
+ id: token.id,
token_type: "Bearer",
access_token: token.token,
refresh_token: token.refresh_token,
diff --git a/lib/pleroma/web/o_auth/scopes.ex b/lib/pleroma/web/o_auth/scopes.ex
index 90b9a0471..f33fc21c4 100644
--- a/lib/pleroma/web/o_auth/scopes.ex
+++ b/lib/pleroma/web/o_auth/scopes.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.OAuth.Scopes do
diff --git a/lib/pleroma/web/o_auth/token.ex b/lib/pleroma/web/o_auth/token.ex
index 886117d15..26de7bb10 100644
--- a/lib/pleroma/web/o_auth/token.ex
+++ b/lib/pleroma/web/o_auth/token.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.OAuth.Token do
diff --git a/lib/pleroma/web/o_auth/token/query.ex b/lib/pleroma/web/o_auth/token/query.ex
index fd6d9b112..4a4d2d3ef 100644
--- a/lib/pleroma/web/o_auth/token/query.ex
+++ b/lib/pleroma/web/o_auth/token/query.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.OAuth.Token.Query do
diff --git a/lib/pleroma/web/o_auth/token/strategy/refresh_token.ex b/lib/pleroma/web/o_auth/token/strategy/refresh_token.ex
index 625b0fde2..6b0d9503c 100644
--- a/lib/pleroma/web/o_auth/token/strategy/refresh_token.ex
+++ b/lib/pleroma/web/o_auth/token/strategy/refresh_token.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.OAuth.Token.Strategy.RefreshToken do
diff --git a/lib/pleroma/web/o_auth/token/strategy/revoke.ex b/lib/pleroma/web/o_auth/token/strategy/revoke.ex
index 069c1ee21..3b265b339 100644
--- a/lib/pleroma/web/o_auth/token/strategy/revoke.ex
+++ b/lib/pleroma/web/o_auth/token/strategy/revoke.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.OAuth.Token.Strategy.Revoke do
@@ -21,6 +21,18 @@ defmodule Pleroma.Web.OAuth.Token.Strategy.Revoke do
@doc "Revokes access token"
@spec revoke(Token.t()) :: {:ok, Token.t()} | {:error, Ecto.Changeset.t()}
def revoke(%Token{} = token) do
- Repo.delete(token)
+ with {:ok, token} <- Repo.delete(token) do
+ Task.Supervisor.start_child(
+ Pleroma.TaskSupervisor,
+ Pleroma.Web.Streamer,
+ :close_streams_by_oauth_token,
+ [token],
+ restart: :transient
+ )
+
+ {:ok, token}
+ else
+ result -> result
+ end
end
end
diff --git a/lib/pleroma/web/o_auth/token/utils.ex b/lib/pleroma/web/o_auth/token/utils.ex
index 43aeab6b0..773cd9792 100644
--- a/lib/pleroma/web/o_auth/token/utils.ex
+++ b/lib/pleroma/web/o_auth/token/utils.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.OAuth.Token.Utils do
diff --git a/lib/pleroma/web/o_status/o_status_controller.ex b/lib/pleroma/web/o_status/o_status_controller.ex
index 668ae0ea4..ea4994bd0 100644
--- a/lib/pleroma/web/o_status/o_status_controller.ex
+++ b/lib/pleroma/web/o_status/o_status_controller.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.OStatus.OStatusController do
@@ -73,15 +73,11 @@ defmodule Pleroma.Web.OStatus.OStatusController do
%User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
cond do
format in ["json", "activity+json"] ->
- if activity.local do
- %{data: %{"id" => redirect_url}} = Object.normalize(activity)
- redirect(conn, external: redirect_url)
- else
- {:error, :not_found}
- end
+ %{data: %{"id" => redirect_url}} = Object.normalize(activity, fetch: false)
+ redirect(conn, external: redirect_url)
activity.data["type"] == "Create" ->
- %Object{} = object = Object.normalize(activity)
+ %Object{} = object = Object.normalize(activity, fetch: false)
RedirectController.redirector_with_meta(
conn,
@@ -112,7 +108,7 @@ defmodule Pleroma.Web.OStatus.OStatusController do
with %Activity{data: %{"type" => "Create"}} = activity <- Activity.get_by_id_with_object(id),
true <- Visibility.is_public?(activity),
{_, true} <- {:visible?, Visibility.visible_for_user?(activity, _reading_user = nil)},
- %Object{} = object <- Object.normalize(activity),
+ %Object{} = object <- Object.normalize(activity, fetch: false),
%{data: %{"attachment" => [%{"url" => [url | _]} | _]}} <- object,
true <- String.starts_with?(url["mediaType"], ["audio", "video"]) do
conn
diff --git a/lib/pleroma/web/pleroma_api/controllers/account_controller.ex b/lib/pleroma/web/pleroma_api/controllers/account_controller.ex
index 30cf83567..591391b60 100644
--- a/lib/pleroma/web/pleroma_api/controllers/account_controller.ex
+++ b/lib/pleroma/web/pleroma_api/controllers/account_controller.ex
@@ -1,17 +1,21 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.PleromaAPI.AccountController do
use Pleroma.Web, :controller
import Pleroma.Web.ControllerHelper,
- only: [json_response: 3, add_link_headers: 2, assign_account_by_id: 2]
+ only: [
+ json_response: 3,
+ add_link_headers: 2,
+ embed_relationships?: 1,
+ assign_account_by_id: 2
+ ]
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.MastodonAPI.StatusView
- alias Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug
alias Pleroma.Web.Plugs.OAuthScopesPlug
alias Pleroma.Web.Plugs.RateLimiter
@@ -29,10 +33,7 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do
plug(Pleroma.Web.ApiSpec.CastAndValidate)
- plug(
- :skip_plug,
- [OAuthScopesPlug, EnsurePublicOrAuthenticatedPlug] when action == :confirmation_resend
- )
+ plug(:skip_auth when action == :confirmation_resend)
plug(
OAuthScopesPlug,
@@ -44,10 +45,23 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do
%{scopes: ["read:favourites"], fallback: :proceed_unauthenticated} when action == :favourites
)
+ plug(
+ OAuthScopesPlug,
+ %{fallback: :proceed_unauthenticated, scopes: ["read:accounts"]}
+ when action == :endorsements
+ )
+
+ plug(
+ OAuthScopesPlug,
+ %{scopes: ["read:accounts"]} when action == :birthdays
+ )
+
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)
+ plug(
+ :assign_account_by_id
+ when action in [:favourites, :endorsements, :subscribe, :unsubscribe]
+ )
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaAccountOperation
@@ -56,7 +70,7 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do
nickname_or_email = params[:email] || params[:nickname]
with %User{} = user <- User.get_by_nickname_or_email(nickname_or_email),
- {:ok, _} <- User.try_send_confirmation_email(user) do
+ {:ok, _} <- User.maybe_send_confirmation_email(user) do
json_response(conn, :no_content, "")
end
end
@@ -95,6 +109,22 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do
)
end
+ @doc "GET /api/v1/pleroma/accounts/:id/endorsements"
+ def endorsements(%{assigns: %{user: for_user, account: user}} = conn, params) do
+ users =
+ user
+ |> User.endorsed_users_relation(_restrict_deactivated = true)
+ |> Pleroma.Repo.all()
+
+ conn
+ |> render("index.json",
+ for: for_user,
+ users: users,
+ as: :user,
+ embed_relationships: embed_relationships?(params)
+ )
+ end
+
@doc "POST /api/v1/pleroma/accounts/:id/subscribe"
def subscribe(%{assigns: %{user: user, account: subscription_target}} = conn, _params) do
with {:ok, _subscription} <- User.subscribe(user, subscription_target) do
@@ -112,4 +142,18 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do
{:error, message} -> json_response(conn, :forbidden, %{error: message})
end
end
+
+ @doc "GET /api/v1/pleroma/birthdays"
+ def birthdays(%{assigns: %{user: %User{} = user}} = conn, %{day: day, month: month} = _params) do
+ birthdays =
+ User.get_friends_birthdays_query(user, day, month)
+ |> Pleroma.Repo.all()
+
+ conn
+ |> render("index.json",
+ for: user,
+ users: birthdays,
+ as: :user
+ )
+ end
end
diff --git a/lib/pleroma/web/pleroma_api/controllers/app_controller.ex b/lib/pleroma/web/pleroma_api/controllers/app_controller.ex
new file mode 100644
index 000000000..3e84f75a4
--- /dev/null
+++ b/lib/pleroma/web/pleroma_api/controllers/app_controller.ex
@@ -0,0 +1,23 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.PleromaAPI.AppController do
+ use Pleroma.Web, :controller
+
+ alias Pleroma.Web.OAuth.App
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
+
+ plug(OAuthScopesPlug, %{scopes: ["follow", "read"]} when action in [:index])
+
+ plug(Pleroma.Web.ApiSpec.CastAndValidate)
+
+ defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaAppOperation
+
+ @doc "GET /api/v1/pleroma/apps"
+ def index(%{assigns: %{user: user}} = conn, _params) do
+ with apps <- App.get_user_apps(user) do
+ render(conn, "index.json", %{apps: apps})
+ end
+ end
+end
diff --git a/lib/pleroma/web/pleroma_api/controllers/backup_controller.ex b/lib/pleroma/web/pleroma_api/controllers/backup_controller.ex
index dd0a2e22f..b9daed22b 100644
--- a/lib/pleroma/web/pleroma_api/controllers/backup_controller.ex
+++ b/lib/pleroma/web/pleroma_api/controllers/backup_controller.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.PleromaAPI.BackupController do
@@ -9,8 +9,8 @@ defmodule Pleroma.Web.PleromaAPI.BackupController do
alias Pleroma.Web.Plugs.OAuthScopesPlug
action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
- plug(OAuthScopesPlug, %{scopes: ["read:accounts"]} when action in [:index, :create])
- plug(OpenApiSpex.Plug.CastAndValidate, render_error: Pleroma.Web.ApiSpec.RenderError)
+ plug(OAuthScopesPlug, %{scopes: ["read:backups"]} when action in [:index, :create])
+ plug(Pleroma.Web.ApiSpec.CastAndValidate)
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaBackupOperation
diff --git a/lib/pleroma/web/pleroma_api/controllers/chat_controller.ex b/lib/pleroma/web/pleroma_api/controllers/chat_controller.ex
index bfc0a1f19..3d7b6a4a7 100644
--- a/lib/pleroma/web/pleroma_api/controllers/chat_controller.ex
+++ b/lib/pleroma/web/pleroma_api/controllers/chat_controller.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.PleromaAPI.ChatController do
use Pleroma.Web, :controller
@@ -35,10 +35,10 @@ defmodule Pleroma.Web.PleromaAPI.ChatController do
plug(
OAuthScopesPlug,
- %{scopes: ["read:chats"]} when action in [:messages, :index, :show]
+ %{scopes: ["read:chats"]} when action in [:messages, :index, :index2, :show]
)
- plug(OpenApiSpex.Plug.CastAndValidate, render_error: Pleroma.Web.ApiSpec.RenderError)
+ plug(Pleroma.Web.ApiSpec.CastAndValidate)
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.ChatOperation
@@ -82,7 +82,7 @@ defmodule Pleroma.Web.PleromaAPI.ChatController do
media_id: params[:media_id],
idempotency_key: idempotency_key(conn)
),
- message <- Object.normalize(activity, false),
+ message <- Object.normalize(activity, fetch: false),
cm_ref <- MessageReference.for_chat_and_object(chat, message) do
conn
|> put_view(MessageReferenceView)
@@ -138,20 +138,34 @@ defmodule Pleroma.Web.PleromaAPI.ChatController do
end
end
- def index(%{assigns: %{user: %{id: user_id} = user}} = conn, params) do
- exclude_users =
- User.cached_blocked_users_ap_ids(user) ++
- if params[:with_muted], do: [], else: User.cached_muted_users_ap_ids(user)
-
+ def index(%{assigns: %{user: user}} = conn, params) do
chats =
- user_id
- |> Chat.for_user_query()
- |> where([c], c.recipient not in ^exclude_users)
+ index_query(user, params)
|> Repo.all()
render(conn, "index.json", chats: chats)
end
+ def index2(%{assigns: %{user: user}} = conn, params) do
+ chats =
+ index_query(user, params)
+ |> Pagination.fetch_paginated(params)
+
+ conn
+ |> add_link_headers(chats)
+ |> render("index.json", chats: chats)
+ end
+
+ defp index_query(%{id: user_id} = user, params) do
+ exclude_users =
+ User.cached_blocked_users_ap_ids(user) ++
+ if params[:with_muted], do: [], else: User.cached_muted_users_ap_ids(user)
+
+ user_id
+ |> Chat.for_user_query()
+ |> where([c], c.recipient not in ^exclude_users)
+ end
+
def create(%{assigns: %{user: user}} = conn, %{id: id}) do
with %User{ap_id: recipient} <- User.get_cached_by_id(id),
{:ok, %Chat{} = chat} <- Chat.get_or_create(user.id, recipient) do
diff --git a/lib/pleroma/web/pleroma_api/controllers/conversation_controller.ex b/lib/pleroma/web/pleroma_api/controllers/conversation_controller.ex
index df52b7566..37990db54 100644
--- a/lib/pleroma/web/pleroma_api/controllers/conversation_controller.ex
+++ b/lib/pleroma/web/pleroma_api/controllers/conversation_controller.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.PleromaAPI.ConversationController do
@@ -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(
diff --git a/lib/pleroma/web/pleroma_api/controllers/emoji_file_controller.ex b/lib/pleroma/web/pleroma_api/controllers/emoji_file_controller.ex
index c15980ff0..f854cf9c1 100644
--- a/lib/pleroma/web/pleroma_api/controllers/emoji_file_controller.ex
+++ b/lib/pleroma/web/pleroma_api/controllers/emoji_file_controller.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.PleromaAPI.EmojiFileController do
@@ -12,7 +12,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiFileController do
plug(
Pleroma.Web.Plugs.OAuthScopesPlug,
- %{scopes: ["write"], admin: true}
+ %{scopes: ["admin:write"]}
when action in [
:create,
:update,
diff --git a/lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex b/lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex
index bc4c8d840..420fea12c 100644
--- a/lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex
+++ b/lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.PleromaAPI.EmojiPackController do
@@ -11,7 +11,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackController do
plug(
Pleroma.Web.Plugs.OAuthScopesPlug,
- %{scopes: ["write"], admin: true}
+ %{scopes: ["admin:write"]}
when action in [
:import_from_filesystem,
:remote,
@@ -22,11 +22,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackController do
]
)
- @skip_plugs [
- Pleroma.Web.Plugs.OAuthScopesPlug,
- Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug
- ]
- plug(:skip_plug, @skip_plugs when action in [:index, :archive, :show])
+ plug(:skip_auth when action in [:index, :archive, :show])
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaEmojiPackOperation
diff --git a/lib/pleroma/web/pleroma_api/controllers/emoji_reaction_controller.ex b/lib/pleroma/web/pleroma_api/controllers/emoji_reaction_controller.ex
index dd9c746dc..78fd0b219 100644
--- a/lib/pleroma/web/pleroma_api/controllers/emoji_reaction_controller.ex
+++ b/lib/pleroma/web/pleroma_api/controllers/emoji_reaction_controller.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.PleromaAPI.EmojiReactionController do
@@ -29,7 +29,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiReactionController do
with true <- Pleroma.Config.get([:instance, :show_reactions]),
%Activity{} = activity <- Activity.get_by_id_with_object(activity_id),
%Object{data: %{"reactions" => reactions}} when is_list(reactions) <-
- Object.normalize(activity) do
+ Object.normalize(activity, fetch: false) do
reactions =
reactions
|> filter(params)
diff --git a/lib/pleroma/web/pleroma_api/controllers/instances_controller.ex b/lib/pleroma/web/pleroma_api/controllers/instances_controller.ex
index 9e97480df..6257e3153 100644
--- a/lib/pleroma/web/pleroma_api/controllers/instances_controller.ex
+++ b/lib/pleroma/web/pleroma_api/controllers/instances_controller.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.PleromaAPI.InstancesController do
diff --git a/lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex b/lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex
index 15210f1e6..66e9d8481 100644
--- a/lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex
+++ b/lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.PleromaAPI.MascotController do
diff --git a/lib/pleroma/web/pleroma_api/controllers/notification_controller.ex b/lib/pleroma/web/pleroma_api/controllers/notification_controller.ex
index fa32aaa84..87ea81cef 100644
--- a/lib/pleroma/web/pleroma_api/controllers/notification_controller.ex
+++ b/lib/pleroma/web/pleroma_api/controllers/notification_controller.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.PleromaAPI.NotificationController do
@@ -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/controllers/report_controller.ex b/lib/pleroma/web/pleroma_api/controllers/report_controller.ex
new file mode 100644
index 000000000..1f0a82cee
--- /dev/null
+++ b/lib/pleroma/web/pleroma_api/controllers/report_controller.ex
@@ -0,0 +1,46 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.PleromaAPI.ReportController do
+ use Pleroma.Web, :controller
+
+ alias Pleroma.Activity
+ alias Pleroma.Web.ActivityPub.Utils
+ alias Pleroma.Web.AdminAPI.Report
+
+ action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
+ plug(Pleroma.Web.ApiSpec.CastAndValidate)
+ plug(Pleroma.Web.Plugs.OAuthScopesPlug, %{scopes: ["read:reports"]})
+
+ defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaReportOperation
+
+ @doc "GET /api/v0/pleroma/reports"
+ def index(%{assigns: %{user: user}, body_params: params} = conn, _) do
+ params =
+ params
+ |> Map.put(:actor_id, user.ap_id)
+
+ reports = Utils.get_reports(params, Map.get(params, :page, 1), Map.get(params, :size, 20))
+
+ render(conn, "index.json", %{reports: reports, for: user})
+ end
+
+ @doc "GET /api/v0/pleroma/reports/:id"
+ def show(%{assigns: %{user: user}} = conn, %{id: id}) do
+ with %Activity{} = report <- Activity.get_report(id),
+ true <- report.actor == user.ap_id,
+ %{} = report_info <- Report.extract_report_info(report) do
+ render(conn, "show.json", Map.put(report_info, :for, user))
+ else
+ false ->
+ {:error, :not_found}
+
+ nil ->
+ {:error, :not_found}
+
+ e ->
+ {:error, inspect(e)}
+ end
+ end
+end
diff --git a/lib/pleroma/web/pleroma_api/controllers/scrobble_controller.ex b/lib/pleroma/web/pleroma_api/controllers/scrobble_controller.ex
index 632d65434..bf6dc500c 100644
--- a/lib/pleroma/web/pleroma_api/controllers/scrobble_controller.ex
+++ b/lib/pleroma/web/pleroma_api/controllers/scrobble_controller.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.PleromaAPI.ScrobbleController do
diff --git a/lib/pleroma/web/pleroma_api/controllers/settings_controller.ex b/lib/pleroma/web/pleroma_api/controllers/settings_controller.ex
new file mode 100644
index 000000000..1136575b6
--- /dev/null
+++ b/lib/pleroma/web/pleroma_api/controllers/settings_controller.ex
@@ -0,0 +1,79 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.PleromaAPI.SettingsController do
+ use Pleroma.Web, :controller
+
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
+
+ plug(Pleroma.Web.ApiSpec.CastAndValidate)
+
+ plug(
+ OAuthScopesPlug,
+ %{scopes: ["write:accounts"]} when action in [:update]
+ )
+
+ plug(
+ OAuthScopesPlug,
+ %{scopes: ["read:accounts"]} when action in [:show]
+ )
+
+ defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaSettingsOperation
+
+ @doc "GET /api/v1/pleroma/settings/:app"
+ def show(%{assigns: %{user: user}} = conn, %{app: app} = _params) do
+ conn
+ |> json(get_settings(user, app))
+ end
+
+ @doc "PATCH /api/v1/pleroma/settings/:app"
+ def update(%{assigns: %{user: user}, body_params: body_params} = conn, %{app: app} = _params) do
+ settings =
+ get_settings(user, app)
+ |> merge_recursively(body_params)
+
+ with changeset <-
+ Pleroma.User.update_changeset(
+ user,
+ %{pleroma_settings_store: %{app => settings}}
+ ),
+ {:ok, _} <- Pleroma.Repo.update(changeset) do
+ conn
+ |> json(settings)
+ end
+ end
+
+ defp merge_recursively(old, %{} = new) do
+ old = ensure_object(old)
+
+ Enum.reduce(
+ new,
+ old,
+ fn
+ {k, nil}, acc ->
+ Map.drop(acc, [k])
+
+ {k, %{} = new_child}, acc ->
+ Map.put(acc, k, merge_recursively(acc[k], new_child))
+
+ {k, v}, acc ->
+ Map.put(acc, k, v)
+ end
+ )
+ end
+
+ defp get_settings(user, app) do
+ user.pleroma_settings_store
+ |> Map.get(app, %{})
+ |> ensure_object()
+ end
+
+ defp ensure_object(%{} = object) do
+ object
+ end
+
+ defp ensure_object(_) do
+ %{}
+ end
+end
diff --git a/lib/pleroma/web/pleroma_api/controllers/two_factor_authentication_controller.ex b/lib/pleroma/web/pleroma_api/controllers/two_factor_authentication_controller.ex
index eba452300..e69847b00 100644
--- a/lib/pleroma/web/pleroma_api/controllers/two_factor_authentication_controller.ex
+++ b/lib/pleroma/web/pleroma_api/controllers/two_factor_authentication_controller.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.PleromaAPI.TwoFactorAuthenticationController do
diff --git a/lib/pleroma/web/pleroma_api/controllers/user_import_controller.ex b/lib/pleroma/web/pleroma_api/controllers/user_import_controller.ex
index 7f089af1c..90428a532 100644
--- a/lib/pleroma/web/pleroma_api/controllers/user_import_controller.ex
+++ b/lib/pleroma/web/pleroma_api/controllers/user_import_controller.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.PleromaAPI.UserImportController do
@@ -15,7 +15,7 @@ defmodule Pleroma.Web.PleromaAPI.UserImportController do
plug(OAuthScopesPlug, %{scopes: ["follow", "write:blocks"]} when action == :blocks)
plug(OAuthScopesPlug, %{scopes: ["follow", "write:mutes"]} when action == :mutes)
- plug(OpenApiSpex.Plug.CastAndValidate)
+ plug(Pleroma.Web.ApiSpec.CastAndValidate)
defdelegate open_api_operation(action), to: ApiSpec.UserImportOperation
def follow(%{body_params: %{list: %Plug.Upload{path: path}}} = 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
index 000000000..910bcee16
--- /dev/null
+++ b/lib/pleroma/web/pleroma_api/views/account_view.ex
@@ -0,0 +1,10 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 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/app_view.ex b/lib/pleroma/web/pleroma_api/views/app_view.ex
new file mode 100644
index 000000000..1fdc3c224
--- /dev/null
+++ b/lib/pleroma/web/pleroma_api/views/app_view.ex
@@ -0,0 +1,11 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.PleromaAPI.AppView do
+ use Pleroma.Web, :view
+
+ def render("index.json", %{apps: apps}) do
+ render_many(apps, Pleroma.Web.MastodonAPI.AppView, "show.json")
+ end
+end
diff --git a/lib/pleroma/web/pleroma_api/views/backup_view.ex b/lib/pleroma/web/pleroma_api/views/backup_view.ex
index af75876aa..d778590f0 100644
--- a/lib/pleroma/web/pleroma_api/views/backup_view.ex
+++ b/lib/pleroma/web/pleroma_api/views/backup_view.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.PleromaAPI.BackupView do
@@ -10,6 +10,7 @@ defmodule Pleroma.Web.PleromaAPI.BackupView do
def render("show.json", %{backup: %Backup{} = backup}) do
%{
+ id: backup.id,
content_type: backup.content_type,
url: download_url(backup),
file_size: backup.file_size,
@@ -23,6 +24,6 @@ defmodule Pleroma.Web.PleromaAPI.BackupView do
end
def download_url(%Backup{file_name: file_name}) do
- Pleroma.Web.Endpoint.url() <> "/media/backups/" <> file_name
+ Pleroma.Upload.base_url() <> "/backups/" <> file_name
end
end
diff --git a/lib/pleroma/web/pleroma_api/views/chat/message_reference_view.ex b/lib/pleroma/web/pleroma_api/views/chat/message_reference_view.ex
index df48044e3..241bf0010 100644
--- a/lib/pleroma/web/pleroma_api/views/chat/message_reference_view.ex
+++ b/lib/pleroma/web/pleroma_api/views/chat/message_reference_view.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.PleromaAPI.Chat.MessageReferenceView do
diff --git a/lib/pleroma/web/pleroma_api/views/chat_view.ex b/lib/pleroma/web/pleroma_api/views/chat_view.ex
index 04dc20d51..db6c13c05 100644
--- a/lib/pleroma/web/pleroma_api/views/chat_view.ex
+++ b/lib/pleroma/web/pleroma_api/views/chat_view.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.PleromaAPI.ChatView do
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
index 000000000..2c9c8d1c6
--- /dev/null
+++ b/lib/pleroma/web/pleroma_api/views/conversation_view.ex
@@ -0,0 +1,10 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 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/emoji_reaction_view.ex b/lib/pleroma/web/pleroma_api/views/emoji_reaction_view.ex
index 110e8a041..68ebd8292 100644
--- a/lib/pleroma/web/pleroma_api/views/emoji_reaction_view.ex
+++ b/lib/pleroma/web/pleroma_api/views/emoji_reaction_view.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.PleromaAPI.EmojiReactionView do
@@ -26,7 +26,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiReactionView do
user_ap_ids
|> Enum.map(&Pleroma.User.get_cached_by_ap_id/1)
|> Enum.filter(fn
- %{deactivated: false} -> true
+ %{is_active: true} -> true
_ -> false
end)
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
index 000000000..db7f94803
--- /dev/null
+++ b/lib/pleroma/web/pleroma_api/views/notification_view.ex
@@ -0,0 +1,10 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 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
diff --git a/lib/pleroma/web/pleroma_api/views/report_view.ex b/lib/pleroma/web/pleroma_api/views/report_view.ex
new file mode 100644
index 000000000..127e8f0cb
--- /dev/null
+++ b/lib/pleroma/web/pleroma_api/views/report_view.ex
@@ -0,0 +1,55 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.PleromaAPI.ReportView do
+ use Pleroma.Web, :view
+
+ alias Pleroma.HTML
+ alias Pleroma.Web.AdminAPI.Report
+ alias Pleroma.Web.CommonAPI.Utils
+ alias Pleroma.Web.MastodonAPI.AccountView
+ alias Pleroma.Web.MastodonAPI.StatusView
+
+ def render("index.json", %{reports: reports, for: for_user}) do
+ %{
+ reports:
+ reports[:items]
+ |> Enum.map(&Report.extract_report_info/1)
+ |> Enum.map(&render(__MODULE__, "show.json", Map.put(&1, :for, for_user))),
+ total: reports[:total]
+ }
+ end
+
+ def render("show.json", %{
+ report: report,
+ user: actor,
+ account: account,
+ statuses: statuses,
+ for: for_user
+ }) do
+ created_at = Utils.to_masto_date(report.data["published"])
+
+ content =
+ unless is_nil(report.data["content"]) do
+ HTML.filter_tags(report.data["content"])
+ else
+ nil
+ end
+
+ %{
+ id: report.id,
+ account: AccountView.render("show.json", %{user: account, for: for_user}),
+ actor: AccountView.render("show.json", %{user: actor, for: for_user}),
+ content: content,
+ created_at: created_at,
+ statuses:
+ StatusView.render("index.json", %{
+ activities: statuses,
+ as: :activity,
+ for: for_user
+ }),
+ state: report.data["state"]
+ }
+ end
+end
diff --git a/lib/pleroma/web/pleroma_api/views/scrobble_view.ex b/lib/pleroma/web/pleroma_api/views/scrobble_view.ex
index 95bd4c368..a5985fb2a 100644
--- a/lib/pleroma/web/pleroma_api/views/scrobble_view.ex
+++ b/lib/pleroma/web/pleroma_api/views/scrobble_view.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.PleromaAPI.ScrobbleView do
@@ -15,7 +15,7 @@ defmodule Pleroma.Web.PleromaAPI.ScrobbleView do
alias Pleroma.Web.MastodonAPI.AccountView
def render("show.json", %{activity: %Activity{data: %{"type" => "Listen"}} = activity} = opts) do
- object = Object.normalize(activity)
+ object = Object.normalize(activity, fetch: false)
user = CommonAPI.get_user(activity.data["actor"])
created_at = Utils.to_masto_date(activity.data["published"])
diff --git a/lib/pleroma/web/plug.ex b/lib/pleroma/web/plug.ex
index 840b35072..29ab52e55 100644
--- a/lib/pleroma/web/plug.ex
+++ b/lib/pleroma/web/plug.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Plug do
diff --git a/lib/pleroma/web/plugs/admin_secret_authentication_plug.ex b/lib/pleroma/web/plugs/admin_secret_authentication_plug.ex
index ff851a874..2e4702fa1 100644
--- a/lib/pleroma/web/plugs/admin_secret_authentication_plug.ex
+++ b/lib/pleroma/web/plugs/admin_secret_authentication_plug.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Plugs.AdminSecretAuthenticationPlug do
diff --git a/lib/pleroma/web/plugs/authentication_plug.ex b/lib/pleroma/web/plugs/authentication_plug.ex
index a7b8a9bfe..a7fd697b5 100644
--- a/lib/pleroma/web/plugs/authentication_plug.ex
+++ b/lib/pleroma/web/plugs/authentication_plug.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Plugs.AuthenticationPlug do
@@ -48,7 +48,7 @@ defmodule Pleroma.Web.Plugs.AuthenticationPlug do
end
def checkpw(password, "$pbkdf2" <> _ = password_hash) do
- Pbkdf2.verify_pass(password, password_hash)
+ Pleroma.Password.Pbkdf2.verify_pass(password, password_hash)
end
def checkpw(_password, _password_hash) do
diff --git a/lib/pleroma/web/plugs/basic_auth_decoder_plug.ex b/lib/pleroma/web/plugs/basic_auth_decoder_plug.ex
index 97529aedb..3eb13f955 100644
--- a/lib/pleroma/web/plugs/basic_auth_decoder_plug.ex
+++ b/lib/pleroma/web/plugs/basic_auth_decoder_plug.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Plugs.BasicAuthDecoderPlug do
diff --git a/lib/pleroma/web/plugs/cache.ex b/lib/pleroma/web/plugs/cache.ex
index 18880716a..667477857 100644
--- a/lib/pleroma/web/plugs/cache.ex
+++ b/lib/pleroma/web/plugs/cache.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Plugs.Cache do
@@ -97,13 +97,21 @@ defmodule Pleroma.Web.Plugs.Cache do
key = cache_key(conn, opts)
content_type = content_type(conn)
+ should_cache = not Map.get(conn.assigns, :skip_cache, false)
+
conn =
unless opts[:tracking_fun] do
- @cachex.put(:web_resp_cache, key, {content_type, body}, ttl: ttl)
+ if should_cache do
+ @cachex.put(:web_resp_cache, key, {content_type, body}, ttl: ttl)
+ end
+
conn
else
tracking_fun_data = Map.get(conn.assigns, :tracking_fun_data, nil)
- @cachex.put(:web_resp_cache, key, {content_type, body, tracking_fun_data}, ttl: ttl)
+
+ if should_cache do
+ @cachex.put(:web_resp_cache, key, {content_type, body, tracking_fun_data}, ttl: ttl)
+ end
opts.tracking_fun.(conn, tracking_fun_data)
end
diff --git a/lib/pleroma/web/plugs/digest_plug.ex b/lib/pleroma/web/plugs/digest_plug.ex
index fb2723b97..20e265f8f 100644
--- a/lib/pleroma/web/plugs/digest_plug.ex
+++ b/lib/pleroma/web/plugs/digest_plug.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Plugs.DigestPlug do
diff --git a/lib/pleroma/web/plugs/ensure_authenticated_plug.ex b/lib/pleroma/web/plugs/ensure_authenticated_plug.ex
index ea2af6881..8044a6bb2 100644
--- a/lib/pleroma/web/plugs/ensure_authenticated_plug.ex
+++ b/lib/pleroma/web/plugs/ensure_authenticated_plug.ex
@@ -1,8 +1,12 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Plugs.EnsureAuthenticatedPlug do
+ @moduledoc """
+ Ensures _user_ authentication (app-bound user-unbound tokens are not accepted).
+ """
+
import Plug.Conn
import Pleroma.Web.TranslationHelpers
diff --git a/lib/pleroma/web/plugs/ensure_public_or_authenticated_plug.ex b/lib/pleroma/web/plugs/ensure_public_or_authenticated_plug.ex
index 3bebdac6d..e98a3b661 100644
--- a/lib/pleroma/web/plugs/ensure_public_or_authenticated_plug.ex
+++ b/lib/pleroma/web/plugs/ensure_public_or_authenticated_plug.ex
@@ -1,8 +1,13 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug do
+ @moduledoc """
+ Ensures instance publicity or _user_ authentication
+ (app-bound user-unbound tokens are accepted only if the instance is public).
+ """
+
import Pleroma.Web.TranslationHelpers
import Plug.Conn
diff --git a/lib/pleroma/web/plugs/ensure_staff_privileged_plug.ex b/lib/pleroma/web/plugs/ensure_staff_privileged_plug.ex
new file mode 100644
index 000000000..3c2109496
--- /dev/null
+++ b/lib/pleroma/web/plugs/ensure_staff_privileged_plug.ex
@@ -0,0 +1,36 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.EnsureStaffPrivilegedPlug do
+ @moduledoc """
+ Ensures staff are privileged enough to do certain tasks.
+ """
+ import Pleroma.Web.TranslationHelpers
+ import Plug.Conn
+
+ alias Pleroma.Config
+ alias Pleroma.User
+
+ def init(options) do
+ options
+ end
+
+ def call(%{assigns: %{user: %User{is_admin: true}}} = conn, _), do: conn
+
+ def call(%{assigns: %{user: %User{is_moderator: true}}} = conn, _) do
+ if Config.get!([:instance, :privileged_staff]) do
+ conn
+ else
+ conn
+ |> render_error(:forbidden, "User is not an admin.")
+ |> halt()
+ end
+ end
+
+ def call(conn, _) do
+ conn
+ |> render_error(:forbidden, "User is not a staff member.")
+ |> halt()
+ end
+end
diff --git a/lib/pleroma/web/plugs/ensure_user_token_assigns_plug.ex b/lib/pleroma/web/plugs/ensure_user_token_assigns_plug.ex
index 4253458b2..5c57d2707 100644
--- a/lib/pleroma/web/plugs/ensure_user_token_assigns_plug.ex
+++ b/lib/pleroma/web/plugs/ensure_user_token_assigns_plug.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Plugs.EnsureUserTokenAssignsPlug do
@@ -28,6 +28,11 @@ defmodule Pleroma.Web.Plugs.EnsureUserTokenAssignsPlug do
end
end
+ # App-bound token case (obtained with client_id and client_secret)
+ def call(%{assigns: %{token: %Token{user_id: nil}}} = conn, _) do
+ assign(conn, :user, nil)
+ end
+
def call(conn, _) do
conn
|> assign(:user, nil)
diff --git a/lib/pleroma/web/plugs/expect_authenticated_check_plug.ex b/lib/pleroma/web/plugs/expect_authenticated_check_plug.ex
index 0925ded4d..d1403e2bd 100644
--- a/lib/pleroma/web/plugs/expect_authenticated_check_plug.ex
+++ b/lib/pleroma/web/plugs/expect_authenticated_check_plug.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Plugs.ExpectAuthenticatedCheckPlug do
diff --git a/lib/pleroma/web/plugs/expect_public_or_authenticated_check_plug.ex b/lib/pleroma/web/plugs/expect_public_or_authenticated_check_plug.ex
index ace512a78..a74c76395 100644
--- a/lib/pleroma/web/plugs/expect_public_or_authenticated_check_plug.ex
+++ b/lib/pleroma/web/plugs/expect_public_or_authenticated_check_plug.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Plugs.ExpectPublicOrAuthenticatedCheckPlug do
diff --git a/lib/pleroma/web/plugs/federating_plug.ex b/lib/pleroma/web/plugs/federating_plug.ex
index 3c90a7644..d5b8ef3ed 100644
--- a/lib/pleroma/web/plugs/federating_plug.ex
+++ b/lib/pleroma/web/plugs/federating_plug.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Plugs.FederatingPlug do
diff --git a/lib/pleroma/web/plugs/frontend_static.ex b/lib/pleroma/web/plugs/frontend_static.ex
index 1b0b36813..6ab8e4667 100644
--- a/lib/pleroma/web/plugs/frontend_static.ex
+++ b/lib/pleroma/web/plugs/frontend_static.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Plugs.FrontendStatic do
@@ -34,7 +34,8 @@ defmodule Pleroma.Web.Plugs.FrontendStatic do
end
def call(conn, opts) do
- with false <- invalid_path?(conn.path_info),
+ with false <- api_route?(conn.path_info),
+ false <- invalid_path?(conn.path_info),
frontend_type <- Map.get(opts, :frontend_type, :primary),
path when not is_nil(path) <- file_path("", frontend_type) do
call_static(conn, opts, path)
@@ -52,6 +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?([]), 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)
diff --git a/lib/pleroma/web/plugs/http_security_plug.ex b/lib/pleroma/web/plugs/http_security_plug.ex
index 45aaf188e..34895c8d5 100644
--- a/lib/pleroma/web/plugs/http_security_plug.ex
+++ b/lib/pleroma/web/plugs/http_security_plug.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Plugs.HTTPSecurityPlug do
@@ -20,9 +20,26 @@ defmodule Pleroma.Web.Plugs.HTTPSecurityPlug do
end
end
- defp headers do
+ def primary_frontend do
+ with %{"name" => frontend} <- Config.get([:frontends, :primary]),
+ available <- Config.get([:frontends, :available]),
+ %{} = primary_frontend <- Map.get(available, frontend) do
+ {:ok, primary_frontend}
+ end
+ end
+
+ def custom_http_frontend_headers do
+ with {:ok, %{"custom-http-headers" => custom_headers}} <- primary_frontend() do
+ custom_headers
+ else
+ _ -> []
+ end
+ end
+
+ def headers do
referrer_policy = Config.get([:http_security, :referrer_policy])
report_uri = Config.get([:http_security, :report_uri])
+ custom_http_frontend_headers = custom_http_frontend_headers()
headers = [
{"x-xss-protection", "1; mode=block"},
@@ -31,9 +48,17 @@ 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 =
+ if custom_http_frontend_headers do
+ custom_http_frontend_headers ++ headers
+ else
+ headers
+ end
+
if report_uri do
report_group = %{
"group" => "csp-endpoint",
@@ -43,7 +68,7 @@ defmodule Pleroma.Web.Plugs.HTTPSecurityPlug do
]
}
- [{"reply-to", Jason.encode!(report_group)} | headers]
+ [{"report-to", Jason.encode!(report_group)} | headers]
else
headers
end
@@ -92,7 +117,7 @@ defmodule Pleroma.Web.Plugs.HTTPSecurityPlug do
if Config.get(:env) == :dev do
"script-src 'self' 'unsafe-eval'"
else
- "script-src 'self'"
+ "script-src 'self' 'wasm-unsafe-eval'"
end
report = if report_uri, do: ["report-uri ", report_uri, ";report-to csp-endpoint"]
diff --git a/lib/pleroma/web/plugs/http_signature_plug.ex b/lib/pleroma/web/plugs/http_signature_plug.ex
index 036e2a773..4bf325218 100644
--- a/lib/pleroma/web/plugs/http_signature_plug.ex
+++ b/lib/pleroma/web/plugs/http_signature_plug.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Plugs.HTTPSignaturePlug do
@@ -25,21 +25,58 @@ defmodule Pleroma.Web.Plugs.HTTPSignaturePlug do
end
end
+ defp validate_signature(conn, request_target) do
+ # Newer drafts for HTTP signatures now use @request-target instead of the
+ # old (request-target). We'll now support both for incoming signatures.
+ conn =
+ conn
+ |> put_req_header("(request-target)", request_target)
+ |> put_req_header("@request-target", request_target)
+
+ HTTPSignatures.validate_conn(conn)
+ end
+
+ defp validate_signature(conn) do
+ # This (request-target) is non-standard, but many implementations do it
+ # this way due to a misinterpretation of
+ # https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures-06
+ # "path" was interpreted as not having the query, though later examples
+ # show that it must be the absolute path + query. This behavior is kept to
+ # make sure most software (Pleroma itself, Mastodon, and probably others)
+ # do not break.
+ request_target = String.downcase("#{conn.method}") <> " #{conn.request_path}"
+
+ # This is the proper way to build the @request-target, as expected by
+ # many HTTP signature libraries, clarified in the following draft:
+ # https://www.ietf.org/archive/id/draft-ietf-httpbis-message-signatures-11.html#section-2.2.6
+ # It is the same as before, but containing the query part as well.
+ proper_target = request_target <> "?#{conn.query_string}"
+
+ cond do
+ # Normal, non-standard behavior but expected by Pleroma and more.
+ validate_signature(conn, request_target) ->
+ true
+
+ # Has query string and the previous one failed: let's try the standard.
+ conn.query_string != "" ->
+ validate_signature(conn, proper_target)
+
+ # If there's no query string and signature fails, it's rotten.
+ true ->
+ false
+ end
+ end
+
defp maybe_assign_valid_signature(conn) do
if has_signature_header?(conn) do
- # set (request-target) header to the appropriate value
- # we also replace the digest header with the one we computed
- request_target = String.downcase("#{conn.method}") <> " #{conn.request_path}"
-
+ # we replace the digest header with the one we computed in DigestPlug
conn =
- conn
- |> put_req_header("(request-target)", request_target)
- |> case do
+ case conn do
%{assigns: %{digest: digest}} = conn -> put_req_header(conn, "digest", digest)
conn -> conn
end
- assign(conn, :valid_signature, HTTPSignatures.validate_conn(conn))
+ assign(conn, :valid_signature, validate_signature(conn))
else
Logger.debug("No signature header!")
conn
diff --git a/lib/pleroma/web/plugs/idempotency_plug.ex b/lib/pleroma/web/plugs/idempotency_plug.ex
index 4f908779c..a3b7af869 100644
--- a/lib/pleroma/web/plugs/idempotency_plug.ex
+++ b/lib/pleroma/web/plugs/idempotency_plug.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Plugs.IdempotencyPlug do
diff --git a/lib/pleroma/web/plugs/instance_static.ex b/lib/pleroma/web/plugs/instance_static.ex
index 54b9175df..75bfdd65b 100644
--- a/lib/pleroma/web/plugs/instance_static.ex
+++ b/lib/pleroma/web/plugs/instance_static.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Plugs.InstanceStatic do
diff --git a/lib/pleroma/web/plugs/mapped_signature_to_identity_plug.ex b/lib/pleroma/web/plugs/mapped_signature_to_identity_plug.ex
index a0a0c5a9b..c6d531086 100644
--- a/lib/pleroma/web/plugs/mapped_signature_to_identity_plug.ex
+++ b/lib/pleroma/web/plugs/mapped_signature_to_identity_plug.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Plugs.MappedSignatureToIdentityPlug do
diff --git a/lib/pleroma/web/plugs/o_auth_plug.ex b/lib/pleroma/web/plugs/o_auth_plug.ex
index eb287318b..ba04ddb72 100644
--- a/lib/pleroma/web/plugs/o_auth_plug.ex
+++ b/lib/pleroma/web/plugs/o_auth_plug.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Plugs.OAuthPlug do
@@ -47,15 +47,17 @@ defmodule Pleroma.Web.Plugs.OAuthPlug do
#
@spec fetch_user_and_token(String.t()) :: {:ok, User.t(), Token.t()} | nil
defp fetch_user_and_token(token) do
- query =
+ token_query =
from(t in Token,
- where: t.token == ^token,
- join: user in assoc(t, :user),
- preload: [user: user]
+ where: t.token == ^token
)
- with %Token{user: user} = token_record <- Repo.one(query) do
+ with %Token{user_id: user_id} = token_record <- Repo.one(token_query),
+ false <- is_nil(user_id),
+ %User{} = user <- User.get_cached_by_id(user_id) do
{:ok, user, token_record}
+ else
+ _ -> nil
end
end
diff --git a/lib/pleroma/web/plugs/o_auth_scopes_plug.ex b/lib/pleroma/web/plugs/o_auth_scopes_plug.ex
index e6d398b14..faf0fd8c6 100644
--- a/lib/pleroma/web/plugs/o_auth_scopes_plug.ex
+++ b/lib/pleroma/web/plugs/o_auth_scopes_plug.ex
@@ -1,12 +1,11 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Plugs.OAuthScopesPlug do
import Plug.Conn
import Pleroma.Web.Gettext
- alias Pleroma.Config
alias Pleroma.Helpers.AuthHelper
use Pleroma.Web, :plug
@@ -18,7 +17,6 @@ defmodule Pleroma.Web.Plugs.OAuthScopesPlug do
op = options[:op] || :|
token = assigns[:token]
- scopes = transform_scopes(scopes, options)
matched_scopes = (token && filter_descendants(scopes, token.scopes)) || []
cond do
@@ -57,13 +55,4 @@ defmodule Pleroma.Web.Plugs.OAuthScopesPlug do
end
)
end
-
- @doc "Transforms scopes by applying supported options (e.g. :admin)"
- def transform_scopes(scopes, options) do
- if options[:admin] do
- Config.oauth_admin_scopes(scopes)
- else
- scopes
- end
- end
end
diff --git a/lib/pleroma/web/plugs/plug_helper.ex b/lib/pleroma/web/plugs/plug_helper.ex
index b314e7596..21bf03523 100644
--- a/lib/pleroma/web/plugs/plug_helper.ex
+++ b/lib/pleroma/web/plugs/plug_helper.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Plugs.PlugHelper do
diff --git a/lib/pleroma/web/plugs/rate_limiter.ex b/lib/pleroma/web/plugs/rate_limiter.ex
index 034a5bbe2..2080b06bd 100644
--- a/lib/pleroma/web/plugs/rate_limiter.ex
+++ b/lib/pleroma/web/plugs/rate_limiter.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Plugs.RateLimiter do
diff --git a/lib/pleroma/web/plugs/rate_limiter/limiter_supervisor.ex b/lib/pleroma/web/plugs/rate_limiter/limiter_supervisor.ex
index 5642bb205..a96be670a 100644
--- a/lib/pleroma/web/plugs/rate_limiter/limiter_supervisor.ex
+++ b/lib/pleroma/web/plugs/rate_limiter/limiter_supervisor.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Plugs.RateLimiter.LimiterSupervisor do
diff --git a/lib/pleroma/web/plugs/rate_limiter/supervisor.ex b/lib/pleroma/web/plugs/rate_limiter/supervisor.ex
index a1c84063d..f00f3d95e 100644
--- a/lib/pleroma/web/plugs/rate_limiter/supervisor.ex
+++ b/lib/pleroma/web/plugs/rate_limiter/supervisor.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Plugs.RateLimiter.Supervisor do
diff --git a/lib/pleroma/web/plugs/remote_ip.ex b/lib/pleroma/web/plugs/remote_ip.ex
index 401e2cbfa..f207d9fef 100644
--- a/lib/pleroma/web/plugs/remote_ip.ex
+++ b/lib/pleroma/web/plugs/remote_ip.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Plugs.RemoteIp do
diff --git a/lib/pleroma/web/plugs/set_format_plug.ex b/lib/pleroma/web/plugs/set_format_plug.ex
index c16d2f81d..84c67e7f0 100644
--- a/lib/pleroma/web/plugs/set_format_plug.ex
+++ b/lib/pleroma/web/plugs/set_format_plug.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Plugs.SetFormatPlug do
diff --git a/lib/pleroma/web/plugs/set_locale_plug.ex b/lib/pleroma/web/plugs/set_locale_plug.ex
index d9d24b93f..271912ace 100644
--- a/lib/pleroma/web/plugs/set_locale_plug.ex
+++ b/lib/pleroma/web/plugs/set_locale_plug.ex
@@ -1,23 +1,61 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
# NOTE: this module is based on https://github.com/smeevil/set_locale
defmodule Pleroma.Web.Plugs.SetLocalePlug do
import Plug.Conn, only: [get_req_header: 2, assign: 3]
+ def frontend_language_cookie_name, do: "userLanguage"
+
def init(_), do: nil
def call(conn, _) do
- locale = get_locale_from_header(conn) || Gettext.get_locale()
- Gettext.put_locale(locale)
- assign(conn, :locale, locale)
+ locales = get_locales_from_header(conn)
+ first_locale = Enum.at(locales, 0, Gettext.get_locale())
+
+ Pleroma.Web.Gettext.put_locales(locales)
+
+ conn
+ |> assign(:locale, first_locale)
+ |> assign(:locales, locales)
end
- defp get_locale_from_header(conn) do
+ defp get_locales_from_header(conn) do
conn
- |> extract_accept_language()
- |> Enum.find(&supported_locale?/1)
+ |> extract_preferred_language()
+ |> normalize_language_codes()
+ |> all_supported()
+ |> Enum.uniq()
+ end
+
+ defp all_supported(locales) do
+ locales
+ |> Pleroma.Web.Gettext.ensure_fallbacks()
+ |> Enum.filter(&supported_locale?/1)
+ end
+
+ defp normalize_language_codes(codes) do
+ codes
+ |> Enum.map(fn code -> Pleroma.Web.Gettext.normalize_locale(code) end)
+ end
+
+ defp extract_preferred_language(conn) do
+ extract_frontend_language(conn) ++ extract_accept_language(conn)
+ end
+
+ defp extract_frontend_language(conn) do
+ %{req_cookies: cookies} =
+ conn
+ |> Plug.Conn.fetch_cookies()
+
+ case cookies[frontend_language_cookie_name()] do
+ nil ->
+ []
+
+ fe_lang ->
+ String.split(fe_lang, ",")
+ end
end
defp extract_accept_language(conn) do
@@ -29,7 +67,6 @@ defmodule Pleroma.Web.Plugs.SetLocalePlug do
|> Enum.sort(&(&1.quality > &2.quality))
|> Enum.map(& &1.tag)
|> Enum.reject(&is_nil/1)
- |> ensure_language_fallbacks()
_ ->
[]
@@ -37,9 +74,7 @@ defmodule Pleroma.Web.Plugs.SetLocalePlug do
end
defp supported_locale?(locale) do
- Pleroma.Web.Gettext
- |> Gettext.known_locales()
- |> Enum.member?(locale)
+ Pleroma.Web.Gettext.supports_locale?(locale)
end
defp parse_language_option(string) do
@@ -53,11 +88,4 @@ defmodule Pleroma.Web.Plugs.SetLocalePlug do
%{tag: captures["tag"], quality: quality}
end
-
- defp ensure_language_fallbacks(tags) do
- Enum.flat_map(tags, fn tag ->
- [language | _] = String.split(tag, "-")
- if Enum.member?(tags, language), do: [tag], else: [tag, language]
- end)
- end
end
diff --git a/lib/pleroma/web/plugs/set_user_session_id_plug.ex b/lib/pleroma/web/plugs/set_user_session_id_plug.ex
index 9f4a6b6ac..c37214573 100644
--- a/lib/pleroma/web/plugs/set_user_session_id_plug.ex
+++ b/lib/pleroma/web/plugs/set_user_session_id_plug.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Plugs.SetUserSessionIdPlug do
diff --git a/lib/pleroma/web/plugs/static_fe_plug.ex b/lib/pleroma/web/plugs/static_fe_plug.ex
index 658a1052e..9a364fdbc 100644
--- a/lib/pleroma/web/plugs/static_fe_plug.ex
+++ b/lib/pleroma/web/plugs/static_fe_plug.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Plugs.StaticFEPlug do
diff --git a/lib/pleroma/web/plugs/trailing_format_plug.ex b/lib/pleroma/web/plugs/trailing_format_plug.ex
index e3f57c14a..a883ba541 100644
--- a/lib/pleroma/web/plugs/trailing_format_plug.ex
+++ b/lib/pleroma/web/plugs/trailing_format_plug.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Plugs.TrailingFormatPlug do
diff --git a/lib/pleroma/web/plugs/uploaded_media.ex b/lib/pleroma/web/plugs/uploaded_media.ex
index 402a8bb34..ad8143234 100644
--- a/lib/pleroma/web/plugs/uploaded_media.ex
+++ b/lib/pleroma/web/plugs/uploaded_media.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Plugs.UploadedMedia do
@@ -62,7 +62,7 @@ defmodule Pleroma.Web.Plugs.UploadedMedia do
def call(conn, _opts), do: conn
defp media_is_banned(%{request_path: path} = _conn, {:static_dir, _}) do
- MediaProxy.in_banned_urls(Pleroma.Web.base_url() <> path)
+ MediaProxy.in_banned_urls(Pleroma.Upload.base_url() <> path)
end
defp media_is_banned(_, {:url, url}), do: MediaProxy.in_banned_urls(url)
@@ -87,8 +87,15 @@ defmodule Pleroma.Web.Plugs.UploadedMedia do
end
defp get_media(conn, {:url, url}, true, _) do
+ proxy_opts = [
+ http: [
+ follow_redirect: true,
+ pool: :upload
+ ]
+ ]
+
conn
- |> Pleroma.ReverseProxy.call(url, Pleroma.Config.get([Pleroma.Upload, :proxy_opts], []))
+ |> Pleroma.ReverseProxy.call(url, proxy_opts)
end
defp get_media(conn, {:url, url}, _, _) do
diff --git a/lib/pleroma/web/plugs/user_enabled_plug.ex b/lib/pleroma/web/plugs/user_enabled_plug.ex
index 4f1b163bd..ca104166a 100644
--- a/lib/pleroma/web/plugs/user_enabled_plug.ex
+++ b/lib/pleroma/web/plugs/user_enabled_plug.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Plugs.UserEnabledPlug do
diff --git a/lib/pleroma/web/plugs/user_fetcher_plug.ex b/lib/pleroma/web/plugs/user_fetcher_plug.ex
index 89e16b49f..87bc27393 100644
--- a/lib/pleroma/web/plugs/user_fetcher_plug.ex
+++ b/lib/pleroma/web/plugs/user_fetcher_plug.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Plugs.UserFetcherPlug do
diff --git a/lib/pleroma/web/plugs/user_is_admin_plug.ex b/lib/pleroma/web/plugs/user_is_admin_plug.ex
index 531c965f0..548eb9b9a 100644
--- a/lib/pleroma/web/plugs/user_is_admin_plug.ex
+++ b/lib/pleroma/web/plugs/user_is_admin_plug.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Plugs.UserIsAdminPlug do
diff --git a/lib/pleroma/web/plugs/user_is_staff_plug.ex b/lib/pleroma/web/plugs/user_is_staff_plug.ex
new file mode 100644
index 000000000..951e14674
--- /dev/null
+++ b/lib/pleroma/web/plugs/user_is_staff_plug.ex
@@ -0,0 +1,23 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.UserIsStaffPlug do
+ import Pleroma.Web.TranslationHelpers
+ import Plug.Conn
+
+ alias Pleroma.User
+
+ def init(options) do
+ options
+ end
+
+ def call(%{assigns: %{user: %User{is_admin: true}}} = conn, _), do: conn
+ def call(%{assigns: %{user: %User{is_moderator: true}}} = conn, _), do: conn
+
+ def call(conn, _) do
+ conn
+ |> render_error(:forbidden, "User is not a staff member.")
+ |> halt()
+ end
+end
diff --git a/lib/pleroma/web/plugs/user_tracking_plug.ex b/lib/pleroma/web/plugs/user_tracking_plug.ex
new file mode 100644
index 000000000..9b52fd505
--- /dev/null
+++ b/lib/pleroma/web/plugs/user_tracking_plug.ex
@@ -0,0 +1,30 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.UserTrackingPlug do
+ alias Pleroma.User
+
+ import Plug.Conn, only: [assign: 3]
+
+ @update_interval :timer.hours(24)
+
+ def init(opts), do: opts
+
+ def call(%{assigns: %{user: %User{id: id} = user}} = conn, _) when not is_nil(id) do
+ with true <- needs_update?(user),
+ {:ok, user} <- User.update_last_active_at(user) do
+ assign(conn, :user, user)
+ else
+ _ -> conn
+ end
+ end
+
+ def call(conn, _), do: conn
+
+ defp needs_update?(%User{last_active_at: nil}), do: true
+
+ defp needs_update?(%User{last_active_at: last_active_at}) do
+ NaiveDateTime.diff(NaiveDateTime.utc_now(), last_active_at, :millisecond) >= @update_interval
+ end
+end
diff --git a/lib/pleroma/web/preload.ex b/lib/pleroma/web/preload.ex
index 90e454468..4485383f9 100644
--- a/lib/pleroma/web/preload.ex
+++ b/lib/pleroma/web/preload.ex
@@ -1,10 +1,9 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Preload do
alias Phoenix.HTML
- require Logger
def build_tags(_conn, params) do
preload_data =
diff --git a/lib/pleroma/web/preload/providers/instance.ex b/lib/pleroma/web/preload/providers/instance.ex
index a549bb1eb..6183f7b70 100644
--- a/lib/pleroma/web/preload/providers/instance.ex
+++ b/lib/pleroma/web/preload/providers/instance.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Preload.Providers.Instance do
diff --git a/lib/pleroma/web/preload/providers/provider.ex b/lib/pleroma/web/preload/providers/provider.ex
index 7ef595a34..85e2badaa 100644
--- a/lib/pleroma/web/preload/providers/provider.ex
+++ b/lib/pleroma/web/preload/providers/provider.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Preload.Providers.Provider do
diff --git a/lib/pleroma/web/preload/providers/timelines.ex b/lib/pleroma/web/preload/providers/timelines.ex
index b279a865d..1031f48d2 100644
--- a/lib/pleroma/web/preload/providers/timelines.ex
+++ b/lib/pleroma/web/preload/providers/timelines.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Preload.Providers.Timelines do
diff --git a/lib/pleroma/web/preload/providers/user.ex b/lib/pleroma/web/preload/providers/user.ex
index b3d2e9b8d..74550cd56 100644
--- a/lib/pleroma/web/preload/providers/user.ex
+++ b/lib/pleroma/web/preload/providers/user.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Preload.Providers.User do
diff --git a/lib/pleroma/web/push.ex b/lib/pleroma/web/push.ex
index b80a6438d..9665b0b4a 100644
--- a/lib/pleroma/web/push.ex
+++ b/lib/pleroma/web/push.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Push do
diff --git a/lib/pleroma/web/push/impl.ex b/lib/pleroma/web/push/impl.ex
index 82152dffa..3c5f00764 100644
--- a/lib/pleroma/web/push/impl.ex
+++ b/lib/pleroma/web/push/impl.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Push.Impl do
@@ -16,7 +16,7 @@ defmodule Pleroma.Web.Push.Impl do
require Logger
import Ecto.Query
- @types ["Create", "Follow", "Announce", "Like", "Move", "EmojiReact"]
+ @types ["Create", "Follow", "Announce", "Like", "Move", "EmojiReact", "Update"]
@doc "Performs sending notifications for user subscriptions"
@spec perform(Notification.t()) :: list(any) | :error | {:error, :unknown_type}
@@ -32,7 +32,7 @@ defmodule Pleroma.Web.Push.Impl do
mastodon_type = notification.type
gcm_api_key = Application.get_env(:web_push_encryption, :gcm_api_key)
avatar_url = User.avatar_url(actor)
- object = Object.normalize(activity, false)
+ object = Object.normalize(activity, fetch: false)
user = User.get_cached_by_id(user_id)
direct_conversation_id = Activity.direct_conversation_id(activity, user)
@@ -124,8 +124,8 @@ defmodule Pleroma.Web.Push.Impl do
def format_body(activity, actor, object, mastodon_type \\ nil)
- def format_body(_activity, actor, %{data: %{"type" => "ChatMessage", "content" => content}}, _) do
- case content do
+ def format_body(_activity, actor, %{data: %{"type" => "ChatMessage"} = data}, _) do
+ case data["content"] do
nil -> "@#{actor.nickname}: (Attachment)"
content -> "@#{actor.nickname}: #{Utils.scrub_html_and_truncate(content, 80)}"
end
@@ -174,6 +174,15 @@ defmodule Pleroma.Web.Push.Impl do
end
end
+ def format_body(
+ %{activity: %{data: %{"type" => "Update"}}},
+ actor,
+ _object,
+ _mastodon_type
+ ) do
+ "@#{actor.nickname} edited a status"
+ end
+
def format_title(activity, mastodon_type \\ nil)
def format_title(%{activity: %{data: %{"directMessage" => true}}}, _mastodon_type) do
@@ -187,6 +196,7 @@ defmodule Pleroma.Web.Push.Impl do
"follow_request" -> "New Follow Request"
"reblog" -> "New Repeat"
"favourite" -> "New Favorite"
+ "update" -> "New Update"
"pleroma:chat_mention" -> "New Chat Message"
"pleroma:emoji_reaction" -> "New Reaction"
type -> "New #{String.capitalize(type || "event")}"
diff --git a/lib/pleroma/web/push/subscription.ex b/lib/pleroma/web/push/subscription.ex
index 749a573ba..6fc45bd61 100644
--- a/lib/pleroma/web/push/subscription.ex
+++ b/lib/pleroma/web/push/subscription.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Push.Subscription do
@@ -26,7 +26,7 @@ defmodule Pleroma.Web.Push.Subscription do
end
# credo:disable-for-next-line Credo.Check.Readability.MaxLineLength
- @supported_alert_types ~w[follow favourite mention reblog pleroma:chat_mention pleroma:emoji_reaction]a
+ @supported_alert_types ~w[follow favourite mention reblog poll pleroma:chat_mention pleroma:emoji_reaction]a
defp alerts(%{data: %{alerts: alerts}}) do
alerts = Map.take(alerts, @supported_alert_types)
diff --git a/lib/pleroma/web/rel_me.ex b/lib/pleroma/web/rel_me.ex
index 650c6a3fc..98fbc1c59 100644
--- a/lib/pleroma/web/rel_me.ex
+++ b/lib/pleroma/web/rel_me.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.RelMe do
diff --git a/lib/pleroma/web/rich_media/helpers.ex b/lib/pleroma/web/rich_media/helpers.ex
index 442bf9995..0488df30e 100644
--- a/lib/pleroma/web/rich_media/helpers.ex
+++ b/lib/pleroma/web/rich_media/helpers.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright _ 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.RichMedia.Helpers do
@@ -69,7 +69,7 @@ defmodule Pleroma.Web.RichMedia.Helpers do
def fetch_data_for_activity(%Activity{data: %{"type" => "Create"}} = activity) do
with true <- Config.get([:rich_media, :enabled]),
- %Object{} = object <- Object.normalize(activity) do
+ %Object{} = object <- Object.normalize(activity, fetch: false) do
fetch_data_for_object(object)
else
_ -> %{}
diff --git a/lib/pleroma/web/rich_media/parser.ex b/lib/pleroma/web/rich_media/parser.ex
index d7a491198..dbe81eabb 100644
--- a/lib/pleroma/web/rich_media/parser.ex
+++ b/lib/pleroma/web/rich_media/parser.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.RichMedia.Parser do
diff --git a/lib/pleroma/web/rich_media/parser/ttl.ex b/lib/pleroma/web/rich_media/parser/ttl.ex
index 8353f0fff..59d7f87ab 100644
--- a/lib/pleroma/web/rich_media/parser/ttl.ex
+++ b/lib/pleroma/web/rich_media/parser/ttl.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.RichMedia.Parser.TTL do
diff --git a/lib/pleroma/web/rich_media/parser/ttl/aws_signed_url.ex b/lib/pleroma/web/rich_media/parser/ttl/aws_signed_url.ex
index fc4ef79c0..fa41c160d 100644
--- a/lib/pleroma/web/rich_media/parser/ttl/aws_signed_url.ex
+++ b/lib/pleroma/web/rich_media/parser/ttl/aws_signed_url.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.RichMedia.Parser.TTL.AwsSignedUrl do
diff --git a/lib/pleroma/web/rich_media/parsers/meta_tags_parser.ex b/lib/pleroma/web/rich_media/parsers/meta_tags_parser.ex
index 3d577e254..320a5f515 100644
--- a/lib/pleroma/web/rich_media/parsers/meta_tags_parser.ex
+++ b/lib/pleroma/web/rich_media/parsers/meta_tags_parser.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.RichMedia.Parsers.MetaTagsParser do
diff --git a/lib/pleroma/web/rich_media/parsers/o_embed.ex b/lib/pleroma/web/rich_media/parsers/o_embed.ex
index 1fe6729c3..75318d9c7 100644
--- a/lib/pleroma/web/rich_media/parsers/o_embed.ex
+++ b/lib/pleroma/web/rich_media/parsers/o_embed.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.RichMedia.Parsers.OEmbed do
diff --git a/lib/pleroma/web/rich_media/parsers/ogp.ex b/lib/pleroma/web/rich_media/parsers/ogp.ex
index b3b3b059c..b7f2b4216 100644
--- a/lib/pleroma/web/rich_media/parsers/ogp.ex
+++ b/lib/pleroma/web/rich_media/parsers/ogp.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.RichMedia.Parsers.OGP do
diff --git a/lib/pleroma/web/rich_media/parsers/twitter_card.ex b/lib/pleroma/web/rich_media/parsers/twitter_card.ex
index 4a04865d2..cc653729d 100644
--- a/lib/pleroma/web/rich_media/parsers/twitter_card.ex
+++ b/lib/pleroma/web/rich_media/parsers/twitter_card.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.RichMedia.Parsers.TwitterCard do
diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex
index aefc9f0be..2ea3ea7c1 100644
--- a/lib/pleroma/web/router.ex
+++ b/lib/pleroma/web/router.ex
@@ -1,9 +1,10 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Router do
use Pleroma.Web, :router
+ import Phoenix.LiveDashboard.Router
pipeline :accepts_html do
plug(:accepts, ["html"])
@@ -37,11 +38,13 @@ defmodule Pleroma.Web.Router do
plug(Pleroma.Web.Plugs.EnsureUserTokenAssignsPlug)
end
- pipeline :expect_authentication do
+ # Note: expects _user_ authentication (user-unbound app-bound tokens don't qualify)
+ pipeline :expect_user_authentication do
plug(Pleroma.Web.Plugs.ExpectAuthenticatedCheckPlug)
end
- pipeline :expect_public_instance_or_authentication do
+ # Note: expects public instance or _user_ authentication (user-unbound tokens don't qualify)
+ pipeline :expect_public_instance_or_user_authentication do
plug(Pleroma.Web.Plugs.ExpectPublicOrAuthenticatedCheckPlug)
end
@@ -56,6 +59,7 @@ defmodule Pleroma.Web.Router do
plug(Pleroma.Web.Plugs.UserEnabledPlug)
plug(Pleroma.Web.Plugs.SetUserSessionIdPlug)
plug(Pleroma.Web.Plugs.EnsureUserTokenAssignsPlug)
+ plug(Pleroma.Web.Plugs.UserTrackingPlug)
end
pipeline :base_api do
@@ -65,35 +69,44 @@ defmodule Pleroma.Web.Router do
plug(OpenApiSpex.Plug.PutApiSpec, module: Pleroma.Web.ApiSpec)
end
- pipeline :api do
- plug(:expect_public_instance_or_authentication)
+ pipeline :no_auth_or_privacy_expectations_api do
plug(:base_api)
plug(:after_auth)
plug(Pleroma.Web.Plugs.IdempotencyPlug)
end
+ # Pipeline for app-related endpoints (no user auth checks — app-bound tokens must be supported)
+ pipeline :app_api do
+ plug(:no_auth_or_privacy_expectations_api)
+ end
+
+ pipeline :api do
+ plug(:expect_public_instance_or_user_authentication)
+ plug(:no_auth_or_privacy_expectations_api)
+ end
+
pipeline :authenticated_api do
- plug(:expect_authentication)
- plug(:base_api)
- plug(:after_auth)
+ plug(:expect_user_authentication)
+ plug(:no_auth_or_privacy_expectations_api)
plug(Pleroma.Web.Plugs.EnsureAuthenticatedPlug)
- plug(Pleroma.Web.Plugs.IdempotencyPlug)
end
pipeline :admin_api do
- plug(:expect_authentication)
+ plug(:expect_user_authentication)
plug(:base_api)
plug(Pleroma.Web.Plugs.AdminSecretAuthenticationPlug)
plug(:after_auth)
plug(Pleroma.Web.Plugs.EnsureAuthenticatedPlug)
- plug(Pleroma.Web.Plugs.UserIsAdminPlug)
+ plug(Pleroma.Web.Plugs.UserIsStaffPlug)
plug(Pleroma.Web.Plugs.IdempotencyPlug)
end
- pipeline :mastodon_html do
- plug(:browser)
- plug(:authenticate)
- plug(:after_auth)
+ pipeline :require_privileged_staff do
+ plug(Pleroma.Web.Plugs.EnsureStaffPrivilegedPlug)
+ end
+
+ pipeline :require_admin do
+ plug(Pleroma.Web.Plugs.UserIsAdminPlug)
end
pipeline :pleroma_html do
@@ -130,7 +143,11 @@ defmodule Pleroma.Web.Router do
plug(Pleroma.Web.Plugs.MappedSignatureToIdentityPlug)
end
- scope "/api/pleroma", Pleroma.Web.TwitterAPI do
+ pipeline :static_fe do
+ plug(Pleroma.Web.Plugs.StaticFEPlug)
+ end
+
+ scope "/api/v1/pleroma", Pleroma.Web.TwitterAPI do
pipe_through(:pleroma_api)
get("/password_reset/:token", PasswordController, :reset, as: :reset_password)
@@ -138,19 +155,19 @@ defmodule Pleroma.Web.Router do
get("/emoji", UtilController, :emoji)
get("/captcha", UtilController, :captcha)
get("/healthcheck", UtilController, :healthcheck)
+ post("/remote_interaction", UtilController, :remote_interaction)
end
- scope "/api/pleroma", Pleroma.Web do
+ scope "/api/v1/pleroma", Pleroma.Web do
pipe_through(:pleroma_api)
post("/uploader_callback/:upload_path", UploaderController, :callback)
end
- scope "/api/pleroma/admin", Pleroma.Web.AdminAPI do
- pipe_through(:admin_api)
+ # AdminAPI: only admins can perform these actions
+ scope "/api/v1/pleroma/admin", Pleroma.Web.AdminAPI do
+ pipe_through([:admin_api, :require_admin])
put("/users/disable_mfa", AdminAPIController, :disable_mfa)
- put("/users/tag", AdminAPIController, :tag_users)
- delete("/users/tag", AdminAPIController, :untag_users)
get("/users/:nickname/permission_group", AdminAPIController, :right_get)
get("/users/:nickname/permission_group/:permission_group", AdminAPIController, :right_get)
@@ -173,34 +190,19 @@ defmodule Pleroma.Web.Router do
post("/users/follow", UserController, :follow)
post("/users/unfollow", UserController, :unfollow)
- delete("/users", UserController, :delete)
post("/users", UserController, :create)
- patch("/users/:nickname/toggle_activation", UserController, :toggle_activation)
- patch("/users/activate", UserController, :activate)
- patch("/users/deactivate", UserController, :deactivate)
- patch("/users/approve", UserController, :approve)
+
+ patch("/users/suggest", UserController, :suggest)
+ patch("/users/unsuggest", UserController, :unsuggest)
get("/relay", RelayController, :index)
post("/relay", RelayController, :follow)
delete("/relay", RelayController, :unfollow)
- post("/users/invite_token", InviteController, :create)
- get("/users/invites", InviteController, :index)
- post("/users/revoke_invite", InviteController, :revoke)
- post("/users/email_invite", InviteController, :email)
-
- get("/users/:nickname/password_reset", AdminAPIController, :get_password_reset)
patch("/users/force_password_reset", AdminAPIController, :force_password_reset)
get("/users/:nickname/credentials", AdminAPIController, :show_user_credentials)
patch("/users/:nickname/credentials", AdminAPIController, :update_user_credentials)
- get("/users", UserController, :list)
- get("/users/:nickname", UserController, :show)
- get("/users/:nickname/statuses", AdminAPIController, :list_user_statuses)
- get("/users/:nickname/chats", AdminAPIController, :list_user_chats)
-
- get("/instances/:instance/statuses", AdminAPIController, :list_instance_statuses)
-
get("/instance_document/:name", InstanceDocumentController, :show)
patch("/instance_document/:name", InstanceDocumentController, :update)
delete("/instance_document/:name", InstanceDocumentController, :delete)
@@ -208,28 +210,12 @@ defmodule Pleroma.Web.Router do
patch("/users/confirm_email", AdminAPIController, :confirm_email)
patch("/users/resend_confirmation_email", AdminAPIController, :resend_confirmation_email)
- get("/reports", ReportController, :index)
- get("/reports/:id", ReportController, :show)
- patch("/reports", ReportController, :update)
- post("/reports/:id/notes", ReportController, :notes_create)
- delete("/reports/:report_id/notes/:id", ReportController, :notes_delete)
-
- get("/statuses/:id", StatusController, :show)
- put("/statuses/:id", StatusController, :update)
- delete("/statuses/:id", StatusController, :delete)
- get("/statuses", StatusController, :index)
-
get("/config", ConfigController, :show)
post("/config", ConfigController, :update)
get("/config/descriptions", ConfigController, :descriptions)
get("/need_reboot", AdminAPIController, :need_reboot)
get("/restart", AdminAPIController, :restart)
- get("/moderation_log", AdminAPIController, :list_log)
-
- post("/reload_emoji", AdminAPIController, :reload_emoji)
- get("/stats", AdminAPIController, :stats)
-
get("/oauth_app", OAuthAppController, :index)
post("/oauth_app", OAuthAppController, :create)
patch("/oauth_app/:id", OAuthAppController, :update)
@@ -239,17 +225,78 @@ defmodule Pleroma.Web.Router do
post("/media_proxy_caches/delete", MediaProxyCacheController, :delete)
post("/media_proxy_caches/purge", MediaProxyCacheController, :purge)
- get("/chats/:id", ChatController, :show)
- get("/chats/:id/messages", ChatController, :messages)
- delete("/chats/:id/messages/:message_id", ChatController, :delete_message)
-
get("/frontends", FrontendController, :index)
post("/frontends/install", FrontendController, :install)
post("/backups", AdminAPIController, :create_backup)
+
+ get("/announcements", AnnouncementController, :index)
+ post("/announcements", AnnouncementController, :create)
+ get("/announcements/:id", AnnouncementController, :show)
+ patch("/announcements/:id", AnnouncementController, :change)
+ delete("/announcements/:id", AnnouncementController, :delete)
end
- scope "/api/pleroma/emoji", Pleroma.Web.PleromaAPI do
+ # AdminAPI: admins and mods (staff) can perform these actions (if enabled by config)
+ scope "/api/v1/pleroma/admin", Pleroma.Web.AdminAPI do
+ pipe_through([:admin_api, :require_privileged_staff])
+
+ delete("/users", UserController, :delete)
+
+ get("/users/:nickname/password_reset", AdminAPIController, :get_password_reset)
+ patch("/users/:nickname/credentials", AdminAPIController, :update_user_credentials)
+
+ get("/users/:nickname/statuses", AdminAPIController, :list_user_statuses)
+ get("/users/:nickname/chats", AdminAPIController, :list_user_chats)
+
+ get("/statuses", StatusController, :index)
+
+ get("/chats/:id", ChatController, :show)
+ get("/chats/:id/messages", ChatController, :messages)
+ end
+
+ # AdminAPI: admins and mods (staff) can perform these actions
+ scope "/api/v1/pleroma/admin", Pleroma.Web.AdminAPI do
+ pipe_through(:admin_api)
+
+ put("/users/tag", AdminAPIController, :tag_users)
+ delete("/users/tag", AdminAPIController, :untag_users)
+
+ patch("/users/:nickname/toggle_activation", UserController, :toggle_activation)
+ patch("/users/activate", UserController, :activate)
+ patch("/users/deactivate", UserController, :deactivate)
+ patch("/users/approve", UserController, :approve)
+
+ post("/users/invite_token", InviteController, :create)
+ get("/users/invites", InviteController, :index)
+ post("/users/revoke_invite", InviteController, :revoke)
+ post("/users/email_invite", InviteController, :email)
+
+ get("/users", UserController, :index)
+ get("/users/:nickname", UserController, :show)
+
+ get("/instances/:instance/statuses", InstanceController, :list_statuses)
+ delete("/instances/:instance", InstanceController, :delete)
+
+ get("/reports", ReportController, :index)
+ get("/reports/:id", ReportController, :show)
+ patch("/reports", ReportController, :update)
+ post("/reports/:id/notes", ReportController, :notes_create)
+ delete("/reports/:report_id/notes/:id", ReportController, :notes_delete)
+
+ get("/statuses/:id", StatusController, :show)
+ put("/statuses/:id", StatusController, :update)
+ delete("/statuses/:id", StatusController, :delete)
+
+ get("/moderation_log", AdminAPIController, :list_log)
+
+ post("/reload_emoji", AdminAPIController, :reload_emoji)
+ get("/stats", AdminAPIController, :stats)
+
+ delete("/chats/:id/messages/:message_id", ChatController, :delete_message)
+ end
+
+ scope "/api/v1/pleroma/emoji", Pleroma.Web.PleromaAPI do
scope "/pack" do
pipe_through(:admin_api)
@@ -290,6 +337,7 @@ defmodule Pleroma.Web.Router do
pipe_through(:pleroma_html)
post("/main/ostatus", UtilController, :remote_subscribe)
+ get("/main/ostatus", UtilController, :show_subscribe_form)
get("/ostatus_subscribe", RemoteFollowController, :follow)
post("/ostatus_subscribe", RemoteFollowController, :do_follow)
end
@@ -302,6 +350,11 @@ defmodule Pleroma.Web.Router do
post("/delete_account", UtilController, :delete_account)
put("/notification_settings", UtilController, :update_notificaton_settings)
post("/disable_account", UtilController, :disable_account)
+ post("/move_account", UtilController, :move_account)
+
+ put("/aliases", UtilController, :add_alias)
+ get("/aliases", UtilController, :list_aliases)
+ delete("/aliases", UtilController, :delete_alias)
end
scope "/api/pleroma", Pleroma.Web.PleromaAPI do
@@ -319,6 +372,8 @@ defmodule Pleroma.Web.Router do
end
scope "/oauth", Pleroma.Web.OAuth do
+ # Note: use /api/v1/accounts/verify_credentials for userinfo of signed-in user
+
get("/registration_details", OAuthController, :registration_details)
post("/mfa/verify", MFAController, :verify, as: :mfa_verify)
@@ -352,10 +407,17 @@ defmodule Pleroma.Web.Router do
scope "/api/v1/pleroma", Pleroma.Web.PleromaAPI do
pipe_through(:api)
+ get("/apps", AppController, :index)
get("/statuses/:id/reactions/:emoji", EmojiReactionController, :index)
get("/statuses/:id/reactions", EmojiReactionController, :index)
end
+ scope "/api/v0/pleroma", Pleroma.Web.PleromaAPI do
+ pipe_through(:authenticated_api)
+ get("/reports", ReportController, :index)
+ get("/reports/:id", ReportController, :show)
+ end
+
scope "/api/v1/pleroma", Pleroma.Web.PleromaAPI do
scope [] do
pipe_through(:authenticated_api)
@@ -390,6 +452,7 @@ defmodule Pleroma.Web.Router do
scope [] do
pipe_through(:api)
get("/accounts/:id/favourites", AccountController, :favourites)
+ get("/accounts/:id/endorsements", AccountController, :endorsements)
end
scope [] do
@@ -397,6 +460,15 @@ defmodule Pleroma.Web.Router do
post("/accounts/:id/subscribe", AccountController, :subscribe)
post("/accounts/:id/unsubscribe", AccountController, :unsubscribe)
+
+ get("/birthdays", AccountController, :birthdays)
+ end
+
+ scope [] do
+ pipe_through(:authenticated_api)
+
+ get("/settings/:app", SettingsController, :show)
+ patch("/settings/:app", SettingsController, :update)
end
post("/accounts/confirmation_resend", AccountController, :confirmation_resend)
@@ -408,6 +480,13 @@ defmodule Pleroma.Web.Router do
get("/federation_status", InstancesController, :show)
end
+ scope "/api/v2/pleroma", Pleroma.Web.PleromaAPI do
+ scope [] do
+ pipe_through(:authenticated_api)
+ get("/chats", ChatController, :index2)
+ end
+ end
+
scope "/api/v1", Pleroma.Web.MastodonAPI do
pipe_through(:authenticated_api)
@@ -428,11 +507,14 @@ defmodule Pleroma.Web.Router do
post("/accounts/:id/unblock", AccountController, :unblock)
post("/accounts/:id/mute", AccountController, :mute)
post("/accounts/:id/unmute", AccountController, :unmute)
-
- get("/apps/verify_credentials", AppController, :verify_credentials)
+ post("/accounts/:id/note", AccountController, :note)
+ post("/accounts/:id/pin", AccountController, :endorse)
+ post("/accounts/:id/unpin", AccountController, :unendorse)
+ post("/accounts/:id/remove_from_followers", AccountController, :remove_from_followers)
get("/conversations", ConversationController, :index)
post("/conversations/:id/read", ConversationController, :mark_as_read)
+ delete("/conversations/:id", ConversationController, :delete)
get("/domain_blocks", DomainBlockController, :index)
post("/domain_blocks", DomainBlockController, :create)
@@ -490,6 +572,7 @@ defmodule Pleroma.Web.Router do
get("/bookmarks", StatusController, :bookmarks)
post("/statuses", StatusController, :create)
+ put("/statuses/:id", StatusController, :update)
delete("/statuses/:id", StatusController, :delete)
post("/statuses/:id/reblog", StatusController, :reblog)
post("/statuses/:id/unreblog", StatusController, :unreblog)
@@ -508,17 +591,21 @@ defmodule Pleroma.Web.Router do
delete("/push/subscription", SubscriptionController, :delete)
get("/suggestions", SuggestionController, :index)
+ delete("/suggestions/:account_id", SuggestionController, :dismiss)
get("/timelines/home", TimelineController, :home)
get("/timelines/direct", TimelineController, :direct)
get("/timelines/list/:list_id", TimelineController, :list)
+
+ get("/announcements", AnnouncementController, :index)
+ post("/announcements/:id/dismiss", AnnouncementController, :mark_read)
end
- scope "/api/web", Pleroma.Web do
- pipe_through(:authenticated_api)
+ scope "/api/v1", Pleroma.Web.MastodonAPI do
+ pipe_through(:app_api)
- # Backend-obscure settings blob for MastoFE, don't parse/reuse elsewhere
- put("/settings", MastoFEController, :put_settings)
+ post("/apps", AppController, :create)
+ get("/apps/verify_credentials", AppController, :verify_credentials)
end
scope "/api/v1", Pleroma.Web.MastodonAPI do
@@ -527,6 +614,8 @@ defmodule Pleroma.Web.Router do
get("/accounts/search", SearchController, :account_search)
get("/search", SearchController, :search)
+ get("/accounts/lookup", AccountController, :lookup)
+
get("/accounts/:id/statuses", AccountController, :statuses)
get("/accounts/:id/followers", AccountController, :followers)
get("/accounts/:id/following", AccountController, :following)
@@ -537,14 +626,14 @@ defmodule Pleroma.Web.Router do
get("/instance", InstanceController, :show)
get("/instance/peers", InstanceController, :peers)
- post("/apps", AppController, :create)
-
get("/statuses", StatusController, :index)
get("/statuses/:id", StatusController, :show)
get("/statuses/:id/context", StatusController, :context)
get("/statuses/:id/card", StatusController, :card)
get("/statuses/:id/favourited_by", StatusController, :favourited_by)
get("/statuses/:id/reblogged_by", StatusController, :reblogged_by)
+ get("/statuses/:id/history", StatusController, :show_history)
+ get("/statuses/:id/source", StatusController, :show_source)
get("/custom_emojis", CustomEmojiController, :index)
@@ -554,6 +643,8 @@ defmodule Pleroma.Web.Router do
get("/timelines/tag/:tag", TimelineController, :hashtag)
get("/polls/:id", PollController, :show)
+
+ get("/directory", DirectoryController, :index)
end
scope "/api/v2", Pleroma.Web.MastodonAPI do
@@ -561,6 +652,8 @@ defmodule Pleroma.Web.Router do
get("/search", SearchController, :search2)
post("/media", MediaController, :create2)
+
+ get("/suggestions", SuggestionController, :index2)
end
scope "/api", Pleroma.Web do
@@ -591,18 +684,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)
@@ -616,7 +703,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)
@@ -624,7 +711,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
@@ -675,6 +762,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
@@ -719,27 +807,20 @@ defmodule Pleroma.Web.Router do
scope "/", Pleroma.Web do
pipe_through(:api)
- get("/web/manifest.json", MastoFEController, :manifest)
+ get("/manifest.json", ManifestController, :show)
end
scope "/", Pleroma.Web do
- pipe_through(:mastodon_html)
-
- get("/web/login", MastodonAPI.AuthController, :login)
- delete("/auth/sign_out", MastodonAPI.AuthController, :logout)
-
- post("/auth/password", MastodonAPI.AuthController, :password_reset)
-
- get("/web/*path", MastoFEController, :index)
+ pipe_through(:pleroma_html)
- get("/embed/:id", EmbedController, :show)
+ post("/auth/password", TwitterAPI.PasswordController, :request)
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
@@ -750,6 +831,11 @@ defmodule Pleroma.Web.Router do
end
end
+ scope "/" do
+ pipe_through([:pleroma_html, :authenticate, :require_admin])
+ live_dashboard("/phoenix/live_dashboard")
+ end
+
# Test-only routes needed to test action dispatching and plug chain execution
if Pleroma.Config.get(:env) == :test do
@test_actions [
@@ -786,9 +872,22 @@ defmodule Pleroma.Web.Router do
scope "/", Pleroma.Web.Fallback do
get("/registration/:token", RedirectController, :registration_page)
get("/:maybe_nickname_or_id", RedirectController, :redirector_with_meta)
+ match(:*, "/api/pleroma*path", LegacyPleromaApiRerouterPlug, [])
get("/api*path", RedirectController, :api_not_implemented)
get("/*path", RedirectController, :redirector_with_preload)
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
diff --git a/lib/pleroma/web/chat_channel.ex b/lib/pleroma/web/shout_channel.ex
index 3b1469c19..928f0a1dd 100644
--- a/lib/pleroma/web/chat_channel.ex
+++ b/lib/pleroma/web/shout_channel.ex
@@ -1,13 +1,13 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 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
diff --git a/lib/pleroma/web/static_fe/static_fe_controller.ex b/lib/pleroma/web/static_fe/static_fe_controller.ex
index bdec0897a..97c41c6f9 100644
--- a/lib/pleroma/web/static_fe/static_fe_controller.ex
+++ b/lib/pleroma/web/static_fe/static_fe_controller.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.StaticFE.StaticFEController do
@@ -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"]
@@ -122,7 +121,7 @@ defmodule Pleroma.Web.StaticFE.StaticFEController do
end
defp get_counts(%Activity{} = activity) do
- %Object{data: data} = Object.normalize(activity)
+ %Object{data: data} = Object.normalize(activity, fetch: false)
%{
likes: data["like_count"] || 0,
diff --git a/lib/pleroma/web/static_fe/static_fe_view.ex b/lib/pleroma/web/static_fe/static_fe_view.ex
index b3d1d1ec8..8e23a79a3 100644
--- a/lib/pleroma/web/static_fe/static_fe_view.ex
+++ b/lib/pleroma/web/static_fe/static_fe_view.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.StaticFE.StaticFEView do
diff --git a/lib/pleroma/web/streamer.ex b/lib/pleroma/web/streamer.ex
index 7d4a1304a..3c0da5c27 100644
--- a/lib/pleroma/web/streamer.ex
+++ b/lib/pleroma/web/streamer.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Streamer do
@@ -37,7 +37,7 @@ defmodule Pleroma.Web.Streamer do
{:ok, topic :: String.t()} | {:error, :bad_topic} | {:error, :unauthorized}
def get_topic_and_add_socket(stream, user, oauth_token, params \\ %{}) do
with {:ok, topic} <- get_topic(stream, user, oauth_token, params) do
- add_socket(topic, user)
+ add_socket(topic, oauth_token)
end
end
@@ -120,10 +120,10 @@ defmodule Pleroma.Web.Streamer do
end
@doc "Registers the process for streaming. Use `get_topic/3` to get the full authorized topic."
- def add_socket(topic, user) do
+ def add_socket(topic, oauth_token) do
if should_env_send?() do
- auth? = if user, do: true
- Registry.register(@registry, topic, auth?)
+ oauth_token_id = if oauth_token, do: oauth_token.id, else: false
+ Registry.register(@registry, topic, oauth_token_id)
end
{:ok, topic}
@@ -151,7 +151,7 @@ defmodule Pleroma.Web.Streamer do
recipients = MapSet.new(item.recipients)
domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks)
- with parent <- Object.normalize(item) || item,
+ with parent <- Object.normalize(item, fetch: false) || item,
true <- Enum.all?([blocked_ap_ids, muted_ap_ids], &(item.actor not in &1)),
true <- item.data["type"] != "Announce" || item.actor not in reblog_muted_ap_ids,
true <-
@@ -296,6 +296,24 @@ defmodule Pleroma.Web.Streamer do
defp push_to_socket(_topic, %Activity{data: %{"type" => "Delete"}}), do: :noop
+ defp push_to_socket(topic, %Activity{data: %{"type" => "Update"}} = item) do
+ create_activity =
+ Pleroma.Activity.get_create_by_object_ap_id(item.object.data["id"])
+ |> Map.put(:object, item.object)
+
+ anon_render = StreamerView.render("status_update.json", create_activity)
+
+ Registry.dispatch(@registry, topic, fn list ->
+ Enum.each(list, fn {pid, auth?} ->
+ if auth? do
+ send(pid, {:render_with_user, StreamerView, "status_update.json", create_activity})
+ else
+ send(pid, {:text, anon_render})
+ end
+ end)
+ end)
+ end
+
defp push_to_socket(topic, item) do
anon_render = StreamerView.render("update.json", item)
@@ -320,6 +338,22 @@ defmodule Pleroma.Web.Streamer do
end
end
+ def close_streams_by_oauth_token(oauth_token) do
+ if should_env_send?() do
+ Registry.select(
+ @registry,
+ [
+ {
+ {:"$1", :"$2", :"$3"},
+ [{:==, :"$3", oauth_token.id}],
+ [:"$2"]
+ }
+ ]
+ )
+ |> Enum.each(fn pid -> send(pid, :close) end)
+ end
+ end
+
# In test environement, only return true if the registry is started.
# In benchmark environment, returns false.
# In any other environment, always returns true.
diff --git a/lib/pleroma/web/templates/email/digest.html.eex b/lib/pleroma/web/templates/email/digest.html.eex
index 60eceff22..1efc76e1a 100644
--- a/lib/pleroma/web/templates/email/digest.html.eex
+++ b/lib/pleroma/web/templates/email/digest.html.eex
@@ -160,7 +160,7 @@
<div
style="font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif;line-height: 14px; color: <%= @styling.header_color %>;">
<p style="line-height: 36px; text-align: center; margin: 0;"><span
- style="font-size: 30px; color: <%= @styling.header_color %>;">Hey <%= @user.nickname %>, here is what you've missed!</span></p>
+ style="font-size: 30px; color: <%= @styling.header_color %>;"><%= Gettext.dpgettext("static_pages", "digest email header line", "Hey %{nickname}, here is what you've missed!", nickname: @user.nickname) %></span></p>
</div>
</div>
<!--[if mso]></td></tr></table><![endif]-->
@@ -382,7 +382,7 @@
<div
style="font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; font-size: 12px; line-height: 14px; color: <%= @styling.text_color %>;">
<p style="font-size: 12px; line-height: 24px; text-align: center; margin: 0;"><span
- style="font-size: 20px;"><%= length(@followers) %> New Followers</span><span
+ style="font-size: 20px;"><%= Gettext.dpngettext("static_pages", "new followers count header", "%{count} New Follower", "%{count} New Followers", length(@followers), count: length(@followers)) %></span><span
style="font-size: 20px; line-height: 24px;"></span></p>
</div>
</div>
@@ -535,16 +535,16 @@
style="color:<%= @styling.text_color %>;font-family:Arial, 'Helvetica Neue', Helvetica, sans-serif;line-height:120%;padding-top:10px;padding-right:10px;padding-bottom:10px;padding-left:10px;">
<p
style="font-size: 12px; line-height: 16px; text-align: center; color: <%= @styling.text_color %>; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; margin: 0;">
- <span style="font-size: 14px;">You have received this email because you have signed up to receive digest emails from <b><%= @instance %></b> Pleroma instance.</span></p>
+ <span style="font-size: 14px;"><%= raw Gettext.dpgettext("static_pages", "digest email sending reason", "You have received this email because you have signed up to receive digest emails from <b>%{instance}</b> Pleroma instance.", instance: safe_to_string(html_escape(@instance))) %></span></p>
<p
style="font-size: 12px; line-height: 14px; text-align: center; color: <%= @styling.text_color %>; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; margin: 0;">
 </p>
<p
style="font-size: 12px; line-height: 16px; text-align: center; color: <%= @styling.text_color %>; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; margin: 0;">
- <span style="font-size: 14px;">The email address you are subscribed as is <a href="mailto:<%= @user.email %>" style="color: <%= @styling.link_color %>;text-decoration: none;"><%= @user.email %></a>. </span></p>
+ <span style="font-size: 14px;"><%= raw Gettext.dpgettext("static_pages", "digest email receiver address", "The email address you are subscribed as is <a href='mailto:%{@user.email}' style='color: %{color};text-decoration: none;'>%{email}</a>. ", color: safe_to_string(html_escape(@styling.link_color)), email: safe_to_string(html_escape(@user.email))) %></span></p>
<p
style="font-size: 12px; line-height: 16px; text-align: center; color: <%= @styling.text_color %>; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; margin: 0;">
- <span style="font-size: 14px;">To unsubscribe, please go <%= link "here", style: "color: #{@styling.link_color};text-decoration: none;", to: @unsubscribe_link %>.</span></p>
+ <span style="font-size: 14px;"><%= raw Gettext.dpgettext("static_pages", "digest email unsubscribe action", "To unsubscribe, please go %{here}.", here: safe_to_string link(Gettext.dpgettext("static_pages", "digest email unsubscribe action link text", "here"), style: "color: #{@styling.link_color};text-decoration: none;", to: @unsubscribe_link)) %></span></p>
</div>
<!--[if mso]></td></tr></table><![endif]-->
<!--[if (!mso)&(!IE)]><!-->
diff --git a/lib/pleroma/web/templates/embed/show.html.eex b/lib/pleroma/web/templates/embed/show.html.eex
index 05a3f0ee3..092b52b70 100644
--- a/lib/pleroma/web/templates/embed/show.html.eex
+++ b/lib/pleroma/web/templates/embed/show.html.eex
@@ -6,7 +6,7 @@
</div>
<span class="display-name" style="padding-left: 0.5em;">
<bdi><%= raw (@author.name |> Formatter.emojify(@author.emoji)) %></bdi>
- <span class="nickname"><%= full_nickname(@author) %></span>
+ <span class="nickname">@<%= full_nickname(@author) %></span>
</span>
</a>
</div>
diff --git a/lib/pleroma/web/templates/feed/feed/_activity.atom.eex b/lib/pleroma/web/templates/feed/feed/_activity.atom.eex
index 3fd150c4e..260338772 100644
--- a/lib/pleroma/web/templates/feed/feed/_activity.atom.eex
+++ b/lib/pleroma/web/templates/feed/feed/_activity.atom.eex
@@ -3,15 +3,15 @@
<activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
<id><%= @data["id"] %></id>
<title><%= activity_title(@data, Keyword.get(@feed_config, :post_title, %{})) %></title>
- <content type="html"><%= activity_content(@data) %></content>
- <published><%= @activity.data["published"] %></published>
- <updated><%= @activity.data["published"] %></updated>
+ <content type="html"><%= activity_description(@data) %></content>
+ <published><%= to_rfc3339(@activity.data["published"]) %></published>
+ <updated><%= to_rfc3339(@activity.data["published"]) %></updated>
<ostatus:conversation ref="<%= activity_context(@activity) %>">
<%= activity_context(@activity) %>
</ostatus:conversation>
<link href="<%= activity_context(@activity) %>" rel="ostatus:conversation"/>
- <%= if @data["summary"] do %>
+ <%= if @data["summary"] != "" do %>
<summary><%= escape(@data["summary"]) %></summary>
<% end %>
@@ -22,7 +22,7 @@
<link type="text/html" href='<%= @data["external_url"] %>' rel="alternate"/>
<% end %>
- <%= for tag <- @data["tag"] || [] do %>
+ <%= for tag <- Pleroma.Object.hashtags(@object) do %>
<category term="<%= tag %>"></category>
<% end %>
@@ -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 %>
diff --git a/lib/pleroma/web/templates/feed/feed/_activity.rss.eex b/lib/pleroma/web/templates/feed/feed/_activity.rss.eex
index 42960de7d..5c8f35fe4 100644
--- a/lib/pleroma/web/templates/feed/feed/_activity.rss.eex
+++ b/lib/pleroma/web/templates/feed/feed/_activity.rss.eex
@@ -3,17 +3,11 @@
<activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
<guid><%= @data["id"] %></guid>
<title><%= activity_title(@data, Keyword.get(@feed_config, :post_title, %{})) %></title>
- <description><%= activity_content(@data) %></description>
- <pubDate><%= @activity.data["published"] %></pubDate>
- <updated><%= @activity.data["published"] %></updated>
+ <description><%= activity_description(@data) %></description>
+ <pubDate><%= to_rfc2822(@activity.data["published"]) %></pubDate>
<ostatus:conversation ref="<%= activity_context(@activity) %>">
<%= activity_context(@activity) %>
</ostatus:conversation>
- <link rel="ostatus:conversation"><%= activity_context(@activity) %></link>
-
- <%= if @data["summary"] do %>
- <description><%= escape(@data["summary"]) %></description>
- <% end %>
<%= if @activity.local do %>
<link><%= @data["id"] %></link>
@@ -21,12 +15,14 @@
<link><%= @data["external_url"] %></link>
<% end %>
- <%= for tag <- @data["tag"] || [] do %>
+ <link rel="ostatus:conversation"><%= activity_context(@activity) %></link>
+
+ <%= for tag <- Pleroma.Object.hashtags(@object) do %>
<category term="<%= tag %>"></category>
<% end %>
<%= for attachment <- @data["attachment"] || [] do %>
- <link type="<%= attachment_type(attachment) %>"><%= attachment_href(attachment) %></link>
+ <enclosure url="<%= attachment_href(attachment) %>" type="<%= attachment_type(attachment) %>" />
<% end %>
<%= if @data["inReplyTo"] do %>
@@ -37,7 +33,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 %>
diff --git a/lib/pleroma/web/templates/feed/feed/_author.atom.eex b/lib/pleroma/web/templates/feed/feed/_author.atom.eex
index 25cbffada..90be8a559 100644
--- a/lib/pleroma/web/templates/feed/feed/_author.atom.eex
+++ b/lib/pleroma/web/templates/feed/feed/_author.atom.eex
@@ -1,17 +1,14 @@
<author>
- <id><%= @user.ap_id %></id>
- <activity:object>http://activitystrea.ms/schema/1.0/person</activity:object>
<uri><%= @user.ap_id %></uri>
+ <name><%= @user.nickname %></name>
+ <activity:object>http://activitystrea.ms/schema/1.0/person</activity:object>
+ <activity:displayName><%= @user.name %></activity:displayName>
+ <activity:image><%= User.avatar_url(@user) %></activity:image>
+ <activity:id><%= @user.ap_id %></activity:id>
+ <activity:published><%= to_rfc3339(@user.inserted_at) %></activity:published>
+ <activity:updated><%= to_rfc3339(@user.updated_at) %></activity:updated>
+ <activity:url><%= @user.ap_id %></activity:url>
<poco:preferredUsername><%= @user.nickname %></poco:preferredUsername>
<poco:displayName><%= @user.name %></poco:displayName>
<poco:note><%= escape(@user.bio) %></poco:note>
- <summary><%= escape(@user.bio) %></summary>
- <name><%= @user.nickname %></name>
- <link rel="avatar" href="<%= User.avatar_url(@user) %>"/>
- <%= if User.banner_url(@user) do %>
- <link rel="header" href="<%= User.banner_url(@user) %>"/>
- <% end %>
- <%= if @user.local do %>
- <ap_enabled>true</ap_enabled>
- <% end %>
</author>
diff --git a/lib/pleroma/web/templates/feed/feed/_author.rss.eex b/lib/pleroma/web/templates/feed/feed/_author.rss.eex
index 526aeddcf..22477e6b1 100644
--- a/lib/pleroma/web/templates/feed/feed/_author.rss.eex
+++ b/lib/pleroma/web/templates/feed/feed/_author.rss.eex
@@ -1,17 +1,10 @@
-<managingEditor>
- <guid><%= @user.ap_id %></guid>
- <activity:object>http://activitystrea.ms/schema/1.0/person</activity:object>
- <uri><%= @user.ap_id %></uri>
- <poco:preferredUsername><%= @user.nickname %></poco:preferredUsername>
- <poco:displayName><%= @user.name %></poco:displayName>
- <poco:note><%= escape(@user.bio) %></poco:note>
- <description><%= escape(@user.bio) %></description>
- <name><%= @user.nickname %></name>
- <link rel="avatar"><%= User.avatar_url(@user) %></link>
- <%= if User.banner_url(@user) do %>
- <link rel="header"><%= User.banner_url(@user) %></link>
- <% end %>
- <%= if @user.local do %>
- <ap_enabled>true</ap_enabled>
- <% end %>
-</managingEditor>
+<managingEditor><%= "#{email(@user)} (#{escape(@user.name)})" %></managingEditor>
+<activity:object>http://activitystrea.ms/schema/1.0/person</activity:object>
+<activity:displayName><%= @user.name %></activity:displayName>
+<activity:image><%= User.avatar_url(@user) %></activity:image>
+<activity:id><%= @user.ap_id %></activity:id>
+<activity:published><%= to_rfc3339(@user.inserted_at) %></activity:published>
+<activity:updated><%= to_rfc3339(@user.updated_at) %></activity:updated>
+<poco:preferredUsername><%= @user.nickname %></poco:preferredUsername>
+<poco:displayName><%= @user.name %></poco:displayName>
+<poco:note><%= escape(@user.bio) %></poco:note>
diff --git a/lib/pleroma/web/templates/feed/feed/_tag_activity.atom.eex b/lib/pleroma/web/templates/feed/feed/_tag_activity.atom.eex
index cf5874a91..25980c1e4 100644
--- a/lib/pleroma/web/templates/feed/feed/_tag_activity.atom.eex
+++ b/lib/pleroma/web/templates/feed/feed/_tag_activity.atom.eex
@@ -1,12 +1,22 @@
<entry>
- <activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type>
- <activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
-
- <%= render @view_module, "_tag_author.atom", assigns %>
-
- <id><%= @data["id"] %></id>
- <title><%= activity_title(@data, Keyword.get(@feed_config, :post_title, %{})) %></title>
- <content type="html"><%= activity_content(@data) %></content>
+ <activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type>
+ <activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
+
+ <%= render Phoenix.Controller.view_module(@conn), "_tag_author.atom", assigns %>
+
+ <id><%= @data["id"] %></id>
+ <title><%= activity_title(@data, Keyword.get(@feed_config, :post_title, %{})) %></title>
+ <content type="html"><%= activity_description(@data) %></content>
+ <published><%= to_rfc3339(@activity.data["published"]) %></published>
+ <updated><%= to_rfc3339(@activity.data["published"]) %></updated>
+ <ostatus:conversation ref="<%= activity_context(@activity) %>">
+ <%= activity_context(@activity) %>
+ </ostatus:conversation>
+ <link href="<%= activity_context(@activity) %>" rel="ostatus:conversation"/>
+
+ <%= if @data["summary"] != "" do %>
+ <summary><%= @data["summary"] %></summary>
+ <% end %>
<%= if @activity.local do %>
<link type="application/atom+xml" href='<%= @data["id"] %>' rel="self"/>
@@ -15,37 +25,25 @@
<link type="text/html" href='<%= @data["external_url"] %>' rel="alternate"/>
<% end %>
- <published><%= @activity.data["published"] %></published>
- <updated><%= @activity.data["published"] %></updated>
-
- <ostatus:conversation ref="<%= activity_context(@activity) %>">
- <%= activity_context(@activity) %>
- </ostatus:conversation>
- <link href="<%= activity_context(@activity) %>" rel="ostatus:conversation"/>
-
- <%= if @data["summary"] do %>
- <summary><%= @data["summary"] %></summary>
- <% end %>
-
- <%= for id <- @activity.recipients do %>
- <%= if id == Pleroma.Constants.as_public() do %>
+ <%= for id <- @activity.recipients do %>
+ <%= 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.Endpoint.url()}.+followers$/, id) 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 %>
- <link rel="mentioned"
- ostatus:object-type="http://activitystrea.ms/schema/1.0/person"
- href="<%= id %>" />
- <% end %>
+ ostatus:object-type="http://activitystrea.ms/schema/1.0/person"
+ href="<%= id %>" />
<% end %>
<% end %>
+ <% end %>
- <%= for tag <- @data["tag"] || [] do %>
- <category term="<%= tag %>"></category>
- <% end %>
+ <%= for tag <- Pleroma.Object.hashtags(@object) do %>
+ <category term="<%= tag %>"></category>
+ <% end %>
- <%= for {emoji, file} <- @data["emoji"] || %{} do %>
- <link name="<%= emoji %>" rel="emoji" href="<%= file %>"/>
- <% end %>
+ <%= for {emoji, file} <- @data["emoji"] || %{} do %>
+ <link name="<%= emoji %>" rel="emoji" href="<%= file %>"/>
+ <% end %>
</entry>
diff --git a/lib/pleroma/web/templates/feed/feed/_tag_activity.xml.eex b/lib/pleroma/web/templates/feed/feed/_tag_activity.xml.eex
index 2334e24a2..d582c83e8 100644
--- a/lib/pleroma/web/templates/feed/feed/_tag_activity.xml.eex
+++ b/lib/pleroma/web/templates/feed/feed/_tag_activity.xml.eex
@@ -4,9 +4,9 @@
<guid isPermalink="true"><%= activity_context(@activity) %></guid>
<link><%= activity_context(@activity) %></link>
- <pubDate><%= pub_date(@activity.data["published"]) %></pubDate>
+ <pubDate><%= to_rfc2822(@activity.data["published"]) %></pubDate>
- <description><%= activity_content(@data) %></description>
+ <description><%= activity_description(@data) %></description>
<%= for attachment <- @data["attachment"] || [] do %>
<enclosure url="<%= attachment_href(attachment) %>" type="<%= attachment_type(attachment) %>"/>
<% end %>
diff --git a/lib/pleroma/web/templates/feed/feed/_tag_author.atom.eex b/lib/pleroma/web/templates/feed/feed/_tag_author.atom.eex
index 997c4936e..71c696832 100644
--- a/lib/pleroma/web/templates/feed/feed/_tag_author.atom.eex
+++ b/lib/pleroma/web/templates/feed/feed/_tag_author.atom.eex
@@ -1,18 +1,14 @@
<author>
- <activity:object-type>http://activitystrea.ms/schema/1.0/person</activity:object-type>
- <id><%= @actor.ap_id %></id>
- <uri><%= @actor.ap_id %></uri>
- <name><%= @actor.nickname %></name>
- <summary><%= escape(@actor.bio) %></summary>
- <link rel="avatar" href="<%= User.avatar_url(@actor) %>"/>
- <%= if User.banner_url(@actor) do %>
- <link rel="header" href="<%= User.banner_url(@actor) %>"/>
- <% end %>
- <%= if @actor.local do %>
- <ap_enabled>true</ap_enabled>
- <% end %>
-
- <poco:preferredUsername><%= @actor.nickname %></poco:preferredUsername>
- <poco:displayName><%= @actor.name %></poco:displayName>
- <poco:note><%= escape(@actor.bio) %></poco:note>
+ <uri><%= @actor.ap_id %></uri>
+ <name><%= @actor.nickname %></name>
+ <activity:object-type>http://activitystrea.ms/schema/1.0/person</activity:object-type>
+ <activity:displayName><%= @actor.name %></activity:displayName>
+ <activity:image><%= User.avatar_url(@actor) %></activity:image>
+ <activity:id><%= @actor.ap_id %></activity:id>
+ <activity:published><%= to_rfc3339(@actor.inserted_at) %></activity:published>
+ <activity:updated><%= to_rfc3339(@actor.updated_at) %></activity:updated>
+ <activity:url><%= @actor.ap_id %></activity:url>
+ <poco:preferredUsername><%= @actor.nickname %></poco:preferredUsername>
+ <poco:displayName><%= @actor.name %></poco:displayName>
+ <poco:note><%= escape(@actor.bio) %></poco:note>
</author>
diff --git a/lib/pleroma/web/templates/feed/feed/tag.atom.eex b/lib/pleroma/web/templates/feed/feed/tag.atom.eex
index a288539ed..14b0ee594 100644
--- a/lib/pleroma/web/templates/feed/feed/tag.atom.eex
+++ b/lib/pleroma/web/templates/feed/feed/tag.atom.eex
@@ -1,22 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
+<feed
+ xmlns="http://www.w3.org/2005/Atom"
+ xmlns:thr="http://purl.org/syndication/thread/1.0"
+ xmlns:activity="http://activitystrea.ms/spec/1.0/"
+ xmlns:poco="http://portablecontacts.net/spec/1.0"
+ xmlns:ostatus="http://ostatus.org/schema/1.0"
+ xmlns:statusnet="http://status.net/schema/api/1/">
-<feed xml:lang="en-US" xmlns="http://www.w3.org/2005/Atom"
- xmlns:thr="http://purl.org/syndication/thread/1.0"
- xmlns:georss="http://www.georss.org/georss"
- xmlns:activity="http://activitystrea.ms/spec/1.0/"
- xmlns:media="http://purl.org/syndication/atommedia"
- xmlns:poco="http://portablecontacts.net/spec/1.0"
- xmlns:ostatus="http://ostatus.org/schema/1.0"
- xmlns:statusnet="http://status.net/schema/api/1/">
+ <id><%= Routes.tag_feed_url(@conn, :feed, @tag) <> ".atom" %></id>
+ <title>#<%= @tag %></title>
+ <subtitle><%= Gettext.dpgettext("static_pages", "tag feed description", "These are public toots tagged with #%{tag}. You can interact with them if you have an account anywhere in the fediverse.", tag: @tag) %></subtitle>
+ <logo><%= feed_logo() %></logo>
+ <updated><%= most_recent_update(@activities) %></updated>
+ <link rel="self" href="<%= '#{Routes.tag_feed_url(@conn, :feed, @tag)}.atom' %>" type="application/atom+xml"/>
- <id><%= '#{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"/>
- <%= for activity <- @activities do %>
- <%= render @view_module, "_tag_activity.atom", Map.merge(assigns, prepare_activity(activity, actor: true)) %>
- <% end %>
+ <%= for activity <- @activities do %>
+ <%= render Phoenix.Controller.view_module(@conn), "_tag_activity.atom", Map.merge(assigns, prepare_activity(activity, actor: true)) %>
+ <% end %>
</feed>
diff --git a/lib/pleroma/web/templates/feed/feed/tag.rss.eex b/lib/pleroma/web/templates/feed/feed/tag.rss.eex
index eeda01a04..27dde5627 100644
--- a/lib/pleroma/web/templates/feed/feed/tag.rss.eex
+++ b/lib/pleroma/web/templates/feed/feed/tag.rss.eex
@@ -1,15 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
-<rss version="2.0" xmlns:webfeeds="http://webfeeds.org/rss/1.0">
+<rss version="2.0"
+ xmlns:webfeeds="http://webfeeds.org/rss/1.0"
+ xmlns:thr="http://purl.org/syndication/thread/1.0">
<channel>
-
<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>
+ <description><%= Gettext.dpgettext("static_pages", "tag feed description", "These are public toots tagged with #%{tag}. You can interact with them if you have an account anywhere in the fediverse.", tag: @tag) %></description>
+ <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 %>
- <%= render @view_module, "_tag_activity.xml", Map.merge(assigns, prepare_activity(activity)) %>
+ <%= render Phoenix.Controller.view_module(@conn), "_tag_activity.xml", Map.merge(assigns, prepare_activity(activity)) %>
<% end %>
</channel>
</rss>
diff --git a/lib/pleroma/web/templates/feed/feed/user.atom.eex b/lib/pleroma/web/templates/feed/feed/user.atom.eex
index c6acd848f..e36bfc66c 100644
--- a/lib/pleroma/web/templates/feed/feed/user.atom.eex
+++ b/lib/pleroma/web/templates/feed/feed/user.atom.eex
@@ -6,19 +6,20 @@
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>
+ <subtitle><%= escape(@user.bio) %></subtitle>
+ <updated><%= most_recent_update(@activities, @user, :atom) %></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 %>
+ <%= render Phoenix.Controller.view_module(@conn), "_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 %>
- <%= render @view_module, "_activity.atom", Map.merge(assigns, prepare_activity(activity)) %>
+ <%= render Phoenix.Controller.view_module(@conn), "_activity.atom", Map.merge(assigns, prepare_activity(activity)) %>
<% end %>
</feed>
diff --git a/lib/pleroma/web/templates/feed/feed/user.rss.eex b/lib/pleroma/web/templates/feed/feed/user.rss.eex
index d69120480..fae3fcf3d 100644
--- a/lib/pleroma/web/templates/feed/feed/user.rss.eex
+++ b/lib/pleroma/web/templates/feed/feed/user.rss.eex
@@ -1,20 +1,30 @@
<?xml version="1.0" encoding="UTF-8" ?>
-<rss version="2.0">
+<rss version="2.0"
+ xmlns:atom="http://www.w3.org/2005/Atom"
+ xmlns:thr="http://purl.org/syndication/thread/1.0"
+ xmlns:activity="http://activitystrea.ms/spec/1.0/"
+ xmlns:ostatus="http://ostatus.org/schema/1.0"
+ xmlns:poco="http://portablecontacts.net/spec/1.0">
<channel>
- <guid><%= 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>
+ <atom:link href="<%= Routes.user_feed_url(@conn, :feed, @user.nickname) <> ".atom" %>"
+ rel="self" type="application/rss+xml" />
+ <description><%= escape(@user.bio) %></description>
+ <image>
+ <url><%= logo(@user) %></url>
+ <title><%= @user.nickname <> "'s timeline" %></title>
+ <link><%= '#{Routes.user_feed_url(@conn, :feed, @user.nickname)}.rss' %></link>
+ </image>
- <%= render @view_module, "_author.rss", assigns %>
+ <%= render Phoenix.Controller.view_module(@conn), "_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 %>
- <%= render @view_module, "_activity.rss", Map.merge(assigns, prepare_activity(activity)) %>
+ <%= render Phoenix.Controller.view_module(@conn), "_activity.rss", Map.merge(assigns, prepare_activity(activity)) %>
<% end %>
</channel>
</rss>
diff --git a/lib/pleroma/web/templates/layout/app.html.eex b/lib/pleroma/web/templates/layout/app.html.eex
index 1ede59fd8..e33bada85 100644
--- a/lib/pleroma/web/templates/layout/app.html.eex
+++ b/lib/pleroma/web/templates/layout/app.html.eex
@@ -1,5 +1,5 @@
<!DOCTYPE html>
-<html lang="en">
+<html lang="<%= Pleroma.Web.Gettext.language_tag() %>">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,minimal-ui">
diff --git a/lib/pleroma/web/templates/layout/email.html.eex b/lib/pleroma/web/templates/layout/email.html.eex
index f6dcd7f0f..5858e48b4 100644
--- a/lib/pleroma/web/templates/layout/email.html.eex
+++ b/lib/pleroma/web/templates/layout/email.html.eex
@@ -1,10 +1,10 @@
<!DOCTYPE html>
-<html lang="en">
+<html lang="<%= Pleroma.Web.Gettext.language_tag() %>">
<head>
<meta charset="utf-8">
<title><%= @email.subject %></title>
</head>
<body>
- <%= render @view_module, @view_template, assigns %>
+ <%= render Phoenix.Controller.view_module(@conn), Phoenix.Controller.view_template(@conn), assigns %>
</body>
-</html> \ No newline at end of file
+</html>
diff --git a/lib/pleroma/web/templates/layout/embed.html.eex b/lib/pleroma/web/templates/layout/embed.html.eex
index 8b905f070..1197288e5 100644
--- a/lib/pleroma/web/templates/layout/embed.html.eex
+++ b/lib/pleroma/web/templates/layout/embed.html.eex
@@ -10,6 +10,6 @@
<base target="_parent">
</head>
<body>
- <%= render @view_module, @view_template, assigns %>
+ <%= render Phoenix.Controller.view_module(@conn), Phoenix.Controller.view_template(@conn), assigns %>
</body>
</html>
diff --git a/lib/pleroma/web/templates/mailer/subscription/unsubscribe_failure.html.eex b/lib/pleroma/web/templates/mailer/subscription/unsubscribe_failure.html.eex
index 7b476f02d..df090ffcd 100644
--- a/lib/pleroma/web/templates/mailer/subscription/unsubscribe_failure.html.eex
+++ b/lib/pleroma/web/templates/mailer/subscription/unsubscribe_failure.html.eex
@@ -1 +1 @@
-<h1>UNSUBSCRIBE FAILURE</h1>
+<h1><%= Gettext.dpgettext("static_pages", "mailer unsubscribe failed message", "UNSUBSCRIBE FAILURE") %></h1>
diff --git a/lib/pleroma/web/templates/mailer/subscription/unsubscribe_success.html.eex b/lib/pleroma/web/templates/mailer/subscription/unsubscribe_success.html.eex
index 6dfa2c185..cbce495d4 100644
--- a/lib/pleroma/web/templates/mailer/subscription/unsubscribe_success.html.eex
+++ b/lib/pleroma/web/templates/mailer/subscription/unsubscribe_success.html.eex
@@ -1 +1 @@
-<h1>UNSUBSCRIBE SUCCESSFUL</h1>
+<h1><%= Gettext.dpgettext("static_pages", "mailer unsubscribe successful message", "UNSUBSCRIBE SUCCESSFUL") %></h1>
diff --git a/lib/pleroma/web/templates/masto_fe/index.html.eex b/lib/pleroma/web/templates/masto_fe/index.html.eex
deleted file mode 100644
index c330960fa..000000000
--- a/lib/pleroma/web/templates/masto_fe/index.html.eex
+++ /dev/null
@@ -1,35 +0,0 @@
-<!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="<%= 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>
diff --git a/lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex b/lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex
index 5ab59b57b..e45d13bdf 100644
--- a/lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex
+++ b/lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex
@@ -5,11 +5,11 @@
<p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p>
<% end %>
-<h2>Two-factor recovery</h2>
+<h2><%= Gettext.dpgettext("static_pages", "mfa recover page title", "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" %>
+ <%= label f, :code, Gettext.dpgettext("static_pages", "mfa recover recovery code prompt", "Recovery code") %>
<%= text_input f, :code, [autocomplete: false, autocorrect: "off", autocapitalize: "off", autofocus: true, spellcheck: false] %>
<%= hidden_input f, :mfa_token, value: @mfa_token %>
<%= hidden_input f, :state, value: @state %>
@@ -17,8 +17,8 @@
<%= hidden_input f, :challenge_type, value: "recovery" %>
</div>
-<%= submit "Verify" %>
+<%= submit Gettext.dpgettext("static_pages", "mfa recover verify recovery code button", "Verify") %>
<% end %>
-<a href="<%= mfa_path(@conn, :show, %{challenge_type: "totp", mfa_token: @mfa_token, state: @state, redirect_uri: @redirect_uri}) %>">
- Enter a two-factor code
+<a href="<%= Routes.mfa_path(@conn, :show, %{challenge_type: "totp", mfa_token: @mfa_token, state: @state, redirect_uri: @redirect_uri}) %>">
+ <%= Gettext.dpgettext("static_pages", "mfa recover use 2fa code link", "Enter a two-factor code") %>
</a>
diff --git a/lib/pleroma/web/templates/o_auth/mfa/totp.html.eex b/lib/pleroma/web/templates/o_auth/mfa/totp.html.eex
index af85777eb..50e6c04b6 100644
--- a/lib/pleroma/web/templates/o_auth/mfa/totp.html.eex
+++ b/lib/pleroma/web/templates/o_auth/mfa/totp.html.eex
@@ -5,20 +5,20 @@
<p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p>
<% end %>
-<h2>Two-factor authentication</h2>
+<h2><%= Gettext.dpgettext("static_pages", "mfa auth page title", "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] %>
+ <%= label f, :code, Gettext.dpgettext("static_pages", "mfa auth code prompt", "Authentication code") %>
+ <%= text_input f, :code, [autocomplete: "one-time-code", autocorrect: "off", autocapitalize: "off", autofocus: true, pattern: "[0-9]*", spellcheck: false] %>
<%= hidden_input f, :mfa_token, value: @mfa_token %>
<%= hidden_input f, :state, value: @state %>
<%= hidden_input f, :redirect_uri, value: @redirect_uri %>
<%= hidden_input f, :challenge_type, value: "totp" %>
</div>
-<%= submit "Verify" %>
+<%= submit Gettext.dpgettext("static_pages", "mfa auth verify code button", "Verify") %>
<% end %>
-<a href="<%= mfa_path(@conn, :show, %{challenge_type: "recovery", mfa_token: @mfa_token, state: @state, redirect_uri: @redirect_uri}) %>">
- Enter a two-factor recovery code
+<a href="<%= Routes.mfa_path(@conn, :show, %{challenge_type: "recovery", mfa_token: @mfa_token, state: @state, redirect_uri: @redirect_uri}) %>">
+ <%= Gettext.dpgettext("static_pages", "mfa auth page use recovery code link", "Enter a two-factor recovery code") %>
</a>
diff --git a/lib/pleroma/web/templates/o_auth/o_auth/_scopes.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/_scopes.html.eex
index c9ec1ecbf..73115e92a 100644
--- a/lib/pleroma/web/templates/o_auth/o_auth/_scopes.html.eex
+++ b/lib/pleroma/web/templates/o_auth/o_auth/_scopes.html.eex
@@ -1,5 +1,5 @@
<div class="scopes-input">
- <%= label @form, :scope, "The following permissions will be granted" %>
+ <%= label @form, :scope, Gettext.dpgettext("static_pages", "oauth scopes message", "The following permissions will be granted") %>
<div class="scopes">
<%= for scope <- @available_scopes do %>
<%# Note: using hidden input with `unchecked_value` in order to distinguish user's empty selection from `scope` param being omitted %>
diff --git a/lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex
index 4a0718851..98904ad64 100644
--- a/lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex
+++ b/lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex
@@ -1,8 +1,8 @@
-<h2>Sign in with external provider</h2>
+<h2><%= Gettext.dpgettext("static_pages", "oauth external provider page title", "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}) %>
+ <%= render Phoenix.Controller.view_module(@conn), "_scopes.html", Map.merge(assigns, %{form: f}) %>
</div>
<%= hidden_input f, :client_id, value: @client_id %>
@@ -10,6 +10,6 @@
<%= hidden_input f, :state, value: @state %>
<%= for strategy <- Pleroma.Config.oauth_consumer_strategies() do %>
- <%= submit "Sign in with #{String.capitalize(strategy)}", name: "provider", value: strategy %>
+ <%= submit Gettext.dpgettext("static_pages", "oauth external provider sign in button", "Sign in with %{strategy}", strategy: String.capitalize(strategy)), name: "provider", value: strategy %>
<% end %>
<% end %>
diff --git a/lib/pleroma/web/templates/o_auth/o_auth/oob_authorization_created.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/oob_authorization_created.html.eex
index ffabe29a6..76ed3fda5 100644
--- a/lib/pleroma/web/templates/o_auth/o_auth/oob_authorization_created.html.eex
+++ b/lib/pleroma/web/templates/o_auth/o_auth/oob_authorization_created.html.eex
@@ -1,2 +1,2 @@
-<h1>Successfully authorized</h1>
-<h2>Token code is <br><%= @auth.token %></h2>
+<h1><%= Gettext.dpgettext("static_pages", "oauth authorized page title", "Successfully authorized") %></h1>
+<h2><%= raw Gettext.dpgettext("static_pages", "oauth token code message", "Token code is <br>%{token}", token: safe_to_string(html_escape(@auth.token))) %></h2>
diff --git a/lib/pleroma/web/templates/o_auth/o_auth/oob_token_exists.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/oob_token_exists.html.eex
index 82785c4b9..754bf2eb0 100644
--- a/lib/pleroma/web/templates/o_auth/o_auth/oob_token_exists.html.eex
+++ b/lib/pleroma/web/templates/o_auth/o_auth/oob_token_exists.html.eex
@@ -1,2 +1,2 @@
-<h1>Authorization exists</h1>
-<h2>Access token is <br><%= @token.token %></h2>
+<h1><%= Gettext.dpgettext("static_pages", "oauth authorization exists page title", "Authorization exists") %></h1>
+<h2><%= raw Gettext.dpgettext("static_pages", "oauth token code message", "Token code is <br>%{token}", token: safe_to_string(html_escape(@token.token))) %></h2>
diff --git a/lib/pleroma/web/templates/o_auth/o_auth/register.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/register.html.eex
index facedc8db..1f661efb2 100644
--- a/lib/pleroma/web/templates/o_auth/o_auth/register.html.eex
+++ b/lib/pleroma/web/templates/o_auth/o_auth/register.html.eex
@@ -5,34 +5,34 @@
<p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p>
<% end %>
-<h2>Registration Details</h2>
+<h2><%= Gettext.dpgettext("static_pages", "oauth register page title", "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 -> %>
+<p><%= Gettext.dpgettext("static_pages", "oauth register page fill form prompt", "If you'd like to register a new account, please provide the details below.") %></p>
+<%= form_for @conn, Routes.o_auth_path(@conn, :register), [as: "authorization"], fn f -> %>
<div class="input">
- <%= label f, :nickname, "Nickname" %>
- <%= text_input f, :nickname, value: @nickname %>
+ <%= label f, :nickname, Gettext.dpgettext("static_pages", "oauth register page nickname prompt", "Nickname") %>
+ <%= text_input f, :nickname, value: @nickname, autocomplete: "username" %>
</div>
<div class="input">
- <%= label f, :email, "Email" %>
- <%= text_input f, :email, value: @email %>
+ <%= label f, :email, Gettext.dpgettext("static_pages", "oauth register page email prompt", "Email") %>
+ <%= text_input f, :email, value: @email, autocomplete: "email" %>
</div>
-<%= submit "Proceed as new user", name: "op", value: "register" %>
+<%= submit Gettext.dpgettext("static_pages", "oauth register page register button", "Proceed as new user"), name: "op", value: "register" %>
-<p>Alternatively, sign in to connect to existing account.</p>
+<p><%= Gettext.dpgettext("static_pages", "oauth register page login prompt", "Alternatively, sign in to connect to existing account.") %></p>
<div class="input">
- <%= label f, :name, "Name or email" %>
- <%= text_input f, :name %>
+ <%= label f, :name, Gettext.dpgettext("static_pages", "oauth register page login username prompt", "Name or email") %>
+ <%= text_input f, :name, autocomplete: "username" %>
</div>
<div class="input">
- <%= label f, :password, "Password" %>
- <%= password_input f, :password %>
+ <%= label f, :password, Gettext.dpgettext("static_pages", "oauth register page login password prompt", "Password") %>
+ <%= password_input f, :password, autocomplete: "password" %>
</div>
-<%= submit "Proceed as existing user", name: "op", value: "connect" %>
+<%= submit Gettext.dpgettext("static_pages", "oauth register page login button", "Proceed as existing user"), name: "op", value: "connect" %>
<%= hidden_input f, :client_id, value: @client_id %>
<%= hidden_input f, :redirect_uri, value: @redirect_uri %>
diff --git a/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex
index 1a85818ec..b3654f3eb 100644
--- a/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex
+++ b/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex
@@ -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">
@@ -20,36 +20,38 @@
<div class="container__content">
<%= if @app do %>
- <p>Application <strong><%= @app.client_name %></strong> is requesting access to your account.</p>
- <%= render @view_module, "_scopes.html", Map.merge(assigns, %{form: f}) %>
+ <p><%= raw Gettext.dpgettext("static_pages", "oauth authorize message", "Application <strong>%{client_name}</strong> is requesting access to your account.", client_name: safe_to_string(html_escape(@app.client_name))) %></p>
+ <%= render Phoenix.Controller.view_module(@conn), "_scopes.html", Map.merge(assigns, %{form: f}) %>
<% end %>
<%= if @user do %>
<div class="actions">
- <a class="button button--cancel" href="/">Cancel</a>
- <%= submit "Approve", class: "button--approve" %>
+ <a class="button button--cancel" href="/">
+ <%= Gettext.dpgettext("static_pages", "oauth authorize cancel button", "Cancel") %>
+ </a>
+ <%= submit Gettext.dpgettext("static_pages", "oauth authorize approve button", "Approve"), class: "button--approve" %>
</div>
<% else %>
<%= if @params["registration"] in ["true", true] do %>
- <h3>This is the first time you visit! Please enter your Pleroma handle.</h3>
- <p>Choose carefully! You won't be able to change this later. You will be able to change your display name, though.</p>
+ <h3><%= Gettext.dpgettext("static_pages", "oauth register page title", "This is the first time you visit! Please enter your Pleroma handle.") %></h3>
+ <p><%= Gettext.dpgettext("static_pages", "oauth register nickname unchangeable warning", "Choose carefully! You won't be able to change this later. You will be able to change your display name, though.") %></p>
<div class="input">
- <%= label f, :nickname, "Pleroma Handle" %>
- <%= text_input f, :nickname, placeholder: "lain" %>
+ <%= label f, :nickname, Gettext.dpgettext("static_pages", "oauth register nickname prompt", "Pleroma Handle") %>
+ <%= text_input f, :nickname, placeholder: "lain", autocomplete: "username" %>
</div>
<%= hidden_input f, :name, value: @params["name"] %>
<%= hidden_input f, :password, value: @params["password"] %>
<br>
<% else %>
<div class="input">
- <%= label f, :name, "Username" %>
+ <%= label f, :name, Gettext.dpgettext("static_pages", "oauth login username prompt", "Username") %>
<%= text_input f, :name %>
</div>
<div class="input">
- <%= label f, :password, "Password" %>
+ <%= label f, :password, Gettext.dpgettext("static_pages", "oauth login password prompt", "Password") %>
<%= password_input f, :password %>
</div>
- <%= submit "Log In" %>
+ <%= submit Gettext.dpgettext("static_pages", "oauth login button", "Log In") %>
<% end %>
<% end %>
</div>
@@ -61,5 +63,5 @@
<% end %>
<%= if Pleroma.Config.oauth_consumer_enabled?() do %>
- <%= render @view_module, Pleroma.Web.Auth.Authenticator.oauth_consumer_template(), assigns %>
+ <%= render Phoenix.Controller.view_module(@conn), Pleroma.Web.Auth.WrapperAuthenticator.oauth_consumer_template(), assigns %>
<% end %>
diff --git a/lib/pleroma/web/templates/static_fe/static_fe/profile.html.eex b/lib/pleroma/web/templates/static_fe/static_fe/profile.html.eex
index 3191bf450..a14ca305e 100644
--- a/lib/pleroma/web/templates/static_fe/static_fe/profile.html.eex
+++ b/lib/pleroma/web/templates/static_fe/static_fe/profile.html.eex
@@ -5,7 +5,7 @@
<form class="pull-right collapse" method="POST" action="<%= Helpers.util_path(@conn, :remote_subscribe) %>">
<input type="hidden" name="nickname" value="<%= @user.nickname %>">
<input type="hidden" name="profile" value="">
- <button type="submit" class="collapse">Remote follow</button>
+ <button type="submit" class="collapse"><%= Gettext.dpgettext("static_pages", "static fe profile page remote follow button", "Remote follow") %></button>
</form>
<%= raw Formatter.emojify(@user.name, @user.emoji) %> |
<%= link "@#{@user.nickname}@#{Endpoint.host()}", to: (@user.uri || @user.ap_id) %>
diff --git a/lib/pleroma/web/templates/twitter_api/password/invalid_token.html.eex b/lib/pleroma/web/templates/twitter_api/password/invalid_token.html.eex
index ee84750c7..5ac0aa4e0 100644
--- a/lib/pleroma/web/templates/twitter_api/password/invalid_token.html.eex
+++ b/lib/pleroma/web/templates/twitter_api/password/invalid_token.html.eex
@@ -1 +1 @@
-<h2>Invalid Token</h2>
+<h2><%= Gettext.dpgettext("static_pages", "password reset invalid token message", "Invalid Token") %></h2>
diff --git a/lib/pleroma/web/templates/twitter_api/password/reset.html.eex b/lib/pleroma/web/templates/twitter_api/password/reset.html.eex
index 7d3ef6b0d..6a544af51 100644
--- a/lib/pleroma/web/templates/twitter_api/password/reset.html.eex
+++ b/lib/pleroma/web/templates/twitter_api/password/reset.html.eex
@@ -1,13 +1,13 @@
<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" %>
+ <%= label f, :password, Gettext.dpgettext("static_pages", "password reset form password prompt", "Password") %>
<%= password_input f, :password %>
</div>
<div class="form-row">
- <%= label f, :password_confirmation, "Confirmation" %>
+ <%= label f, :password_confirmation, Gettext.dpgettext("static_pages", "password reset form confirm password prompt", "Confirmation") %>
<%= password_input f, :password_confirmation %>
</div>
<%= hidden_input f, :token, value: @token.token %>
- <%= submit "Reset" %>
+ <%= submit Gettext.dpgettext("static_pages", "password reset button", "Reset") %>
<% end %>
diff --git a/lib/pleroma/web/templates/twitter_api/password/reset_failed.html.eex b/lib/pleroma/web/templates/twitter_api/password/reset_failed.html.eex
index df037c01e..774e3462a 100644
--- a/lib/pleroma/web/templates/twitter_api/password/reset_failed.html.eex
+++ b/lib/pleroma/web/templates/twitter_api/password/reset_failed.html.eex
@@ -1,2 +1,6 @@
-<h2>Password reset failed</h2>
-<h3><a href="<%= Pleroma.Web.base_url() %>">Homepage</a></h3>
+<h2><%= Gettext.dpgettext("static_pages", "password reset failed message", "Password reset failed") %></h2>
+<h3>
+ <a href="<%= Pleroma.Web.Endpoint.url() %>">
+ <%= Gettext.dpgettext("static_pages", "password reset failed homepage link", "Homepage") %>
+ </a>
+</h3>
diff --git a/lib/pleroma/web/templates/twitter_api/password/reset_success.html.eex b/lib/pleroma/web/templates/twitter_api/password/reset_success.html.eex
index f30ba3274..40f6bb3fc 100644
--- a/lib/pleroma/web/templates/twitter_api/password/reset_success.html.eex
+++ b/lib/pleroma/web/templates/twitter_api/password/reset_success.html.eex
@@ -1,2 +1,2 @@
-<h2>Password changed!</h2>
-<h3><a href="<%= Pleroma.Web.base_url() %>">Homepage</a></h3>
+<h2><%= Gettext.dpgettext("static_pages", "password reset successful message", "Password changed!") %></h2>
+<h3><a href="<%= Pleroma.Web.Endpoint.url() %>"><%= Gettext.dpgettext("static_pages", "password reset successful homepage link", "Homepage") %></a></h3>
diff --git a/lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex b/lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex
index 5ba192cd7..e2d251fac 100644
--- a/lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex
+++ b/lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex
@@ -1,11 +1,11 @@
<%= if @error == :error do %>
- <h2>Error fetching user</h2>
+ <h2><%= Gettext.dpgettext("static_pages", "remote follow error", "Error fetching user") %></h2>
<% else %>
- <h2>Remote follow</h2>
+ <h2><%= Gettext.dpgettext("static_pages", "remote follow header", "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" %>
+ <%= submit Gettext.dpgettext("static_pages", "remote follow authorization button", "Authorize") %>
<% end %>
<% end %>
diff --git a/lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex b/lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex
index df44988ee..26340a906 100644
--- a/lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex
+++ b/lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex
@@ -1,14 +1,14 @@
<%= if @error do %>
<h2><%= @error %></h2>
<% end %>
-<h2>Log in to follow</h2>
+<h2><%= Gettext.dpgettext("static_pages", "remote follow header, need login", "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 -> %>
-<%= text_input f, :name, placeholder: "Username", required: true %>
+<%= form_for @conn, Routes.remote_follow_path(@conn, :do_follow), [as: "authorization"], fn f -> %>
+<%= text_input f, :name, placeholder: Gettext.dpgettext("static_pages", "placeholder text for username entry", "Username"), required: true, autocomplete: "username" %>
<br>
-<%= password_input f, :password, placeholder: "Password", required: true %>
+<%= password_input f, :password, placeholder: Gettext.dpgettext("static_pages", "placeholder text for password entry", "Password"), required: true, autocomplete: "password" %>
<br>
<%= hidden_input f, :id, value: @followee.id %>
-<%= submit "Authorize" %>
+<%= submit Gettext.dpgettext("static_pages", "remote follow authorization button for login", "Authorize") %>
<% end %>
diff --git a/lib/pleroma/web/templates/twitter_api/remote_follow/follow_mfa.html.eex b/lib/pleroma/web/templates/twitter_api/remote_follow/follow_mfa.html.eex
index adc3a3e3d..638212c1e 100644
--- a/lib/pleroma/web/templates/twitter_api/remote_follow/follow_mfa.html.eex
+++ b/lib/pleroma/web/templates/twitter_api/remote_follow/follow_mfa.html.eex
@@ -1,13 +1,13 @@
<%= if @error do %>
<h2><%= @error %></h2>
<% end %>
-<h2>Two-factor authentication</h2>
+<h2><%= Gettext.dpgettext("static_pages", "remote follow mfa header", "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 -> %>
-<%= text_input f, :code, placeholder: "Authentication code", required: true %>
+<%= form_for @conn, Routes.remote_follow_path(@conn, :do_follow), [as: "mfa"], fn f -> %>
+<%= text_input f, :code, placeholder: Gettext.dpgettext("static_pages", "placeholder text for auth code entry", "Authentication code"), required: true %>
<br>
<%= hidden_input f, :id, value: @followee.id %>
<%= hidden_input f, :token, value: @mfa_token %>
-<%= submit "Authorize" %>
+<%= submit Gettext.dpgettext("static_pages", "remote follow authorization button for mfa", "Authorize") %>
<% end %>
diff --git a/lib/pleroma/web/templates/twitter_api/remote_follow/followed.html.eex b/lib/pleroma/web/templates/twitter_api/remote_follow/followed.html.eex
index da473d502..2fb4cc5d3 100644
--- a/lib/pleroma/web/templates/twitter_api/remote_follow/followed.html.eex
+++ b/lib/pleroma/web/templates/twitter_api/remote_follow/followed.html.eex
@@ -1,6 +1,5 @@
<%= if @error do %>
-<p>Error following account</p>
+<p><%= Gettext.dpgettext("static_pages", "remote follow error", "Error following account") %></p>
<% else %>
-<h2>Account followed!</h2>
+<h2><%= Gettext.dpgettext("static_pages", "remote follow success", "Account followed!") %></h2>
<% end %>
-
diff --git a/lib/pleroma/web/templates/twitter_api/util/status_interact.html.eex b/lib/pleroma/web/templates/twitter_api/util/status_interact.html.eex
new file mode 100644
index 000000000..d77174967
--- /dev/null
+++ b/lib/pleroma/web/templates/twitter_api/util/status_interact.html.eex
@@ -0,0 +1,10 @@
+<%= if @error do %>
+ <h2><%= Gettext.dpgettext("static_pages", "status interact error", "Error: %{error}", error: @error) %></h2>
+<% else %>
+ <h2><%= raw Gettext.dpgettext("static_pages", "status interact header", "Interacting with %{nickname}'s %{status_link}", nickname: safe_to_string(html_escape(@nickname)), status_link: safe_to_string(link(Gettext.dpgettext("static_pages", "status interact header - status link text", "status"), to: @status_link))) %></h2>
+ <%= form_for @conn, Routes.util_path(@conn, :remote_subscribe), [as: "status"], fn f -> %>
+ <%= hidden_input f, :status_id, value: @status_id %>
+ <%= text_input f, :profile, placeholder: Gettext.dpgettext("static_pages", "placeholder text for account id", "Your account ID, e.g. lain@quitter.se") %>
+ <%= submit Gettext.dpgettext("static_pages", "status interact authorization button", "Interact") %>
+ <% end %>
+<% end %>
diff --git a/lib/pleroma/web/templates/twitter_api/util/subscribe.html.eex b/lib/pleroma/web/templates/twitter_api/util/subscribe.html.eex
index f60accebf..848660f26 100644
--- a/lib/pleroma/web/templates/twitter_api/util/subscribe.html.eex
+++ b/lib/pleroma/web/templates/twitter_api/util/subscribe.html.eex
@@ -1,10 +1,10 @@
<%= if @error do %>
- <h2>Error: <%= @error %></h2>
+ <h2><%= Gettext.dpgettext("static_pages", "remote follow error", "Error: %{error}", error: @error) %></h2>
<% else %>
- <h2>Remotely follow <%= @nickname %></h2>
- <%= form_for @conn, util_path(@conn, :remote_subscribe), [as: "user"], fn f -> %>
+ <h2><%= Gettext.dpgettext("static_pages", "remote follow header", "Remotely follow %{nickname}", nickname: @nickname) %></h2>
+ <%= 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" %>
+ <%= text_input f, :profile, placeholder: Gettext.dpgettext("static_pages", "placeholder text for account id", "Your account ID, e.g. lain@quitter.se") %>
+ <%= submit Gettext.dpgettext("static_pages", "remote follow authorization button for following with a remote account", "Follow") %>
<% end %>
<% end %>
diff --git a/lib/pleroma/web/translation_helpers.ex b/lib/pleroma/web/translation_helpers.ex
index 7f78ce1b9..e9638b00a 100644
--- a/lib/pleroma/web/translation_helpers.ex
+++ b/lib/pleroma/web/translation_helpers.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.TranslationHelpers do
diff --git a/lib/pleroma/web/twitter_api/controller.ex b/lib/pleroma/web/twitter_api/controller.ex
index 16f43863c..6db3d6067 100644
--- a/lib/pleroma/web/twitter_api/controller.ex
+++ b/lib/pleroma/web/twitter_api/controller.ex
@@ -1,36 +1,25 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
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
alias Pleroma.Web.Plugs.OAuthScopesPlug
alias Pleroma.Web.TwitterAPI.TokenView
require Logger
- plug(
- OAuthScopesPlug,
- %{scopes: ["write:notifications"]} when action == :mark_notifications_as_read
- )
-
- plug(
- :skip_plug,
- [OAuthScopesPlug, EnsurePublicOrAuthenticatedPlug] when action == :confirm_email
- )
-
+ plug(:skip_auth when action == :confirm_email)
plug(:skip_plug, OAuthScopesPlug when action in [:oauth_tokens, :revoke_token])
action_fallback(:errors)
def confirm_email(conn, %{"user_id" => uid, "token" => token}) do
with %User{} = user <- User.get_cached_by_id(uid),
- true <- user.local and user.confirmation_pending and user.confirmation_token == token,
+ true <- user.local and !user.is_confirmed and user.confirmation_token == token,
{:ok, _} <- User.confirm(user) do
redirect(conn, to: "/")
end
@@ -67,31 +56,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
diff --git a/lib/pleroma/web/twitter_api/controllers/password_controller.ex b/lib/pleroma/web/twitter_api/controllers/password_controller.ex
index b1a9d810e..31b7dd728 100644
--- a/lib/pleroma/web/twitter_api/controllers/password_controller.ex
+++ b/lib/pleroma/web/twitter_api/controllers/password_controller.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.TwitterAPI.PasswordController do
@@ -11,9 +11,23 @@ defmodule Pleroma.Web.TwitterAPI.PasswordController do
require Logger
+ import Pleroma.Web.ControllerHelper, only: [json_response: 3]
+
alias Pleroma.PasswordResetToken
alias Pleroma.Repo
alias Pleroma.User
+ alias Pleroma.Web.TwitterAPI.TwitterAPI
+
+ plug(Pleroma.Web.Plugs.RateLimiter, [name: :request] when action == :request)
+
+ @doc "POST /auth/password"
+ def request(conn, params) do
+ nickname_or_email = params["email"] || params["nickname"]
+
+ TwitterAPI.password_reset(nickname_or_email)
+
+ json_response(conn, :no_content, "")
+ end
def reset(conn, %{"token" => token}) do
with %{used: false} = token <- Repo.get_by(PasswordResetToken, %{token: token}),
diff --git a/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex b/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex
index 4480a4922..6229d5d05 100644
--- a/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex
+++ b/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.TwitterAPI.RemoteFollowController do
@@ -11,8 +11,8 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowController do
alias Pleroma.MFA
alias Pleroma.Object.Fetcher
alias Pleroma.User
- alias Pleroma.Web.Auth.Authenticator
alias Pleroma.Web.Auth.TOTPAuthenticator
+ alias Pleroma.Web.Auth.WrapperAuthenticator
alias Pleroma.Web.CommonAPI
@status_types ["Article", "Event", "Note", "Video", "Page", "Question"]
@@ -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)
@@ -88,7 +88,7 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowController do
#
def do_follow(conn, %{"authorization" => %{"name" => _, "password" => _, "id" => id}}) do
with {_, %User{} = followee} <- {:fetch_user, User.get_cached_by_id(id)},
- {_, {:ok, user}, _} <- {:auth, Authenticator.get_user(conn), followee},
+ {_, {:ok, user}, _} <- {:auth, WrapperAuthenticator.get_user(conn), followee},
{_, _, _, false} <- {:mfa_required, followee, user, MFA.require?(user)},
{:ok, _, _, _} <- CommonAPI.follow(user, followee) do
redirect(conn, to: "/users/#{followee.id}")
diff --git a/lib/pleroma/web/twitter_api/controllers/util_controller.ex b/lib/pleroma/web/twitter_api/controllers/util_controller.ex
index 9ead0d626..d5a24ae6c 100644
--- a/lib/pleroma/web/twitter_api/controllers/util_controller.ex
+++ b/lib/pleroma/web/twitter_api/controllers/util_controller.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.TwitterAPI.UtilController do
@@ -7,16 +7,26 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
require Logger
+ alias Pleroma.Activity
alias Pleroma.Config
alias Pleroma.Emoji
alias Pleroma.Healthcheck
- alias Pleroma.Notification
alias Pleroma.User
+ alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.Plugs.OAuthScopesPlug
alias Pleroma.Web.WebFinger
- plug(Pleroma.Web.Plugs.FederatingPlug when action == :remote_subscribe)
+ plug(
+ Pleroma.Web.ApiSpec.CastAndValidate
+ when action != :remote_subscribe and action != :show_subscribe_form
+ )
+
+ plug(
+ Pleroma.Web.Plugs.FederatingPlug
+ when action == :remote_subscribe
+ when action == :show_subscribe_form
+ )
plug(
OAuthScopesPlug,
@@ -26,13 +36,24 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
:change_password,
:delete_account,
:update_notificaton_settings,
- :disable_account
+ :disable_account,
+ :move_account,
+ :add_alias,
+ :delete_alias
]
)
- plug(OAuthScopesPlug, %{scopes: ["write:notifications"]} when action == :notifications_read)
+ plug(
+ OAuthScopesPlug,
+ %{scopes: ["read:accounts"]}
+ when action in [
+ :list_aliases
+ ]
+ )
- def remote_subscribe(conn, %{"nickname" => nick, "profile" => _}) do
+ defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.TwitterUtilOperation
+
+ def show_subscribe_form(conn, %{"nickname" => nick}) do
with %User{} = user <- User.get_cached_by_nickname(nick),
avatar = User.avatar_url(user) do
conn
@@ -42,11 +63,52 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
render(conn, "subscribe.html", %{
nickname: nick,
avatar: nil,
- error: "Could not find user"
+ error:
+ Pleroma.Web.Gettext.dpgettext(
+ "static_pages",
+ "remote follow error message - user not found",
+ "Could not find user"
+ )
+ })
+ end
+ end
+
+ def show_subscribe_form(conn, %{"status_id" => id}) do
+ with %Activity{} = activity <- Activity.get_by_id(id),
+ {:ok, ap_id} <- get_ap_id(activity),
+ %User{} = user <- User.get_cached_by_ap_id(activity.actor),
+ avatar = User.avatar_url(user) do
+ conn
+ |> render("status_interact.html", %{
+ status_link: ap_id,
+ status_id: id,
+ nickname: user.nickname,
+ avatar: avatar,
+ error: false
+ })
+ else
+ _e ->
+ render(conn, "status_interact.html", %{
+ status_id: id,
+ avatar: nil,
+ error:
+ Pleroma.Web.Gettext.dpgettext(
+ "static_pages",
+ "status interact error message - status not found",
+ "Could not find status"
+ )
})
end
end
+ def remote_subscribe(conn, %{"nickname" => nick, "profile" => _}) do
+ show_subscribe_form(conn, %{"nickname" => nick})
+ end
+
+ def remote_subscribe(conn, %{"status_id" => id, "profile" => _}) do
+ show_subscribe_form(conn, %{"status_id" => id})
+ end
+
def remote_subscribe(conn, %{"user" => %{"nickname" => nick, "profile" => profile}}) do
with {:ok, %{"subscribe_address" => template}} <- WebFinger.finger(profile),
%User{ap_id: ap_id} <- User.get_cached_by_nickname(nick) do
@@ -57,19 +119,52 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
render(conn, "subscribe.html", %{
nickname: nick,
avatar: nil,
- error: "Something went wrong."
+ error:
+ Pleroma.Web.Gettext.dpgettext(
+ "static_pages",
+ "remote follow error message - unknown error",
+ "Something went wrong."
+ )
})
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"})
+ def remote_subscribe(conn, %{"status" => %{"status_id" => id, "profile" => profile}}) do
+ with {:ok, %{"subscribe_address" => template}} <- WebFinger.finger(profile),
+ %Activity{} = activity <- Activity.get_by_id(id),
+ {:ok, ap_id} <- get_ap_id(activity) do
+ conn
+ |> Phoenix.Controller.redirect(external: String.replace(template, "{uri}", ap_id))
else
- {:error, message} ->
- conn
- |> put_resp_content_type("application/json")
- |> send_resp(403, Jason.encode!(%{"error" => message}))
+ _e ->
+ render(conn, "status_interact.html", %{
+ status_id: id,
+ avatar: nil,
+ error:
+ Pleroma.Web.Gettext.dpgettext(
+ "static_pages",
+ "status interact error message - unknown error",
+ "Something went wrong."
+ )
+ })
+ end
+ end
+
+ def remote_interaction(%{body_params: %{ap_id: ap_id, profile: profile}} = conn, _params) do
+ with {:ok, %{"subscribe_address" => template}} <- WebFinger.finger(profile) do
+ conn
+ |> json(%{url: String.replace(template, "{uri}", ap_id)})
+ else
+ _e -> json(conn, %{error: "Couldn't find user"})
+ end
+ end
+
+ defp get_ap_id(activity) do
+ object = Pleroma.Object.normalize(activity, fetch: false)
+
+ case object do
+ %{data: %{"id" => ap_id}} -> {:ok, ap_id}
+ _ -> {:no_ap_id, nil}
end
end
@@ -92,13 +187,13 @@ 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}, body_params: body_params} = conn, %{}) do
+ case CommonAPI.Utils.confirm_current_password(user, body_params.password) do
{:ok, user} ->
with {:ok, _user} <-
User.reset_password(user, %{
- password: params["new_password"],
- password_confirmation: params["new_password_confirmation"]
+ password: body_params.new_password,
+ password_confirmation: body_params.new_password_confirmation
}) do
json(conn, %{status: "success"})
else
@@ -115,10 +210,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}, body_params: body_params} = conn, %{}) do
+ case CommonAPI.Utils.confirm_current_password(user, body_params.password) do
{:ok, user} ->
- with {:ok, _user} <- User.change_email(user, params["email"]) do
+ with {:ok, _user} <- User.change_email(user, body_params.email) do
json(conn, %{status: "success"})
else
{:error, changeset} ->
@@ -134,8 +229,10 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
end
end
- def delete_account(%{assigns: %{user: user}} = conn, params) do
- password = params["password"] || ""
+ def delete_account(%{assigns: %{user: user}, body_params: body_params} = conn, params) do
+ # This endpoint can accept a query param or JSON body for backwards-compatibility.
+ # Submitting a JSON body is recommended, so passwords don't end up in server logs.
+ password = body_params[:password] || params[:password] || ""
case CommonAPI.Utils.confirm_current_password(user, password) do
{:ok, user} ->
@@ -148,9 +245,9 @@ 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.deactivate_async(user)
+ User.set_activation_async(user, false)
json(conn, %{status: "success"})
{:error, msg} ->
@@ -158,6 +255,91 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
end
end
+ def move_account(%{assigns: %{user: user}, body_params: body_params} = conn, %{}) do
+ case CommonAPI.Utils.confirm_current_password(user, body_params.password) do
+ {:ok, user} ->
+ with {:ok, target_user} <- find_or_fetch_user_by_nickname(body_params.target_account),
+ {:ok, _user} <- ActivityPub.move(user, target_user) do
+ json(conn, %{status: "success"})
+ else
+ {:not_found, _} ->
+ conn
+ |> put_status(404)
+ |> json(%{error: "Target account not found."})
+
+ {:error, error} ->
+ json(conn, %{error: error})
+ end
+
+ {:error, msg} ->
+ json(conn, %{error: msg})
+ end
+ end
+
+ def add_alias(%{assigns: %{user: user}, body_params: body_params} = conn, _) do
+ with {:ok, alias_user} <- find_user_by_nickname(body_params.alias),
+ {:ok, _user} <- user |> User.add_alias(alias_user) do
+ json(conn, %{status: "success"})
+ else
+ {:not_found, _} ->
+ conn
+ |> put_status(404)
+ |> json(%{error: "Target account does not exist."})
+
+ {:error, error} ->
+ json(conn, %{error: error})
+ end
+ end
+
+ def delete_alias(%{assigns: %{user: user}, body_params: body_params} = conn, _) do
+ with {:ok, alias_user} <- find_user_by_nickname(body_params.alias),
+ {:ok, _user} <- user |> User.delete_alias(alias_user) do
+ json(conn, %{status: "success"})
+ else
+ {:error, :no_such_alias} ->
+ conn
+ |> put_status(404)
+ |> json(%{error: "Account has no such alias."})
+
+ {:error, error} ->
+ json(conn, %{error: error})
+ end
+ end
+
+ def list_aliases(%{assigns: %{user: user}} = conn, %{}) do
+ alias_nicks =
+ user
+ |> User.alias_users()
+ |> Enum.map(&User.full_nickname/1)
+
+ json(conn, %{aliases: alias_nicks})
+ end
+
+ defp find_user_by_nickname(nickname) do
+ user = User.get_cached_by_nickname(nickname)
+
+ if user == nil do
+ {:not_found, nil}
+ else
+ {:ok, user}
+ end
+ end
+
+ defp find_or_fetch_user_by_nickname(nickname) do
+ user = User.get_by_nickname(nickname)
+
+ if user != nil and user.local do
+ {:ok, user}
+ else
+ with {:ok, user} <- User.fetch_by_nickname(nickname) do
+ {:ok, user}
+ else
+ _ ->
+ {:not_found, nil}
+ end
+ end
+ end
+
def captcha(conn, _params) do
json(conn, Pleroma.Captcha.new())
end
diff --git a/lib/pleroma/web/twitter_api/twitter_api.ex b/lib/pleroma/web/twitter_api/twitter_api.ex
index 8e20b0d55..ef2eb75f4 100644
--- a/lib/pleroma/web/twitter_api/twitter_api.ex
+++ b/lib/pleroma/web/twitter_api/twitter_api.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
@@ -12,6 +12,8 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
alias Pleroma.UserInviteToken
def register_user(params, opts \\ []) do
+ fallback_language = Gettext.get_locale()
+
params =
params
|> Map.take([:email, :token, :password])
@@ -20,6 +22,11 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
|> Map.put(:name, Map.get(params, :fullname, params[:username]))
|> Map.put(:password_confirmation, params[:password])
|> Map.put(:registration_reason, params[:reason])
+ |> Map.put(:birthday, params[:birthday])
+ |> Map.put(
+ :language,
+ Pleroma.Web.Gettext.normalize_locale(params[:language]) || fallback_language
+ )
if Pleroma.Config.get([:instance, :registrations_open]) do
create_user(params, opts)
@@ -59,7 +66,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
def password_reset(nickname_or_email) do
with true <- is_binary(nickname_or_email),
- %User{local: true, email: email, deactivated: false} = user when is_binary(email) <-
+ %User{local: true, email: email, is_active: true} = user when is_binary(email) <-
User.get_by_nickname_or_email(nickname_or_email),
{:ok, token_record} <- Pleroma.PasswordResetToken.create_token(user) do
user
diff --git a/lib/pleroma/web/twitter_api/views/password_view.ex b/lib/pleroma/web/twitter_api/views/password_view.ex
index 41462e4af..55790941f 100644
--- a/lib/pleroma/web/twitter_api/views/password_view.ex
+++ b/lib/pleroma/web/twitter_api/views/password_view.ex
@@ -1,8 +1,9 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.TwitterAPI.PasswordView do
use Pleroma.Web, :view
import Phoenix.HTML.Form
+ alias Pleroma.Web.Gettext
end
diff --git a/lib/pleroma/web/twitter_api/views/remote_follow_view.ex b/lib/pleroma/web/twitter_api/views/remote_follow_view.ex
index c05c7821c..8902261b0 100644
--- a/lib/pleroma/web/twitter_api/views/remote_follow_view.ex
+++ b/lib/pleroma/web/twitter_api/views/remote_follow_view.ex
@@ -1,10 +1,15 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.TwitterAPI.RemoteFollowView do
use Pleroma.Web, :view
import Phoenix.HTML.Form
+ alias Pleroma.Web.Gettext
- defdelegate avatar_url(user), to: Pleroma.User
+ def avatar_url(user) do
+ user
+ |> Pleroma.User.avatar_url()
+ |> Pleroma.Web.MediaProxy.url()
+ end
end
diff --git a/lib/pleroma/web/twitter_api/views/token_view.ex b/lib/pleroma/web/twitter_api/views/token_view.ex
index c36303625..2e492c13f 100644
--- a/lib/pleroma/web/twitter_api/views/token_view.ex
+++ b/lib/pleroma/web/twitter_api/views/token_view.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.TwitterAPI.TokenView do
diff --git a/lib/pleroma/web/twitter_api/views/util_view.ex b/lib/pleroma/web/twitter_api/views/util_view.ex
index 98eea1d18..31b7c0c0c 100644
--- a/lib/pleroma/web/twitter_api/views/util_view.ex
+++ b/lib/pleroma/web/twitter_api/views/util_view.ex
@@ -1,19 +1,22 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.TwitterAPI.UtilView do
use Pleroma.Web, :view
+ import Phoenix.HTML
import Phoenix.HTML.Form
+ import Phoenix.HTML.Link
alias Pleroma.Config
- alias Pleroma.Web
+ alias Pleroma.Web.Endpoint
+ alias Pleroma.Web.Gettext
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/uploader_controller.ex b/lib/pleroma/web/uploader_controller.ex
index 6533f1c0e..d5c804932 100644
--- a/lib/pleroma/web/uploader_controller.ex
+++ b/lib/pleroma/web/uploader_controller.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.UploaderController do
diff --git a/lib/pleroma/web/utils/guards.ex b/lib/pleroma/web/utils/guards.ex
new file mode 100644
index 000000000..8a61421fa
--- /dev/null
+++ b/lib/pleroma/web/utils/guards.ex
@@ -0,0 +1,13 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Utils.Guards do
+ @moduledoc """
+ Project-wide custom guards.
+ See: https://hexdocs.pm/elixir/master/patterns-and-guards.html#custom-patterns-and-guards-expressions
+ """
+
+ @doc "Checks for non-empty string"
+ defguard not_empty_string(string) when is_binary(string) and string != ""
+end
diff --git a/lib/pleroma/web/utils/params.ex b/lib/pleroma/web/utils/params.ex
new file mode 100644
index 000000000..636e3de33
--- /dev/null
+++ b/lib/pleroma/web/utils/params.ex
@@ -0,0 +1,16 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Utils.Params do
+ # As in Mastodon API, per https://api.rubyonrails.org/classes/ActiveModel/Type/Boolean.html
+ @falsy_param_values [false, 0, "0", "f", "F", "false", "False", "FALSE", "off", "OFF"]
+
+ defp explicitly_falsy_param?(value), do: value in @falsy_param_values
+
+ # Note: `nil` and `""` are considered falsy values in Pleroma
+ defp falsy_param?(value),
+ do: explicitly_falsy_param?(value) or value in [nil, ""]
+
+ def truthy_param?(value), do: not falsy_param?(value)
+end
diff --git a/lib/pleroma/web/views/email_view.ex b/lib/pleroma/web/views/email_view.ex
index bcdee6571..9ab708212 100644
--- a/lib/pleroma/web/views/email_view.ex
+++ b/lib/pleroma/web/views/email_view.ex
@@ -1,11 +1,12 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.EmailView do
use Pleroma.Web, :view
import Phoenix.HTML
import Phoenix.HTML.Link
+ alias Pleroma.Web.Gettext
def avatar_url(user) do
Pleroma.User.avatar_url(user)
diff --git a/lib/pleroma/web/views/embed_view.ex b/lib/pleroma/web/views/embed_view.ex
index 5f50bd155..1bfd8c523 100644
--- a/lib/pleroma/web/views/embed_view.ex
+++ b/lib/pleroma/web/views/embed_view.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.EmbedView do
@@ -17,6 +17,8 @@ defmodule Pleroma.Web.EmbedView do
use Phoenix.HTML
+ defdelegate full_nickname(user), to: User
+
@media_types ["image", "audio", "video"]
defp fetch_media_type(%{"mediaType" => mediaType}) do
@@ -30,11 +32,6 @@ defmodule Pleroma.Web.EmbedView do
)
end
- defp full_nickname(user) do
- %{host: host} = URI.parse(user.ap_id)
- "@" <> user.nickname <> "@" <> host
- end
-
defp status_title(%Activity{object: %Object{data: %{"name" => name}}}) when is_binary(name),
do: name
diff --git a/lib/pleroma/web/views/error_helpers.ex b/lib/pleroma/web/views/error_helpers.ex
index df657a343..fd85f7c15 100644
--- a/lib/pleroma/web/views/error_helpers.ex
+++ b/lib/pleroma/web/views/error_helpers.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ErrorHelpers do
diff --git a/lib/pleroma/web/views/error_view.ex b/lib/pleroma/web/views/error_view.ex
index e68d55e08..9ab16323d 100644
--- a/lib/pleroma/web/views/error_view.ex
+++ b/lib/pleroma/web/views/error_view.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ErrorView do
diff --git a/lib/pleroma/web/views/layout_view.ex b/lib/pleroma/web/views/layout_view.ex
index 3e49c6549..3161bb1ae 100644
--- a/lib/pleroma/web/views/layout_view.ex
+++ b/lib/pleroma/web/views/layout_view.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.LayoutView do
diff --git a/lib/pleroma/web/views/mailer/subscription_view.ex b/lib/pleroma/web/views/mailer/subscription_view.ex
index 4562a9d6c..037fb6578 100644
--- a/lib/pleroma/web/views/mailer/subscription_view.ex
+++ b/lib/pleroma/web/views/mailer/subscription_view.ex
@@ -1,7 +1,8 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Mailer.SubscriptionView do
use Pleroma.Web, :view
+ alias Pleroma.Web.Gettext
end
diff --git a/lib/pleroma/web/views/manifest_view.ex b/lib/pleroma/web/views/manifest_view.ex
new file mode 100644
index 000000000..2ae82191e
--- /dev/null
+++ b/lib/pleroma/web/views/manifest_view.ex
@@ -0,0 +1,28 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ManifestView do
+ use Pleroma.Web, :view
+ alias Pleroma.Config
+ alias Pleroma.Web.Endpoint
+
+ 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: Endpoint.url(),
+ start_url: "/",
+ categories: [
+ "social"
+ ],
+ serviceworker: %{
+ src: "/sw.js"
+ }
+ }
+ end
+end
diff --git a/lib/pleroma/web/views/masto_fe_view.ex b/lib/pleroma/web/views/masto_fe_view.ex
deleted file mode 100644
index b1669d198..000000000
--- a/lib/pleroma/web/views/masto_fe_view.ex
+++ /dev/null
@@ -1,91 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.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.base_url(),
- start_url: masto_fe_path(Pleroma.Web.Endpoint, :index, ["getting-started"]),
- categories: [
- "social"
- ],
- serviceworker: %{
- src: "/sw.js"
- }
- }
- end
-end
diff --git a/lib/pleroma/web/views/streamer_view.ex b/lib/pleroma/web/views/streamer_view.ex
index 4fc14166d..6a55242b0 100644
--- a/lib/pleroma/web/views/streamer_view.ex
+++ b/lib/pleroma/web/views/streamer_view.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.StreamerView do
@@ -25,6 +25,20 @@ defmodule Pleroma.Web.StreamerView do
|> Jason.encode!()
end
+ def render("status_update.json", %Activity{} = activity, %User{} = user) do
+ %{
+ event: "status.update",
+ payload:
+ Pleroma.Web.MastodonAPI.StatusView.render(
+ "show.json",
+ activity: activity,
+ for: user
+ )
+ |> Jason.encode!()
+ }
+ |> Jason.encode!()
+ end
+
def render("notification.json", %Notification{} = notify, %User{} = user) do
%{
event: "notification",
@@ -51,6 +65,19 @@ defmodule Pleroma.Web.StreamerView do
|> Jason.encode!()
end
+ def render("status_update.json", %Activity{} = activity) do
+ %{
+ event: "status.update",
+ payload:
+ Pleroma.Web.MastodonAPI.StatusView.render(
+ "show.json",
+ activity: activity
+ )
+ |> Jason.encode!()
+ }
+ |> Jason.encode!()
+ end
+
def render("chat_update.json", %{chat_message_reference: cm_ref}) do
# Explicitly giving the cmr for the object here, so we don't accidentally
# send a later 'last_message' that was inserted between inserting this and
diff --git a/lib/pleroma/web/web_finger.ex b/lib/pleroma/web/web_finger.ex
index a109e1acc..f95dc2458 100644
--- a/lib/pleroma/web/web_finger.ex
+++ b/lib/pleroma/web/web_finger.ex
@@ -1,11 +1,11 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
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,
@@ -32,7 +32,13 @@ defmodule Pleroma.Web.WebFinger do
def webfinger(resource, fmt) when fmt in ["XML", "JSON"] do
host = Pleroma.Web.Endpoint.host()
- regex = ~r/(acct:)?(?<username>[a-z0-9A-Z_\.-]+)@#{host}/
+
+ regex =
+ if webfinger_domain = Pleroma.Config.get([__MODULE__, :domain]) do
+ ~r/(acct:)?(?<username>[a-z0-9A-Z_\.-]+)@(#{host}|#{webfinger_domain})/
+ else
+ ~r/(acct:)?(?<username>[a-z0-9A-Z_\.-]+)@#{host}/
+ end
with %{"username" => username} <- Regex.named_captures(regex, resource),
%User{} = user <- User.get_cached_by_nickname(username) do
@@ -63,18 +69,14 @@ defmodule Pleroma.Web.WebFinger do
end
def represent_user(user, "JSON") do
- {:ok, user} = User.ensure_keys_present(user)
-
%{
- "subject" => "acct:#{user.nickname}@#{Pleroma.Web.Endpoint.host()}",
+ "subject" => "acct:#{user.nickname}@#{domain()}",
"aliases" => gather_aliases(user),
"links" => gather_links(user)
}
end
def represent_user(user, "XML") do
- {:ok, user} = User.ensure_keys_present(user)
-
aliases =
user
|> gather_aliases()
@@ -88,75 +90,81 @@ defmodule Pleroma.Web.WebFinger do
:XRD,
%{xmlns: "http://docs.oasis-open.org/ns/xri/xrd-1.0"},
[
- {:Subject, "acct:#{user.nickname}@#{Pleroma.Web.Endpoint.host()}"}
+ {:Subject, "acct:#{user.nickname}@#{domain()}"}
] ++ aliases ++ links
}
|> XmlBuilder.to_doc()
end
- defp webfinger_from_xml(doc) do
- subject = XML.string_from_xpath("//Subject", doc)
+ defp domain do
+ Pleroma.Config.get([__MODULE__, :domain]) || Pleroma.Web.Endpoint.host()
+ end
- subscribe_address =
- ~s{//Link[@rel="http://ostatus.org/schema/1.0/subscribe"]/@template}
- |> XML.string_from_xpath(doc)
+ defp webfinger_from_xml(body) do
+ with {:ok, doc} <- XML.parse_document(body) do
+ subject = XML.string_from_xpath("//Subject", doc)
- ap_id =
- ~s{//Link[@rel="self" and @type="application/activity+json"]/@href}
- |> XML.string_from_xpath(doc)
+ subscribe_address =
+ ~s{//Link[@rel="http://ostatus.org/schema/1.0/subscribe"]/@template}
+ |> XML.string_from_xpath(doc)
- data = %{
- "subject" => subject,
- "subscribe_address" => subscribe_address,
- "ap_id" => ap_id
- }
+ ap_id =
+ ~s{//Link[@rel="self" and @type="application/activity+json"]/@href}
+ |> XML.string_from_xpath(doc)
- {:ok, data}
+ data = %{
+ "subject" => subject,
+ "subscribe_address" => subscribe_address,
+ "ap_id" => ap_id
+ }
+
+ {:ok, data}
+ end
end
- defp webfinger_from_json(doc) do
- data =
- Enum.reduce(doc["links"], %{"subject" => doc["subject"]}, fn link, data ->
- case {link["type"], link["rel"]} do
- {"application/activity+json", "self"} ->
- Map.put(data, "ap_id", link["href"])
+ defp webfinger_from_json(body) do
+ with {:ok, doc} <- Jason.decode(body) do
+ data =
+ Enum.reduce(doc["links"], %{"subject" => doc["subject"]}, fn link, data ->
+ case {link["type"], link["rel"]} do
+ {"application/activity+json", "self"} ->
+ Map.put(data, "ap_id", link["href"])
- {"application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"", "self"} ->
- Map.put(data, "ap_id", link["href"])
+ {"application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"", "self"} ->
+ Map.put(data, "ap_id", link["href"])
- {nil, "http://ostatus.org/schema/1.0/subscribe"} ->
- Map.put(data, "subscribe_address", link["template"])
+ {nil, "http://ostatus.org/schema/1.0/subscribe"} ->
+ Map.put(data, "subscribe_address", link["template"])
- _ ->
- Logger.debug("Unhandled type: #{inspect(link["type"])}")
- data
- end
- end)
+ _ ->
+ Logger.debug("Unhandled type: #{inspect(link["type"])}")
+ data
+ end
+ end)
- {:ok, data}
+ {:ok, data}
+ end
end
def get_template_from_xml(body) do
xpath = "//Link[@rel='lrdd']/@template"
- with doc when doc != :error <- XML.parse_document(body),
+ with {:ok, doc} <- XML.parse_document(body),
template when template != nil <- XML.string_from_xpath(xpath, doc) do
{:ok, template}
end
end
def find_lrdd_template(domain) do
- with {:ok, %{status: status, body: body}} when status in 200..299 <-
- HTTP.get("http://#{domain}/.well-known/host-meta") do
+ # WebFinger is restricted to HTTPS - https://tools.ietf.org/html/rfc7033#section-9.1
+ meta_url = "https://#{domain}/.well-known/host-meta"
+
+ with {:ok, %{status: status, body: body}} when status in 200..299 <- HTTP.get(meta_url) do
get_template_from_xml(body)
else
- _ ->
- with {:ok, %{body: body, status: status}} when status in 200..299 <-
- HTTP.get("https://#{domain}/.well-known/host-meta") do
- get_template_from_xml(body)
- else
- e -> {:error, "Can't find LRDD template: #{inspect(e)}"}
- end
+ error ->
+ Logger.warn("Can't find LRDD template in #{inspect(meta_url)}: #{inspect(error)}")
+ {:error, :lrdd_not_found}
end
end
@@ -170,7 +178,7 @@ defmodule Pleroma.Web.WebFinger do
end
end
- defp get_address_from_domain(_, _), do: nil
+ defp get_address_from_domain(_, _), do: {:error, :webfinger_no_domain}
@spec finger(String.t()) :: {:ok, map()} | {:error, any()}
def finger(account) do
@@ -187,26 +195,31 @@ defmodule Pleroma.Web.WebFinger do
encoded_account = URI.encode("acct:#{account}")
with address when is_binary(address) <- get_address_from_domain(domain, encoded_account),
- response <-
+ {:ok, %{status: status, body: body, headers: headers}} when status in 200..299 <-
HTTP.get(
address,
[{"accept", "application/xrd+xml,application/jrd+json"}]
- ),
- {:ok, %{status: status, body: body}} when status in 200..299 <- response do
- doc = XML.parse_document(body)
-
- if doc != :error do
- webfinger_from_xml(doc)
- else
- with {:ok, doc} <- Jason.decode(body) do
- webfinger_from_json(doc)
- end
+ ) do
+ case List.keyfind(headers, "content-type", 0) do
+ {_, content_type} ->
+ case Plug.Conn.Utils.media_type(content_type) do
+ {:ok, "application", subtype, _} when subtype in ~w(xrd+xml xml) ->
+ webfinger_from_xml(body)
+
+ {:ok, "application", subtype, _} when subtype in ~w(jrd+json json) ->
+ webfinger_from_json(body)
+
+ _ ->
+ {:error, {:content_type, content_type}}
+ end
+
+ _ ->
+ {:error, {:content_type, nil}}
end
else
- e ->
- Logger.debug(fn -> "Couldn't finger #{account}" end)
- Logger.debug(fn -> inspect(e) end)
- {:error, e}
+ error ->
+ Logger.debug("Couldn't finger #{account}: #{inspect(error)}")
+ error
end
end
end
diff --git a/lib/pleroma/web/web_finger/web_finger_controller.ex b/lib/pleroma/web/web_finger/web_finger_controller.ex
index 9f0938fc0..9e5efb77f 100644
--- a/lib/pleroma/web/web_finger/web_finger_controller.ex
+++ b/lib/pleroma/web/web_finger/web_finger_controller.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.WebFinger.WebFingerController do
diff --git a/lib/pleroma/web/xml.ex b/lib/pleroma/web/xml.ex
index c69a86a1e..b699446b0 100644
--- a/lib/pleroma/web/xml.ex
+++ b/lib/pleroma/web/xml.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.XML do
@@ -31,7 +31,7 @@ defmodule Pleroma.Web.XML do
|> :binary.bin_to_list()
|> :xmerl_scan.string(quiet: true)
- doc
+ {:ok, doc}
rescue
_e ->
Logger.debug("Couldn't parse XML: #{inspect(text)}")
diff --git a/lib/pleroma/workers/attachments_cleanup_worker.ex b/lib/pleroma/workers/attachments_cleanup_worker.ex
index 58226b395..4c1764053 100644
--- a/lib/pleroma/workers/attachments_cleanup_worker.ex
+++ b/lib/pleroma/workers/attachments_cleanup_worker.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Workers.AttachmentsCleanupWorker do
@@ -17,36 +17,35 @@ defmodule Pleroma.Workers.AttachmentsCleanupWorker do
"object" => %{"data" => %{"attachment" => [_ | _] = attachments, "actor" => actor}}
}
}) do
- attachments
- |> Enum.flat_map(fn item -> Enum.map(item["url"], & &1["href"]) end)
- |> fetch_objects
- |> prepare_objects(actor, Enum.map(attachments, & &1["name"]))
- |> filter_objects
- |> do_clean
+ if Pleroma.Config.get([:instance, :cleanup_attachments], false) do
+ attachments
+ |> Enum.flat_map(fn item -> Enum.map(item["url"], & &1["href"]) end)
+ |> fetch_objects
+ |> prepare_objects(actor, Enum.map(attachments, & &1["name"]))
+ |> filter_objects
+ |> do_clean
+ end
{:ok, :success}
end
def perform(%Job{args: %{"op" => "cleanup_attachments", "object" => _object}}), do: {:ok, :skip}
+ @impl Oban.Worker
+ def timeout(_job), do: :timer.seconds(900)
+
defp do_clean({object_ids, attachment_urls}) do
uploader = Pleroma.Config.get([Pleroma.Upload, :uploader])
- prefix =
- case Pleroma.Config.get([Pleroma.Upload, :base_url]) do
- nil -> "media"
- _ -> ""
- end
-
base_url =
String.trim_trailing(
- Pleroma.Config.get([Pleroma.Upload, :base_url], Pleroma.Web.base_url()),
+ Pleroma.Upload.base_url(),
"/"
)
Enum.each(attachment_urls, fn href ->
href
- |> String.trim_leading("#{base_url}/#{prefix}")
+ |> String.trim_leading("#{base_url}")
|> uploader.delete_file()
end)
diff --git a/lib/pleroma/workers/background_worker.ex b/lib/pleroma/workers/background_worker.ex
index 0647c65ae..3805293bc 100644
--- a/lib/pleroma/workers/background_worker.ex
+++ b/lib/pleroma/workers/background_worker.ex
@@ -1,17 +1,18 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Workers.BackgroundWorker do
+ alias Pleroma.Instances.Instance
alias Pleroma.User
use Pleroma.Workers.WorkerHelper, queue: "background"
@impl Oban.Worker
- def perform(%Job{args: %{"op" => "deactivate_user", "user_id" => user_id, "status" => status}}) do
+ def perform(%Job{args: %{"op" => "user_activation", "user_id" => user_id, "status" => status}}) do
user = User.get_cached_by_id(user_id)
- User.perform(:deactivate_async, user, status)
+ User.perform(:set_activation_async, user, status)
end
def perform(%Job{args: %{"op" => "delete_user", "user_id" => user_id}}) do
@@ -38,4 +39,11 @@ defmodule Pleroma.Workers.BackgroundWorker do
Pleroma.FollowingRelationship.move_following(origin, target)
end
+
+ def perform(%Job{args: %{"op" => "delete_instance", "host" => host}}) do
+ Instance.perform(:delete_instance, host)
+ end
+
+ @impl Oban.Worker
+ def timeout(_job), do: :timer.seconds(5)
end
diff --git a/lib/pleroma/workers/backup_worker.ex b/lib/pleroma/workers/backup_worker.ex
index 5b4985983..12ee70f00 100644
--- a/lib/pleroma/workers/backup_worker.ex
+++ b/lib/pleroma/workers/backup_worker.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Workers.BackupWorker do
@@ -30,6 +30,7 @@ defmodule Pleroma.Workers.BackupWorker do
|> Oban.insert()
end
+ @impl Oban.Worker
def perform(%Job{
args: %{"op" => "process", "backup_id" => backup_id, "admin_user_id" => admin_user_id}
}) do
@@ -37,10 +38,7 @@ defmodule Pleroma.Workers.BackupWorker do
backup_id |> Backup.get() |> Backup.process(),
{:ok, _job} <- schedule_deletion(backup),
:ok <- Backup.remove_outdated(backup),
- {:ok, _} <-
- backup
- |> Pleroma.Emails.UserEmail.backup_is_ready_email(admin_user_id)
- |> Pleroma.Emails.Mailer.deliver() do
+ :ok <- maybe_deliver_email(backup, admin_user_id) do
{:ok, backup}
end
end
@@ -51,4 +49,26 @@ defmodule Pleroma.Workers.BackupWorker do
nil -> :ok
end
end
+
+ @impl Oban.Worker
+ def timeout(_job), do: :timer.seconds(900)
+
+ defp has_email?(user) do
+ not is_nil(user.email) and user.email != ""
+ end
+
+ defp maybe_deliver_email(backup, admin_user_id) do
+ has_mailer = Pleroma.Config.get([Pleroma.Emails.Mailer, :enabled])
+ backup = backup |> Pleroma.Repo.preload(:user)
+
+ if has_email?(backup.user) and has_mailer do
+ backup
+ |> Pleroma.Emails.UserEmail.backup_is_ready_email(admin_user_id)
+ |> Pleroma.Emails.Mailer.deliver()
+
+ :ok
+ else
+ :ok
+ end
+ end
end
diff --git a/lib/pleroma/workers/cron/digest_emails_worker.ex b/lib/pleroma/workers/cron/digest_emails_worker.ex
index 0c56f00fb..1540c1605 100644
--- a/lib/pleroma/workers/cron/digest_emails_worker.ex
+++ b/lib/pleroma/workers/cron/digest_emails_worker.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Workers.Cron.DigestEmailsWorker do
diff --git a/lib/pleroma/workers/cron/new_users_digest_worker.ex b/lib/pleroma/workers/cron/new_users_digest_worker.ex
index 8bbaed83d..267fe2837 100644
--- a/lib/pleroma/workers/cron/new_users_digest_worker.ex
+++ b/lib/pleroma/workers/cron/new_users_digest_worker.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Workers.Cron.NewUsersDigestWorker do
diff --git a/lib/pleroma/workers/mailer_worker.ex b/lib/pleroma/workers/mailer_worker.ex
index 32273cfa5..940716558 100644
--- a/lib/pleroma/workers/mailer_worker.ex
+++ b/lib/pleroma/workers/mailer_worker.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Workers.MailerWorker do
@@ -12,4 +12,7 @@ defmodule Pleroma.Workers.MailerWorker do
|> :erlang.binary_to_term()
|> Pleroma.Emails.Mailer.deliver(config)
end
+
+ @impl Oban.Worker
+ def timeout(_job), do: :timer.seconds(5)
end
diff --git a/lib/pleroma/workers/mute_expire_worker.ex b/lib/pleroma/workers/mute_expire_worker.ex
index 32a12ba85..8ce458d48 100644
--- a/lib/pleroma/workers/mute_expire_worker.ex
+++ b/lib/pleroma/workers/mute_expire_worker.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Workers.MuteExpireWorker do
@@ -17,4 +17,7 @@ defmodule Pleroma.Workers.MuteExpireWorker do
Pleroma.Web.CommonAPI.remove_mute(user_id, activity_id)
:ok
end
+
+ @impl Oban.Worker
+ def timeout(_job), do: :timer.seconds(5)
end
diff --git a/lib/pleroma/workers/poll_worker.ex b/lib/pleroma/workers/poll_worker.ex
new file mode 100644
index 000000000..022d026f8
--- /dev/null
+++ b/lib/pleroma/workers/poll_worker.ex
@@ -0,0 +1,48 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Workers.PollWorker do
+ @moduledoc """
+ Generates notifications when a poll ends.
+ """
+ use Pleroma.Workers.WorkerHelper, queue: "poll_notifications"
+
+ alias Pleroma.Activity
+ alias Pleroma.Notification
+ alias Pleroma.Object
+
+ @impl Oban.Worker
+ def perform(%Job{args: %{"op" => "poll_end", "activity_id" => activity_id}}) do
+ with %Activity{} = activity <- find_poll_activity(activity_id) do
+ Notification.create_poll_notifications(activity)
+ end
+ end
+
+ @impl Oban.Worker
+ def timeout(_job), do: :timer.seconds(5)
+
+ defp find_poll_activity(activity_id) do
+ with nil <- Activity.get_by_id(activity_id) do
+ {:error, :poll_activity_not_found}
+ end
+ end
+
+ def schedule_poll_end(%Activity{data: %{"type" => "Create"}, id: activity_id} = activity) do
+ with %Object{data: %{"type" => "Question", "closed" => closed}} when is_binary(closed) <-
+ Object.normalize(activity),
+ {:ok, end_time} <- NaiveDateTime.from_iso8601(closed),
+ :gt <- NaiveDateTime.compare(end_time, NaiveDateTime.utc_now()) do
+ %{
+ op: "poll_end",
+ activity_id: activity_id
+ }
+ |> new(scheduled_at: end_time)
+ |> Oban.insert()
+ else
+ _ -> {:error, activity}
+ end
+ end
+
+ def schedule_poll_end(activity), do: {:error, activity}
+end
diff --git a/lib/pleroma/workers/publisher_worker.ex b/lib/pleroma/workers/publisher_worker.ex
index e739c3cd0..598ae3779 100644
--- a/lib/pleroma/workers/publisher_worker.ex
+++ b/lib/pleroma/workers/publisher_worker.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Workers.PublisherWorker do
@@ -22,4 +22,7 @@ defmodule Pleroma.Workers.PublisherWorker do
params = Map.new(params, fn {k, v} -> {String.to_atom(k), v} end)
Federator.perform(:publish_one, String.to_atom(module_name), params)
end
+
+ @impl Oban.Worker
+ def timeout(_job), do: :timer.seconds(10)
end
diff --git a/lib/pleroma/workers/purge_expired_activity.ex b/lib/pleroma/workers/purge_expired_activity.ex
index c168890a2..e554684fe 100644
--- a/lib/pleroma/workers/purge_expired_activity.ex
+++ b/lib/pleroma/workers/purge_expired_activity.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Workers.PurgeExpiredActivity do
@@ -7,7 +7,7 @@ defmodule Pleroma.Workers.PurgeExpiredActivity do
Worker which purges expired activity.
"""
- use Oban.Worker, queue: :activity_expiration, max_attempts: 1
+ use Oban.Worker, queue: :activity_expiration, max_attempts: 1, unique: [period: :infinity]
import Ecto.Query
@@ -35,6 +35,9 @@ defmodule Pleroma.Workers.PurgeExpiredActivity do
end
end
+ @impl Oban.Worker
+ def timeout(_job), do: :timer.seconds(5)
+
defp enabled? do
with false <- Pleroma.Config.get([__MODULE__, :enabled], false) do
{:error, :expired_activities_disabled}
diff --git a/lib/pleroma/workers/purge_expired_filter.ex b/lib/pleroma/workers/purge_expired_filter.ex
new file mode 100644
index 000000000..9114aeb7f
--- /dev/null
+++ b/lib/pleroma/workers/purge_expired_filter.ex
@@ -0,0 +1,46 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Workers.PurgeExpiredFilter do
+ @moduledoc """
+ Worker which purges expired filters
+ """
+
+ use Oban.Worker, queue: :filter_expiration, max_attempts: 1, unique: [period: :infinity]
+
+ import Ecto.Query
+
+ alias Oban.Job
+ alias Pleroma.Repo
+
+ @spec enqueue(%{filter_id: integer(), expires_at: DateTime.t()}) ::
+ {:ok, Job.t()} | {:error, Ecto.Changeset.t()}
+ def enqueue(args) do
+ {scheduled_at, args} = Map.pop(args, :expires_at)
+
+ args
+ |> new(scheduled_at: scheduled_at)
+ |> Oban.insert()
+ end
+
+ @impl true
+ def perform(%Job{args: %{"filter_id" => id}}) do
+ Pleroma.Filter
+ |> Repo.get(id)
+ |> Repo.delete()
+ end
+
+ @impl Oban.Worker
+ def timeout(_job), do: :timer.seconds(5)
+
+ @spec get_expiration(pos_integer()) :: Job.t() | nil
+ def get_expiration(id) do
+ from(j in Job,
+ where: j.state == "scheduled",
+ where: j.queue == "filter_expiration",
+ where: fragment("?->'filter_id' = ?", j.args, ^id)
+ )
+ |> Repo.one()
+ end
+end
diff --git a/lib/pleroma/workers/purge_expired_token.ex b/lib/pleroma/workers/purge_expired_token.ex
index a81e0cd28..2ccd9e80b 100644
--- a/lib/pleroma/workers/purge_expired_token.ex
+++ b/lib/pleroma/workers/purge_expired_token.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Workers.PurgeExpiredToken do
@@ -26,4 +26,7 @@ defmodule Pleroma.Workers.PurgeExpiredToken do
|> Pleroma.Repo.get(id)
|> Pleroma.Repo.delete()
end
+
+ @impl Oban.Worker
+ def timeout(_job), do: :timer.seconds(5)
end
diff --git a/lib/pleroma/workers/receiver_worker.ex b/lib/pleroma/workers/receiver_worker.ex
index 1b97af1a8..4f513b907 100644
--- a/lib/pleroma/workers/receiver_worker.ex
+++ b/lib/pleroma/workers/receiver_worker.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Workers.ReceiverWorker do
@@ -9,6 +9,15 @@ defmodule Pleroma.Workers.ReceiverWorker do
@impl Oban.Worker
def perform(%Job{args: %{"op" => "incoming_ap_doc", "params" => params}}) do
- Federator.perform(:incoming_ap_doc, params)
+ with {:ok, res} <- Federator.perform(:incoming_ap_doc, params) do
+ {:ok, res}
+ else
+ {:error, :origin_containment_failed} -> {:cancel, :origin_containment_failed}
+ {:error, {:reject, reason}} -> {:cancel, reason}
+ e -> e
+ end
end
+
+ @impl Oban.Worker
+ def timeout(_job), do: :timer.seconds(5)
end
diff --git a/lib/pleroma/workers/remote_fetcher_worker.ex b/lib/pleroma/workers/remote_fetcher_worker.ex
index 27e2e3386..d2a77aa17 100644
--- a/lib/pleroma/workers/remote_fetcher_worker.ex
+++ b/lib/pleroma/workers/remote_fetcher_worker.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Workers.RemoteFetcherWorker do
@@ -11,4 +11,7 @@ defmodule Pleroma.Workers.RemoteFetcherWorker do
def perform(%Job{args: %{"op" => "fetch_remote", "id" => id} = args}) do
{:ok, _object} = Fetcher.fetch_object_from_id(id, depth: args["depth"])
end
+
+ @impl Oban.Worker
+ def timeout(_job), do: :timer.seconds(10)
end
diff --git a/lib/pleroma/workers/scheduled_activity_worker.ex b/lib/pleroma/workers/scheduled_activity_worker.ex
index dd9986fe4..4df84d00f 100644
--- a/lib/pleroma/workers/scheduled_activity_worker.ex
+++ b/lib/pleroma/workers/scheduled_activity_worker.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Workers.ScheduledActivityWorker do
@@ -9,38 +9,53 @@ defmodule Pleroma.Workers.ScheduledActivityWorker do
use Pleroma.Workers.WorkerHelper, queue: "scheduled_activities"
- alias Pleroma.Config
+ alias Pleroma.Repo
alias Pleroma.ScheduledActivity
alias Pleroma.User
- alias Pleroma.Web.CommonAPI
require Logger
@impl Oban.Worker
def perform(%Job{args: %{"activity_id" => activity_id}}) do
- if Config.get([ScheduledActivity, :enabled]) do
- case Pleroma.Repo.get(ScheduledActivity, activity_id) do
- %ScheduledActivity{} = scheduled_activity ->
- post_activity(scheduled_activity)
-
- _ ->
- Logger.error("#{__MODULE__} Couldn't find scheduled activity: #{activity_id}")
- end
+ with %ScheduledActivity{} = scheduled_activity <- find_scheduled_activity(activity_id),
+ %User{} = user <- find_user(scheduled_activity.user_id) do
+ params = atomize_keys(scheduled_activity.params)
+
+ Repo.transaction(fn ->
+ {:ok, activity} = Pleroma.Web.CommonAPI.post(user, params)
+ {:ok, _} = ScheduledActivity.delete(scheduled_activity)
+ activity
+ end)
+ else
+ {:error, :scheduled_activity_not_found} = error ->
+ Logger.error("#{__MODULE__} Couldn't find scheduled activity: #{activity_id}")
+ error
+
+ {:error, :user_not_found} = error ->
+ Logger.error("#{__MODULE__} Couldn't find user for scheduled activity: #{activity_id}")
+ error
end
end
- defp post_activity(%ScheduledActivity{user_id: user_id, params: params} = scheduled_activity) do
- params = Map.new(params, fn {key, value} -> {String.to_existing_atom(key), value} end)
+ @impl Oban.Worker
+ def timeout(_job), do: :timer.seconds(5)
- with {:delete, {:ok, _}} <- {:delete, ScheduledActivity.delete(scheduled_activity)},
- {:user, %User{} = user} <- {:user, User.get_cached_by_id(user_id)},
- {:post, {:ok, _}} <- {:post, CommonAPI.post(user, params)} do
- :ok
- else
- error ->
- Logger.error(
- "#{__MODULE__} Couldn't create a status from the scheduled activity: #{inspect(error)}"
- )
+ defp find_scheduled_activity(id) do
+ with nil <- Repo.get(ScheduledActivity, id) do
+ {:error, :scheduled_activity_not_found}
end
end
+
+ defp find_user(id) do
+ with nil <- User.get_cached_by_id(id) do
+ {:error, :user_not_found}
+ end
+ end
+
+ defp atomize_keys(map) do
+ Map.new(map, fn
+ {key, value} when is_map(value) -> {String.to_existing_atom(key), atomize_keys(value)}
+ {key, value} -> {String.to_existing_atom(key), value}
+ end)
+ end
end
diff --git a/lib/pleroma/workers/transmogrifier_worker.ex b/lib/pleroma/workers/transmogrifier_worker.ex
index 15f36375c..1f3f5385e 100644
--- a/lib/pleroma/workers/transmogrifier_worker.ex
+++ b/lib/pleroma/workers/transmogrifier_worker.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Workers.TransmogrifierWorker do
@@ -12,4 +12,7 @@ defmodule Pleroma.Workers.TransmogrifierWorker do
user = User.get_cached_by_id(user_id)
Pleroma.Web.ActivityPub.Transmogrifier.perform(:user_upgrade, user)
end
+
+ @impl Oban.Worker
+ def timeout(_job), do: :timer.seconds(5)
end
diff --git a/lib/pleroma/workers/web_pusher_worker.ex b/lib/pleroma/workers/web_pusher_worker.ex
index 0cfdc6a6f..67e84b0c9 100644
--- a/lib/pleroma/workers/web_pusher_worker.ex
+++ b/lib/pleroma/workers/web_pusher_worker.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Workers.WebPusherWorker do
@@ -17,4 +17,7 @@ defmodule Pleroma.Workers.WebPusherWorker do
Pleroma.Web.Push.Impl.perform(notification)
end
+
+ @impl Oban.Worker
+ def timeout(_job), do: :timer.seconds(5)
end
diff --git a/lib/pleroma/workers/worker_helper.ex b/lib/pleroma/workers/worker_helper.ex
index 7d1289be2..1d20cbd89 100644
--- a/lib/pleroma/workers/worker_helper.ex
+++ b/lib/pleroma/workers/worker_helper.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Workers.WorkerHelper do
diff --git a/lib/pleroma/xml_builder.ex b/lib/pleroma/xml_builder.ex
index 33b63a71f..cd74cfbec 100644
--- a/lib/pleroma/xml_builder.ex
+++ b/lib/pleroma/xml_builder.ex
@@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.XmlBuilder do