Browse Source

Merge tag 'v4.0.0' into bida

jops 1 year ago
parent
commit
a113ebc399
100 changed files with 2408 additions and 1128 deletions
  1. 16 2
      .circleci/config.yml
  2. 3 5
      .codeclimate.yml
  3. 1 1
      .devcontainer/devcontainer.json
  4. 7 0
      .devcontainer/docker-compose.yml
  5. 0 254
      .env.nanobox
  6. 8 0
      .env.production.sample
  7. 2 2
      .eslintrc.js
  8. 14 0
      .github/ISSUE_TEMPLATE/1.bug_report.yml
  9. 1 4
      .github/ISSUE_TEMPLATE/config.yml
  10. 8 0
      .github/dependabot.yml
  11. 21 0
      .github/stylelint-matcher.json
  12. 12 8
      .github/workflows/build-image.yml
  13. 4 1
      .github/workflows/check-i18n.yml
  14. 83 0
      .github/workflows/linter.yml
  15. 138 0
      .github/workflows/test-chart.yml
  16. 3 0
      .gitignore
  17. 5 2
      .rubocop.yml
  18. 1 0
      .ruby-gemset
  19. 1 1
      .ruby-version
  20. 0 37
      .sass-lint.yml
  21. 740 325
      AUTHORS.md
  22. 2 2
      Aptfile
  23. 61 0
      CHANGELOG.md
  24. 2 0
      CONTRIBUTING.md
  25. 3 3
      Dockerfile
  26. 26 24
      Gemfile
  27. 188 179
      Gemfile.lock
  28. 8 5
      README.md
  29. 6 9
      SECURITY.md
  30. 6 1
      app.json
  31. 4 53
      app/controllers/about_controller.rb
  32. 0 12
      app/controllers/account_follow_controller.rb
  33. 0 12
      app/controllers/account_unfollow_controller.rb
  34. 1 59
      app/controllers/accounts_controller.rb
  35. 1 1
      app/controllers/activitypub/claims_controller.rb
  36. 1 1
      app/controllers/activitypub/collections_controller.rb
  37. 1 1
      app/controllers/activitypub/followers_synchronizations_controller.rb
  38. 5 5
      app/controllers/activitypub/inboxes_controller.rb
  39. 1 1
      app/controllers/activitypub/outboxes_controller.rb
  40. 1 1
      app/controllers/activitypub/replies_controller.rb
  41. 4 0
      app/controllers/admin/account_actions_controller.rb
  42. 7 1
      app/controllers/admin/accounts_controller.rb
  43. 4 1
      app/controllers/admin/action_logs_controller.rb
  44. 1 1
      app/controllers/admin/base_controller.rb
  45. 1 1
      app/controllers/admin/confirmations_controller.rb
  46. 3 1
      app/controllers/admin/custom_emojis_controller.rb
  47. 3 1
      app/controllers/admin/dashboard_controller.rb
  48. 2 0
      app/controllers/admin/email_domain_blocks_controller.rb
  49. 2 0
      app/controllers/admin/follow_recommendations_controller.rb
  50. 3 1
      app/controllers/admin/ip_blocks_controller.rb
  51. 1 1
      app/controllers/admin/relationships_controller.rb
  52. 57 11
      app/controllers/admin/roles_controller.rb
  53. 9 0
      app/controllers/admin/settings/about_controller.rb
  54. 9 0
      app/controllers/admin/settings/appearance_controller.rb
  55. 9 0
      app/controllers/admin/settings/branding_controller.rb
  56. 9 0
      app/controllers/admin/settings/content_retention_controller.rb
  57. 9 0
      app/controllers/admin/settings/discovery_controller.rb
  58. 9 0
      app/controllers/admin/settings/registrations_controller.rb
  59. 7 3
      app/controllers/admin/settings_controller.rb
  60. 1 1
      app/controllers/admin/site_uploads_controller.rb
  61. 14 2
      app/controllers/admin/statuses_controller.rb
  62. 0 20
      app/controllers/admin/subscriptions_controller.rb
  63. 3 1
      app/controllers/admin/tags_controller.rb
  64. 4 2
      app/controllers/admin/trends/links/preview_card_providers_controller.rb
  65. 5 2
      app/controllers/admin/trends/links_controller.rb
  66. 5 2
      app/controllers/admin/trends/statuses_controller.rb
  67. 4 2
      app/controllers/admin/trends/tags_controller.rb
  68. 34 0
      app/controllers/admin/users/roles_controller.rb
  69. 1 1
      app/controllers/admin/users/two_factor_authentications_controller.rb
  70. 19 0
      app/controllers/admin/webhooks/secrets_controller.rb
  71. 77 0
      app/controllers/admin/webhooks_controller.rb
  72. 12 2
      app/controllers/api/base_controller.rb
  73. 1 1
      app/controllers/api/v1/accounts/follower_accounts_controller.rb
  74. 1 1
      app/controllers/api/v1/accounts/following_accounts_controller.rb
  75. 1 1
      app/controllers/api/v1/accounts/pins_controller.rb
  76. 3 3
      app/controllers/api/v1/accounts_controller.rb
  77. 6 1
      app/controllers/api/v1/admin/account_actions_controller.rb
  78. 6 5
      app/controllers/api/v1/admin/accounts_controller.rb
  79. 95 0
      app/controllers/api/v1/admin/canonical_email_blocks_controller.rb
  80. 5 1
      app/controllers/api/v1/admin/dimensions_controller.rb
  81. 95 0
      app/controllers/api/v1/admin/domain_allows_controller.rb
  82. 108 0
      app/controllers/api/v1/admin/domain_blocks_controller.rb
  83. 88 0
      app/controllers/api/v1/admin/email_domain_blocks_controller.rb
  84. 93 0
      app/controllers/api/v1/admin/ip_blocks_controller.rb
  85. 5 1
      app/controllers/api/v1/admin/measures_controller.rb
  86. 1 1
      app/controllers/api/v1/admin/reports_controller.rb
  87. 5 1
      app/controllers/api/v1/admin/retention_controller.rb
  88. 11 9
      app/controllers/api/v1/admin/trends/links_controller.rb
  89. 11 9
      app/controllers/api/v1/admin/trends/statuses_controller.rb
  90. 11 9
      app/controllers/api/v1/admin/trends/tags_controller.rb
  91. 1 1
      app/controllers/api/v1/featured_tags/suggestions_controller.rb
  92. 3 5
      app/controllers/api/v1/featured_tags_controller.rb
  93. 28 9
      app/controllers/api/v1/filters_controller.rb
  94. 52 0
      app/controllers/api/v1/followed_tags_controller.rb
  95. 23 0
      app/controllers/api/v1/instances/domain_blocks_controller.rb
  96. 18 0
      app/controllers/api/v1/instances/extended_descriptions_controller.rb
  97. 18 0
      app/controllers/api/v1/instances/privacy_policies_controller.rb
  98. 1 1
      app/controllers/api/v1/instances_controller.rb
  99. 4 0
      app/controllers/api/v1/lists_controller.rb
  100. 1 1
      app/controllers/api/v1/push/subscriptions_controller.rb

+ 16 - 2
.circleci/config.yml

@@ -134,6 +134,12 @@ jobs:
           command: ./bin/rails tests:migrations:populate_v2_4
           name: Populate database with test data
       - run:
+          command: ./bin/rails db:migrate VERSION=20180707154237
+          name: Run migrations up to v2.4.3
+      - run:
+          command: ./bin/rails tests:migrations:populate_v2_4_3
+          name: Populate database with test data
+      - run:
           command: ./bin/rails db:migrate
           name: Run all remaining migrations
       - run:
@@ -168,13 +174,21 @@ jobs:
           command: ./bin/rails tests:migrations:populate_v2_4
           name: Populate database with test data
       - run:
+          command: ./bin/rails db:migrate VERSION=20180707154237
+          name: Run migrations up to v2.4.3
+          environment:
+            SKIP_POST_DEPLOYMENT_MIGRATIONS: true
+      - run:
+          command: ./bin/rails tests:migrations:populate_v2_4_3
+          name: Populate database with test data
+      - run:
           command: ./bin/rails db:migrate
-          name: Run all pre-deployment migrations
+          name: Run all remaining pre-deployment migrations
           environment:
             SKIP_POST_DEPLOYMENT_MIGRATIONS: true
       - run:
           command: ./bin/rails db:migrate
-          name: Run all post-deployment remaining migrations
+          name: Run all post-deployment migrations
       - run:
           command: ./bin/rails tests:migrations:check_database
           name: Check migration result

+ 3 - 5
.codeclimate.yml

@@ -26,13 +26,11 @@ plugins:
   bundler-audit:
     enabled: true
   eslint:
-    enabled: true
-    channel: eslint-7
+    enabled: false
   rubocop:
-    enabled: true
-    channel: rubocop-1-9-1
+    enabled: false
   sass-lint:
-    enabled: true
+    enabled: false
 exclude_patterns:
   - spec/
   - vendor/asset/

+ 1 - 1
.devcontainer/devcontainer.json

@@ -20,7 +20,7 @@
   "forwardPorts": [3000, 4000],
 
   // Use 'postCreateCommand' to run commands after the container is created.
-  "postCreateCommand": "bundle install --path vendor/bundle && yarn install && ./bin/rails db:setup",
+  "postCreateCommand": "bundle install --path vendor/bundle && yarn install && git checkout -- Gemfile.lock && ./bin/rails db:setup",
 
   // Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
   "remoteUser": "vscode"

+ 7 - 0
.devcontainer/docker-compose.yml

@@ -27,6 +27,7 @@ services:
       ES_ENABLED: 'true'
       ES_HOST: es
       ES_PORT: '9200'
+      LIBRE_TRANSLATE_ENDPOINT: http://libretranslate:5000
     # Overrides default command so things don't shut down after the process ends.
     command: sleep infinity
     networks:
@@ -72,6 +73,12 @@ services:
         soft: -1
         hard: -1
 
+  libretranslate:
+    image: libretranslate/libretranslate:v1.2.9
+    restart: unless-stopped
+    networks:
+      - internal_network
+
 volumes:
   postgres-data:
   redis-data:

+ 0 - 254
.env.nanobox

@@ -1,254 +0,0 @@
-# Service dependencies
-# You may set REDIS_URL instead for more advanced options
-REDIS_HOST=$DATA_REDIS_HOST
-REDIS_PORT=6379
-# REDIS_DB=0
-
-# You may set DATABASE_URL instead for more advanced options
-DB_HOST=$DATA_DB_HOST
-DB_USER=$DATA_DB_USER
-DB_NAME=gonano
-DB_PASS=$DATA_DB_PASS
-DB_PORT=5432
-
-# DATABASE_URL=postgresql://$DATA_DB_USER:$DATA_DB_PASS@$DATA_DB_HOST/gonano
-
-# Optional Elasticsearch configuration
-ES_ENABLED=true
-ES_HOST=$DATA_ELASTIC_HOST
-ES_PORT=9200
-
-BIND=0.0.0.0
-
-# Federation
-# Note: Changing LOCAL_DOMAIN at a later time will cause unwanted side effects, including breaking all existing federation.
-# LOCAL_DOMAIN should *NOT* contain the protocol part of the domain e.g https://example.com.
-LOCAL_DOMAIN=${APP_NAME}.nanoapp.io
-
-# Changing LOCAL_HTTPS in production is no longer supported. (Mastodon will always serve https:// links)
-
-# Use this only if you need to run mastodon on a different domain than the one used for federation.
-# You can read more about this option on https://github.com/tootsuite/documentation/blob/master/Running-Mastodon/Serving_a_different_domain.md
-# DO *NOT* USE THIS UNLESS YOU KNOW *EXACTLY* WHAT YOU ARE DOING.
-# WEB_DOMAIN=mastodon.example.com
-
-# Use this if you want to have several aliases handler@example1.com
-# handler@example2.com etc. for the same user. LOCAL_DOMAIN should not
-# be added. Comma separated values
-# ALTERNATE_DOMAINS=example1.com,example2.com
-
-# Application secrets
-# Generate each with the `rake secret` task (`nanobox run bundle exec rake secret`)
-SECRET_KEY_BASE=$SECRET_KEY_BASE
-OTP_SECRET=$OTP_SECRET
-
-# VAPID keys (used for push notifications)
-# You can generate the keys using the following command (first is the private key, second is the public one)
-# You should only generate this once per instance. If you later decide to change it, all push subscription will
-# be invalidated, requiring the users to access the website again to resubscribe.
-#
-# Generate with `rake mastodon:webpush:generate_vapid_key` task (`nanobox run bundle exec rake mastodon:webpush:generate_vapid_key`)
-#
-# For more information visit https://rossta.net/blog/using-the-web-push-api-with-vapid.html
-VAPID_PRIVATE_KEY=$VAPID_PRIVATE_KEY
-VAPID_PUBLIC_KEY=$VAPID_PUBLIC_KEY
-
-# Registrations
-# Single user mode will disable registrations and redirect frontpage to the first profile
-# SINGLE_USER_MODE=true
-# Prevent registrations with following e-mail domains
-# EMAIL_DOMAIN_BLACKLIST=example1.com|example2.de|etc
-# Only allow registrations with the following e-mail domains
-# EMAIL_DOMAIN_WHITELIST=example1.com|example2.de|etc
-
-# Optionally change default language
-# DEFAULT_LOCALE=de
-
-# E-mail configuration
-# Note: Mailgun and SparkPost (https://sparkpo.st/smtp) each have good free tiers
-# If you want to use an SMTP server without authentication (e.g local Postfix relay)
-# then set SMTP_AUTH_METHOD and SMTP_OPENSSL_VERIFY_MODE to 'none' and
-# *comment* SMTP_LOGIN and SMTP_PASSWORD (leaving them blank is not enough).
-SMTP_SERVER=$SMTP_SERVER
-SMTP_PORT=587
-SMTP_LOGIN=$SMTP_LOGIN
-SMTP_PASSWORD=$SMTP_PASSWORD
-SMTP_FROM_ADDRESS=notifications@${APP_NAME}.nanoapp.io
-#SMTP_REPLY_TO=
-#SMTP_DOMAIN= # defaults to LOCAL_DOMAIN
-#SMTP_DELIVERY_METHOD=smtp # delivery method can also be sendmail
-#SMTP_AUTH_METHOD=plain
-#SMTP_CA_FILE=/etc/ssl/certs/ca-certificates.crt
-#SMTP_OPENSSL_VERIFY_MODE=peer
-#SMTP_ENABLE_STARTTLS_AUTO=true
-#SMTP_TLS=true
-
-# Optional user upload path and URL (images, avatars). Default is :rails_root/public/system. If you set this variable, you are responsible for making your HTTP server (eg. nginx) serve these files.
-# PAPERCLIP_ROOT_PATH=/var/lib/mastodon/public-system
-# PAPERCLIP_ROOT_URL=/system
-
-# Optional asset host for multi-server setups
-# The asset host must allow cross origin request from WEB_DOMAIN or LOCAL_DOMAIN
-# if WEB_DOMAIN is not set. For example, the server may have the
-# following header field:
-# Access-Control-Allow-Origin: https://example.com/
-# CDN_HOST=https://assets.example.com
-
-# S3 (optional)
-# The attachment host must allow cross origin request from WEB_DOMAIN or
-# LOCAL_DOMAIN if WEB_DOMAIN is not set. For example, the server may have the
-# following header field:
-# Access-Control-Allow-Origin: https://192.168.1.123:9000/
-# S3_ENABLED=true
-# S3_BUCKET=
-# AWS_ACCESS_KEY_ID=
-# AWS_SECRET_ACCESS_KEY=
-# S3_REGION=
-# S3_PROTOCOL=http
-# S3_HOSTNAME=192.168.1.123:9000
-
-# S3 (Minio Config (optional) Please check Minio instance for details)
-# The attachment host must allow cross origin request - see the description
-# above.
-# S3_ENABLED=true
-# S3_BUCKET=
-# AWS_ACCESS_KEY_ID=
-# AWS_SECRET_ACCESS_KEY=
-# S3_REGION=
-# S3_PROTOCOL=https
-# S3_HOSTNAME=
-# S3_ENDPOINT=
-# S3_SIGNATURE_VERSION=
-
-# Google Cloud Storage (optional)
-# Use S3 compatible API. Since GCS does not support Multipart Upload,
-# increase the value of S3_MULTIPART_THRESHOLD to disable Multipart Upload.
-# The attachment host must allow cross origin request - see the description
-# above.
-# S3_ENABLED=true
-# AWS_ACCESS_KEY_ID=
-# AWS_SECRET_ACCESS_KEY=
-# S3_REGION=
-# S3_PROTOCOL=https
-# S3_HOSTNAME=storage.googleapis.com
-# S3_ENDPOINT=https://storage.googleapis.com
-# S3_MULTIPART_THRESHOLD=52428801 # 50.megabytes
-
-# Swift (optional)
-# The attachment host must allow cross origin request - see the description
-# above.
-# SWIFT_ENABLED=true
-# SWIFT_USERNAME=
-# For Keystone V3, the value for SWIFT_TENANT should be the project name
-# SWIFT_TENANT=
-# SWIFT_PASSWORD=
-# Some OpenStack V3 providers require PROJECT_ID (optional)
-# SWIFT_PROJECT_ID=
-# Keystone V2 and V3 URLs are supported. Use a V3 URL if possible to avoid
-# issues with token rate-limiting during high load.
-# SWIFT_AUTH_URL=
-# SWIFT_CONTAINER=
-# SWIFT_OBJECT_URL=
-# SWIFT_REGION=
-# Defaults to 'default'
-# SWIFT_DOMAIN_NAME=
-# Defaults to 60 seconds. Set to 0 to disable
-# SWIFT_CACHE_TTL=
-
-# Optional alias for S3 (e.g. to serve files on a custom domain, possibly using Cloudfront or Cloudflare)
-# S3_ALIAS_HOST=
-
-# Streaming API integration
-# STREAMING_API_BASE_URL=
-
-# Advanced settings
-# If you need to use pgBouncer, you need to disable prepared statements:
-# PREPARED_STATEMENTS=false
-
-# Cluster number setting for streaming API server.
-# If you comment out following line, cluster number will be `numOfCpuCores - 1`.
-# STREAMING_CLUSTER_NUM=1
-
-# Docker mastodon user
-# If you use Docker, you may want to assign UID/GID manually.
-# UID=1000
-# GID=1000
-
-# LDAP authentication (optional)
-# LDAP_ENABLED=true
-# LDAP_HOST=localhost
-# LDAP_PORT=389
-# LDAP_METHOD=simple_tls
-# LDAP_BASE=
-# LDAP_BIND_DN=
-# LDAP_PASSWORD=
-# LDAP_UID=cn
-# LDAP_MAIL=mail
-# LDAP_SEARCH_FILTER=(|(%{uid}=%{email})(%{mail}=%{email}))
-# LDAP_UID_CONVERSION_ENABLED=true
-# LDAP_UID_CONVERSION_SEARCH=., -
-# LDAP_UID_CONVERSION_REPLACE=_
-
-# PAM authentication (optional)
-# PAM authentication uses for the email generation the "email" pam variable
-# and optional as fallback PAM_DEFAULT_SUFFIX
-# The pam environment variable "email" is provided by:
-# https://github.com/devkral/pam_email_extractor
-# PAM_ENABLED=true
-# Fallback email domain for email address generation (LOCAL_DOMAIN by default)
-# PAM_EMAIL_DOMAIN=example.com
-# Name of the pam service (pam "auth" section is evaluated)
-# PAM_DEFAULT_SERVICE=rpam
-# Name of the pam service used for checking if an user can register (pam "account" section is evaluated) (nil (disabled) by default)
-# PAM_CONTROLLED_SERVICE=rpam
-
-# Optional CAS authentication (cf. omniauth-cas) :
-# CAS_ENABLED=true
-# CAS_URL=https://sso.myserver.com/
-# CAS_HOST=sso.myserver.com/
-# CAS_PORT=443
-# CAS_SSL=true
-# CAS_VALIDATE_URL=
-# CAS_CALLBACK_URL=
-# CAS_LOGOUT_URL=
-# CAS_LOGIN_URL=
-# CAS_UID_FIELD='user'
-# CAS_CA_PATH=
-# CAS_DISABLE_SSL_VERIFICATION=false
-# CAS_UID_KEY='user'
-# CAS_NAME_KEY='name'
-# CAS_EMAIL_KEY='email'
-# CAS_NICKNAME_KEY='nickname'
-# CAS_FIRST_NAME_KEY='firstname'
-# CAS_LAST_NAME_KEY='lastname'
-# CAS_LOCATION_KEY='location'
-# CAS_IMAGE_KEY='image'
-# CAS_PHONE_KEY='phone'
-# CAS_SECURITY_ASSUME_EMAIL_IS_VERIFIED=true
-
-# Optional SAML authentication (cf. omniauth-saml)
-# SAML_ENABLED=true
-# SAML_ACS_URL=http://localhost:3000/auth/auth/saml/callback
-# SAML_ISSUER=https://example.com
-# SAML_IDP_SSO_TARGET_URL=https://idp.testshib.org/idp/profile/SAML2/Redirect/SSO
-# SAML_IDP_CERT=
-# SAML_IDP_CERT_FINGERPRINT=
-# SAML_NAME_IDENTIFIER_FORMAT=
-# SAML_CERT=
-# SAML_PRIVATE_KEY=
-# SAML_SECURITY_WANT_ASSERTION_SIGNED=true
-# SAML_SECURITY_WANT_ASSERTION_ENCRYPTED=true
-# SAML_SECURITY_ASSUME_EMAIL_IS_VERIFIED=true
-# SAML_ATTRIBUTES_STATEMENTS_UID="urn:oid:0.9.2342.19200300.100.1.1"
-# SAML_ATTRIBUTES_STATEMENTS_EMAIL="urn:oid:1.3.6.1.4.1.5923.1.1.1.6"
-# SAML_ATTRIBUTES_STATEMENTS_FULL_NAME="urn:oid:2.16.840.1.113730.3.1.241"
-# SAML_ATTRIBUTES_STATEMENTS_FIRST_NAME="urn:oid:2.5.4.42"
-# SAML_ATTRIBUTES_STATEMENTS_LAST_NAME="urn:oid:2.5.4.4"
-# SAML_UID_ATTRIBUTE="urn:oid:0.9.2342.19200300.100.1.1"
-# SAML_ATTRIBUTES_STATEMENTS_VERIFIED=
-# SAML_ATTRIBUTES_STATEMENTS_VERIFIED_EMAIL=
-
-# Use HTTP proxy for outgoing request (optional)
-# http_proxy=http://gateway.local:8118
-# Access control for hidden service.
-# ALLOW_ACCESS_TO_HIDDEN_SERVICE=true

+ 8 - 0
.env.production.sample

@@ -67,3 +67,11 @@ S3_BUCKET=files.example.com
 AWS_ACCESS_KEY_ID=
 AWS_SECRET_ACCESS_KEY=
 S3_ALIAS_HOST=files.example.com
+
+# IP and session retention
+# -----------------------
+# Make sure to modify the scheduling of ip_cleanup_scheduler in config/sidekiq.yml
+# to be less than daily if you lower IP_RETENTION_PERIOD below two days (172800).
+# -----------------------
+IP_RETENTION_PERIOD=31556952
+SESSION_RETENTION_PERIOD=31556952

+ 2 - 2
.eslintrc.js

@@ -12,7 +12,7 @@ module.exports = {
     ATTACHMENT_HOST: false,
   },
 
-  parser: 'babel-eslint',
+  parser: '@babel/eslint-parser',
 
   plugins: [
     'react',
@@ -27,7 +27,7 @@ module.exports = {
       experimentalObjectRestSpread: true,
       jsx: true,
     },
-    ecmaVersion: 2018,
+    ecmaVersion: 2021,
   },
 
   settings: {

+ 14 - 0
.github/ISSUE_TEMPLATE/1.bug_report.yml

@@ -33,10 +33,24 @@ body:
       required: true
   - type: textarea
     attributes:
+      label: Detailed description
+    validations:
+      required: false
+  - type: textarea
+    attributes:
       label: Specifications
       description: |
         What version or commit hash of Mastodon did you find this bug in?
 
         If a front-end issue, what browser and operating systems were you using?
+      placeholder: |
+        Mastodon 3.5.3 (or Edge)
+        Ruby 2.7.6 (or v3.1.2)
+        Node.js 16.18.0
+
+        Google Chrome 106.0.5249.119
+        Firefox 105.0.3
+        
+        etc...
     validations:
       required: true

+ 1 - 4
.github/ISSUE_TEMPLATE/config.yml

@@ -2,7 +2,4 @@ blank_issues_enabled: false
 contact_links:
   - name: GitHub Discussions
     url: https://github.com/mastodon/mastodon/discussions
-    about: Please ask and answer questions here.
-  - name: Bug Bounty Program
-    url: https://app.intigriti.com/programs/mastodon/mastodonio/detail
-    about: Please report security vulnerabilities here.
+    about: Please ask and answer questions here.

+ 8 - 0
.github/dependabot.yml

@@ -20,3 +20,11 @@ updates:
     open-pull-requests-limit: 99
     allow:
       - dependency-type: direct
+
+  - package-ecosystem: github-actions
+    directory: '/'
+    schedule:
+      interval: weekly
+    open-pull-requests-limit: 99
+    allow:
+      - dependency-type: direct

+ 21 - 0
.github/stylelint-matcher.json

@@ -0,0 +1,21 @@
+{
+  "problemMatcher": [
+    {
+      "owner": "stylelint",
+      "pattern": [
+        {
+          "regexp": "^([^\\s].*)$",
+          "file": 1
+        },
+        {
+          "regexp": "^\\s+((\\d+):(\\d+))?\\s+(✖|×)\\s+(.*)\\s{2,}(.*)$",
+          "line": 2,
+          "column": 3,
+          "message": 5,
+          "code": 6,
+          "loop": true
+        }
+      ]
+    }
+  ]
+}

+ 12 - 8
.github/workflows/build-image.yml

@@ -10,19 +10,22 @@ on:
     paths:
       - .github/workflows/build-image.yml
       - Dockerfile
+permissions:
+  contents: read
+
 jobs:
   build-image:
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v2
-      - uses: docker/setup-qemu-action@v1
-      - uses: docker/setup-buildx-action@v1
-      - uses: docker/login-action@v1
+      - uses: actions/checkout@v3
+      - uses: docker/setup-qemu-action@v2
+      - uses: docker/setup-buildx-action@v2
+      - uses: docker/login-action@v2
         with:
           username: ${{ secrets.DOCKERHUB_USERNAME }}
           password: ${{ secrets.DOCKERHUB_TOKEN }}
         if: github.event_name != 'pull_request'
-      - uses: docker/metadata-action@v3
+      - uses: docker/metadata-action@v4
         id: meta
         with:
           images: tootsuite/mastodon
@@ -30,13 +33,14 @@ jobs:
             latest=auto
           tags: |
             type=edge,branch=main
-            type=match,pattern=v(.*),group=0
+            type=pep440,pattern={{raw}}
+            type=pep440,pattern=v{{major}}.{{minor}}
             type=ref,event=pr
-      - uses: docker/build-push-action@v2
+      - uses: docker/build-push-action@v3
         with:
           context: .
           platforms: linux/amd64,linux/arm64
           push: ${{ github.event_name != 'pull_request' }}
           tags: ${{ steps.meta.outputs.tags }}
-          cache-from: type=registry,ref=tootsuite/mastodon:latest
+          cache-from: type=registry,ref=tootsuite/mastodon:edge
           cache-to: type=inline

+ 4 - 1
.github/workflows/check-i18n.yml

@@ -9,12 +9,15 @@ on:
 env:
   RAILS_ENV: test
 
+permissions:
+  contents: read
+
 jobs:
   check-i18n:
     runs-on: ubuntu-latest
 
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v3
       - name: Install system dependencies
         run: |
           sudo apt-get update

+ 83 - 0
.github/workflows/linter.yml

@@ -0,0 +1,83 @@
+---
+#################################
+#################################
+## Super Linter GitHub Actions ##
+#################################
+#################################
+name: Lint Code Base
+
+#
+# Documentation:
+# https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions
+#
+
+#############################
+# Start the job on all push #
+#############################
+on:
+  push:
+    branches-ignore: [main]
+    # Remove the line above to run when pushing to master
+  pull_request:
+    branches: [main]
+
+###############
+# Set the Job #
+###############
+permissions:
+  checks: write
+  contents: read
+  pull-requests: write
+  statuses: write
+
+jobs:
+  build:
+    # Name the Job
+    name: Lint Code Base
+    # Set the agent to run on
+    runs-on: ubuntu-latest
+
+    ##################
+    # Load all steps #
+    ##################
+    steps:
+      ##########################
+      # Checkout the code base #
+      ##########################
+      - name: Checkout Code
+        uses: actions/checkout@v3
+        with:
+          # Full git history is needed to get a proper list of changed files within `super-linter`
+          fetch-depth: 0
+
+      - name: Set-up Node.js
+        uses: actions/setup-node@v3
+        with:
+          node-version: 16.x
+          cache: yarn
+      - name: Install dependencies
+        run: yarn install --frozen-lockfile
+      - name: Set-up RuboCop Problem Mathcher
+        uses: r7kamura/rubocop-problem-matchers-action@v1
+      - name: Set-up Stylelint Problem Matcher
+        uses: xt0rted/stylelint-problem-matcher@v1
+      # https://github.com/xt0rted/stylelint-problem-matcher/issues/360
+      - run: echo "::add-matcher::.github/stylelint-matcher.json" 
+
+      ################################
+      # Run Linter against code base #
+      ################################
+      - name: Lint Code Base
+        uses: github/super-linter@v4
+        env:
+          CSS_FILE_NAME: stylelint.config.js
+          DEFAULT_BRANCH: main
+          NO_COLOR: 1 # https://github.com/xt0rted/stylelint-problem-matcher/issues/360
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+          JAVASCRIPT_ES_CONFIG_FILE: .eslintrc.js
+          LINTER_RULES_PATH: .
+          RUBY_CONFIG_FILE: .rubocop.yml
+          VALIDATE_ALL_CODEBASE: false
+          VALIDATE_CSS: true
+          VALIDATE_JAVASCRIPT_ES: true
+          VALIDATE_RUBY: true

+ 138 - 0
.github/workflows/test-chart.yml

@@ -0,0 +1,138 @@
+# This is a GitHub workflow defining a set of jobs with a set of steps.
+# ref: https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions
+#
+name: Test chart
+
+on:
+  pull_request:
+    paths:
+      - "chart/**"
+      - "!**.md"
+      - ".github/workflows/test-chart.yml"
+  push:
+    paths:
+      - "chart/**"
+      - "!**.md"
+      - ".github/workflows/test-chart.yml"
+    branches-ignore:
+      - "dependabot/**"
+  workflow_dispatch:
+
+permissions:
+  contents: read
+
+defaults:
+  run:
+    working-directory: chart
+
+jobs:
+  lint-templates:
+    runs-on: ubuntu-22.04
+
+    steps:
+      - uses: actions/checkout@v3
+      - uses: actions/setup-python@v4
+        with:
+          python-version: "3.x"
+
+      - name: Install dependencies (yamllint)
+        run: pip install yamllint
+
+      - run: helm dependency update
+
+      - name: helm lint
+        run: |
+          helm lint . \
+              --values dev-values.yaml
+
+      - name: helm template
+        run: |
+          helm template . \
+              --values dev-values.yaml \
+              --output-dir rendered-templates
+
+      - name: yamllint (only on templates we manage)
+        run: |
+          rm -rf rendered-templates/mastodon/charts
+
+          yamllint rendered-templates \
+            --config-data "{rules: {indentation: {spaces: 2}, line-length: disable}}"
+
+  # This job helps us validate that rendered templates are valid k8s resources
+  # against a k8s api-server, via "helm template --validate", but also that a
+  # basic configuration can be used to successfully startup mastodon.
+  #
+  test-install:
+    runs-on: ubuntu-22.04
+    timeout-minutes: 15
+
+    strategy:
+      fail-fast: false
+      matrix:
+        include:
+          # k3s-channel reference: https://update.k3s.io/v1-release/channels
+          - k3s-channel: latest
+          - k3s-channel: stable
+
+          # This represents the oldest configuration we test against.
+          #
+          # The k8s version chosen is based on the oldest still supported k8s
+          # version among two managed k8s services, GKE, EKS.
+          # - GKE: https://endoflife.date/google-kubernetes-engine
+          # - EKS: https://endoflife.date/amazon-eks
+          #
+          # The helm client's version can influence what helper functions is
+          # available for use in the templates, currently we need v3.6.0 or
+          # higher.
+          #
+          - k3s-channel: v1.21
+            helm-version: v3.6.0
+
+    steps:
+      - uses: actions/checkout@v3
+
+      # This action starts a k8s cluster with NetworkPolicy enforcement and
+      # installs both kubectl and helm.
+      #
+      # ref: https://github.com/jupyterhub/action-k3s-helm#readme
+      #
+      - uses: jupyterhub/action-k3s-helm@v3
+        with:
+          k3s-channel: ${{ matrix.k3s-channel }}
+          helm-version: ${{ matrix.helm-version }}
+          metrics-enabled: false
+          traefik-enabled: false
+          docker-enabled: false
+
+      - run: helm dependency update
+
+      # Validate rendered helm templates against the k8s api-server
+      - name: helm template --validate
+        run: |
+          helm template --validate mastodon . \
+              --values dev-values.yaml
+
+      - name: helm install
+        run: |
+          helm install mastodon . \
+              --values dev-values.yaml \
+              --timeout 10m
+
+      # This actions provides a report about the state of the k8s cluster,
+      # providing logs etc on anything that has failed and workloads marked as
+      # important.
+      #
+      # ref: https://github.com/jupyterhub/action-k8s-namespace-report#readme
+      #
+      - name: Kubernetes namespace report
+        uses: jupyterhub/action-k8s-namespace-report@v1
+        if: always()
+        with:
+          important-workloads: >-
+            deploy/mastodon-sidekiq
+            deploy/mastodon-streaming
+            deploy/mastodon-web
+            job/mastodon-assets-precompile
+            job/mastodon-chewy-upgrade
+            job/mastodon-create-admin
+            job/mastodon-db-migrate

+ 3 - 0
.gitignore

@@ -44,6 +44,9 @@
 /redis
 /elasticsearch
 
+# ignore Helm charts
+/chart/*.tgz
+
 # ignore Helm dependency charts
 /chart/charts/*.tgz
 

+ 5 - 2
.rubocop.yml

@@ -67,7 +67,7 @@ Lint/UselessAccessModifier:
     - class_methods
 
 Metrics/AbcSize:
-  Max: 100
+  Max: 115
   Exclude:
     - 'lib/mastodon/*_cli.rb'
 
@@ -84,7 +84,7 @@ Metrics/BlockNesting:
 
 Metrics/ClassLength:
   CountComments: false
-  Max: 400
+  Max: 500
   Exclude:
     - 'lib/mastodon/*_cli.rb'
 
@@ -281,6 +281,9 @@ Style/RedundantRegexpEscape:
 Style/RedundantReturn:
   Enabled: true
 
+Style/RedundantBegin:
+  Enabled: false
+
 Style/RegexpLiteral:
   Enabled: false
 

+ 1 - 0
.ruby-gemset

@@ -0,0 +1 @@
+mastodon

+ 1 - 1
.ruby-version

@@ -1 +1 @@
-3.0.3
+3.0.4

+ 0 - 37
.sass-lint.yml

@@ -1,37 +0,0 @@
-# Linter Documentation:
-# https://github.com/sasstools/sass-lint/tree/v1.13.1/docs/options
-
-files:
-  include: app/javascript/styles/**/*.scss
-  ignore:
-    - app/javascript/styles/mastodon/reset.scss
-
-rules:
-  # Disallows
-  no-color-literals: 0
-  no-css-comments: 0
-  no-duplicate-properties: 0
-  no-ids: 0
-  no-important: 0
-  no-mergeable-selectors: 0
-  no-misspelled-properties: 0
-  no-qualifying-elements: 0
-  no-transition-all: 0
-  no-vendor-prefixes: 0
-
-  # Nesting
-  force-element-nesting: 0
-  force-attribute-nesting: 0
-  force-pseudo-nesting: 0
-
-  # Name Formats
-  class-name-format: 0
-  leading-zero: 0
-
-  # Style Guide
-  attribute-quotes: 0
-  hex-length: 0
-  indentation: 0
-  nesting-depth: 0
-  property-sort-order: 0
-  quotes: 0

+ 740 - 325
AUTHORS.md

@@ -15,32 +15,32 @@ and provided thanks to the work of the following contributors:
 * [noellabo](https://github.com/noellabo)
 * [abcang](https://github.com/abcang)
 * [yiskah](https://github.com/yiskah)
+* [tribela](https://github.com/tribela)
 * [mayaeh](https://github.com/mayaeh)
 * [nolanlawson](https://github.com/nolanlawson)
 * [ysksn](https://github.com/ysksn)
-* [tribela](https://github.com/tribela)
 * [sorin-davidoi](https://github.com/sorin-davidoi)
 * [lynlynlynx](https://github.com/lynlynlynx)
 * [m4sk1n](mailto:me@m4sk.in)
 * [Marcin Mikołajczak](mailto:me@m4sk.in)
-* [renatolond](https://github.com/renatolond)
 * [shleeable](https://github.com/shleeable)
-* [alpaca-tc](https://github.com/alpaca-tc)
+* [renatolond](https://github.com/renatolond)
 * [zunda](https://github.com/zunda)
+* [alpaca-tc](https://github.com/alpaca-tc)
 * [nclm](https://github.com/nclm)
 * [ineffyble](https://github.com/ineffyble)
 * [ariasuni](https://github.com/ariasuni)
 * [Masoud Abkenar](mailto:ampbox@gmail.com)
 * [blackle](https://github.com/blackle)
 * [Quent-in](https://github.com/Quent-in)
-* [JantsoP](https://github.com/JantsoP)
 * [Brawaru](https://github.com/Brawaru)
+* [JantsoP](https://github.com/JantsoP)
+* [trwnh](https://github.com/trwnh)
 * [nullkal](https://github.com/nullkal)
 * [yookoala](https://github.com/yookoala)
 * [dunn](https://github.com/dunn)
 * [Aditoo17](https://github.com/Aditoo17)
 * [Quenty31](https://github.com/Quenty31)
-* [marek-lach](https://github.com/marek-lach)
 * [shuheiktgw](https://github.com/shuheiktgw)
 * [ashfurrow](https://github.com/ashfurrow)
 * [danhunsaker](https://github.com/danhunsaker)
@@ -48,7 +48,6 @@ and provided thanks to the work of the following contributors:
 * [Jeroen](mailto:jeroenpraat@users.noreply.github.com)
 * [takayamaki](https://github.com/takayamaki)
 * [masarakki](https://github.com/masarakki)
-* [trwnh](https://github.com/trwnh)
 * [ticky](https://github.com/ticky)
 * [ThisIsMissEm](https://github.com/ThisIsMissEm)
 * [hinaloe](https://github.com/hinaloe)
@@ -57,16 +56,19 @@ and provided thanks to the work of the following contributors:
 * [Wonderfall](https://github.com/Wonderfall)
 * [matteoaquila](https://github.com/matteoaquila)
 * [yukimochi](https://github.com/yukimochi)
-* [palindromordnilap](https://github.com/palindromordnilap)
+* [nightpool](https://github.com/nightpool)
+* [alixrossi](https://github.com/alixrossi)
 * [rkarabut](https://github.com/rkarabut)
 * [jeroenpraat](mailto:jeroenpraat@users.noreply.github.com)
-* [nightpool](https://github.com/nightpool)
+* [marek-lach](https://github.com/marek-lach)
 * [Artoria2e5](https://github.com/Artoria2e5)
+* [rinsuki](https://github.com/rinsuki)
 * [marrus-sh](https://github.com/marrus-sh)
 * [krainboltgreene](https://github.com/krainboltgreene)
 * [pfigel](https://github.com/pfigel)
 * [BoFFire](https://github.com/BoFFire)
 * [Aldarone](https://github.com/Aldarone)
+* [deepy](https://github.com/deepy)
 * [clworld](https://github.com/clworld)
 * [MasterGroosha](https://github.com/MasterGroosha)
 * [dracos](https://github.com/dracos)
@@ -75,7 +77,6 @@ and provided thanks to the work of the following contributors:
 * [Sylvhem](https://github.com/Sylvhem)
 * [koyuawsmbrtn](https://github.com/koyuawsmbrtn)
 * [MitarashiDango](https://github.com/MitarashiDango)
-* [rinsuki](https://github.com/rinsuki)
 * [angristan](https://github.com/angristan)
 * [JeanGauthier](https://github.com/JeanGauthier)
 * [kschaper](https://github.com/kschaper)
@@ -87,14 +88,15 @@ and provided thanks to the work of the following contributors:
 * [MightyPork](https://github.com/MightyPork)
 * [ashleyhull-versent](https://github.com/ashleyhull-versent)
 * [yhirano55](https://github.com/yhirano55)
+* [mashirozx](https://github.com/mashirozx)
 * [devkral](https://github.com/devkral)
 * [camponez](https://github.com/camponez)
 * [Hugo Gameiro](mailto:hmgameiro@gmail.com)
+* [Marek Ľach](mailto:graweeld@googlemail.com)
 * [SerCom_KC](mailto:szescxz@gmail.com)
 * [aschmitz](https://github.com/aschmitz)
 * [mfmfuyu](https://github.com/mfmfuyu)
 * [kedamaDQ](https://github.com/kedamaDQ)
-* [mashirozx](https://github.com/mashirozx)
 * [fpiesche](https://github.com/fpiesche)
 * [gandaro](https://github.com/gandaro)
 * [johnsudaar](https://github.com/johnsudaar)
@@ -106,8 +108,10 @@ and provided thanks to the work of the following contributors:
 * [hikari-no-yume](https://github.com/hikari-no-yume)
 * [seefood](https://github.com/seefood)
 * [jackjennings](https://github.com/jackjennings)
+* [sunny](https://github.com/sunny)
 * [puckipedia](https://github.com/puckipedia)
-* [spla](mailto:spla@mastodont.cat)
+* [splaGit](https://github.com/splaGit)
+* [tateisu](https://github.com/tateisu)
 * [walf443](https://github.com/walf443)
 * [JoelQ](https://github.com/JoelQ)
 * [mistydemeo](https://github.com/mistydemeo)
@@ -118,13 +122,15 @@ and provided thanks to the work of the following contributors:
 * [tsuwatch](https://github.com/tsuwatch)
 * [progval](https://github.com/progval)
 * [victorhck](https://github.com/victorhck)
+* [Izorkin](https://github.com/Izorkin)
 * [manuelviens](mailto:manuelviens@users.noreply.github.com)
-* [tateisu](https://github.com/tateisu)
 * [fvh-P](https://github.com/fvh-P)
 * [lfuelling](https://github.com/lfuelling)
 * [rtucker](https://github.com/rtucker)
 * [Anna e só](mailto:contraexemplos@gmail.com)
+* [danieljakots](https://github.com/danieljakots)
 * [dariusk](https://github.com/dariusk)
+* [Gomasy](https://github.com/Gomasy)
 * [kazu9su](https://github.com/kazu9su)
 * [komic](https://github.com/komic)
 * [lmorchard](https://github.com/lmorchard)
@@ -137,6 +143,7 @@ and provided thanks to the work of the following contributors:
 * [goofy-bz](mailto:goofy@babelzilla.org)
 * [kadiix](https://github.com/kadiix)
 * [kodacs](https://github.com/kodacs)
+* [luzpaz](https://github.com/luzpaz)
 * [marcin mikołajczak](mailto:me@m4sk.in)
 * [berkes](https://github.com/berkes)
 * [KScl](https://github.com/KScl)
@@ -145,23 +152,26 @@ and provided thanks to the work of the following contributors:
 * [AA4ch1](https://github.com/AA4ch1)
 * [alexgleason](https://github.com/alexgleason)
 * [cpytel](https://github.com/cpytel)
+* [cutls](https://github.com/cutls)
 * [northerner](https://github.com/northerner)
 * [weex](https://github.com/weex)
+* [erbridge](https://github.com/erbridge)
 * [fhemberger](https://github.com/fhemberger)
-* [Gomasy](https://github.com/Gomasy)
 * [greysteil](https://github.com/greysteil)
 * [henrycatalinismith](https://github.com/henrycatalinismith)
+* [HolgerHuo](https://github.com/HolgerHuo)
 * [d6rkaiz](https://github.com/d6rkaiz)
 * [ladyisatis](https://github.com/ladyisatis)
 * [JMendyk](https://github.com/JMendyk)
+* [kescherCode](https://github.com/kescherCode)
 * [JohnD28](https://github.com/JohnD28)
 * [znz](https://github.com/znz)
 * [saper](https://github.com/saper)
 * [Naouak](https://github.com/Naouak)
 * [pawelngei](https://github.com/pawelngei)
+* [rgroothuijsen](https://github.com/rgroothuijsen)
 * [reneklacan](https://github.com/reneklacan)
 * [ekiru](https://github.com/ekiru)
-* [Izorkin](https://github.com/Izorkin)
 * [unasuke](https://github.com/unasuke)
 * [geta6](https://github.com/geta6)
 * [happycoloredbanana](https://github.com/happycoloredbanana)
@@ -174,7 +184,6 @@ and provided thanks to the work of the following contributors:
 * [aji-su](https://github.com/aji-su)
 * [ikuradon](https://github.com/ikuradon)
 * [nzws](https://github.com/nzws)
-* [duxovni](https://github.com/duxovni)
 * [SuperSandro2000](https://github.com/SuperSandro2000)
 * [178inaba](https://github.com/178inaba)
 * [acid-chicken](https://github.com/acid-chicken)
@@ -183,7 +192,6 @@ and provided thanks to the work of the following contributors:
 * [aablinov](https://github.com/aablinov)
 * [stalker314314](https://github.com/stalker314314)
 * [cohosh](https://github.com/cohosh)
-* [cutls](https://github.com/cutls)
 * [huertanix](https://github.com/huertanix)
 * [eleboucher](https://github.com/eleboucher)
 * [halkeye](https://github.com/halkeye)
@@ -191,6 +199,7 @@ and provided thanks to the work of the following contributors:
 * [treby](https://github.com/treby)
 * [jpdevries](https://github.com/jpdevries)
 * [gdpelican](https://github.com/gdpelican)
+* [pbzweihander](https://github.com/pbzweihander)
 * [MonaLisaOverrdrive](https://github.com/MonaLisaOverrdrive)
 * [Kurtis Rainbolt-Greene](mailto:me@kurtisrainboltgreene.name)
 * [panarom](https://github.com/panarom)
@@ -201,7 +210,6 @@ and provided thanks to the work of the following contributors:
 * [pierreozoux](https://github.com/pierreozoux)
 * [qguv](https://github.com/qguv)
 * [Ram Lmn](mailto:ramlmn@users.noreply.github.com)
-* [rgroothuijsen](https://github.com/rgroothuijsen)
 * [Sascha](mailto:sascha@serenitylabs.cloud)
 * [harukasan](https://github.com/harukasan)
 * [stamak](https://github.com/stamak)
@@ -217,11 +225,14 @@ and provided thanks to the work of the following contributors:
 * [chr-1x](https://github.com/chr-1x)
 * [esetomo](https://github.com/esetomo)
 * [foxiehkins](https://github.com/foxiehkins)
+* [gol-cha](https://github.com/gol-cha)
 * [highemerly](https://github.com/highemerly)
 * [hoodie](mailto:hoodiekitten@outlook.com)
 * [kaiyou](https://github.com/kaiyou)
 * [007lva](https://github.com/007lva)
 * [luzi82](https://github.com/luzi82)
+* [prplecake](https://github.com/prplecake)
+* [duxovni](https://github.com/duxovni)
 * [slice](https://github.com/slice)
 * [tmm576](https://github.com/tmm576)
 * [unsmell](mailto:unsmell@users.noreply.github.com)
@@ -251,25 +262,27 @@ and provided thanks to the work of the following contributors:
 * [cdutson](https://github.com/cdutson)
 * [farlistener](https://github.com/farlistener)
 * [baby-gnu](https://github.com/baby-gnu)
-* [danieljakots](https://github.com/danieljakots)
 * [divergentdave](https://github.com/divergentdave)
 * [DavidLibeau](https://github.com/DavidLibeau)
 * [dmerejkowsky](https://github.com/dmerejkowsky)
 * [ddevault](https://github.com/ddevault)
+* [emilyst](https://github.com/emilyst)
+* [consideRatio](https://github.com/consideRatio)
 * [Fjoerfoks](https://github.com/Fjoerfoks)
 * [fmauNeko](https://github.com/fmauNeko)
 * [gloaec](https://github.com/gloaec)
 * [unstabler](https://github.com/unstabler)
 * [potato4d](https://github.com/potato4d)
 * [h-izumi](https://github.com/h-izumi)
-* [HolgerHuo](https://github.com/HolgerHuo)
 * [ErikXXon](https://github.com/ErikXXon)
 * [ian-kelling](https://github.com/ian-kelling)
 * [eltociear](https://github.com/eltociear)
 * [immae](https://github.com/immae)
 * [J0WI](https://github.com/J0WI)
-* [vahnj](https://github.com/vahnj)
+* [koboldunderlord](https://github.com/koboldunderlord)
 * [foozmeat](https://github.com/foozmeat)
+* [jgsmith](https://github.com/jgsmith)
+* [raggi](https://github.com/raggi)
 * [jasonrhodes](https://github.com/jasonrhodes)
 * [Jason Snell](mailto:jason@newrelic.com)
 * [jviide](https://github.com/jviide)
@@ -287,21 +300,25 @@ and provided thanks to the work of the following contributors:
 * [Markus Amalthea Magnuson](mailto:markus.magnuson@gmail.com)
 * [madmath03](https://github.com/madmath03)
 * [mig5](https://github.com/mig5)
+* [mohe2015](https://github.com/mohe2015)
 * [moritzheiber](https://github.com/moritzheiber)
 * [Nathaniel Suchy](mailto:me@lunorian.is)
 * [ndarville](https://github.com/ndarville)
 * [NimaBoscarino](https://github.com/NimaBoscarino)
 * [aquarla](https://github.com/aquarla)
 * [Abzol](https://github.com/Abzol)
+* [unextro](https://github.com/unextro)
 * [PatOnTheBack](https://github.com/PatOnTheBack)
 * [xPaw](https://github.com/xPaw)
 * [petzah](https://github.com/petzah)
 * [PeterDaveHello](https://github.com/PeterDaveHello)
 * [ignisf](https://github.com/ignisf)
+* [postmodern](https://github.com/postmodern)
 * [lumenwrites](https://github.com/lumenwrites)
 * [remram44](https://github.com/remram44)
 * [sts10](https://github.com/sts10)
 * [u1-liquid](https://github.com/u1-liquid)
+* [SISheogorath](https://github.com/SISheogorath)
 * [rosylilly](https://github.com/rosylilly)
 * [withshubh](https://github.com/withshubh)
 * [sim6](https://github.com/sim6)
@@ -328,23 +345,23 @@ and provided thanks to the work of the following contributors:
 * [bsky](mailto:me@imbsky.net)
 * [codl](https://github.com/codl)
 * [cpsdqs](https://github.com/cpsdqs)
+* [dogelover911](https://github.com/dogelover911)
 * [barzamin](https://github.com/barzamin)
-* [gol-cha](https://github.com/gol-cha)
 * [gunchleoc](https://github.com/gunchleoc)
 * [fhalna](https://github.com/fhalna)
 * [haoyayoi](https://github.com/haoyayoi)
+* [helloworldstack](https://github.com/helloworldstack)
 * [ik11235](https://github.com/ik11235)
 * [kawax](https://github.com/kawax)
 * [shrft](https://github.com/shrft)
 * [luigi](mailto:lvargas@rankia.com)
-* [luzpaz](https://github.com/luzpaz)
 * [mbajur](https://github.com/mbajur)
 * [matsurai25](https://github.com/matsurai25)
 * [mecab](https://github.com/mecab)
 * [nicobz25](https://github.com/nicobz25)
 * [niwatori24](https://github.com/niwatori24)
 * [noiob](https://github.com/noiob)
-* [oliverkeeble](https://github.com/oliverkeeble)
+* [oliverkeeble](mailto:oliverkeeble@users.noreply.github.com)
 * [partev](https://github.com/partev)
 * [pinfort](https://github.com/pinfort)
 * [rbaumert](https://github.com/rbaumert)
@@ -360,7 +377,7 @@ and provided thanks to the work of the following contributors:
 * [clarfonthey](https://github.com/clarfonthey)
 * [cygnan](https://github.com/cygnan)
 * [Awea](https://github.com/Awea)
-* [eai04191](https://github.com/eai04191)
+* [single-right-quote](https://github.com/single-right-quote)
 * [8398a7](https://github.com/8398a7)
 * [857b](https://github.com/857b)
 * [insom](https://github.com/insom)
@@ -373,9 +390,10 @@ and provided thanks to the work of the following contributors:
 * [unleashed](https://github.com/unleashed)
 * [alxrcs](https://github.com/alxrcs)
 * [console-cowboy](https://github.com/console-cowboy)
+* [Saiv46](https://github.com/Saiv46)
 * [Alkarex](https://github.com/Alkarex)
 * [a2](https://github.com/a2)
-* [alfiedotwtf](https://github.com/alfiedotwtf)
+* [Alfie John](mailto:33c6c91f3bb4a391082e8a29642cafaf@alfie.wtf)
 * [0xa](https://github.com/0xa)
 * [ashpieboop](https://github.com/ashpieboop)
 * [virtualpain](https://github.com/virtualpain)
@@ -391,24 +409,34 @@ and provided thanks to the work of the following contributors:
 * [orlea](https://github.com/orlea)
 * [armandfardeau](https://github.com/armandfardeau)
 * [raboof](https://github.com/raboof)
+* [v-aisac](https://github.com/v-aisac)
+* [gi-yt](https://github.com/gi-yt)
+* [boahc077](https://github.com/boahc077)
 * [aldatsa](https://github.com/aldatsa)
 * [jumbosushi](https://github.com/jumbosushi)
 * [acuteaura](https://github.com/acuteaura)
 * [ayumin](https://github.com/ayumin)
 * [bzg](https://github.com/bzg)
 * [BastienDurel](https://github.com/BastienDurel)
+* [bearice](https://github.com/bearice)
 * [li-bei](https://github.com/li-bei)
+* [hardillb](https://github.com/hardillb)
 * [Benedikt Geißler](mailto:benedikt@g5r.eu)
 * [BenisonSebastian](https://github.com/BenisonSebastian)
 * [Blake](mailto:blake.barnett@postmates.com)
 * [Brad Janke](mailto:brad.janke@gmail.com)
+* [braydofficial](https://github.com/braydofficial)
 * [bclindner](https://github.com/bclindner)
 * [brycied00d](https://github.com/brycied00d)
 * [carlosjs23](https://github.com/carlosjs23)
+* [WyriHaximus](https://github.com/WyriHaximus)
 * [cgxxx](https://github.com/cgxxx)
 * [kibitan](https://github.com/kibitan)
+* [cdzombak](https://github.com/cdzombak)
 * [chrisheninger](https://github.com/chrisheninger)
 * [chris-martin](https://github.com/chris-martin)
+* [offbyone](https://github.com/offbyone)
+* [cclauss](https://github.com/cclauss)
 * [DoubleMalt](https://github.com/DoubleMalt)
 * [Moosh-be](https://github.com/Moosh-be)
 * [cchoi12](https://github.com/cchoi12)
@@ -417,6 +445,8 @@ and provided thanks to the work of the following contributors:
 * [csu](https://github.com/csu)
 * [kklleemm](https://github.com/kklleemm)
 * [colindean](https://github.com/colindean)
+* [CommanderRoot](https://github.com/CommanderRoot)
+* [connorshea](https://github.com/connorshea)
 * [DeeUnderscore](https://github.com/DeeUnderscore)
 * [dachinat](https://github.com/dachinat)
 * [Daggertooth](mailto:dev@monsterpit.net)
@@ -428,35 +458,40 @@ and provided thanks to the work of the following contributors:
 * [dar5hak](https://github.com/dar5hak)
 * [kant](https://github.com/kant)
 * [maxolasersquad](https://github.com/maxolasersquad)
-* [singingwolfboy](https://github.com/singingwolfboy)
-* [caldwell](https://github.com/caldwell)
-* [davidcelis](https://github.com/davidcelis)
-* [davefp](https://github.com/davefp)
-* [hannahwhy](https://github.com/hannahwhy)
-* [debanshuk](https://github.com/debanshuk)
-* [mascali33](https://github.com/mascali33)
-* [DerekNonGeneric](https://github.com/DerekNonGeneric)
-* [dblandin](https://github.com/dblandin)
-* [Aranaur](https://github.com/Aranaur)
-* [dtschust](https://github.com/dtschust)
-* [Dryusdan](https://github.com/Dryusdan)
-* [d3vgru](https://github.com/d3vgru)
-* [Elizafox](https://github.com/Elizafox)
-* [enewhuis](https://github.com/enewhuis)
-* [ericblade](https://github.com/ericblade)
-* [mikoim](https://github.com/mikoim)
-* [espenronnevik](https://github.com/espenronnevik)
+* [David Baumgold](mailto:david@davidbaumgold.com)
+* [David Caldwell](mailto:david+github@porkrind.org)
+* [David Celis](mailto:me@davidcel.is)
+* [David Hewitt](mailto:davidmhewitt@users.noreply.github.com)
+* [David Underwood](mailto:davefp@gmail.com)
+* [David Yip](mailto:yipdw@member.fsf.org)
+* [Debanshu Kundu](mailto:debanshu.kundu@joshtechnologygroup.com)
+* [Denis Teyssier](mailto:admin@mascali.ovh)
+* [Derek Lewis](mailto:derekcecillewis@gmail.com)
+* [Devon Blandin](mailto:dblandin@gmail.com)
+* [Drew Gates](mailto:aranaur@users.noreply.github.com)
+* [Drew Schuster](mailto:dtschust@gmail.com)
+* [Dryusdan](mailto:dryusdan@dryusdan.fr)
+* [Eai](mailto:eai@mizle.net)
+* [Ed Knutson](mailto:knutsoned@gmail.com)
+* [Effy Elden](mailto:effy@effy.space)
+* [Elizabeth Myers](mailto:elizabeth@interlinked.me)
+* [Eric](mailto:enewhuis@gmail.com)
+* [Eric Blade](mailto:blade.eric@gmail.com)
+* [Eshin Kunishima](mailto:mikoim@users.noreply.github.com)
+* [Espen Rønnevik](mailto:espen@ronnevik.net)
 * [Expenses](mailto:expenses@airmail.cc)
-* [fabianonline](https://github.com/fabianonline)
-* [shello](https://github.com/shello)
-* [Finariel](https://github.com/Finariel)
-* [siuying](https://github.com/siuying)
-* [zoc](https://github.com/zoc)
-* [fwenzel](https://github.com/fwenzel)
-* [gabrielrumiranda](https://github.com/gabrielrumiranda)
-* [GenbuHase](https://github.com/GenbuHase)
-* [nilsding](https://github.com/nilsding)
-* [hattori6789](https://github.com/hattori6789)
+* [Fabian Schlenz](mailto:mail@fabianonline.de)
+* [Faye Duxovni](mailto:duxovni@duxovni.org)
+* [Filipe Rodrigues](mailto:shello@shello.org)
+* [Finariel](mailto:finariel@gmail.com)
+* [Francis Chong](mailto:francis@ignition.hk)
+* [Franck Zoccolo](mailto:franck@zoccolo.com)
+* [Fred Wenzel](mailto:fwenzel@users.noreply.github.com)
+* [Gabriel Rubens](mailto:gabrielrumiranda@gmail.com)
+* [Gaelan Steele](mailto:gbs@canishe.com)
+* [Genbu Hase](mailto:hasegenbu@gmail.com)
+* [Georg Gadinger](mailto:nilsding@nilsding.org)
+* [George Hattori](mailto:hattori6789@users.noreply.github.com)
 * [Gergely Nagy](mailto:algernon@users.noreply.github.com)
 * [Giuseppe Pignataro](mailto:rogepix@gmail.com)
 * [Greg V](mailto:greg@unrelenting.technology)
@@ -466,7 +501,9 @@ and provided thanks to the work of the following contributors:
 * [György Nádudvari](mailto:reedcourty@users.noreply.github.com)
 * [HIKARU KOBORI](mailto:hk.uec.univ@gmail.com)
 * [Haelwenn Monnier](mailto:lanodan@users.noreply.github.com)
+* [Hampton Lintorn-Catlin](mailto:hcatlin@gmail.com)
 * [Harmon](mailto:harmon758@gmail.com)
+* [Hayden](mailto:contact@winisreallybored.com)
 * [HellPie](mailto:hellpie@users.noreply.github.com)
 * [Herbert Kagumba](mailto:habukagumba@gmail.com)
 * [Hiroe Jun](mailto:jun.hiroe@gmail.com)
@@ -479,6 +516,7 @@ and provided thanks to the work of the following contributors:
 * [Ian McDowell](mailto:me@ianmcdowell.net)
 * [Iijima Yasushi](mailto:kurage.cc@gmail.com)
 * [Ingo Blechschmidt](mailto:iblech@web.de)
+* [Irie Aoi](mailto:eai@mizle.net)
 * [J Yeary](mailto:usbsnowcrash@users.noreply.github.com)
 * [Jack Michaud](mailto:jack-michaud@users.noreply.github.com)
 * [Jakub Mendyk](mailto:jakubmendyk.szkola@gmail.com)
@@ -493,6 +531,7 @@ and provided thanks to the work of the following contributors:
 * [Jo Decker](mailto:trolldecker@users.noreply.github.com)
 * [Joan Montané](mailto:jmontane@users.noreply.github.com)
 * [Joe](mailto:401283+htmlbyjoe@users.noreply.github.com)
+* [Joe Friedl](mailto:stuff@joefriedl.net)
 * [Jonathan Klee](mailto:klee.jonathan@gmail.com)
 * [Jordan Guerder](mailto:jguerder@fr.pulseheberg.net)
 * [Joseph Mingrone](mailto:jehops@users.noreply.github.com)
@@ -502,6 +541,7 @@ and provided thanks to the work of the following contributors:
 * [Julien](mailto:tiwy57@users.noreply.github.com)
 * [Julien Deswaef](mailto:juego@requiem4tv.com)
 * [June Sallou](mailto:jnsll@users.noreply.github.com)
+* [Justin Thomas](mailto:justin@jdt.io)
 * [Jérémy Benoist](mailto:j0k3r@users.noreply.github.com)
 * [KEINOS](mailto:github@keinos.com)
 * [Kairui Song | 宋恺睿](mailto:ryncsn@gmail.com)
@@ -522,6 +562,7 @@ and provided thanks to the work of the following contributors:
 * [Mantas](mailto:mistermantas@users.noreply.github.com)
 * [Mareena Kunjachan](mailto:mareenakunjachan@gmail.com)
 * [Marek Lach](mailto:marek.brohatwack.lach@gmail.com)
+* [Markus Petzsch](mailto:markus@petzsch.eu)
 * [Markus R](mailto:wirehack7@users.noreply.github.com)
 * [Marty McGuire](mailto:schmartissimo@gmail.com)
 * [Marvin Kopf](mailto:marvinkopf@posteo.de)
@@ -531,12 +572,15 @@ and provided thanks to the work of the following contributors:
 * [Mathias B](mailto:10813340+mathias-b@users.noreply.github.com)
 * [Mathieu Brunot](mailto:mb.mathieu.brunot@gmail.com)
 * [Matt](mailto:matt-auckland@users.noreply.github.com)
+* [Matt Corallo](mailto:649246+thebluematt@users.noreply.github.com)
 * [Matt Sweetman](mailto:webroo@gmail.com)
+* [Matthias Bethke](mailto:matthias@towiski.de)
 * [Matthias Beyer](mailto:mail@beyermatthias.de)
 * [Matthias Jouan](mailto:matthias.jouan@gmail.com)
 * [Matthieu Paret](mailto:matthieuparet69@gmail.com)
 * [Maxime BORGES](mailto:maxime.borges@gmail.com)
 * [Mayu Laierlence](mailto:minacle@live.com)
+* [Meisam](mailto:39205857+mftabriz@users.noreply.github.com)
 * [Michael Deeb](mailto:michaeldeeb@me.com)
 * [Michael Vieira](mailto:dtox94@gmail.com)
 * [Michel](mailto:michel@cyweo.com)
@@ -558,6 +602,7 @@ and provided thanks to the work of the following contributors:
 * [Nanamachi](mailto:town7.haruki@gmail.com)
 * [Nathaniel Ekoniak](mailto:nekoniak@ennate.tech)
 * [NecroTechno](mailto:necrotechno@riseup.net)
+* [Nicholas La Roux](mailto:larouxn@gmail.com)
 * [Nick Gerakines](mailto:nick@gerakines.net)
 * [Nicolai von Neudeck](mailto:nicolai@vonneudeck.com)
 * [Ninetailed](mailto:ninetailed@gmail.com)
@@ -575,18 +620,25 @@ and provided thanks to the work of the following contributors:
 * [PatrickRWells](mailto:32802366+patrickrwells@users.noreply.github.com)
 * [Paul](mailto:naydex.mc+github@gmail.com)
 * [Pete Keen](mailto:pete@petekeen.net)
+* [Pierre Bourdon](mailto:delroth@gmail.com)
 * [Pierre-Morgan Gate](mailto:pgate@users.noreply.github.com)
 * [Ratmir Karabut](mailto:rkarabut@sfmodern.ru)
 * [Reto Kromer](mailto:retokromer@users.noreply.github.com)
+* [Rob Petti](mailto:rob.petti@gmail.com)
 * [Rob Watson](mailto:rfwatson@users.noreply.github.com)
+* [Robert Laurenz](mailto:8169746+laurenzcodes@users.noreply.github.com)
 * [Rohan Sharma](mailto:i.am.lone.survivor@protonmail.com)
+* [Roni Laukkarinen](mailto:roni@laukkarinen.info)
 * [Ryan Freebern](mailto:ryan@freebern.org)
 * [Ryan Wade](mailto:ryan.wade@protonmail.com)
 * [Ryo Kajiwara](mailto:kfe-fecn6.prussian@s01.info)
 * [S.H](mailto:gamelinks007@gmail.com)
+* [SJang1](mailto:git@sjang.dev)
 * [Sadiq Saif](mailto:staticsafe@users.noreply.github.com)
 * [Sam Hewitt](mailto:hewittsamuel@gmail.com)
+* [Samuel Kaiser](mailto:sk22@mailbox.org)
 * [Sara Aimée Smiseth](mailto:51710585+sarasmiseth@users.noreply.github.com)
+* [Sara Golemon](mailto:pollita@php.net)
 * [Satoshi KOJIMA](mailto:skoji@mac.com)
 * [ScienJus](mailto:i@scienjus.com)
 * [Scott Larkin](mailto:scott@codeclimate.com)
@@ -607,6 +659,7 @@ and provided thanks to the work of the following contributors:
 * [Spanky](mailto:2788886+spankyworks@users.noreply.github.com)
 * [Stanislas](mailto:stanislas.lange@pm.me)
 * [StefOfficiel](mailto:pichard.stephane@free.fr)
+* [Stefano Pigozzi](mailto:ste.pigozzi@gmail.com)
 * [Steven Tappert](mailto:admin@dark-it.net)
 * [Stéphane Guillou](mailto:stephane.guillou@member.fsf.org)
 * [Su Yang](mailto:soulteary@users.noreply.github.com)
@@ -619,13 +672,16 @@ and provided thanks to the work of the following contributors:
 * [TakesxiSximada](mailto:takesxi.sximada@gmail.com)
 * [Tao Bror Bojlén](mailto:brortao@users.noreply.github.com)
 * [Taras Gogol](mailto:taras2358@gmail.com)
+* [The Stranjer](mailto:791672+thestranjer@users.noreply.github.com)
 * [TheInventrix](mailto:theinventrix@users.noreply.github.com)
 * [TheMainOne](mailto:50847364+theevilskeleton@users.noreply.github.com)
 * [Thomas Alberola](mailto:thomas@needacoffee.fr)
+* [Thomas Citharel](mailto:github@tcit.fr)
 * [Toby Deshane](mailto:fortyseven@users.noreply.github.com)
 * [Toby Pinder](mailto:gigitrix@gmail.com)
 * [Tomonori Murakami](mailto:crosslife777@gmail.com)
 * [TomoyaShibata](mailto:wind.of.hometown@gmail.com)
+* [Tony Jiang](mailto:yujiang99@gmail.com)
 * [Treyssat-Vincent Nino](mailto:treyssatvincent@users.noreply.github.com)
 * [Truong Nguyen](mailto:truongnmt.dev@gmail.com)
 * [Udo Kramer](mailto:optik@fluffel.io)
@@ -634,6 +690,7 @@ and provided thanks to the work of the following contributors:
 * [Ushitora Anqou](mailto:ushitora_anqou@yahoo.co.jp)
 * [Valentin Lorentz](mailto:progval+git@progval.net)
 * [Vladimir Mincev](mailto:vladimir@canicinteractive.com)
+* [Vyr Cossont](mailto:vyrcossont@users.noreply.github.com)
 * [Waldir Pimenta](mailto:waldyrious@gmail.com)
 * [Wenceslao Páez Chávez](mailto:wcpaez@gmail.com)
 * [Wesley Ellis](mailto:tahnok@gmail.com)
@@ -648,10 +705,12 @@ and provided thanks to the work of the following contributors:
 * [YaQ](mailto:i_k_o_m_a_7@yahoo.co.jp)
 * [Yanaken](mailto:yanakend@gmail.com)
 * [Yann Klis](mailto:yann.klis@gmail.com)
+* [Yarden Shoham](mailto:hrsi88@gmail.com)
 * [Yağızhan](mailto:35808275+yagizhan49@users.noreply.github.com)
 * [Yeechan Lu](mailto:wz.bluesnow@gmail.com)
 * [Your Name](mailto:lorenzd@gmail.com)
 * [Yusuke Abe](mailto:moonset20@gmail.com)
+* [Zach Flanders](mailto:zachflanders@gmail.com)
 * [Zach Neill](mailto:neillz@berea.edu)
 * [Zachary Spector](mailto:logicaldash@gmail.com)
 * [ZiiX](mailto:ziix@users.noreply.github.com)
@@ -666,8 +725,8 @@ and provided thanks to the work of the following contributors:
 * [chrolis](mailto:chrolis@users.noreply.github.com)
 * [cormo](mailto:cormorant2+github@gmail.com)
 * [d0p1](mailto:dopi-sama@hush.com)
-* [dogelover911](mailto:84288771+dogelover911@users.noreply.github.com)
 * [dxwc](mailto:dxwc@users.noreply.github.com)
+* [eai04191](mailto:eai@mizle.net)
 * [evilny0](mailto:evilny0@moomoocamp.net)
 * [febrezo](mailto:felixbrezo@gmail.com)
 * [fsubal](mailto:fsubal@users.noreply.github.com)
@@ -678,7 +737,6 @@ and provided thanks to the work of the following contributors:
 * [hakoai](mailto:hk--76@qa2.so-net.ne.jp)
 * [haosbvnker](mailto:github@chaosbunker.com)
 * [heguro](mailto:65112898+heguro@users.noreply.github.com)
-* [helloworldstack](mailto:66512512+helloworldstack@users.noreply.github.com)
 * [ichi_i](mailto:51489410+ichi-i@users.noreply.github.com)
 * [isati](mailto:phil@juchnowi.cz)
 * [jacob](mailto:jacobherringtondeveloper@gmail.com)
@@ -688,15 +746,18 @@ and provided thanks to the work of the following contributors:
 * [jooops](mailto:joops@autistici.org)
 * [jukper](mailto:jukkaperanto@gmail.com)
 * [jumoru](mailto:jumoru@mailbox.org)
+* [k.bigwheel (kazufumi nishida)](mailto:k.bigwheel+eng@gmail.com)
 * [kaias1jp](mailto:kaias1jp@gmail.com)
 * [karlyeurl](mailto:karl.yeurl@gmail.com)
 * [kawaguchi](mailto:jiikko@users.noreply.github.com)
 * [kedama](mailto:32974885+kedamadq@users.noreply.github.com)
+* [keiya](mailto:keiya_21@yahoo.co.jp)
 * [kuro5hin](mailto:rusty@kuro5hin.org)
 * [leo60228](mailto:leo@60228.dev)
 * [matildepark](mailto:matilde.park@pm.me)
 * [maxypy](mailto:maxime@mpigou.fr)
 * [mhe](mailto:mail@marcus-herrmann.com)
+* [mickkael](mailto:19755421+mickkael@users.noreply.github.com)
 * [mike castleman](mailto:m@mlcastle.net)
 * [mimikun](mailto:dzdzble_effort_311@outlook.jp)
 * [mohemohe](mailto:mohemohe@users.noreply.github.com)
@@ -707,9 +768,11 @@ and provided thanks to the work of the following contributors:
 * [notozeki](mailto:notozeki@users.noreply.github.com)
 * [ntl-purism](mailto:57806346+ntl-purism@users.noreply.github.com)
 * [nzws](mailto:git-yuzu@svk.jp)
+* [pea-sys](mailto:49807271+pea-sys@users.noreply.github.com)
 * [potpro](mailto:pptppctt@gmail.com)
 * [proxy](mailto:51172302+3n-k1@users.noreply.github.com)
 * [rch850](mailto:rich850@gmail.com)
+* [rcombs](mailto:rcombs@rcombs.me)
 * [roikale](mailto:roikale@users.noreply.github.com)
 * [rysiekpl](mailto:rysiek@hackerspace.pl)
 * [sasanquaneuf](mailto:sasanquaneuf@gmail.com)
@@ -726,13 +789,13 @@ and provided thanks to the work of the following contributors:
 * [tmyt](mailto:shigure@refy.net)
 * [trevDev()](mailto:trev@trevdev.ca)
 * [tsia](mailto:github@tsia.de)
+* [txt-file](mailto:44214237+txt-file@users.noreply.github.com)
 * [utam0k](mailto:k0ma@utam0k.jp)
 * [vpzomtrrfrt](mailto:vpzomtrrfrt@gmail.com)
 * [walfie](mailto:walfington@gmail.com)
 * [y-temp4](mailto:y.temp4@gmail.com)
 * [ymmtmdk](mailto:ymmtmdk@gmail.com)
 * [yoshipc](mailto:yoooo@yoshipc.net)
-* [zunda](mailto:zundan@gmail.com)
 * [Özcan Zafer AYAN](mailto:ozcanzaferayan@gmail.com)
 * [ばん](mailto:detteiu0321@gmail.com)
 * [ふるふる](mailto:frfs@users.noreply.github.com)
@@ -752,599 +815,951 @@ This document is provided for informational purposes only. Since it is only upda
 Following people have contributed to translation of Mastodon:
 
 - GunChleoc (*Scottish Gaelic*)
-- ケインツロ 👾 (KNTRO) (*Spanish, Argentina*)
-- Sveinn í Felli (sveinki) (*Icelandic*)
+- ケインツロ ⚧️👾🛸 (KNTRO) (*Spanish, Argentina*)
 - Hồ Nhất Duy (honhatduy) (*Vietnamese*)
+- Sveinn í Felli (sveinki) (*Icelandic*)
+- Kristaps (Kristaps_M) (*Latvian*)
+- NCAA (*Danish, French*)
 - Zoltán Gera (gerazo) (*Hungarian*)
-- Kristaps_M (*Latvian*)
-- NCAA (*French, Danish*)
-- adrmzz (*Sardinian*)
-- Xosé M. (XoseM) (*Spanish, Galician*)
-- Ramdziana F Y (rafeyu) (*Indonesian*)
-- Jeong Arm (Kjwon15) (*Spanish, Japanese, Korean, Esperanto*)
+- ghose (XoseM) (*Galician, Spanish*)
+- Jeong Arm (Kjwon15) (*Korean, Esperanto, Japanese, Spanish*)
 - Emanuel Pina (emanuelpina) (*Portuguese*)
-- qezwan (*Persian, Sorani (Kurdish)*)
-- Besnik_b (*Albanian*)
-- ButterflyOfFire (BoFFire) (*French, Arabic, Kabyle*)
+- Reyzadren (*Ido, Malay*)
 - Thai Localization (thl10n) (*Thai*)
+- Besnik_b (*Albanian*)
+- Joene (joenepraat) (*Dutch*)
 - Cyax (Cyaxares) (*Kurmanji (Kurdish)*)
+- adrmzz (*Sardinian*)
+- Ramdziana F Y (rafeyu) (*Indonesian*)
+- xatier (*Chinese Traditional, Chinese Traditional, Hong Kong*)
+- qezwan (*Sorani (Kurdish), Persian*)
+- spla (*Catalan, Spanish*)
+- ButterflyOfFire (BoFFire) (*Arabic, French, Kabyle*)
+- Martin (miles) (*Slovenian*)
+- නාමල් ජයසිංහ (nimnaya) (*Sinhala*)
+- Asier Iturralde Sarasola (aldatsa) (*Basque*)
+- Ondřej Pokorný (unextro) (*Czech*)
+- Roboron (*Spanish*)
 - taicv (*Vietnamese*)
+- koyu (*German*)
 - Daniele Lira Mereb (danilmereb) (*Portuguese, Brazilian*)
-- spla (*Spanish, Catalan*)
+- T. E. Kalaycı (tekrei) (*Turkish*)
 - Evert Prants (IcyDiamond) (*Estonian*)
-- koyu (*German*)
-- Alix Rossi (palindromordnilap) (*French, Esperanto, Corsican*)
-- Joene (joenepraat) (*Dutch*)
+- Yair Mahalalel (yairm) (*Hebrew*)
+- Ihor Hordiichuk (ihor_ck) (*Ukrainian*)
+- Alessandro Levati (Oct326) (*Italian*)
+- Kimmo Kujansuu (mrkujansuu) (*Finnish*)
+- Alix Rossi (palindromordnilap) (*Corsican, Esperanto, French*)
+- Danial Behzadi (danialbehzadi) (*Persian*)
 - stan ionut (stanionut12) (*Romanian*)
 - Mastodon 中文译者 (mastodon-linguist) (*Chinese Simplified*)
 - Kristijan Tkalec (lapor) (*Slovenian*)
-- Danial Behzadi (danialbehzadi) (*Persian*)
-- Asier Iturralde Sarasola (aldatsa) (*Basque*)
+Alexander Sorokin (Brawaru) (*Russian, Vietnamese, Swedish, Portuguese, Tamil, Kabyle, Polish, Italian, Catalan, Armenian, Hungarian, Albanian, Greek, Galician, Korean, Ukrainian, German, Danish, French*)
 - ManeraKai (*Arabic*)
 - мачко (ma4ko) (*Bulgarian*)
-- Roboron (*Spanish*)
-- Alessandro Levati (Oct326) (*Italian*)
-- xatier (*Chinese Traditional, Chinese Traditional, Hong Kong*)
-- Ondřej Pokorný (unextro) (*Czech*)
-- Alexander Sorokin (Brawaru) (*French, Catalan, Danish, German, Greek, Hungarian, Armenian, Korean, Portuguese, Russian, Albanian, Swedish, Ukrainian, Vietnamese, Galician*)
 - kamee (*Armenian*)
-- Michal Stanke (mstanke) (*Czech*)
+- Yamagishi Kazutoshi (ykzts) (*Japanese, Icelandic, Sorani (Kurdish), Albanian, Vietnamese, Chinese Simplified*)
+- Takeçi (polygoat) (*French, Italian*)
+- REMOVED_USER (*Czech*)
 - borys_sh (*Ukrainian*)
 - Imre Kristoffer Eilertsen (DandelionSprout) (*Norwegian*)
-- yeft (*Chinese Traditional, Chinese Traditional, Hong Kong*)
+- Marek Ľach (mareklach) (*Slovak, Polish*)
+- yeft (*Chinese Traditional, Hong Kong, Chinese Traditional*)
+- D. Cederberg (cederberget) (*Swedish*)
 - Miguel Mayol (mitcoes) (*Spanish, Catalan*)
-- Marek Ľach (mareklach) (*Polish, Slovak*)
+- enolp (*Asturian*)
 - Manuel Viens (manuelviens) (*French*)
-- Kimmo Kujansuu (mrkujansuu) (*Finnish*)
+- cybergene (*Japanese*)
+- REMOVED_USER (*Turkish*)
+- xpil (*Polish, Scottish Gaelic*)
+- Balázs Meskó (mesko.balazs) (*Hungarian, Czech*)
 - Koala Yeung (yookoala) (*Chinese Traditional, Hong Kong*)
-- enolp (*Asturian*)
 - Osoitz (*Basque*)
-- Peterandre (*Norwegian, Norwegian Nynorsk*)
-- tzium (*Sardinian*)
+- Amir Rubinstein - TAU (AmirrTAU) (*Hebrew, Indonesian*)
 - Maya Minatsuki (mayaeh) (*Japanese*)
-- Mélanie Chauvel (ariasuni) (*French, Arabic, Czech, German, Greek, Hungarian, Slovenian, Ukrainian, Chinese Simplified, Portuguese, Brazilian, Persian, Norwegian Nynorsk, Esperanto, Breton, Corsican, Sardinian, Kabyle*)
-- T. E. Kalaycı (tekrei) (*Turkish*)
-- Takeçi (polygoat) (*French, Italian*)
+- Peterandre (*Norwegian Nynorsk, Norwegian*)
+Mélanie Chauvel (ariasuni) (*French, Esperanto, Norwegian Nynorsk, Persian, Kabyle, Sardinian, Corsican, Breton, Portuguese, Brazilian, Arabic, Chinese Simplified, Ukrainian, Slovenian, Greek, German, Czech, Hungarian*)
+- tzium (*Sardinian*)
+- Diluns (*Occitan*)
 - Galician Translator (Galician_translator) (*Galician*)
+- Marcin Mikołajczak (mkljczkk) (*Polish, Czech, Russian*)
+- Jeff Huang (s8321414) (*Chinese Traditional*)
+- Pixelcode (realpixelcode) (*German*)
+- Allen Zhong (AstroProfundis) (*Chinese Simplified*)
 - lamnatos (*Greek*)
 - Sean Young (assanges) (*Chinese Traditional*)
+- retiolus (*Catalan, French, Spanish*)
 - tolstoevsky (*Russian*)
-- Ihor Hordiichuk (ihor_ck) (*Ukrainian*)
 - Ali Demirtaş (alidemirtas) (*Turkish*)
-- Jasmine Cam Andrever (gourmas) (*Cornish*)
+- J. Cam Andrever-Wright (gourmas) (*Cornish*)
 - coxde (*Chinese Simplified*)
+- Dremski (*Bulgarian*)
 - gagik_ (*Armenian*)
 - Masoud Abkenar (mabkenar) (*Persian*)
 - arshat (*Kazakh*)
-- Marcin Mikołajczak (mkljczkk) (*Czech, Polish, Russian*)
-- Jeff Huang (s8321414) (*Chinese Traditional*)
+- Ira (seefood) (*Hebrew*)
+- Linerly (*Indonesian*)
 - Blak Ouille (BlakOuille16) (*French*)
 - e (diveedd) (*Kurmanji (Kurdish)*)
 - Em St Cenydd (cancennau) (*Welsh*)
-- Diluns (*Occitan*)
+- Tigran (tigransimonyan) (*Armenian*)
+- Draacoun (*Portuguese, Brazilian*)
+- REMOVED_USER (*Turkish*)
 - Nurul Azeera Hidayah @ Muhammad Nur Hidayat Yasuyoshi (MNH48.moe) (mnh48) (*Malay*)
-- Tagomago (tagomago) (*French, Spanish*)
-- Jurica (ahjk) (*Croatian*)
+- Tagomago (tagomago) (*Spanish, French*)
+- Ashun (ashune) (*Croatian*)
 - Aditoo17 (*Czech*)
-- Tigran (tigransimonyan) (*Armenian*)
 - vishnuvaratharajan (*Tamil*)
 - pulmonarycosignerkindness (*Swedish*)
 - calypsoopenmail (*French*)
-- cybergene (cyber-gene) (*Japanese*)
+- REMOVED_USER (*Kabyle*)
+- snerk (*Norwegian Nynorsk*)
+- Sebastian (SebastianBerlin) (*German*)
+- lisawe (*Norwegian*)
+- serratrad (*Catalan*)
 - Bran_Ruz (*Breton*)
+- ViktorOn (*Russian, Danish*)
 - Gearguy (*Finnish*)
+- Andi Chandler (andibing) (*English, United Kingdom*)
+- Tor Egil Hoftun Kvæstad (Taloran) (*Norwegian Nynorsk*)
 - GiorgioHerbie (*Italian*)
-- Balázs Meskó (mesko.balazs) (*Czech, Hungarian*)
-- Martin (miles) (*Slovenian*)
+- හෙළබස සමූහය (HelaBasa) (*Sinhala*)
+- kat (katktv) (*Ukrainian, Russian*)
+- Yi-Jyun Pan (pan93412) (*Chinese Traditional*)
+- Fjoerfoks (fryskefirefox) (*Frisian, Dutch*)
+- Eshagh (eshagh79) (*Persian*)
 - regulartranslator (*Portuguese, Brazilian*)
 - Saederup92 (*Danish*)
-- ozzii (*French, Serbian (Cyrillic)*)
+- ozzii (Serbian (Cyrillic), French)
 - Irfan (Irfan_Radz) (*Malay*)
-- Yi-Jyun Pan (pan93412) (*Chinese Traditional*)
 - ClearlyClaire (*French, Icelandic*)
+- Sokratis Alichanidis (alichani) (*Greek*)
+- Jiří Podhorecký (trendspotter) (*Czech*)
 - Akarshan Biswas (biswasab) (*Bengali, Sanskrit*)
+- Robert Wolniak (Szkodnix) (*Polish*)
+- Jan Lindblom (janlindblom) (*Swedish*)
+- Dewi (Unkorneg) (*Breton, French*)
 - Kristoffer Grundström (Umeaboy) (*Swedish*)
 - Rafael H L Moretti (Moretti) (*Portuguese, Brazilian*)
 - d5Ziif3K (*Ukrainian*)
-- හෙළබස (HelaBasa) (*Sinhala*)
-- xpil (*Polish*)
-- Rojdayek (*Kurmanji (Kurdish)*)
+- Nemu (Dormemulo) (*Esperanto, French, Italian, Ido, Afrikaans*)
+- Johan Mynhardt (johanmynhardt) (*Afrikaans*)
+- Rojdayek (Kurmanji (Kurdish))
+- REMOVED_USER (*Portuguese, Brazilian*)
+- GCardo (*Portuguese, Brazilian*)
 - christalleras (*Norwegian Nynorsk*)
-- Allen Zhong (AstroProfundis) (*Chinese Simplified*)
-- Taloran (*Norwegian Nynorsk*)
-- Sokratis Alichanidis (alichani) (*Greek*)
+- diorama (*Italian*)
+- Jaz-Michael King (jazmichaelking) (*Welsh*)
 - Catalina (catalina.st) (*Romanian*)
-- otrapersona (*Spanish, Spanish, Mexico*)
 - Ryo (DrRyo) (*Korean*)
-- Mauzi (*German, Swedish*)
+- otrapersona (*Spanish, Mexico, Spanish*)
+- Frontier Translation Ltd. (frontier-translation) (*Chinese Simplified*)
+- Mauzi (*Swedish, German*)
+- Clopsy87 (*Italian*)
 - atarashiako (*Chinese Simplified*)
 - erictapen (*German*)
+- zhen liao (az0189re) (*Chinese Simplified*)
 - 101010 (101010pl) (*Polish*)
-- Jaz-Michael King (jazmichaelking) (*Welsh*)
+- REMOVED_USER (*Norwegian*)
 - axi (*Finnish*)
 - silkevicious (*Italian*)
 - Floxu (fredrikdim1) (*Norwegian Nynorsk*)
-- NadieAishi (*Spanish, Spanish, Mexico*)
+- Nic Dafis (nicdafis) (*Welsh*)
+- NadieAishi (*Spanish, Mexico, Spanish*)
+- 戸渡生野 (aomyouza2543) (*Thai*)
+- Tjipke van der Heide (vancha) (*Frisian*)
+- Erik Mogensen (mogsie) (*Norwegian*)
+- pomoch (*Chinese Traditional, Hong Kong*)
+- Alexandre Brito (alexbrito) (*Portuguese, Brazilian*)
 - Bertil Hedkvist (Berrahed) (*Swedish*)
 - William(ѕ)ⁿ (wmlgr) (*Spanish*)
-- Eshagh (eshagh79) (*Persian*)
 - LNDDYL (*Chinese Traditional*)
+- tanketom (*Norwegian Nynorsk*)
 - norayr (*Armenian*)
+- l3ycle (*German*)
+- strubbl (*German*)
 - Satnam S Virdi (pika10singh) (*Punjabi*)
 - Tiago Epifânio (tfve) (*Portuguese*)
 - Mentor Gashi (mentorgashi.com) (*Albanian*)
+- Sid (autinerd1) (*Dutch, German*)
 - carolinagiorno (*Portuguese, Brazilian*)
+- Em_i (emiliencoss) (*French*)
+- Liam O (liamoshan) (*Irish*)
 - Hayk Khachatryan (brutusromanus123) (*Armenian*)
 - Roby Thomas (roby.thomas) (*Malayalam*)
+- ThonyVezbe (*Breton*)
+- Percy (kecrily) (*Chinese Simplified*)
 - Bharat Kumar (Marwari) (*Hindi*)
 - Austra Muizniece (aus_m) (*Latvian*)
-- ThonyVezbe (*Breton*)
+- Urubu Lageano (urubulageano) (*Portuguese, Brazilian*)
 - Just Spanish (7_7) (*Spanish, Mexico*)
 - v4vachan (*Malayalam*)
 - bilfri (*Danish*)
+- IamHappy (mrmx2013) (*Ukrainian*)
 - dkdarshan760 (*Sanskrit*)
 - Timur Seber (seber) (*Tatar*)
 - Slimane Selyan AMIRI (SelyanKab) (*Kabyle*)
 - VaiTon (*Italian*)
-- Vik (ViktorOn) (*Danish, Russian*)
 - tykayn (*French*)
-- GCardo (*Portuguese, Brazilian*)
+- Abdulaziz Aljaber (kuwaitna) (*Arabic*)
 - taoxvx (*Danish*)
-- Hrach Mkrtchyan (mhrach87) (*Armenian*)
+- Hrach Mkrtchyan (hrachmk) (*Armenian*)
 - sabri (thetomatoisavegetable) (*Spanish, Spanish, Argentina*)
-- Dewi (Unkorneg) (*French, Breton*)
 - CoelacanthusHex (*Chinese Simplified*)
 - Rhys Harrison (rhedders) (*Esperanto*)
-- syncopams (*Chinese Simplified, Chinese Traditional, Chinese Traditional, Hong Kong*)
+- syncopams (*Chinese Traditional, Hong Kong, Chinese Traditional, Chinese Simplified*)
 - SteinarK (*Norwegian Nynorsk*)
+- REMOVED_USER (*Standard Moroccan Tamazight*)
 - Maxine B. Vågnes (vagnes) (*Norwegian, Norwegian Nynorsk*)
-- Hakim Oubouali (zenata1) (*Standard Moroccan Tamazight*)
+- Rikard Linde (rikardlinde) (*Swedish*)
 - ahangarha (*Persian*)
 - Lalo Tafolla (lalotafo) (*Spanish, Spanish, Mexico*)
-- dashersyed (*Urdu (Pakistan)*)
+- Larissa Cruz (larissacruz) (*Portuguese, Brazilian*)
+- dashersyed (Urdu (Pakistan))
+- camerongreer21 (*English, United Kingdom*)
+- REMOVED_USER (*Ukrainian*)
 - Conight Wang (xfddwhh) (*Chinese Simplified*)
 - liffon (*Swedish*)
 - Damjan Dimitrioski (gnud) (*Macedonian*)
-- Rikard Linde (rikardlinde) (*Swedish*)
 - rondnunes (*Portuguese, Brazilian*)
-- strubbl (*German*)
 - PPNplus (*Thai*)
-- Frontier Translation Ltd. (frontier-translation) (*Chinese Simplified*)
+- Steven Ritchie (Steaph38) (*Scottish Gaelic*)
+- 游荡 (MamaShip) (*Chinese Simplified*)
+- Edward Navarro (EdwardNavarro) (*Spanish*)
 - shioko (*Chinese Simplified*)
+- gnu-ewm (*Polish*)
 - Kahina Mess (K_hina) (*Kabyle*)
+- Hexandcube (hexandcube) (*Polish*)
+- Scott Starkey (yekrats) (*Esperanto*)
 - ZiriSut (*Kabyle*)
+- FreddyG (*Esperanto*)
+- mynameismonkey (*Welsh*)
 - Groosha (groosha) (*Russian*)
-- Hexandcube (hexandcube) (*Polish*)
 - Gwenn (Belvar) (*Breton*)
-- 游荡 (MamaShip) (*Chinese Simplified*)
 - StanleyFrew (*French*)
-- mynameismonkey (*Welsh*)
-- Edward Navarro (EdwardNavarro) (*Spanish*)
+- cathalgarvey (*Irish*)
 - Nikita Epifanov (Nikets) (*Russian*)
+- REMOVED_USER (*Finnish*)
 - jaranta (*Finnish*)
 - Slobodan Simić (Слободан Симић) (slsimic) (*Serbian (Cyrillic)*)
-- retiolus (*Catalan*)
-- iVampireSP (*Chinese Simplified, Chinese Traditional*)
+- iVampireSP (*Chinese Traditional, Chinese Simplified*)
 - Felicia Jongleur (midsommar) (*Swedish*)
 - Denys (dector) (*Ukrainian*)
 - Mo_der Steven (SakuraPuare) (*Chinese Simplified*)
+- REMOVED_USER (*German*)
+- Kishin Sagume (kishinsagi) (*Chinese Simplified*)
+- bennepharaoh (*Chinese Simplified*)
 - Vanege (*Esperanto*)
+- hibiya inemuri (hibiya) (*Korean*)
 - Jess Rafn (therealyez) (*Danish*)
 - Stasiek Michalski (hellcp) (*Polish*)
 - dxwc (*Bengali*)
-- Filbert Salim (gamesbert6) (*Indonesian*)
+- Heran Membingung (heranmembingung) (*Indonesian*)
+- Parodper (*Galician*)
+- rbnval (*Catalan*)
 - Liboide (*Spanish*)
+- hemnaren (*Norwegian Nynorsk*)
 - jmontane (*Catalan*)
+- Andy Kleinert (AndyKl) (*German*)
 - Chris Kay (chriskarasoulis) (*Greek*)
+- CrowdinBRUH (*Vietnamese*)
+- Rhoslyn Prys (Rhoslyn) (*Welsh*)
+- abidin toumi (Zet24) (*Arabic*)
 - Johan Schiff (schyffel) (*Swedish*)
 - Rex_sa (rex07) (*Arabic*)
+- amedcj (*Kurmanji (Kurdish)*)
 - Arunmozhi (tecoholic) (*Tamil*)
 - zer0-x (ZER0-X) (*Arabic*)
-- kat (katktv) (*Russian, Ukrainian*)
+- staticnoisexyz (*Czech*)
 - Lauren Liberda (selfisekai) (*Polish*)
+- Michael Zeevi (maze88) (*Hebrew*)
 - oti4500 (*Hungarian, Ukrainian*)
 - Delta (Delta-Time) (*Japanese*)
-- Michael Zeevi (maze88) (*Hebrew*)
+- Marc Antoine Thevenet (MATsxm) (*French*)
+- AlexKoala (alexkoala) (*Korean*)
 - SarfarazAhmed (*Urdu (Pakistan)*)
+- Ahmad Dakhlallah (MIUIArabia) (*Arabic*)
 - Mats Gunnar Ahlqvist (goqbi) (*Swedish*)
 - diazepan (*Spanish, Spanish, Argentina*)
+- Tiger:blank (tsagaanbar) (*Chinese Simplified*)
+- REMOVED_USER (*Chinese Simplified*)
 - marzuquccen (*Kabyle*)
 - atriix (*Swedish*)
+- Laur (melaur) (*Romanian*)
 - VictorCorreia (victorcorreia1984) (*Afrikaans*)
 - Remito (remitocat) (*Japanese*)
-- AlexKoala (alexkoala) (*Korean*)
 - Juan José Salvador Piedra (JuanjoSalvador) (*Spanish*)
-- BurekzFinezt (*Serbian (Cyrillic)*)
+- REMOVED_USER (*Norwegian*)
 - 森の子リスのミーコの大冒険 (Phroneris) (*Japanese*)
+- Gim_Garam (*Korean*)
+- BurekzFinezt (*Serbian (Cyrillic)*)
+- Pēteris Caune (cuu508) (*Latvian*)
 - asnomgtu (*Hungarian*)
+- bendigeidfran (*Welsh*)
 - SHeija (*Finnish*)
 - Врабац (Slovorad) (*Serbian (Cyrillic)*)
 - Dženan (Dzenan) (*Swedish*)
-- Jack R (isaac.97_WT) (*Spanish*)
+- Gabriel Beecham (lancet) (*Irish*)
 - antonyho (*Chinese Traditional, Hong Kong*)
-- FreddyG (*Esperanto*)
-- andruhov (*Russian, Ukrainian*)
+- Jack R (isaac.97_WT) (*Spanish*)
+- Henrik Mattsson-Mårn (rchk) (*Swedish*)
+- Oguzhan Aydin (aoguzhan) (*Turkish*)
+- Soran730 (*Chinese Simplified*)
+- andruhov (*Ukrainian, Russian*)
+- 北䑓如法 (Nyoho) (*Japanese*)
 - phena109 (*Chinese Traditional, Hong Kong*)
-- Aryamik Sharma (Aryamik) (*Swedish, Hindi*)
+- Aryamik Sharma (Aryamik) (*Hindi, Swedish*)
 - Unmual (*Spanish*)
+- Tobias Bannert (toba) (*German*)
 - Adrián Graña (alaris83) (*Spanish*)
-- cruz2020 (*Portuguese*)
 - vpei (*Chinese Simplified*)
+- cruz2020 (*Portuguese*)
+- papapep (h9f2ycHh-ktOd6_Y) (*Catalan*)
+- Roj (roj1512) (*Sorani (Kurdish), Kurmanji (Kurdish)*)
 - るいーね (ruine) (*Japanese*)
+- aujawindar (*Norwegian Nynorsk*)
+- irithys (*Chinese Simplified*)
 - Sam Tux (imahbub) (*Bengali*)
 - igordrozniak (*Polish*)
+- Johannes Nilsson (nlssn) (*Swedish*)
 - Michał Sidor (michcioperz) (*Polish*)
 - Isaac Huang (caasih) (*Chinese Traditional*)
 - AW Unad (awcodify) (*Indonesian*)
 - 1Alino (*Slovak*)
 - Cutls (cutls) (*Japanese*)
-- Goudarz Jafari (Goudarz) (*Persian*)
-- Parodper (*Galician*)
+- Goudarz Jafari (GoudarzJafari) (*Persian*)
+- Daniel Strömholm (stromholm) (*Swedish*)
 - 1 (Ipsumry) (*Spanish*)
 - Falling Snowdin (tghgg) (*Vietnamese*)
+- Paulino Michelazzo (pmichelazzo) (*Portuguese, Brazilian*)
+- Y.Yamashiro (uist1idrju3i) (*Japanese*)
 - Rasmus Lindroth (RasmusLindroth) (*Swedish*)
 - Gianfranco Fronteddu (gianfro.gianfro) (*Sardinian*)
 - Andrea Lo Iacono (niels0n) (*Italian*)
 - fucsia (*Italian*)
 - Vedran Serbu (SerenoXGen) (*Croatian*)
+- Raphael Das Gupta (das-g) (*Esperanto, German*)
+- yanchan09 (*Estonian*)
+- ainmeolai (*Irish*)
+- REMOVED_USER (*Norwegian*)
+- mian42 (*Bulgarian*)
 - Kinshuk Sunil (kinshuksunil) (*Hindi*)
-- Ullas Joseph (ullasjoseph) (*Malayalam*)
 - al_._ (*German, Russian*)
+- Ullas Joseph (ullasjoseph) (*Malayalam*)
+- sanoth (*Swedish*)
+- Aftab Alam (iaftabalam) (*Hindi*)
+- frumble (*German*)
+- juanda097 (juanda-097) (*Spanish*)
 - Matthías Páll Gissurarson (icetritlo) (*Icelandic*)
-- Percy (kecrily) (*Chinese Simplified*)
-- Yu-Pai Liu (tedliou) (*Chinese Traditional*)
+- Russian Retro (retrograde) (*Russian*)
 - KcKcZi (*Chinese Simplified*)
+- Yu-Pai Liu (tedliou) (*Chinese Traditional*)
 - Amarin Cemthong (acitmaster) (*Thai*)
-- Johannes Nilsson (nlssn) (*Swedish*)
-- juanda097 (juanda-097) (*Spanish*)
+- Etinew (*Hebrew*)
 - xsml (*Chinese Simplified*)
+- S.J. L. (samijuhanilii) (*Finnish*)
 - Anunnakey (*Macedonian*)
 - erikkemp (*Dutch*)
+- Tsl (muun) (*Chinese Simplified*)
+- Renato "Lond" Cerqueira (renatolond) (*Portuguese, Brazilian*)
+- Úna-Minh Kavanagh (yunitex) (*Irish*)
+- kongk (*Norwegian Nynorsk*)
 - erikstl (*Esperanto*)
 - twpenguin (*Chinese Traditional*)
+- JeremyStarTM (*German*)
 - Po-chiang Chao (bobchao) (*Chinese Traditional*)
 - Marcus Myge (mygg-priv) (*Norwegian*)
 - Esther (esthermations) (*Portuguese*)
+- Jiri Grönroos (spammemoreplease) (*Finnish*)
 - MadeInSteak (*Finnish*)
+- witoharmuth (*Swedish*)
+- MESHAL45 (*Arabic*)
+- mcdutchie (*Dutch*)
+- Michal Špondr (michalspondr) (*Czech*)
 - t_aus_m (*German*)
-- serapolis (*Japanese, Chinese Simplified, Chinese Traditional, Chinese Traditional, Hong Kong*)
+- kaki7777 (*Japanese, Chinese Traditional*)
 - Heimen Stoffels (Vistaus) (*Dutch*)
+- serapolis (*Chinese Traditional, Hong Kong, Chinese Traditional, Japanese, Chinese Simplified*)
 - Rajarshi Guha (rajarshiguha) (*Bengali*)
+- Amir Reza (ElAmir) (*Persian*)
+- REMOVED_USER (*Norwegian*)
+- MohammadSaleh Kamyab (mskf1383) (*Persian*)
+- REMOVED_USER (*Romanian*)
 - Gopal Sharma (gopalvirat) (*Hindi*)
+- Вероніка Някшу (pampushkaveronica) (*Russian, Romanian*)
 - Linnéa (lesbian_subnet) (*Swedish*)
-- 北䑓如法 (Nyoho) (*Japanese*)
-- abidin toumi (Zet24) (*Arabic*)
-- Tofiq Abdula (Xwla) (*Sorani (Kurdish)*)
+- Valentin (HDValentin) (*German*)
+- dragnucs2 (*Arabic*)
 - Carlos Solís (csolisr) (*Esperanto*)
-- Yamagishi Kazutoshi (ykzts) (*Japanese, Vietnamese, Icelandic, Sorani (Kurdish)*)
+- Tofiq Abdula (Xwla) (*Sorani (Kurdish)*)
+- halcek (*Slovak*)
+- Tobias Kunze (rixxian) (*German*)
 - Parthan S Ramanujam (parthan) (*Tamil*)
 - Kasper Nymand (KasperNymand) (*Danish*)
-- subram (*Turkish*)
 - TS (morte) (*Finnish*)
-- SensDeViata (*Ukrainian*)
+- REMOVED_USER (*German*)
+- REMOVED_USER (*Basque*)
+- subram (*Turkish*)
+- Gudwin (*Spanish, Mexico, Spanish*)
 - Ptrcmd (ptrcmd) (*Chinese Traditional*)
+- shmuelHal (*Hebrew*)
+- SensDeViata (*Ukrainian*)
 - megaleo (*Portuguese, Brazilian*)
+- Acursen (*German*)
+- NurKai Kai (nurkaiyttv) (*German*)
+- Guttorm (ghveem) (*Norwegian Nynorsk*)
 - SergioFMiranda (*Portuguese, Brazilian*)
-- hiroTS (*Chinese Traditional*)
+- Danni Lundgren (dannilundgren) (*Danish*)
 - Vivek K J (Vivekkj) (*Malayalam*)
-- arielcostas3 (*Galician*)
+- hiroTS (*Chinese Traditional*)
+- teadesu (*Portuguese, Brazilian*)
+- petartrajkov (*Macedonian*)
+- Ariel Costas (arielcostas3) (*Galician*)
+- Ch. (sftblw) (*Korean*)
+- Rintan (*Japanese*)
+- Jair Henrique (jairhenrique) (*Portuguese, Brazilian*)
+- sorcun (*Turkish*)
+- filippodb (*Italian*)
 - johne32rus23 (*Russian*)
-- AzureNya (*Chinese Simplified*)
 - OctolinGamer (octolingamer) (*Portuguese, Brazilian*)
-- filippodb (*Italian*)
+- AzureNya (*Chinese Simplified*)
 - Ram varma (ram4varma) (*Tamil*)
+- REMOVED_USER (Sorani (Kurdish))
+- REMOVED_USER (*Portuguese, Brazilian*)
+- seanmhade (*Irish*)
 - sanser (*Russian*)
-- Y.Yamashiro (uist1idrju3i) (*Japanese*)
+- Vijay (vijayatmin) (*Tamil*)
+- Anomalion (*German*)
 - Pukima (Pukimaa) (*German*)
-- diorama (*Italian*)
-- frumble (*German*)
+- Curtis Lee (CansCurtis) (*Chinese Traditional*)
+- โบโลน่าไวรัส (nullxyz_) (*Thai*)
+- ふぁーらんど (farland1717) (*Japanese*)
+- 3wen (*Breton*)
+- rlafuente (*Portuguese*)
+- Ильзира Рахматуллина (rahmatullinailzira53) (*Tatar*)
+- Code Man (codemansrc) (*Russian*)
+- Philip Gillißen (guerda) (*German*)
 - Daniel Dimitrov (daniel.dimitrov) (*Bulgarian*)
+- Anton (atjn) (*Danish*)
 - kekkepikkuni (*Tamil*)
 - MODcraft (*Chinese Simplified*)
 - oorsutri (*Tamil*)
+- wortfeld (*German*)
 - Neo_Chen (NeoChen1024) (*Chinese Traditional*)
+- Stereopolex (*Polish*)
+- NxOne14 (*Bulgarian*)
+- Juan Ortiz (Kloido) (*Spanish, Catalan*)
 - Nithin V (Nithin896) (*Tamil*)
+- strikeCunny2245 (*Icelandic*)
 - Miro Rauhala (mirorauhala) (*Finnish*)
+- nicoduesing (duconi) (*German, Esperanto*)
+- Gnonthgol (*Norwegian Nynorsk*)
+- WKobes (*Dutch*)
 - Oymate (*Bengali*)
+- mikwee (*Hebrew*)
+- EzigboOmenana (*Igbo, Cornish*)
+- yan Wato (janWato) (*Hindi*)
+- Papuass (*Latvian*)
+- Vincent Orback (vincentorback) (*Swedish*)
+- chettoy (*Chinese Simplified*)
+- 19 (nineteen) (*Chinese Simplified*)
 - ಚಿರಾಗ್ ನಟರಾಜ್ (chiraag-nataraj) (*Kannada*)
+- Layik Hama (layik) (*Sorani (Kurdish)*)
 - Guillaume Turchini (orion78fr) (*French*)
+- Andri Yngvason (andryng) (*Icelandic*)
 - Aswin C (officialcjunior) (*Malayalam*)
-- Ganesh D (auntgd) (*Marathi*)
 - Yuval Nehemia (yuvalne) (*Hebrew*)
 - mawoka-myblock (mawoka) (*German*)
-- dragnucs2 (*Arabic*)
+- Ganesh D (auntgd) (*Marathi*)
+- Lens0021 (lens0021) (*Korean*)
+- An Gafraíoch (angafraioch) (*Irish*)
+- Michael Smith (michaelshmitty) (*Dutch*)
 - Ryan Ho (koungho) (*Chinese Traditional*)
-- Tejas Harad (h_tejas) (*Marathi*)
+- tunisiano187 (*French*)
+- Peter van Mever (SpacemanSpiff) (*Dutch*)
 - Pedro Henrique (exploronauta) (*Portuguese, Brazilian*)
-- Amir Reza (ElAmir) (*Persian*)
-- Tatsuto "Laminne" Yamamoto (laminne) (*Japanese*)
+- REMOVED_USER (*Esperanto, Italian, Japanese*)
+- Tejas Harad (h_tejas) (*Marathi*)
+- Balázs Meskó (meskobalazs) (*Hungarian*)
 - Vasanthan (vasanthan) (*Tamil*)
+- Tatsuto "Laminne" Yamamoto (laminne) (*Japanese*)
+- slbtty (shenlebantongying) (*Chinese Simplified*)
 - 硫酸鶏 (acid_chicken) (*Japanese*)
 - programizer (*German*)
+- guessimmaterialgrl (*Chinese Simplified*)
 - clarmin b8 (clarminb8) (*Sorani (Kurdish)*)
+- Maria Riegler (riegler3m) (*German*)
 - manukp (*Malayalam*)
-- psymyn (*Hebrew*)
 - earth dweller (sanethoughtyt) (*Marathi*)
+- psymyn (*Hebrew*)
+- Aaraon Thomas (aaraon) (*Portuguese, Brazilian*)
+- Rafael Viana (rafacnec) (*Portuguese, Brazilian*)
 - Marek Ľach (marek-lach) (*Slovak*)
 - meijerivoi (toilet) (*Finnish*)
 - essaar (*Tamil*)
-- Nemuj (Dentrado) (*Japanese, Esperanto*)
 - serubeena (*Swedish*)
-- Rintan (*Japanese*)
-- Karol Kosek (krkkPL) (*Polish*)
+- RqndomHax (*French*)
+- REMOVED_USER (*Polish*)
+- ギャラ (gyara) (*Chinese Simplified, Japanese*)
 - Khó͘ Tiatlêng (khotiatleng) (*Chinese Traditional, Taigi*)
-- valarivan (*Tamil*)
-- Hernik (hernik27) (*Czech*)
 - revarioba (*Spanish*)
 - friedbeans (*Croatian*)
+- An (AnTheMaker) (*German*)
 - kuchengrab (*German*)
+- Hernik (hernik27) (*Czech*)
+- valarivan (*Tamil*)
+- אדם לוין (adamlevin) (*Hebrew*)
+- Vít Horčička (legvita123) (*Czech*)
 - Abi Turi (abi123) (*Georgian*)
+- Thomas Munkholt (munkholt) (*Danish*)
+- pparescasellas (*Catalan*)
 - Hinaloe (hinaloe) (*Japanese*)
-- Sebastián Andil (Selrond) (*Slovak*)
 - Ifnuth (*German*)
-- Asbjørn Olling (a2) (*Danish*)
+- Sebastián Andil (Selrond) (*Slovak*)
+- boni777 (*Chinese Simplified*)
 - KEINOS (*Japanese*)
-- Balázs Meskó (meskobalazs) (*Hungarian*)
-- Artem Mikhalitsin (artemmikhalitsin) (*Russian*)
-- Algustionesa Yoshi (algustionesa) (*Indonesian*)
-- Bottle (suryasalem2010) (*Tamil*)
+- Asbjørn Olling (a2) (*Danish*)
+- REMOVED_USER (*Chinese Traditional, Hong Kong*)
+- DarkShy Community (ponyfrost.mc) (*Russian*)
+- Dennis Reimund (reimunddennis7) (*German*)
+- jocafeli (*Spanish, Mexico*)
 - Wrya ali (John12) (*Sorani (Kurdish)*)
+- Bottle (suryasalem2010) (*Tamil*)
+- Algustionesa Yoshi (algustionesa) (*Indonesian*)
 - JzshAC (*Chinese Simplified*)
+- Artem Mikhalitsin (artemmikhalitsin) (*Russian*)
 - siamano (*Thai, Esperanto*)
-- gnu-ewm (*Polish*)
-- Antillion (antillion99) (*Spanish*)
+- KARARTI44 (kararti44) (*Turkish*)
+- c0c (*Irish*)
+- Stefano S. (Sting1_JP) (*Italian*)
+- tommil (*Finnish*)
+- Ignacio Lis (ilis) (*Galician*)
 - Steven Tappert (sammy8806) (*German*)
-- Reg3xp (*Persian*)
+- Antillion (antillion99) (*Spanish*)
+- K.B.Dharun Krishna (kbdharun) (*Tamil*)
 - Wassim EL BOUHAMIDI (elbouhamidiw) (*Arabic*)
+- Reg3xp (*Persian*)
+- florentVgn (*French*)
+- Matt (Exbu) (*Dutch*)
 - Maciej Błędkowski (mble) (*Polish*)
 - gowthamanb (*Tamil*)
 - hiphipvargas (*Portuguese*)
-- tunisiano187 (*French*)
+- GabuVictor (*Portuguese, Brazilian*)
+- Pverte (*French*)
+- REMOVED_USER (*Spanish*)
+- Surindaku (*Chinese Simplified*)
 - Arttu Ylhävuori (arttu.ylhavuori) (*Finnish*)
-- Ch. (sftblw) (*Korean*)
-- eorn (*Breton*)
+- Pabllo Soares (pabllosoarez) (*Portuguese, Brazilian*)
 - Jona (88wcJoWl) (*Spanish*)
-- Mikkel B. Goldschmidt (mikkelbjoern) (*Danish*)
-- Timo Tijhof (Krinkle) (*Dutch*)
 - Ka2n (kaanmetu) (*Turkish*)
 - tctovsli (*Norwegian Nynorsk*)
-- mecqor labi (mecqorlabi) (*Persian*)
+- Timo Tijhof (Krinkle) (*Dutch*)
+- SamitiMed (samiti3d) (*Thai*)
+- Mikkel B. Goldschmidt (mikkelbjoern) (*Danish*)
 - Odyssey346 (alexader612) (*Norwegian*)
+- mecqor labi (mecqorlabi) (*Persian*)
+- Cù Huy Phúc Khang (taamee) (*Vietnamese*)
+- Oskari Lavinto (olavinto) (*Finnish*)
+- Philippe Lemaire (philippe-lemaire) (*Esperanto*)
 - vjasiegd (*Polish*)
-- Eban (ebanDev) (*French, Esperanto*)
-- SamitiMed (samiti3d) (*Thai*)
+- Eban (ebanDev) (*Esperanto, French*)
 - Nícolas Lavinicki (nclavinicki) (*Portuguese, Brazilian*)
+- REMOVED_USER (*Portuguese, Brazilian*)
 - Rekan Adl (rekan-adl1) (*Sorani (Kurdish)*)
-- Antara2Cinta (Se7enTime) (*Indonesian*)
-- Yassine Aït-El-Mouden (yaitelmouden) (*Standard Moroccan Tamazight*)
 - VSx86 (*Russian*)
 - umelard (*Hebrew*)
+- Antara2Cinta (Se7enTime) (*Indonesian*)
+- Lucas_NL (*Dutch*)
+- Yassine Aït-El-Mouden (yaitelmouden) (*Standard Moroccan Tamazight*)
+- Mathieu Marquer (slasherfun) (*French*)
+- Haerul Fuad (Dokuwiki) (*Indonesian*)
 - parnikkapore (*Thai*)
-- Lagash (lagash) (*Esperanto*)
+- Michelle M (MichelleMMM) (*Dutch*)
+- malbona (*Esperanto*)
 - Sherwan Othman (sherwanothman11) (*Sorani (Kurdish)*)
-- SKELET (*Danish*)
-- Exbu (*Dutch*)
+- Lagash (lagash) (*Esperanto*)
 - Chine Sebastien (chine.sebastien) (*French*)
-- Fei Yang (Fei1Yang) (*Chinese Traditional*)
+- bgme (*Chinese Simplified*)
+- Rafael V. (Rafaeeel) (*Portuguese, Brazilian*)
+- SKELET (*Danish*)
 - A A (sebastien.chine) (*French*)
+- Project Z (projectz.1338) (*German*)
+- Fei Yang (Fei1Yang) (*Chinese Traditional*)
 - Ğani (freegnu) (*Tatar*)
-- enipra (*Armenian*)
-- Renato "Lond" Cerqueira (renatolond) (*Portuguese, Brazilian*)
 - musix (*Persian*)
-- ギャラ (gyara) (*Japanese, Chinese Simplified*)
+- REMOVED_USER (*German*)
 - ALEM FARID (faridatcemlulaqbayli) (*Kabyle*)
+- Jean-Pierre MÉRESSE (Jipem) (*French*)
+- enipra (*Armenian*)
+- Serhiy Dmytryshyn (dies) (*Ukrainian*)
+- Eric Brulatout (ebrulato) (*Esperanto*)
 - Hougo (hougo) (*French*)
-- Mordi Sacks (MordiSacks) (*Hebrew*)
-- Trinsec (*Dutch*)
-- Adrián Lattes (haztecaso) (*Spanish*)
+- Sonstwer (*German*)
+- Pedro Fernandes (djprmf) (*Portuguese*)
+- REMOVED_USER (*Norwegian*)
 - Tigran's Tips (tigrank08) (*Armenian*)
 - 亜緯丹穂 (ayiniho) (*Japanese*)
+- maisui (*Chinese Simplified*)
+- Trinsec (*Dutch*)
+- Adrián Lattes (haztecaso) (*Spanish*)
+- webkinzfrog (*Polish*)
 - ybardapurkar (*Marathi*)
+- Mordi Sacks (MordiSacks) (*Hebrew*)
+- Manuel Tassi (Mannivu) (*Italian*)
 - Szabolcs Gál (galszabolcs810624) (*Hungarian*)
-- Vladislav Săcrieriu (vladislavs14) (*Romanian*)
+- rikrise (*Swedish*)
+- when_hurts (*German*)
+- Wojciech Bigosinski (wbigos2) (*Polish*)
+- Vladislav S (vladislavs) (*Romanian*)
+- mikslatvis (*Latvian*)
+- MartinAlstad (*Norwegian*)
 - TracyJacks (*Chinese Simplified*)
 - rasheedgm (*Kannada*)
-- danreznik (*Hebrew*)
 - Cirelli (cirelli94) (*Italian*)
+- danreznik (*Hebrew*)
+- iraline (*Portuguese, Brazilian*)
+- Seán Mór (seanmor3) (*Irish*)
+- vianaweb (*Portuguese, Brazilian*)
 - Siddharastro Doraku (sidharastro) (*Spanish, Mexico*)
-- lexxai (*Ukrainian*)
+- REMOVED_USER (*Spanish*)
 - omquylzu (*Latvian*)
+- Arthegor (*French*)
 - Navjot Singh (nspeaks) (*Hindi*)
 - mkljczk (*Polish*)
 - Belkacem Mohammed (belkacem77) (*Kabyle*)
+- Showfom (*Chinese Simplified*)
+- xemyst (*Catalan*)
+- lexxai (*Ukrainian*)
 - c6ristian (*German*)
-- damascene (*Arabic*)
+- svetlozaurus (*Bulgarian*)
 - Ozai (*German*)
+- damascene (*Arabic*)
+- Jan Ainali (Ainali) (*Swedish*)
 - Sahak Petrosyan (petrosyan) (*Armenian*)
+- Metehan Özyürek (MetehanOzyurek) (*Turkish*)
+- Сау Рэмсон (sawrams) (*Russian*)
+- metehan-arslan (*Turkish*)
 - Viorel-Cătălin Răpițeanu (rapiteanu) (*Romanian*)
-- Siddhartha Sarathi Basu (quinoa_biryani) (*Bengali*)
+- Sébastien SERRE (sebastienserre) (*French*)
+- Eugen Caruntu (eugencaruntu) (*Romanian*)
+- Kevin Scannell (kscanne) (*Irish*)
 - Pachara Chantawong (pachara2202) (*Thai*)
-- Skew (noan.perrot) (*French*)
+- bensch.dev (*German*)
+- LIZH (*French*)
+- Siddhartha Sarathi Basu (quinoa_biryani) (*Bengali*)
+- Overflow Cat (OverflowCat) (*Chinese Traditional, Chinese Simplified*)
+- Stephan Voeth (svoeth) (*German*)
 - Zijian Zhao (jobs2512821228) (*Chinese Simplified*)
-- Overflow Cat (OverflowCat) (*Chinese Simplified, Chinese Traditional*)
+- bugboy-20 (*Esperanto, Italian*)
+- SouthFox (*Chinese Simplified*)
+- Noan (SkewRam) (*French*)
 - dbeaver (*German*)
-- zordsdavini (*Lithuanian*)
-- Guru Prasath Anandapadmanaban (guruprasath) (*Tamil*)
 - turtle836 (*German*)
+- Guru Prasath Anandapadmanaban (guruprasath) (*Tamil*)
+- zordsdavini (*Lithuanian*)
+- Susanna Ånäs (susanna.anas) (*Finnish*)
+- Alessandro (alephoto85) (*Italian*)
 - Marcepanek_ (thekingmarcepan) (*Polish*)
-- Feruz Oripov (FeruzOripov) (*Russian*)
+- Choi Younsoo (usagicore) (*Korean*)
 - Yann Aguettaz (yann-a) (*French*)
+- zylosophe (*French*)
+- Celso Fernandes (Celsof) (*Portuguese, Brazilian*)
+- Feruz Oripov (FeruzOripov) (*Russian*)
+- REMOVED_USER (*French*)
+- Bui Huy Quang (bhuyquang1) (*Vietnamese*)
+- bogomilshopov (*Bulgarian*)
+- REMOVED_USER (*Burmese*)
+- Kaede (kaedech) (*Japanese*)
 - Mick Onio (xgc.redes) (*Asturian*)
 - Malik Mann (dermalikmann) (*German*)
 - padulafacundo (*Spanish*)
+- r3dsp1 (*Chinese Traditional, Hong Kong*)
 - dadosch (*German*)
-- hg6 (*Hindi*)
 - Tianqi Zhang (tina.zhang040609) (*Chinese Simplified*)
-- r3dsp1 (*Chinese Traditional, Hong Kong*)
-- johannes hove-henriksen (J0hsHH) (*Norwegian*)
+- HybridGlucose (*Chinese Traditional*)
+- vmichalak (*French*)
+- hg6 (*Hindi*)
+- marivisales (*Portuguese, Brazilian*)
 - Orlando Murcio (Atos20) (*Spanish, Mexico*)
-- cenegd (*Chinese Simplified*)
+- maa123 (*Japanese*)
+- Julian Doser (julian21) (*English, United Kingdom, German*)
+- johannes hove-henriksen (J0hsHH) (*Norwegian*)
+- Alexander Ivanov (Saiv46) (*Russian*)
+- unstable.icu (*Chinese Simplified*)
+- Padraic Calpin (padraic-padraic) (*Slovenian*)
 - Youngeon Lee (YoungeonLee) (*Korean*)
+- LeJun (le-jun) (*French*)
 - shdy (*German*)
-- Umi (mtrumi) (*Chinese Simplified, Chinese Traditional, Hong Kong*)
-- Padraic Calpin (padraic-padraic) (*Slovenian*)
-- Ильзира Рахматуллина (rahmatullinailzira53) (*Tatar*)
+- REMOVED_USER (*French*)
+- Yonjae Lee (yonjlee) (*Korean*)
+- cenegd (*Chinese Simplified*)
 - piupiupiudiu (*Chinese Simplified*)
-- Pixelcode (realpixelcode) (*German*)
-- Dennis Reimund (reimunddennis7) (*German*)
+- Umi (mtrumi) (*Chinese Traditional, Hong Kong, Chinese Simplified*)
 - Yogesh K S (yogi) (*Kannada*)
+- Ulong32 (*Japanese*)
 - Adithya K (adithyak04) (*Malayalam*)
 - DAI JIE (daijie) (*Chinese Simplified*)
+- Mihael Budeč (milli.pretili) (*Croatian*)
 - Hugh Liu (youloveonlymeh) (*Chinese Simplified*)
-- Rakino (rakino) (*Chinese Simplified*)
 - ZQYD (*Chinese Simplified*)
 - X.M (kimonoki) (*Chinese Simplified*)
-- boni777 (*Chinese Simplified*)
+- Rakino (rakino) (*Chinese Simplified*)
+- paziy Georgi (paziygeorgi4) (*Dutch*)
+- Komeil Parseh (mmdbalkhi) (*Persian*)
 - Jothipazhani Nagarajan (jothipazhani.n) (*Tamil*)
-- Miquel Sabaté Solà (mssola) (*Catalan*)
+- tikky9 (*Portuguese, Brazilian*)
+- horsm (*Finnish*)
+- BenJule (*German*)
 - Stanisław Jelnicki (JelNiSlaw) (*Polish*)
+- Yananas (wangyanyan.hy) (*Chinese Simplified*)
+- Vivamus (elaaksu) (*Turkish*)
+- ihealyou (*Italian*)
 - AmazighNM (*Kabyle*)
+- Miquel Sabaté Solà (mssola) (*Catalan*)
+- residuum (*German*)
+- nua_kr (*Korean*)
+- Andrea Mazzilli (andreamazzilli) (*Italian*)
+- Paula SIMON (EncoreEutIlFalluQueJeLeSusse) (*French*)
+- hallomaurits (*Dutch*)
+- Erfan Kheyrollahi Qaroğlu (ekm507) (*Persian*)
+- REMOVED_USER (*Galician, Spanish*)
 - alnd hezh (alndhezh) (*Sorani (Kurdish)*)
-- CloudSet (*Chinese Simplified*)
 - Clash Clans (KURD12345) (*Sorani (Kurdish)*)
-- Metehan Özyürek (MetehanOzyurek) (*Turkish*)
-- Paula SIMON (EncoreEutIlFalluQueJeLeSusse) (*French*)
+- ruok (*Chinese Simplified*)
+- Frederik-FJ (*German*)
+- CloudSet (*Chinese Simplified*)
 - Solid Rhino (SolidRhino) (*Dutch*)
-- nua_kr (*Korean*)
-- hallomaurits (*Dutch*)
-- 林水溶 (shuiRong) (*Chinese Simplified*)
-- rikrise (*Swedish*)
-- Takeshi Umeda (noellabo) (*Japanese*)
+- hussama (*Portuguese, Brazilian*)
+- jazzynico (*French*)
 - k_taka (peaceroad) (*Japanese*)
+- 林水溶 (shuiRong) (*Chinese Simplified*)
+- Peter Lutz (theellutzo) (*German*)
 - Sébastien Feugère (smonff) (*French*)
+- AnalGoddess770 (*Hebrew*)
+- Sven Goller (svengoller) (*German*)
+- Ahmet (ahmetlii) (*Turkish*)
+- hosted22 (*German*)
 - Hallo Abdullah (hallo_hamza12) (*Sorani (Kurdish)*)
-- hussama (*Portuguese, Brazilian*)
-- EzigboOmenana (*Cornish*)
+- Karam Hamada (TheKaram) (*Arabic*)
+- Takeshi Umeda (noellabo) (*Japanese*)
+- SnDer (*Dutch*)
 - Robert Yano (throwcalmbobaway) (*Spanish, Mexico*)
-- Yasin İsa YILDIRIM (redsfyre) (*Turkish*)
-- PifyZ (*French*)
-- Tagada (Tagadda) (*French*)
-- eichkat3r (*German*)
-- Ashok314 (ashok314) (*Hindi*)
+- Gustav Lindqvist (Reedyn) (*Swedish*)
+- Dagur Ammendrup (dagurp) (*Icelandic*)
+- shafouz (*Portuguese, Brazilian*)
+- Miguel Branco (mglbranco) (*Galician*)
+- Sergey Panteleev (saundefined) (*Russian*)
+- Tom_ (*Czech*)
 - Zlr- (cZeler) (*French*)
-- SnDer (*Dutch*)
-- OminousCry (*Russian*)
+- Ashok314 (ashok314) (*Hindi*)
+- PifyZ (*French*)
+- Zeyi Fan (fanzeyi) (*Chinese Simplified*)
+- OminousCry (*Russian, Ukrainian*)
 - Adam Sapiński (Adamos9898) (*Polish*)
-- Tom_ (*Czech*)
-- shafouz (*Portuguese, Brazilian*)
-- Shrinivasan T (tshrinivasan) (*Tamil*)
-- Kk (kishorkumara3) (*Kannada*)
-- Swati Sani (swatisani) (*Urdu (Pakistan)*)
-- papayaisnotafood (*Chinese Traditional*)
-- さっかりんにーさん (saccharin23) (*Japanese*)
+- eichkat3r (*German*)
+- Yasin İsa YILDIRIM (redsfyre) (*Turkish*)
+- Tagada (Tagadda) (*French*)
+- gasrios (*Portuguese, Brazilian*)
+- 夜楓Yoka (Yoka2627) (*Chinese Simplified*)
+- AniCommieDDR (*Russian*)
+- Nathaël Noguès (NatNgs) (*French*)
 - Daniel M. (daniconil) (*Catalan*)
 - César Daniel Cavanzo Quintero (LeinadCQ) (*Esperanto*)
-- Nathaël Noguès (NatNgs) (*French*)
-- 夜楓Yoka (Yoka2627) (*Chinese Simplified*)
+- Noam Tamim (noamtm) (*Hebrew*)
+- papayaisnotafood (*Chinese Traditional*)
+- さっかりんにーさん (saccharin23) (*Japanese*)
+- Marcin Wolski (martinwolski) (*Polish*)
+- REMOVED_USER (*Chinese Simplified*)
+- Kk (kishorkumara3) (*Kannada*)
+- Shrinivasan T (tshrinivasan) (*Tamil*)
+- REMOVED_USER (Urdu (Pakistan))
+- Kakarico Bra (kakarico20) (*Portuguese, Brazilian*)
+- Swati Sani (swatisani) (*Urdu (Pakistan)*)
+- 快乐的老鼠宝宝 (LaoShuBaby) (*Chinese Simplified, Chinese Traditional*)
 - Mt Front (mtfront) (*Chinese Simplified*)
-- Artem (Artem4ik) (*Russian*)
-- Robin van der Vliet (RobinvanderVliet) (*Esperanto*)
-- Tradjincal (tradjincal) (*French*)
 - SusVersiva (*Catalan*)
+- REMOVED_USER (*Portuguese, Brazilian*)
+- Avinash Mg (hatman290) (*Malayalam*)
+- kruijs (*Dutch*)
+- Artem (Artem4ik) (*Russian*)
 - Zinkokooo (*Basque*)
-- Marvin (magicmarvman) (*German*)
+- 劉昌賢 (twcctz500) (*Chinese Traditional*)
 - Vikatakavi (*Kannada*)
+- Tradjincal (tradjincal) (*French*)
+- Robin van der Vliet (RobinvanderVliet) (*Esperanto*)
+- Marvin (magicmarvman) (*German*)
 - pullopen (*Chinese Simplified*)
-- sergioaraujo1 (*Portuguese, Brazilian*)
-- prabhjot (*Hindi*)
-- CyberAmoeba (pseudoobscura) (*Chinese Simplified*)
-- mmokhi (*Persian*)
+- Tealk (*German*)
+- tibequadorian (*German*)
+- Henk Bulder (henkbulder) (*Dutch*)
+- Edison Lee (edisonlee55) (*Chinese Traditional*)
+- mpdude (*German*)
+- Rijk van Geijtenbeek (rvangeijtenbeek) (*Dutch*)
 - Entelekheia-ousia (*Chinese Simplified*)
+- REMOVED_USER (*Spanish*)
+- sergioaraujo1 (*Portuguese, Brazilian*)
 - Livingston Samuel (livingston) (*Tamil*)
+- mmokhi (*Persian*)
 - tsundoker (*Malayalam*)
-- skaaarrr (*German*)
-- Pierre Morvan (Iriep) (*Breton*)
+- CyberAmoeba (pseudoobscura) (*Chinese Simplified*)
+- prabhjot (*Hindi*)
+- Ikka Putri (ikka240290) (*Indonesian, Danish, English, United Kingdom*)
 - Paz Galindo (paz.almendra.g) (*Spanish*)
-- fedot (*Russian*)
-- mkljczk (mykylyjczyk) (*Polish*)
 - Ricardo Colin (rysard) (*Spanish*)
-- Philipp Fischbeck (PFischbeck) (*German*)
+- Pierre Morvan (Iriep) (*Breton*)
 - oscfd (*Spanish*)
+- Thies Mueller (thies00) (*German*)
+- Lyra (teromene) (*French*)
+- Kedr (lava20121991) (*Esperanto*)
+- mkljczk (mykylyjczyk) (*Polish*)
+- fedot (*Russian*)
+- Philipp Fischbeck (PFischbeck) (*German*)
+- Hasan Berkay Çağır (berkaycagir) (*Turkish*)
+- Silvestri Nicola (nick99silver) (*Italian*)
+- skaaarrr (*German*)
+- Mo Rijndael (mo_rijndael) (*Russian*)
+- tsesunnaallun (orezraey) (*Portuguese, Brazilian*)
+- Lukas Fülling (lfuelling) (*German*)
+- Algo (algovigura) (*Indonesian*)
+- REMOVED_USER (*Spanish*)
+- setthemfree (*Ukrainian*)
+- i fly (ifly3years) (*Chinese Simplified*)
+- ralozkolya (*Georgian*)
 - Zoé Bőle (zoe1337) (*German*)
+- Ville Rantanen (vrntnn) (*Finnish*)
 - GaggiX (*Italian*)
 - JackXu (Merman-Jack) (*Chinese Simplified*)
-- Lukas Fülling (lfuelling) (*German*)
-- ralozkolya (*Georgian*)
-- Jason Gibson (barberpike606) (*Slovenian, Chinese Simplified*)
-- Dremski (*Bulgarian*)
-- Kaede (kaedech) (*Japanese*)
-- Aymeric (AymBroussier) (*French*)
-- mashirozx (*Chinese Simplified*)
-- María José Vera (mjverap) (*Spanish*)
-- asala4544 (*Basque*)
-- ronee (*Kurmanji (Kurdish)*)
-- qwerty287 (*German*)
-- pezcurrel (*Italian*)
-- Anoop (anoopp) (*Malayalam*)
+- ceonia (*Chinese Traditional, Hong Kong*)
+- Emirhan Yavuz (takomlii) (*Turkish*)
+- teezeh (*German*)
+- MevLyshkin (Leinnan) (*Polish*)
 - Apple (blackteaovo) (*Chinese Simplified*)
-- Lilian Nabati (Lilounab49) (*French*)
-- ru_mactunnag (*Scottish Gaelic*)
-- Nocta (*French*)
+- qwerty287 (*German*)
 - Tangcuyu (*Chinese Simplified*)
+- Nocta (*French*)
+- ru_mactunnag (*Scottish Gaelic*)
+- Lilian Nabati (Lilounab49) (*French*)
+- lokalisoija (*Finnish*)
 - Dennis Reimund (reimund_dennis) (*German*)
-- Albatroz Jeremias (albjeremias) (*Portuguese*)
-- Xurxo Guerra (xguerrap) (*Galician*)
+- ronee (*Kurmanji (Kurdish)*)
+- EricVogt_ (*Spanish*)
+- yu miao (metaxx.dev) (*Chinese Simplified*)
+- Anoop (anoopp) (*Malayalam*)
 - Samir Tighzert (samir_t7) (*Kabyle*)
-- lokalisoija (*Finnish*)
+- sn02 (*German*)
+- Yui Karasuma (yui87) (*Japanese*)
+- asala4544 (*Basque*)
+- Thibaut Rousseau (thiht44) (*French*)
+- Jason Gibson (barberpike606) (*Slovenian, Chinese Simplified*)
+- Sugar NO (g1024116707) (*Chinese Simplified*)
+- Aymeric (AymBroussier) (*French*)
+- pezcurrel (*Italian*)
+- Xurxo Guerra (xguerrap) (*Galician*)
+- nicosomb (*French*)
+- Albatroz Jeremias (albjeremias) (*Portuguese*)
+- María José Vera (mjverap) (*Spanish*)
+- mashirozx (*Chinese Simplified*)
 - codl (*French*)
-- thisdudeisvegan (braydofficial) (*German*)
-- tamaina (*Japanese*)
+- Doug (douglasalvespe) (*Portuguese, Brazilian*)
 - Matias Lavik (matiaslavik) (*Norwegian Nynorsk*)
-- Aman Alam (aalam) (*Punjabi*)
+- random_person (*Spanish*)
+- whoeta (wh0eta) (*Russian*)
+- xpac1985 (xpac) (*German*)
+- thisdudeisvegan (braydofficial) (*German*)
+- Fleva (*Sardinian*)
+- Anonymous (Anonymous666) (*Russian*)
+- Mohammad Adnan Mahmood (adnanmig) (*Arabic*)
+- ÀŘǾŚ PÀŚĦÀÍ (arospashai) (*Sorani (Kurdish)*)
+- mikel (mikelalas) (*Spanish*)
+- Trond Boksasp (boksasp) (*Norwegian*)
+- asretro (*Chinese Traditional, Hong Kong*)
 - Holger Huo (holgerhuo) (*Chinese Simplified*)
-- Amith Raj Shetty (amithraj1989) (*Kannada*)
+- Aman Alam (aalam) (*Punjabi*)
+- smedvedev (*Russian*)
+- Jay Lonnquist (crowkeep) (*Japanese*)
 - mimikun (*Japanese*)
+- Mohd Bilal (mdb571) (*Malayalam*)
+- veer66 (*Thai*)
+- OpenAlgeria (*Arabic*)
+- Rave (nayumi-464812844) (*Vietnamese*)
+- ReavedNetwork (*German*)
+- Michael (Discostu36) (*German*)
+- tamaina (*Japanese*)
+- sk22 (*German*)
 - Ragnars Eggerts (rmegg1933) (*Latvian*)
-- ÀŘǾŚ PÀŚĦÀÍ (arospashai) (*Sorani (Kurdish)*)
-- smedvedev (*Russian*)
 - Sais Lakshmanan (Saislakshmanan) (*Tamil*)
-- Mohammad Adnan Mahmood (adnanmig) (*Arabic*)
-- OpenAlgeria (*Arabic*)
-- Trond Boksasp (boksasp) (*Norwegian*)
-- Doug (douglasalvespe) (*Portuguese, Brazilian*)
-- Mohd Bilal (mdb571) (*Malayalam*)
-- Fleva (*Sardinian*)
-- xpac1985 (xpac) (*German*)
-- mikel (mikelalas) (*Spanish*)
-- random_person (*Spanish*)
-- asretro (*Chinese Traditional, Hong Kong*)
-- Arĝentakato (argxentakato) (*Japanese*)
-- Nithya Mary (nithyamary25) (*Tamil*)
-- Azad ahmad (dashty) (*Sorani (Kurdish)*)
+- Amith Raj Shetty (amithraj1989) (*Kannada*)
 - Bartek Fijałkowski (brateq) (*Polish*)
+- Asbeltrion (*Spanish*)
+- Michael Horstmann (mhrstmnn) (*German*)
+- Joffrey Abeilard (Abeilard14) (*French*)
+- capiscuas (*Spanish*)
+- djoerd (*Dutch*)
+- REMOVED_USER (*Spanish*)
+- NeverMine17 (*Russian*)
+- songxianj (songxian_jiang) (*Chinese Simplified*)
+- Ács Zoltán (zoli111) (*Hungarian*)
+- haaninjo (*Swedish*)
+- REMOVED_USER (*Esperanto*)
+- Philip Molares (DerMolly) (*German*)
+- ChalkPE (amato0617) (*Korean*)
 - ebrezhoneg (*Breton*)
+- 디떱 (diddub) (*Korean*)
+- Hans (hansj) (*German*)
+- Nithya Mary (nithyamary25) (*Tamil*)
+- kavitha129 (*Tamil*)
+- waweic (*German*)
+- Aries (orlea) (*Japanese*)
+- おさ (osapon) (*Japanese*)
+- Abijeet Patro (Abijeet) (*Basque*)
+- centumix (*Japanese*)
+- Martin Müller (muellermartin) (*German*)
+- tateisu (*Japanese*)
+- Arĝentakato (argxentakato) (*Japanese*)
+- Benjamin Cobb (benjamincobb) (*German*)
+- deanerschnitzel (*German*)
+- Jill H. (kokakiwi) (*French*)
 - maksutheam (*Finnish*)
+- d0p1 (d0p1s4m4) (*French*)
 - majorblazr (*Danish*)
-- Jill H. (kokakiwi) (*French*)
 - Patrice Boivin (patriceboivin58) (*French*)
-- centumix (*Japanese*)
 - 江尚寒 (jiangshanghan) (*Chinese Simplified*)
-- hud5634j (*Spanish*)
-- おさ (osapon) (*Japanese*)
-- Jiniux (*Italian*)
-- Hannah (Aniqueper1) (*Chinese Simplified*)
-- Ni Futchi (futchitwo) (*Japanese*)
-- dobrado (*Portuguese, Brazilian*)
-- dcapillae (*Spanish*)
+- HSD Channel (kvdbve34) (*Russian*)
+- alwyn joe (iomedivh200) (*Chinese Simplified*)
+- ZHY (sheepzh) (*Chinese Simplified*)
+- Bei Li (libei) (*Chinese Simplified*)
+- Aluo (Aluo_rbszd) (*Chinese Simplified*)
+- clarkzjw (*Chinese Simplified*)
+- Noah Luppe (noahlup) (*German*)
+- araghunde (*Galician*)
+- BratishkaErik (*Russian*)
+- Bunny9568 (*Chinese Simplified*)
+- SamOak (*Portuguese, Brazilian*)
 - Ranj A Abdulqadir (RanjAhmed) (*Sorani (Kurdish)*)
-- Kurdish Translator (Kurdish.boy) (*Sorani (Kurdish)*)
 - Amir Kurdo (kuraking202) (*Sorani (Kurdish)*)
-- umonaca (*Chinese Simplified*)
-- Jari Ronkainen (ronchaine) (*Finnish*)
-- djoerd (*Dutch*)
-- Savarín Electrográfico Marmota Intergalactica (herrero.maty) (*Spanish*)
 - 于晚霞 (xissshawww) (*Chinese Simplified*)
-- tateisu (*Japanese*)
-- NeverMine17 (*Russian*)
+- Fyuoxyjidyho Moiodyyiodyhi (fyuodchiodmoiidiiduh86) (*Chinese Simplified*)
+- RPD0911 (*Hungarian*)
+- dcapillae (*Spanish*)
+- dobrado (*Portuguese, Brazilian*)
+- Hannah (Aniqueper1) (*Chinese Simplified*)
+- Azad ahmad (dashty) (*Sorani (Kurdish)*)
+- Uri Chachick (urich.404) (*Hebrew*)
+- Bnoru (*Portuguese, Brazilian*)
+- Jiniux (*Italian*)
+- REMOVED_USER (*German*)
+- Salh_haji6 (Sorani (Kurdish))
+- Kurdish Translator (*Kurdish.boy) (Sorani (Kurdish)*)
+- Beagle (beagleworks) (*Japanese*)
+- hud5634j (*Spanish*)
+- Kisaragi Hiu (flyingfeather1501) (*Chinese Traditional*)
+- Dominik Ziegler (dodomedia) (*German*)
 - soheilkhanalipur (*Persian*)
-- SamOak (*Portuguese, Brazilian*)
-- kavitha129 (*Tamil*)
-- Salh_haji6 (*Sorani (Kurdish)*)
 - Brodi (brodi1) (*Dutch*)
-- capiscuas (*Spanish*)
-- HSD Channel (kvdbve34) (*Russian*)
-- Abijeet Patro (Abijeet) (*Basque*)
-- Ács Zoltán (zoli111) (*Hungarian*)
-- Benjamin Cobb (benjamincobb) (*German*)
-- waweic (*German*)
-- Aries (orlea) (*Japanese*)
+- Savarín Electrográfico Marmota Intergalactica (herrero.maty) (*Spanish*)
+- Ni Futchi (futchitwo) (*Japanese*)
+- Zois Lee (gcnwm) (*Chinese Simplified*)
+- Arnold Marko (atomicmind) (*Slovenian*)
+- scholzco (*German*)
+- Jari Ronkainen (ronchaine) (*Finnish*)
+- umonaca (*Chinese Simplified*)

+ 2 - 2
Aptfile

@@ -1,8 +1,8 @@
 ffmpeg
 libicu[0-9][0-9]
 libicu-dev
-libidn11
-libidn11-dev
+libidn12
+libidn-dev
 libpq-dev
 libxdamage1
 libxfixes3

File diff suppressed because it is too large
+ 61 - 0
CHANGELOG.md


+ 2 - 0
CONTRIBUTING.md

@@ -42,6 +42,8 @@ It is not always possible to phrase every change in such a manner, but it is des
 - Code style rules (rubocop, eslint)
 - Normalization of locale files (i18n-tasks)
 
+**Note**: You may need to log in and authorise the GitHub account your fork of this repository belongs to with CircleCI to enable some of the automated checks to run.
+
 ## Documentation
 
 The [Mastodon documentation](https://docs.joinmastodon.org) is a statically generated site. You can [submit merge requests to mastodon/documentation](https://github.com/mastodon/documentation).

+ 3 - 3
Dockerfile

@@ -5,7 +5,7 @@ SHELL ["/bin/bash", "-c"]
 RUN echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections
 
 # Install Node v16 (LTS)
-ENV NODE_VER="16.14.2"
+ENV NODE_VER="16.17.1"
 RUN ARCH= && \
     dpkgArch="$(dpkg --print-architecture)" && \
   case "${dpkgArch##*-}" in \
@@ -19,7 +19,7 @@ RUN ARCH= && \
   esac && \
     echo "Etc/UTC" > /etc/localtime && \
 	apt-get update && \
-	apt-get install -y --no-install-recommends ca-certificates wget python apt-utils && \
+	apt-get install -y --no-install-recommends ca-certificates wget python3 apt-utils && \
 	cd ~ && \
 	wget -q https://nodejs.org/download/release/v$NODE_VER/node-v$NODE_VER-linux-$ARCH.tar.gz && \
 	tar xf node-v$NODE_VER-linux-$ARCH.tar.gz && \
@@ -27,7 +27,7 @@ RUN ARCH= && \
 	mv node-v$NODE_VER-linux-$ARCH /opt/node
 
 # Install Ruby 3.0
-ENV RUBY_VER="3.0.3"
+ENV RUBY_VER="3.0.4"
 RUN apt-get update && \
   apt-get install -y --no-install-recommends build-essential \
     bison libyaml-dev libgdbm-dev libreadline-dev libjemalloc-dev \

+ 26 - 24
Gemfile

@@ -7,16 +7,16 @@ gem 'pkg-config', '~> 1.4'
 gem 'rexml', '~> 3.2'
 
 gem 'puma', '~> 5.6'
-gem 'rails', '~> 6.1.6'
+gem 'rails', '~> 6.1.7'
 gem 'sprockets', '~> 3.7.2'
 gem 'thor', '~> 1.2'
-gem 'rack', '~> 2.2.3'
+gem 'rack', '~> 2.2.4'
 
 gem 'hamlit-rails', '~> 0.2'
-gem 'pg', '~> 1.3'
+gem 'pg', '~> 1.4'
 gem 'makara', '~> 0.5'
 gem 'pghero', '~> 2.8'
-gem 'dotenv-rails', '~> 2.7'
+gem 'dotenv-rails', '~> 2.8'
 
 gem 'aws-sdk-s3', '~> 1.114', require: false
 gem 'fog-core', '<= 2.1.0'
@@ -26,7 +26,7 @@ gem 'blurhash', '~> 0.1'
 
 gem 'active_model_serializers', '~> 0.10'
 gem 'addressable', '~> 2.8'
-gem 'bootsnap', '~> 1.11.1', require: false
+gem 'bootsnap', '~> 1.13.0', require: false
 gem 'browser'
 gem 'charlock_holmes', '~> 0.7.7'
 gem 'chewy', '~> 7.2'
@@ -40,22 +40,22 @@ end
 gem 'net-ldap', '~> 0.17'
 gem 'omniauth-cas', '~> 2.0'
 gem 'omniauth-saml', '~> 1.10'
-gem 'gitlab-omniauth-openid-connect', '~>0.9.1', require: 'omniauth_openid_connect'
+gem 'gitlab-omniauth-openid-connect', '~>0.10.0', require: 'omniauth_openid_connect'
 gem 'omniauth', '~> 1.9'
 gem 'omniauth-rails_csrf_protection', '~> 0.1'
 
 gem 'color_diff', '~> 0.1'
 gem 'discard', '~> 1.2'
-gem 'doorkeeper', '~> 5.5'
+gem 'doorkeeper', '~> 5.6'
 gem 'ed25519', '~> 1.3'
 gem 'fast_blank', '~> 1.0'
 gem 'fastimage'
 gem 'hiredis', '~> 0.6'
-gem 'redis-namespace', '~> 1.8'
+gem 'redis-namespace', '~> 1.9'
 gem 'htmlentities', '~> 4.3'
-gem 'http', '~> 5.0'
+gem 'http', '~> 5.1'
 gem 'http_accept_language', '~> 2.1'
-gem 'httplog', '~> 1.5.0'
+gem 'httplog', '~> 1.6.0'
 gem 'idn-ruby', require: 'idn'
 gem 'kaminari', '~> 1.2'
 gem 'link_header', '~> 0.0'
@@ -72,17 +72,18 @@ gem 'rack-attack', '~> 6.6'
 gem 'rack-cors', '~> 1.1', require: 'rack/cors'
 gem 'rails-i18n', '~> 6.0'
 gem 'rails-settings-cached', '~> 0.6'
+gem 'redcarpet', '~> 3.5'
 gem 'redis', '~> 4.5', require: ['redis', 'redis/connection/hiredis']
 gem 'mario-redis-lock', '~> 1.2', require: 'redis_lock'
 gem 'rqrcode', '~> 2.1'
 gem 'ruby-progressbar', '~> 1.11'
 gem 'sanitize', '~> 6.0'
 gem 'scenic', '~> 1.6'
-gem 'sidekiq', '~> 6.4'
+gem 'sidekiq', '~> 6.5'
 gem 'sidekiq-scheduler', '~> 4.0'
 gem 'sidekiq-unique-jobs', '~> 7.1'
 gem 'sidekiq-bulk', '~> 0.2.0'
-gem 'simple-navigation', '~> 4.3'
+gem 'simple-navigation', '~> 4.4'
 gem 'simple_form', '~> 5.1'
 gem 'sprockets-rails', '~> 3.4', require: 'sprockets/railtie'
 gem 'stoplight', '~> 3.0.0'
@@ -91,18 +92,18 @@ gem 'tty-prompt', '~> 0.23', require: false
 gem 'twitter-text', '~> 3.1.0'
 gem 'tzinfo-data', '~> 1.2022'
 gem 'webpacker', '~> 5.4'
-gem 'webpush', '~> 0.3'
-gem 'webauthn', '~> 3.0.0.alpha1'
+gem 'webpush', github: 'ClearlyClaire/webpush', ref: 'f14a4d52e201128b1b00245d11b6de80d6cfdcd9'
+gem 'webauthn', '~> 2.5'
 
 gem 'json-ld'
 gem 'json-ld-preloaded', '~> 3.2'
 gem 'rdf-normalize', '~> 0.5'
 
 group :development, :test do
-  gem 'fabrication', '~> 2.28'
+  gem 'fabrication', '~> 2.30'
   gem 'fuubar', '~> 2.5'
   gem 'i18n-tasks', '~> 1.0', require: false
-  gem 'pry-byebug', '~> 3.9'
+  gem 'pry-byebug', '~> 3.10'
   gem 'pry-rails', '~> 0.3'
   gem 'rspec-rails', '~> 5.1'
 end
@@ -114,13 +115,14 @@ end
 group :test do
   gem 'capybara', '~> 3.37'
   gem 'climate_control', '~> 0.2'
-  gem 'faker', '~> 2.21'
-  gem 'microformats', '~> 4.2'
+  gem 'faker', '~> 2.23'
+  gem 'microformats', '~> 4.4'
   gem 'rails-controller-testing', '~> 1.0'
   gem 'rspec-sidekiq', '~> 3.1'
   gem 'simplecov', '~> 0.21', require: false
-  gem 'webmock', '~> 3.14'
-  gem 'rspec_junit_formatter', '~> 0.5'
+  gem 'webmock', '~> 3.18'
+  gem 'rspec_junit_formatter', '~> 0.6'
+  gem 'rack-test', '~> 2.0'
 end
 
 group :development do
@@ -132,9 +134,9 @@ group :development do
   gem 'letter_opener', '~> 1.8'
   gem 'letter_opener_web', '~> 2.0'
   gem 'memory_profiler'
-  gem 'rubocop', '~> 1.29', require: false
-  gem 'rubocop-rails', '~> 2.14', require: false
-  gem 'brakeman', '~> 5.2', require: false
+  gem 'rubocop', '~> 1.30', require: false
+  gem 'rubocop-rails', '~> 2.15', require: false
+  gem 'brakeman', '~> 5.3', require: false
   gem 'bundler-audit', '~> 0.9', require: false
 
   gem 'capistrano', '~> 3.17'
@@ -151,5 +153,5 @@ end
 
 gem 'concurrent-ruby', require: false
 gem 'connection_pool', require: false
-
 gem 'xorcist', '~> 1.1'
+gem 'cocoon', '~> 1.2'

+ 188 - 179
Gemfile.lock

@@ -1,40 +1,49 @@
+GIT
+  remote: https://github.com/ClearlyClaire/webpush.git
+  revision: f14a4d52e201128b1b00245d11b6de80d6cfdcd9
+  ref: f14a4d52e201128b1b00245d11b6de80d6cfdcd9
+  specs:
+    webpush (0.3.8)
+      hkdf (~> 0.2)
+      jwt (~> 2.0)
+
 GEM
   remote: https://rubygems.org/
   specs:
-    actioncable (6.1.6)
-      actionpack (= 6.1.6)
-      activesupport (= 6.1.6)
+    actioncable (6.1.7)
+      actionpack (= 6.1.7)
+      activesupport (= 6.1.7)
       nio4r (~> 2.0)
       websocket-driver (>= 0.6.1)
-    actionmailbox (6.1.6)
-      actionpack (= 6.1.6)
-      activejob (= 6.1.6)
-      activerecord (= 6.1.6)
-      activestorage (= 6.1.6)
-      activesupport (= 6.1.6)
+    actionmailbox (6.1.7)
+      actionpack (= 6.1.7)
+      activejob (= 6.1.7)
+      activerecord (= 6.1.7)
+      activestorage (= 6.1.7)
+      activesupport (= 6.1.7)
       mail (>= 2.7.1)
-    actionmailer (6.1.6)
-      actionpack (= 6.1.6)
-      actionview (= 6.1.6)
-      activejob (= 6.1.6)
-      activesupport (= 6.1.6)
+    actionmailer (6.1.7)
+      actionpack (= 6.1.7)
+      actionview (= 6.1.7)
+      activejob (= 6.1.7)
+      activesupport (= 6.1.7)
       mail (~> 2.5, >= 2.5.4)
       rails-dom-testing (~> 2.0)
-    actionpack (6.1.6)
-      actionview (= 6.1.6)
-      activesupport (= 6.1.6)
+    actionpack (6.1.7)
+      actionview (= 6.1.7)
+      activesupport (= 6.1.7)
       rack (~> 2.0, >= 2.0.9)
       rack-test (>= 0.6.3)
       rails-dom-testing (~> 2.0)
       rails-html-sanitizer (~> 1.0, >= 1.2.0)
-    actiontext (6.1.6)
-      actionpack (= 6.1.6)
-      activerecord (= 6.1.6)
-      activestorage (= 6.1.6)
-      activesupport (= 6.1.6)
+    actiontext (6.1.7)
+      actionpack (= 6.1.7)
+      activerecord (= 6.1.7)
+      activestorage (= 6.1.7)
+      activesupport (= 6.1.7)
       nokogiri (>= 1.8.5)
-    actionview (6.1.6)
-      activesupport (= 6.1.6)
+    actionview (6.1.7)
+      activesupport (= 6.1.7)
       builder (~> 3.1)
       erubi (~> 1.4)
       rails-dom-testing (~> 2.0)
@@ -45,31 +54,31 @@ GEM
       case_transform (>= 0.2)
       jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
     active_record_query_trace (1.8)
-    activejob (6.1.6)
-      activesupport (= 6.1.6)
+    activejob (6.1.7)
+      activesupport (= 6.1.7)
       globalid (>= 0.3.6)
-    activemodel (6.1.6)
-      activesupport (= 6.1.6)
-    activerecord (6.1.6)
-      activemodel (= 6.1.6)
-      activesupport (= 6.1.6)
-    activestorage (6.1.6)
-      actionpack (= 6.1.6)
-      activejob (= 6.1.6)
-      activerecord (= 6.1.6)
-      activesupport (= 6.1.6)
+    activemodel (6.1.7)
+      activesupport (= 6.1.7)
+    activerecord (6.1.7)
+      activemodel (= 6.1.7)
+      activesupport (= 6.1.7)
+    activestorage (6.1.7)
+      actionpack (= 6.1.7)
+      activejob (= 6.1.7)
+      activerecord (= 6.1.7)
+      activesupport (= 6.1.7)
       marcel (~> 1.0)
       mini_mime (>= 1.1.0)
-    activesupport (6.1.6)
+    activesupport (6.1.7)
       concurrent-ruby (~> 1.0, >= 1.0.2)
       i18n (>= 1.6, < 2)
       minitest (>= 5.1)
       tzinfo (~> 2.0)
       zeitwerk (~> 2.3)
-    addressable (2.8.0)
-      public_suffix (>= 2.0.2, < 5.0)
+    addressable (2.8.1)
+      public_suffix (>= 2.0.2, < 6.0)
     aes_key_wrap (1.1.0)
-    airbrussh (1.4.0)
+    airbrussh (1.4.1)
       sshkit (>= 1.6.1, != 1.7.0)
     android_key_attestation (0.3.0)
     annotate (3.2.0)
@@ -79,7 +88,7 @@ GEM
     attr_encrypted (3.1.0)
       encryptor (~> 3.0.0)
     attr_required (1.0.1)
-    awrence (1.1.1)
+    awrence (1.2.1)
     aws-eventstream (1.2.0)
     aws-partitions (1.587.0)
     aws-sdk-core (3.130.2)
@@ -101,12 +110,11 @@ GEM
       coderay (>= 1.0.0)
       erubi (>= 1.0.0)
       rack (>= 0.9.0)
-    better_html (1.0.16)
-      actionview (>= 4.0)
-      activesupport (>= 4.0)
+    better_html (2.0.1)
+      actionview (>= 6.0)
+      activesupport (>= 6.0)
       ast (~> 2.0)
       erubi (~> 1.4)
-      html_tokenizer (~> 0.0.6)
       parser (>= 2.4)
       smart_properties
     bindata (2.4.10)
@@ -114,22 +122,22 @@ GEM
       debug_inspector (>= 0.0.1)
     blurhash (0.1.6)
       ffi (~> 1.14)
-    bootsnap (1.11.1)
+    bootsnap (1.13.0)
       msgpack (~> 1.2)
-    brakeman (5.2.3)
+    brakeman (5.3.1)
     browser (4.2.0)
     brpoplpush-redis_script (0.1.2)
       concurrent-ruby (~> 1.0, >= 1.0.5)
       redis (>= 1.0, <= 5.0)
     builder (3.2.4)
-    bullet (7.0.1)
+    bullet (7.0.3)
       activesupport (>= 3.0.0)
       uniform_notifier (~> 1.11)
-    bundler-audit (0.9.0.1)
+    bundler-audit (0.9.1)
       bundler (>= 1.2.0, < 3)
       thor (~> 1.0)
     byebug (11.1.3)
-    capistrano (3.17.0)
+    capistrano (3.17.1)
       airbrussh (>= 1.0.0)
       i18n
       rake (>= 10.0.0)
@@ -163,13 +171,14 @@ GEM
       elasticsearch-dsl
     chunky_png (1.4.0)
     climate_control (0.2.0)
+    cocoon (1.2.15)
     coderay (1.1.3)
     color_diff (0.1)
     concurrent-ruby (1.1.10)
-    connection_pool (2.2.5)
-    cose (1.0.0)
+    connection_pool (2.3.0)
+    cose (1.2.1)
       cbor (~> 0.5.9)
-      openssl-signature_algorithm (~> 0.4.0)
+      openssl-signature_algorithm (~> 1.0)
     crack (0.4.5)
       rexml
     crass (1.0.6)
@@ -197,11 +206,11 @@ GEM
     docile (1.3.4)
     domain_name (0.5.20190701)
       unf (>= 0.0.5, < 1.0.0)
-    doorkeeper (5.5.4)
+    doorkeeper (5.6.0)
       railties (>= 5)
-    dotenv (2.7.6)
-    dotenv-rails (2.7.6)
-      dotenv (= 2.7.6)
+    dotenv (2.8.1)
+    dotenv-rails (2.8.1)
+      dotenv (= 2.8.1)
       railties (>= 3.2)
     ed25519 (1.3.0)
     elasticsearch (7.13.3)
@@ -214,12 +223,12 @@ GEM
       faraday (~> 1)
       multi_json
     encryptor (3.0.0)
-    erubi (1.10.0)
+    erubi (1.11.0)
     et-orbi (1.2.7)
       tzinfo
     excon (0.76.0)
-    fabrication (2.28.0)
-    faker (2.21.0)
+    fabrication (2.30.0)
+    faker (2.23.0)
       i18n (>= 1.8.11, < 2)
     faraday (1.9.3)
       faraday-em_http (~> 1.0)
@@ -263,15 +272,15 @@ GEM
       fog-json (>= 1.0)
       ipaddress (>= 0.8)
     formatador (0.2.5)
-    fugit (1.5.3)
+    fugit (1.7.1)
       et-orbi (~> 1, >= 1.2.7)
       raabro (~> 1.4)
     fuubar (2.5.1)
       rspec-core (~> 3.0)
       ruby-progressbar (~> 1.4)
-    gitlab-omniauth-openid-connect (0.9.1)
+    gitlab-omniauth-openid-connect (0.10.0)
       addressable (~> 2.7)
-      omniauth (~> 1.9)
+      omniauth (>= 1.9, < 3)
       openid_connect (~> 1.2)
     globalid (1.0.0)
       activesupport (>= 5.0)
@@ -289,27 +298,26 @@ GEM
     highline (2.0.3)
     hiredis (0.6.3)
     hkdf (0.3.0)
-    html_tokenizer (0.0.7)
     htmlentities (4.3.4)
-    http (5.0.4)
+    http (5.1.0)
       addressable (~> 2.8)
       http-cookie (~> 1.0)
       http-form_data (~> 2.2)
       llhttp-ffi (~> 0.4.0)
-    http-cookie (1.0.4)
+    http-cookie (1.0.5)
       domain_name (~> 0.5)
     http-form_data (2.3.0)
     http_accept_language (2.1.1)
     httpclient (2.8.3)
-    httplog (1.5.0)
-      rack (>= 1.0)
+    httplog (1.6.0)
+      rack (>= 2.0)
       rainbow (>= 2.0.0)
-    i18n (1.10.0)
+    i18n (1.12.0)
       concurrent-ruby (~> 1.0)
-    i18n-tasks (1.0.10)
+    i18n-tasks (1.0.12)
       activesupport (>= 4.0.2)
       ast (>= 2.1.0)
-      better_html (~> 1.0)
+      better_html (>= 1.0, < 3.0)
       erubi
       highline (>= 2.0.0)
       i18n
@@ -320,24 +328,24 @@ GEM
     idn-ruby (0.1.4)
     ipaddress (0.8.3)
     jmespath (1.6.1)
-    json (2.5.1)
+    json (2.6.2)
     json-canonicalization (0.3.0)
     json-jwt (1.13.0)
       activesupport (>= 4.2)
       aes_key_wrap
       bindata
-    json-ld (3.2.0)
+    json-ld (3.2.3)
       htmlentities (~> 4.3)
       json-canonicalization (~> 0.3)
       link_header (~> 0.0, >= 0.0.8)
       multi_json (~> 1.15)
       rack (~> 2.2)
-      rdf (~> 3.2)
+      rdf (~> 3.2, >= 3.2.9)
     json-ld-preloaded (3.2.0)
       json-ld (~> 3.2)
       rdf (~> 3.2)
     jsonapi-renderer (0.2.2)
-    jwt (2.2.2)
+    jwt (2.4.1)
     kaminari (1.2.2)
       activesupport (>= 4.1.0)
       kaminari-actionview (= 1.2.2)
@@ -374,7 +382,7 @@ GEM
       activesupport (>= 4)
       railties (>= 4)
       request_store (~> 1.0)
-    loofah (2.18.0)
+    loofah (2.19.0)
       crass (~> 1.0.2)
       nokogiri (>= 1.5.9)
     mail (2.7.1)
@@ -387,7 +395,7 @@ GEM
     matrix (0.4.2)
     memory_profiler (1.0.0)
     method_source (1.0.0)
-    microformats (4.3.1)
+    microformats (4.4.1)
       json (~> 2.2)
       nokogiri (~> 1.10)
     mime-types (3.4.1)
@@ -395,16 +403,16 @@ GEM
     mime-types-data (3.2022.0105)
     mini_mime (1.1.2)
     mini_portile2 (2.8.0)
-    minitest (5.15.0)
-    msgpack (1.5.1)
+    minitest (5.16.3)
+    msgpack (1.5.4)
     multi_json (1.15.0)
     multipart-post (2.1.1)
-    net-ldap (0.17.0)
-    net-scp (3.0.0)
-      net-ssh (>= 2.6.5, < 7.0.0)
-    net-ssh (6.1.0)
+    net-ldap (0.17.1)
+    net-scp (4.0.0.rc1)
+      net-ssh (>= 2.6.5, < 8.0.0)
+    net-ssh (7.0.1)
     nio4r (2.5.8)
-    nokogiri (1.13.6)
+    nokogiri (1.13.8)
       mini_portile2 (~> 2.8.0)
       racc (~> 1.4)
     nsa (0.2.8)
@@ -412,8 +420,8 @@ GEM
       concurrent-ruby (~> 1.0, >= 1.0.2)
       sidekiq (>= 3.5)
       statsd-ruby (~> 1.4, >= 1.4.0)
-    oj (3.13.11)
-    omniauth (1.9.1)
+    oj (3.13.21)
+    omniauth (1.9.2)
       hashie (>= 3.4.6)
       rack (>= 1.6.2, < 3)
     omniauth-cas (2.0.0)
@@ -436,20 +444,21 @@ GEM
       validate_email
       validate_url
       webfinger (>= 1.0.1)
-    openssl (2.2.0)
-    openssl-signature_algorithm (0.4.0)
+    openssl (3.0.0)
+    openssl-signature_algorithm (1.2.1)
+      openssl (> 2.0, < 3.1)
     orm_adapter (0.5.0)
     ox (2.14.11)
     parallel (1.22.1)
-    parser (3.1.2.0)
+    parser (3.1.2.1)
       ast (~> 2.4.1)
     parslet (2.0.0)
     pastel (0.8.0)
       tty-color (~> 0.5)
-    pg (1.3.5)
+    pg (1.4.3)
     pghero (2.8.3)
       activerecord (>= 5)
-    pkg-config (1.4.7)
+    pkg-config (1.4.9)
     posix-spawn (0.3.15)
     premailer (1.14.2)
       addressable
@@ -459,22 +468,22 @@ GEM
       actionmailer (>= 3)
       premailer (~> 1.7, >= 1.7.9)
     private_address_check (0.5.0)
-    pry (0.13.1)
+    pry (0.14.1)
       coderay (~> 1.1)
       method_source (~> 1.0)
-    pry-byebug (3.9.0)
+    pry-byebug (3.10.1)
       byebug (~> 11.0)
-      pry (~> 0.13.0)
+      pry (>= 0.13, < 0.15)
     pry-rails (0.3.9)
       pry (>= 0.10.4)
-    public_suffix (4.0.7)
-    puma (5.6.4)
+    public_suffix (5.0.0)
+    puma (5.6.5)
       nio4r (~> 2.0)
     pundit (2.2.0)
       activesupport (>= 3.0.0)
     raabro (1.4.0)
     racc (1.6.0)
-    rack (2.2.3)
+    rack (2.2.4)
     rack-attack (6.6.1)
       rack (>= 1.0, < 3)
     rack-cors (1.1.1)
@@ -487,22 +496,22 @@ GEM
       rack (>= 2.1.0)
     rack-proxy (0.7.0)
       rack
-    rack-test (1.1.0)
-      rack (>= 1.0, < 3)
-    rails (6.1.6)
-      actioncable (= 6.1.6)
-      actionmailbox (= 6.1.6)
-      actionmailer (= 6.1.6)
-      actionpack (= 6.1.6)
-      actiontext (= 6.1.6)
-      actionview (= 6.1.6)
-      activejob (= 6.1.6)
-      activemodel (= 6.1.6)
-      activerecord (= 6.1.6)
-      activestorage (= 6.1.6)
-      activesupport (= 6.1.6)
+    rack-test (2.0.2)
+      rack (>= 1.3)
+    rails (6.1.7)
+      actioncable (= 6.1.7)
+      actionmailbox (= 6.1.7)
+      actionmailer (= 6.1.7)
+      actionpack (= 6.1.7)
+      actiontext (= 6.1.7)
+      actionview (= 6.1.7)
+      activejob (= 6.1.7)
+      activemodel (= 6.1.7)
+      activerecord (= 6.1.7)
+      activestorage (= 6.1.7)
+      activesupport (= 6.1.7)
       bundler (>= 1.15.0)
-      railties (= 6.1.6)
+      railties (= 6.1.7)
       sprockets-rails (>= 2.0.0)
     rails-controller-testing (1.0.5)
       actionpack (>= 5.0.1.rc1)
@@ -511,29 +520,30 @@ GEM
     rails-dom-testing (2.0.3)
       activesupport (>= 4.2.0)
       nokogiri (>= 1.6)
-    rails-html-sanitizer (1.4.2)
+    rails-html-sanitizer (1.4.3)
       loofah (~> 2.3)
     rails-i18n (6.0.0)
       i18n (>= 0.7, < 2)
       railties (>= 6.0.0, < 7)
     rails-settings-cached (0.6.6)
       rails (>= 4.2.0)
-    railties (6.1.6)
-      actionpack (= 6.1.6)
-      activesupport (= 6.1.6)
+    railties (6.1.7)
+      actionpack (= 6.1.7)
+      activesupport (= 6.1.7)
       method_source
       rake (>= 12.2)
       thor (~> 1.0)
     rainbow (3.1.1)
     rake (13.0.6)
-    rdf (3.2.3)
+    rdf (3.2.9)
       link_header (~> 0.0, >= 0.0.8)
     rdf-normalize (0.5.0)
       rdf (~> 3.2)
+    redcarpet (3.5.1)
     redis (4.5.1)
-    redis-namespace (1.8.2)
-      redis (>= 3.0.4)
-    regexp_parser (2.4.0)
+    redis-namespace (1.9.0)
+      redis (>= 4)
+    regexp_parser (2.5.0)
     request_store (1.5.1)
       rack (>= 1.4)
     responders (3.0.1)
@@ -542,7 +552,7 @@ GEM
     rexml (3.2.5)
     rotp (6.2.0)
     rpam2 (4.0.2)
-    rqrcode (2.1.1)
+    rqrcode (2.1.2)
       chunky_png (~> 1.0)
       rqrcode_core (~> 1.0)
     rqrcode_core (1.2.0)
@@ -565,21 +575,21 @@ GEM
     rspec-sidekiq (3.1.0)
       rspec-core (~> 3.0, >= 3.0.0)
       sidekiq (>= 2.4.0)
-    rspec-support (3.11.0)
-    rspec_junit_formatter (0.5.1)
+    rspec-support (3.11.1)
+    rspec_junit_formatter (0.6.0)
       rspec-core (>= 2, < 4, != 2.12.0)
-    rubocop (1.29.1)
+    rubocop (1.30.1)
       parallel (~> 1.10)
       parser (>= 3.1.0.0)
       rainbow (>= 2.2.2, < 4.0)
       regexp_parser (>= 1.8, < 3.0)
       rexml (>= 3.2.5, < 4.0)
-      rubocop-ast (>= 1.17.0, < 2.0)
+      rubocop-ast (>= 1.18.0, < 2.0)
       ruby-progressbar (~> 1.7)
       unicode-display_width (>= 1.4.0, < 3.0)
     rubocop-ast (1.18.0)
       parser (>= 3.1.1.0)
-    rubocop-rails (2.14.2)
+    rubocop-rails (2.15.0)
       activesupport (>= 4.2.0)
       rack (>= 1.1)
       rubocop (>= 1.7.0, < 2.0)
@@ -588,7 +598,7 @@ GEM
       nokogiri (>= 1.10.5)
       rexml
     ruby2_keywords (0.0.5)
-    rufus-scheduler (3.8.1)
+    rufus-scheduler (3.8.2)
       fugit (~> 1.1, >= 1.1.6)
     safety_net_attestation (0.4.0)
       jwt (~> 2.0)
@@ -598,25 +608,24 @@ GEM
     scenic (1.6.0)
       activerecord (>= 4.0.0)
       railties (>= 4.0.0)
-    securecompare (1.0.0)
     semantic_range (3.0.0)
-    sidekiq (6.4.2)
-      connection_pool (>= 2.2.2)
+    sidekiq (6.5.7)
+      connection_pool (>= 2.2.5)
       rack (~> 2.0)
-      redis (>= 4.2.0)
+      redis (>= 4.5.0, < 5)
     sidekiq-bulk (0.2.0)
       sidekiq
-    sidekiq-scheduler (4.0.0)
+    sidekiq-scheduler (4.0.3)
       redis (>= 4.2.0)
       rufus-scheduler (~> 3.2)
-      sidekiq (>= 4)
+      sidekiq (>= 4, < 7)
       tilt (>= 1.4.0)
-    sidekiq-unique-jobs (7.1.22)
+    sidekiq-unique-jobs (7.1.27)
       brpoplpush-redis_script (> 0.1.1, <= 2.0.0)
       concurrent-ruby (~> 1.0, >= 1.0.5)
       sidekiq (>= 5.0, < 8.0)
       thor (>= 0.20, < 3.0)
-    simple-navigation (4.3.0)
+    simple-navigation (4.4.0)
       activesupport (>= 2.3.2)
     simple_form (5.1.0)
       actionpack (>= 5.2)
@@ -638,7 +647,7 @@ GEM
     sshkit (1.21.2)
       net-scp (>= 1.1.2)
       net-ssh (>= 2.8.0)
-    stackprof (0.2.19)
+    stackprof (0.2.22)
     statsd-ruby (1.5.0)
     stoplight (3.0.0)
     strong_migrations (0.7.9)
@@ -653,10 +662,11 @@ GEM
     terrapin (0.6.0)
       climate_control (>= 0.0.3, < 1.0)
     thor (1.2.1)
-    tilt (2.0.10)
-    tpm-key_attestation (0.9.0)
+    tilt (2.0.11)
+    tpm-key_attestation (0.11.0)
       bindata (~> 2.4)
-      openssl-signature_algorithm (~> 0.4.0)
+      openssl (> 2.0, < 3.1)
+      openssl-signature_algorithm (~> 1.0)
     tty-color (0.6.0)
     tty-cursor (0.7.1)
     tty-prompt (0.23.1)
@@ -670,37 +680,36 @@ GEM
     twitter-text (3.1.0)
       idn-ruby
       unf (~> 0.1.0)
-    tzinfo (2.0.4)
+    tzinfo (2.0.5)
       concurrent-ruby (~> 1.0)
-    tzinfo-data (1.2022.1)
+    tzinfo-data (1.2022.4)
       tzinfo (>= 1.0.0)
     unf (0.1.4)
       unf_ext
-    unf_ext (0.0.8)
-    unicode-display_width (2.1.0)
-    uniform_notifier (1.14.2)
+    unf_ext (0.0.8.2)
+    unicode-display_width (2.3.0)
+    uniform_notifier (1.16.0)
     validate_email (0.1.6)
       activemodel (>= 3.0)
       mail (>= 2.2.5)
-    validate_url (1.0.13)
+    validate_url (1.0.15)
       activemodel (>= 3.0.0)
       public_suffix
     warden (1.2.9)
       rack (>= 2.0.9)
-    webauthn (3.0.0.alpha1)
+    webauthn (2.5.2)
       android_key_attestation (~> 0.3.0)
       awrence (~> 1.1)
       bindata (~> 2.4)
       cbor (~> 0.5.9)
-      cose (~> 1.0)
-      openssl (~> 2.0)
+      cose (~> 1.1)
+      openssl (>= 2.2, < 3.1)
       safety_net_attestation (~> 0.4.0)
-      securecompare (~> 1.0)
-      tpm-key_attestation (~> 0.9.0)
+      tpm-key_attestation (~> 0.11.0)
     webfinger (1.2.0)
       activesupport
       httpclient (>= 2.4)
-    webmock (3.14.0)
+    webmock (3.18.1)
       addressable (>= 2.8.0)
       crack (>= 0.3.2)
       hashdiff (>= 0.4.0, < 2.0.0)
@@ -709,17 +718,14 @@ GEM
       rack-proxy (>= 0.6.1)
       railties (>= 5.2)
       semantic_range (>= 2.3.0)
-    webpush (0.3.8)
-      hkdf (~> 0.2)
-      jwt (~> 2.0)
     websocket-driver (0.7.5)
       websocket-extensions (>= 0.1.0)
     websocket-extensions (0.1.5)
     wisper (2.0.1)
-    xorcist (1.1.2)
+    xorcist (1.1.3)
     xpath (3.2.0)
       nokogiri (~> 1.8)
-    zeitwerk (2.5.4)
+    zeitwerk (2.6.0)
 
 PLATFORMS
   ruby
@@ -733,8 +739,8 @@ DEPENDENCIES
   better_errors (~> 2.9)
   binding_of_caller (~> 1.0)
   blurhash (~> 0.1)
-  bootsnap (~> 1.11.1)
-  brakeman (~> 5.2)
+  bootsnap (~> 1.13.0)
+  brakeman (~> 5.3)
   browser
   bullet (~> 7.0)
   bundler-audit (~> 0.9)
@@ -746,6 +752,7 @@ DEPENDENCIES
   charlock_holmes (~> 0.7.7)
   chewy (~> 7.2)
   climate_control (~> 0.2)
+  cocoon (~> 1.2)
   color_diff (~> 0.1)
   concurrent-ruby
   connection_pool
@@ -753,23 +760,23 @@ DEPENDENCIES
   devise-two-factor (~> 4.0)
   devise_pam_authenticatable2 (~> 9.2)
   discard (~> 1.2)
-  doorkeeper (~> 5.5)
-  dotenv-rails (~> 2.7)
+  doorkeeper (~> 5.6)
+  dotenv-rails (~> 2.8)
   ed25519 (~> 1.3)
-  fabrication (~> 2.28)
-  faker (~> 2.21)
+  fabrication (~> 2.30)
+  faker (~> 2.23)
   fast_blank (~> 1.0)
   fastimage
   fog-core (<= 2.1.0)
   fog-openstack (~> 0.3)
   fuubar (~> 2.5)
-  gitlab-omniauth-openid-connect (~> 0.9.1)
+  gitlab-omniauth-openid-connect (~> 0.10.0)
   hamlit-rails (~> 0.2)
   hiredis (~> 0.6)
   htmlentities (~> 4.3)
-  http (~> 5.0)
+  http (~> 5.1)
   http_accept_language (~> 2.1)
-  httplog (~> 1.5.0)
+  httplog (~> 1.6.0)
   i18n-tasks (~> 1.0)
   idn-ruby
   json-ld
@@ -783,7 +790,7 @@ DEPENDENCIES
   makara (~> 0.5)
   mario-redis-lock (~> 1.2)
   memory_profiler
-  microformats (~> 4.2)
+  microformats (~> 4.4)
   mime-types (~> 3.4.1)
   net-ldap (~> 0.17)
   nokogiri (~> 1.13)
@@ -795,41 +802,43 @@ DEPENDENCIES
   omniauth-saml (~> 1.10)
   ox (~> 2.14)
   parslet
-  pg (~> 1.3)
+  pg (~> 1.4)
   pghero (~> 2.8)
   pkg-config (~> 1.4)
   posix-spawn
   premailer-rails
   private_address_check (~> 0.5)
-  pry-byebug (~> 3.9)
+  pry-byebug (~> 3.10)
   pry-rails (~> 0.3)
   puma (~> 5.6)
   pundit (~> 2.2)
-  rack (~> 2.2.3)
+  rack (~> 2.2.4)
   rack-attack (~> 6.6)
   rack-cors (~> 1.1)
-  rails (~> 6.1.6)
+  rack-test (~> 2.0)
+  rails (~> 6.1.7)
   rails-controller-testing (~> 1.0)
   rails-i18n (~> 6.0)
   rails-settings-cached (~> 0.6)
   rdf-normalize (~> 0.5)
+  redcarpet (~> 3.5)
   redis (~> 4.5)
-  redis-namespace (~> 1.8)
+  redis-namespace (~> 1.9)
   rexml (~> 3.2)
   rqrcode (~> 2.1)
   rspec-rails (~> 5.1)
   rspec-sidekiq (~> 3.1)
-  rspec_junit_formatter (~> 0.5)
-  rubocop (~> 1.29)
-  rubocop-rails (~> 2.14)
+  rspec_junit_formatter (~> 0.6)
+  rubocop (~> 1.30)
+  rubocop-rails (~> 2.15)
   ruby-progressbar (~> 1.11)
   sanitize (~> 6.0)
   scenic (~> 1.6)
-  sidekiq (~> 6.4)
+  sidekiq (~> 6.5)
   sidekiq-bulk (~> 0.2.0)
   sidekiq-scheduler (~> 4.0)
   sidekiq-unique-jobs (~> 7.1)
-  simple-navigation (~> 4.3)
+  simple-navigation (~> 4.4)
   simple_form (~> 5.1)
   simplecov (~> 0.21)
   sprockets (~> 3.7.2)
@@ -841,8 +850,8 @@ DEPENDENCIES
   tty-prompt (~> 0.23)
   twitter-text (~> 3.1.0)
   tzinfo-data (~> 1.2022)
-  webauthn (~> 3.0.0.alpha1)
-  webmock (~> 3.14)
+  webauthn (~> 2.5)
+  webmock (~> 3.18)
   webpacker (~> 5.4)
-  webpush (~> 0.3)
+  webpush!
   xorcist (~> 1.1)

+ 8 - 5
README.md

@@ -1,5 +1,8 @@
-![Mastodon](https://i.imgur.com/NhZc40l.png)
-========
+<h1><picture>
+  <source media="(prefers-color-scheme: dark)" srcset="./lib/assets/wordmark.dark.png?raw=true">
+  <source media="(prefers-color-scheme: light)" srcset="./lib/assets/wordmark.light.png?raw=true">
+  <img alt="Mastodon" src="./lib/assets/wordmark.light.png?raw=true" height="34">
+</picture></h1>
 
 [![GitHub release](https://img.shields.io/github/release/mastodon/mastodon.svg)][releases]
 [![Build Status](https://img.shields.io/circleci/project/github/mastodon/mastodon.svg)][circleci]
@@ -35,7 +38,7 @@ Click below to **learn more** in a video:
 
 ## Features
 
-<img src="https://docs.joinmastodon.org/elephant.svg" align="right" width="30%" />
+<img src="/app/javascript/images/elephant_ui_working.svg?raw=true" align="right" width="30%" />
 
 ### No vendor lock-in: Fully interoperable with any conforming platform
 
@@ -69,8 +72,8 @@ Mastodon acts as an OAuth2 provider, so 3rd party apps can use the REST and Stre
 
 - **PostgreSQL** 9.5+
 - **Redis** 4+
-- **Ruby** 2.5+
-- **Node.js** 12+
+- **Ruby** 2.6+
+- **Node.js** 14+
 
 The repository includes deployment configurations for **Docker and docker-compose** as well as specific platforms like **Heroku**, **Scalingo**, and **Nanobox**. The [**standalone** installation guide](https://docs.joinmastodon.org/admin/install/) is available in the documentation.
 

+ 6 - 9
SECURITY.md

@@ -1,6 +1,6 @@
 # Security Policy
 
-If you believe you've identified a security vulnerability in Mastodon (a bug that allows something to happen that shouldn't be possible), you should submit the report through our [Bug Bounty Program][bug-bounty]. Alternatively, you can reach us at <hello@joinmastodon.org>.
+If you believe you've identified a security vulnerability in Mastodon (a bug that allows something to happen that shouldn't be possible), you can reach us at <security@joinmastodon.org>.
 
 You should *not* report such issues on GitHub or in other public spaces to give us time to publish a fix for the issue without exposing Mastodon's users to increased risk.
 
@@ -10,11 +10,8 @@ A "vulnerability in Mastodon" is a vulnerability in the code distributed through
 
 ## Supported Versions
 
-| Version | Supported          |
-| ------- | ------------------ |
-| 3.5.x   | Yes                |
-| 3.4.x   | Yes                |
-| 3.3.x   | No                 |
-| < 3.3   | No                 |
-
-[bug-bounty]: https://app.intigriti.com/programs/mastodon/mastodonio/detail
+| Version | Supported |
+| ------- | ----------|
+| 4.0.x   | Yes       |
+| 3.5.x   | Yes       |
+| < 3.5   | No        |

+ 6 - 1
app.json

@@ -79,8 +79,13 @@
       "description": "SMTP server certificate verification mode. Defaults is 'peer'.",
       "required": false
     },
+    "SMTP_ENABLE_STARTTLS": {
+      "description": "Enable STARTTLS? Default is 'auto'.",
+      "value": "auto",
+      "required": false
+    },
     "SMTP_ENABLE_STARTTLS_AUTO": {
-      "description": "Enable STARTTLS if SMTP server supports it? Default is true.",
+      "description": "Enable STARTTLS if SMTP server supports it? Deprecated by SMTP_ENABLE_STARTTLS.",
       "required": false
     }
   },

+ 4 - 53
app/controllers/about_controller.rb

@@ -1,68 +1,19 @@
 # frozen_string_literal: true
 
 class AboutController < ApplicationController
-  include RegistrationSpamConcern
+  include WebAppControllerConcern
 
-  layout 'public'
+  skip_before_action :require_functional!
 
-  before_action :require_open_federation!, only: [:show, :more]
-  before_action :set_body_classes, only: :show
   before_action :set_instance_presenter
-  before_action :set_expires_in, only: [:more, :terms]
-  before_action :set_registration_form_time, only: :show
 
-  skip_before_action :require_functional!, only: [:more, :terms]
-
-  def show; end
-
-  def more
-    flash.now[:notice] = I18n.t('about.instance_actor_flash') if params[:instance_actor]
-
-    toc_generator = TOCGenerator.new(@instance_presenter.site_extended_description)
-
-    @rules             = Rule.ordered
-    @contents          = toc_generator.html
-    @table_of_contents = toc_generator.toc
-    @blocks            = DomainBlock.with_user_facing_limitations.by_severity if display_blocks?
+  def show
+    expires_in 0, public: true unless user_signed_in?
   end
 
-  def terms; end
-
-  helper_method :display_blocks?
-  helper_method :display_blocks_rationale?
-  helper_method :public_fetch_mode?
-  helper_method :new_user
-
   private
 
-  def require_open_federation!
-    not_found if whitelist_mode?
-  end
-
-  def display_blocks?
-    Setting.show_domain_blocks == 'all' || (Setting.show_domain_blocks == 'users' && user_signed_in?)
-  end
-
-  def display_blocks_rationale?
-    Setting.show_domain_blocks_rationale == 'all' || (Setting.show_domain_blocks_rationale == 'users' && user_signed_in?)
-  end
-
-  def new_user
-    User.new.tap do |user|
-      user.build_account
-      user.build_invite_request
-    end
-  end
-
   def set_instance_presenter
     @instance_presenter = InstancePresenter.new
   end
-
-  def set_body_classes
-    @hide_navbar = true
-  end
-
-  def set_expires_in
-    expires_in 0, public: true
-  end
 end

+ 0 - 12
app/controllers/account_follow_controller.rb

@@ -1,12 +0,0 @@
-# frozen_string_literal: true
-
-class AccountFollowController < ApplicationController
-  include AccountControllerConcern
-
-  before_action :authenticate_user!
-
-  def create
-    FollowService.new.call(current_user.account, @account, with_rate_limit: true)
-    redirect_to account_path(@account)
-  end
-end

+ 0 - 12
app/controllers/account_unfollow_controller.rb

@@ -1,12 +0,0 @@
-# frozen_string_literal: true
-
-class AccountUnfollowController < ApplicationController
-  include AccountControllerConcern
-
-  before_action :authenticate_user!
-
-  def create
-    UnfollowService.new.call(current_user.account, @account)
-    redirect_to account_path(@account)
-  end
-end

+ 1 - 59
app/controllers/accounts_controller.rb

@@ -7,9 +7,8 @@ class AccountsController < ApplicationController
   include AccountControllerConcern
   include SignatureAuthentication
 
-  before_action :require_signature!, if: -> { request.format == :json && authorized_fetch_mode? }
+  before_action :require_account_signature!, if: -> { request.format == :json && authorized_fetch_mode? }
   before_action :set_cache_headers
-  before_action :set_body_classes
 
   skip_around_action :set_locale, if: -> { [:json, :rss].include?(request.format&.to_sym) }
   skip_before_action :require_functional!, unless: :whitelist_mode?
@@ -18,24 +17,6 @@ class AccountsController < ApplicationController
     respond_to do |format|
       format.html do
         expires_in 0, public: true unless user_signed_in?
-
-        @pinned_statuses   = []
-        @endorsed_accounts = @account.endorsed_accounts.to_a.sample(4)
-        @featured_hashtags = @account.featured_tags.order(statuses_count: :desc)
-
-        if current_account && @account.blocking?(current_account)
-          @statuses = []
-          return
-        end
-
-        @pinned_statuses = cached_filtered_status_pins if show_pinned_statuses?
-        @statuses        = cached_filtered_status_page
-        @rss_url         = rss_url
-
-        unless @statuses.empty?
-          @older_url = older_url if @statuses.last.id > filtered_statuses.last.id
-          @newer_url = newer_url if @statuses.first.id < filtered_statuses.first.id
-        end
       end
 
       format.rss do
@@ -55,18 +36,6 @@ class AccountsController < ApplicationController
 
   private
 
-  def set_body_classes
-    @body_classes = 'with-modals'
-  end
-
-  def show_pinned_statuses?
-    [replies_requested?, media_requested?, tag_requested?, params[:max_id].present?, params[:min_id].present?].none?
-  end
-
-  def filtered_pinned_statuses
-    @account.pinned_statuses.where(visibility: [:public, :unlisted])
-  end
-
   def filtered_statuses
     default_statuses.tap do |statuses|
       statuses.merge!(hashtag_scope)    if tag_requested?
@@ -113,26 +82,6 @@ class AccountsController < ApplicationController
     end
   end
 
-  def older_url
-    pagination_url(max_id: @statuses.last.id)
-  end
-
-  def newer_url
-    pagination_url(min_id: @statuses.first.id)
-  end
-
-  def pagination_url(max_id: nil, min_id: nil)
-    if tag_requested?
-      short_account_tag_url(@account, params[:tag], max_id: max_id, min_id: min_id)
-    elsif media_requested?
-      short_account_media_url(@account, max_id: max_id, min_id: min_id)
-    elsif replies_requested?
-      short_account_with_replies_url(@account, max_id: max_id, min_id: min_id)
-    else
-      short_account_url(@account, max_id: max_id, min_id: min_id)
-    end
-  end
-
   def media_requested?
     request.path.split('.').first.end_with?('/media') && !tag_requested?
   end
@@ -145,13 +94,6 @@ class AccountsController < ApplicationController
     request.path.split('.').first.end_with?(Addressable::URI.parse("/tagged/#{params[:tag]}").normalize)
   end
 
-  def cached_filtered_status_pins
-    cache_collection(
-      filtered_pinned_statuses,
-      Status
-    )
-  end
-
   def cached_filtered_status_page
     cache_collection_paginated_by_id(
       filtered_statuses,

+ 1 - 1
app/controllers/activitypub/claims_controller.rb

@@ -6,7 +6,7 @@ class ActivityPub::ClaimsController < ActivityPub::BaseController
 
   skip_before_action :authenticate_user!
 
-  before_action :require_signature!
+  before_action :require_account_signature!
   before_action :set_claim_result
 
   def create

+ 1 - 1
app/controllers/activitypub/collections_controller.rb

@@ -4,7 +4,7 @@ class ActivityPub::CollectionsController < ActivityPub::BaseController
   include SignatureVerification
   include AccountOwnedConcern
 
-  before_action :require_signature!, if: :authorized_fetch_mode?
+  before_action :require_account_signature!, if: :authorized_fetch_mode?
   before_action :set_items
   before_action :set_size
   before_action :set_type

+ 1 - 1
app/controllers/activitypub/followers_synchronizations_controller.rb

@@ -4,7 +4,7 @@ class ActivityPub::FollowersSynchronizationsController < ActivityPub::BaseContro
   include SignatureVerification
   include AccountOwnedConcern
 
-  before_action :require_signature!
+  before_action :require_account_signature!
   before_action :set_items
   before_action :set_cache_headers
 

+ 5 - 5
app/controllers/activitypub/inboxes_controller.rb

@@ -6,7 +6,7 @@ class ActivityPub::InboxesController < ActivityPub::BaseController
   include AccountOwnedConcern
 
   before_action :skip_unknown_actor_activity
-  before_action :require_signature!
+  before_action :require_actor_signature!
   skip_before_action :authenticate_user!
 
   def create
@@ -49,17 +49,17 @@ class ActivityPub::InboxesController < ActivityPub::BaseController
   end
 
   def upgrade_account
-    if signed_request_account.ostatus?
+    if signed_request_account&.ostatus?
       signed_request_account.update(last_webfingered_at: nil)
       ResolveAccountWorker.perform_async(signed_request_account.acct)
     end
 
-    DeliveryFailureTracker.reset!(signed_request_account.inbox_url)
+    DeliveryFailureTracker.reset!(signed_request_actor.inbox_url)
   end
 
   def process_collection_synchronization
     raw_params = request.headers['Collection-Synchronization']
-    return if raw_params.blank? || ENV['DISABLE_FOLLOWERS_SYNCHRONIZATION'] == 'true'
+    return if raw_params.blank? || ENV['DISABLE_FOLLOWERS_SYNCHRONIZATION'] == 'true' || signed_request_account.nil?
 
     # Re-using the syntax for signature parameters
     tree   = SignatureParamsParser.new.parse(raw_params)
@@ -71,6 +71,6 @@ class ActivityPub::InboxesController < ActivityPub::BaseController
   end
 
   def process_payload
-    ActivityPub::ProcessingWorker.perform_async(signed_request_account.id, body, @account&.id)
+    ActivityPub::ProcessingWorker.perform_async(signed_request_actor.id, body, @account&.id, signed_request_actor.class.name)
   end
 end

+ 1 - 1
app/controllers/activitypub/outboxes_controller.rb

@@ -6,7 +6,7 @@ class ActivityPub::OutboxesController < ActivityPub::BaseController
   include SignatureVerification
   include AccountOwnedConcern
 
-  before_action :require_signature!, if: :authorized_fetch_mode?
+  before_action :require_account_signature!, if: :authorized_fetch_mode?
   before_action :set_statuses
   before_action :set_cache_headers
 

+ 1 - 1
app/controllers/activitypub/replies_controller.rb

@@ -7,7 +7,7 @@ class ActivityPub::RepliesController < ActivityPub::BaseController
 
   DESCENDANTS_LIMIT = 60
 
-  before_action :require_signature!, if: :authorized_fetch_mode?
+  before_action :require_account_signature!, if: :authorized_fetch_mode?
   before_action :set_status
   before_action :set_cache_headers
   before_action :set_replies

+ 4 - 0
app/controllers/admin/account_actions_controller.rb

@@ -5,11 +5,15 @@ module Admin
     before_action :set_account
 
     def new
+      authorize @account, :show?
+
       @account_action  = Admin::AccountAction.new(type: params[:type], report_id: params[:report_id], send_email_notification: true, include_statuses: true)
       @warning_presets = AccountWarningPreset.all
     end
 
     def create
+      authorize @account, :show?
+
       account_action                 = Admin::AccountAction.new(resource_params)
       account_action.target_account  = @account
       account_action.current_account = current_account

+ 7 - 1
app/controllers/admin/accounts_controller.rb

@@ -14,7 +14,13 @@ module Admin
     end
 
     def batch
-      @form = Form::AccountBatch.new(form_account_batch_params.merge(current_account: current_account, action: action_from_button))
+      authorize :account, :index?
+
+      @form = Form::AccountBatch.new(form_account_batch_params)
+      @form.current_account = current_account
+      @form.action = action_from_button
+      @form.select_all_matching = params[:select_all_matching]
+      @form.query = filtered_accounts
       @form.save
     rescue ActionController::ParameterMissing
       flash[:alert] = I18n.t('admin.accounts.no_account_selected')

+ 4 - 1
app/controllers/admin/action_logs_controller.rb

@@ -4,7 +4,10 @@ module Admin
   class ActionLogsController < BaseController
     before_action :set_action_logs
 
-    def index; end
+    def index
+      authorize :audit_log, :index?
+      @auditable_accounts = Account.where(id: Admin::ActionLog.reorder(nil).select('distinct account_id')).select(:id, :username)
+    end
 
     private
 

+ 1 - 1
app/controllers/admin/base_controller.rb

@@ -7,8 +7,8 @@ module Admin
 
     layout 'admin'
 
-    before_action :require_staff!
     before_action :set_body_classes
+    after_action :verify_authorized
 
     private
 

+ 1 - 1
app/controllers/admin/confirmations_controller.rb

@@ -17,7 +17,7 @@ module Admin
 
       @user.resend_confirmation_instructions
 
-      log_action :confirm, @user
+      log_action :resend, @user
 
       flash[:notice] = I18n.t('admin.accounts.resend_confirmation.success')
       redirect_to admin_accounts_path

+ 3 - 1
app/controllers/admin/custom_emojis_controller.rb

@@ -29,10 +29,12 @@ module Admin
     end
 
     def batch
+      authorize :custom_emoji, :index?
+
       @form = Form::CustomEmojiBatch.new(form_custom_emoji_batch_params.merge(current_account: current_account, action: action_from_button))
       @form.save
     rescue ActionController::ParameterMissing
-      flash[:alert] = I18n.t('admin.accounts.no_account_selected')
+      flash[:alert] = I18n.t('admin.custom_emojis.no_emoji_selected')
     rescue Mastodon::NotPermittedError
       flash[:alert] = I18n.t('admin.custom_emojis.not_permitted')
     ensure

+ 3 - 1
app/controllers/admin/dashboard_controller.rb

@@ -5,7 +5,9 @@ module Admin
     include Redisable
 
     def index
-      @system_checks         = Admin::SystemCheck.perform
+      authorize :dashboard, :index?
+
+      @system_checks         = Admin::SystemCheck.perform(current_user)
       @time_period           = (29.days.ago.to_date...Time.now.utc.to_date)
       @pending_users_count   = User.pending.count
       @pending_reports_count = Report.unresolved.count

+ 2 - 0
app/controllers/admin/email_domain_blocks_controller.rb

@@ -12,6 +12,8 @@ module Admin
     end
 
     def batch
+      authorize :email_domain_block, :index?
+
       @form = Form::EmailDomainBlockBatch.new(form_email_domain_block_batch_params.merge(current_account: current_account, action: action_from_button))
       @form.save
     rescue ActionController::ParameterMissing

+ 2 - 0
app/controllers/admin/follow_recommendations_controller.rb

@@ -12,6 +12,8 @@ module Admin
     end
 
     def update
+      authorize :follow_recommendation, :show?
+
       @form = Form::AccountBatch.new(form_account_batch_params.merge(current_account: current_account, action: action_from_button))
       @form.save
     rescue ActionController::ParameterMissing

+ 3 - 1
app/controllers/admin/ip_blocks_controller.rb

@@ -5,7 +5,7 @@ module Admin
     def index
       authorize :ip_block, :index?
 
-      @ip_blocks = IpBlock.page(params[:page])
+      @ip_blocks = IpBlock.order(ip: :asc).page(params[:page])
       @form      = Form::IpBlockBatch.new
     end
 
@@ -29,6 +29,8 @@ module Admin
     end
 
     def batch
+      authorize :ip_block, :index?
+
       @form = Form::IpBlockBatch.new(form_ip_block_batch_params.merge(current_account: current_account, action: action_from_button))
       @form.save
     rescue ActionController::ParameterMissing

+ 1 - 1
app/controllers/admin/relationships_controller.rb

@@ -7,7 +7,7 @@ module Admin
     PER_PAGE = 40
 
     def index
-      authorize :account, :index?
+      authorize @account, :show?
 
       @accounts = RelationshipFilter.new(@account, filter_params).results.includes(:account_stat, user: [:ips, :invite_request]).page(params[:page]).per(PER_PAGE)
       @form     = Form::AccountBatch.new

+ 57 - 11
app/controllers/admin/roles_controller.rb

@@ -2,20 +2,66 @@
 
 module Admin
   class RolesController < BaseController
-    before_action :set_user
+    before_action :set_role, except: [:index, :new, :create]
 
-    def promote
-      authorize @user, :promote?
-      @user.promote!
-      log_action :promote, @user
-      redirect_to admin_account_path(@user.account_id)
+    def index
+      authorize :user_role, :index?
+
+      @roles = UserRole.order(position: :desc).page(params[:page])
+    end
+
+    def new
+      authorize :user_role, :create?
+
+      @role = UserRole.new
+    end
+
+    def create
+      authorize :user_role, :create?
+
+      @role = UserRole.new(resource_params)
+      @role.current_account = current_account
+
+      if @role.save
+        log_action :create, @role
+        redirect_to admin_roles_path
+      else
+        render :new
+      end
+    end
+
+    def edit
+      authorize @role, :update?
+    end
+
+    def update
+      authorize @role, :update?
+
+      @role.current_account = current_account
+
+      if @role.update(resource_params)
+        log_action :update, @role
+        redirect_to admin_roles_path
+      else
+        render :edit
+      end
+    end
+
+    def destroy
+      authorize @role, :destroy?
+      @role.destroy!
+      log_action :destroy, @role
+      redirect_to admin_roles_path
+    end
+
+    private
+
+    def set_role
+      @role = UserRole.find(params[:id])
     end
 
-    def demote
-      authorize @user, :demote?
-      @user.demote!
-      log_action :demote, @user
-      redirect_to admin_account_path(@user.account_id)
+    def resource_params
+      params.require(:user_role).permit(:name, :color, :highlighted, :position, permissions_as_keys: [])
     end
   end
 end

+ 9 - 0
app/controllers/admin/settings/about_controller.rb

@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class Admin::Settings::AboutController < Admin::SettingsController
+  private
+
+  def after_update_redirect_path
+    admin_settings_about_path
+  end
+end

+ 9 - 0
app/controllers/admin/settings/appearance_controller.rb

@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class Admin::Settings::AppearanceController < Admin::SettingsController
+  private
+
+  def after_update_redirect_path
+    admin_settings_appearance_path
+  end
+end

+ 9 - 0
app/controllers/admin/settings/branding_controller.rb

@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class Admin::Settings::BrandingController < Admin::SettingsController
+  private
+
+  def after_update_redirect_path
+    admin_settings_branding_path
+  end
+end

+ 9 - 0
app/controllers/admin/settings/content_retention_controller.rb

@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class Admin::Settings::ContentRetentionController < Admin::SettingsController
+  private
+
+  def after_update_redirect_path
+    admin_settings_content_retention_path
+  end
+end

+ 9 - 0
app/controllers/admin/settings/discovery_controller.rb

@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class Admin::Settings::DiscoveryController < Admin::SettingsController
+  private
+
+  def after_update_redirect_path
+    admin_settings_discovery_path
+  end
+end

+ 9 - 0
app/controllers/admin/settings/registrations_controller.rb

@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class Admin::Settings::RegistrationsController < Admin::SettingsController
+  private
+
+  def after_update_redirect_path
+    admin_settings_registrations_path
+  end
+end

+ 7 - 3
app/controllers/admin/settings_controller.rb

@@ -2,7 +2,7 @@
 
 module Admin
   class SettingsController < BaseController
-    def edit
+    def show
       authorize :settings, :show?
 
       @admin_settings = Form::AdminSettings.new
@@ -15,14 +15,18 @@ module Admin
 
       if @admin_settings.save
         flash[:notice] = I18n.t('generic.changes_saved_msg')
-        redirect_to edit_admin_settings_path
+        redirect_to after_update_redirect_path
       else
-        render :edit
+        render :show
       end
     end
 
     private
 
+    def after_update_redirect_path
+      raise NotImplementedError
+    end
+
     def settings_params
       params.require(:form_admin_settings).permit(*Form::AdminSettings::KEYS)
     end

+ 1 - 1
app/controllers/admin/site_uploads_controller.rb

@@ -9,7 +9,7 @@ module Admin
 
       @site_upload.destroy!
 
-      redirect_to edit_admin_settings_path, notice: I18n.t('admin.site_uploads.destroyed_msg')
+      redirect_to admin_settings_path, notice: I18n.t('admin.site_uploads.destroyed_msg')
     end
 
     private

+ 14 - 2
app/controllers/admin/statuses_controller.rb

@@ -3,17 +3,24 @@
 module Admin
   class StatusesController < BaseController
     before_action :set_account
-    before_action :set_statuses
+    before_action :set_statuses, except: :show
+    before_action :set_status, only: :show
 
     PER_PAGE = 20
 
     def index
-      authorize :status, :index?
+      authorize [:admin, :status], :index?
 
       @status_batch_action = Admin::StatusBatchAction.new
     end
 
+    def show
+      authorize [:admin, @status], :show?
+    end
+
     def batch
+      authorize [:admin, :status], :index?
+
       @status_batch_action = Admin::StatusBatchAction.new(admin_status_batch_action_params.merge(current_account: current_account, report_id: params[:report_id], type: action_from_button))
       @status_batch_action.save!
     rescue ActionController::ParameterMissing
@@ -30,6 +37,7 @@ module Admin
 
     def after_create_redirect_path
       report_id = @status_batch_action&.report_id || params[:report_id]
+
       if report_id.present?
         admin_report_path(report_id)
       else
@@ -41,6 +49,10 @@ module Admin
       @account = Account.find(params[:account_id])
     end
 
+    def set_status
+      @status = @account.statuses.find(params[:id])
+    end
+
     def set_statuses
       @statuses = Admin::StatusFilter.new(@account, filter_params).results.preload(:application, :preloadable_poll, :media_attachments, active_mentions: :account, reblog: [:account, :application, :preloadable_poll, :media_attachments, active_mentions: :account]).page(params[:page]).per(PER_PAGE)
     end

+ 0 - 20
app/controllers/admin/subscriptions_controller.rb

@@ -1,20 +0,0 @@
-# frozen_string_literal: true
-
-module Admin
-  class SubscriptionsController < BaseController
-    def index
-      authorize :subscription, :index?
-      @subscriptions = ordered_subscriptions.page(requested_page)
-    end
-
-    private
-
-    def ordered_subscriptions
-      Subscription.order(id: :desc).includes(:account)
-    end
-
-    def requested_page
-      params[:page].to_i
-    end
-  end
-end

+ 3 - 1
app/controllers/admin/tags_controller.rb

@@ -16,6 +16,8 @@ module Admin
       if @tag.update(tag_params.merge(reviewed_at: Time.now.utc))
         redirect_to admin_tag_path(@tag.id), notice: I18n.t('admin.tags.updated_msg')
       else
+        @time_period = (6.days.ago.to_date...Time.now.utc.to_date)
+
         render :show
       end
     end
@@ -27,7 +29,7 @@ module Admin
     end
 
     def tag_params
-      params.require(:tag).permit(:name, :trendable, :usable, :listable)
+      params.require(:tag).permit(:name, :display_name, :trendable, :usable, :listable)
     end
   end
 end

+ 4 - 2
app/controllers/admin/trends/links/preview_card_providers_controller.rb

@@ -2,17 +2,19 @@
 
 class Admin::Trends::Links::PreviewCardProvidersController < Admin::BaseController
   def index
-    authorize :preview_card_provider, :index?
+    authorize :preview_card_provider, :review?
 
     @preview_card_providers = filtered_preview_card_providers.page(params[:page])
     @form = Trends::PreviewCardProviderBatch.new
   end
 
   def batch
+    authorize :preview_card_provider, :review?
+
     @form = Trends::PreviewCardProviderBatch.new(trends_preview_card_provider_batch_params.merge(current_account: current_account, action: action_from_button))
     @form.save
   rescue ActionController::ParameterMissing
-    flash[:alert] = I18n.t('admin.accounts.no_account_selected')
+    flash[:alert] = I18n.t('admin.trends.links.publishers.no_publisher_selected')
   ensure
     redirect_to admin_trends_links_preview_card_providers_path(filter_params)
   end

+ 5 - 2
app/controllers/admin/trends/links_controller.rb

@@ -2,17 +2,20 @@
 
 class Admin::Trends::LinksController < Admin::BaseController
   def index
-    authorize :preview_card, :index?
+    authorize :preview_card, :review?
 
+    @locales       = PreviewCardTrend.pluck('distinct language')
     @preview_cards = filtered_preview_cards.page(params[:page])
     @form          = Trends::PreviewCardBatch.new
   end
 
   def batch
+    authorize :preview_card, :review?
+
     @form = Trends::PreviewCardBatch.new(trends_preview_card_batch_params.merge(current_account: current_account, action: action_from_button))
     @form.save
   rescue ActionController::ParameterMissing
-    flash[:alert] = I18n.t('admin.accounts.no_account_selected')
+    flash[:alert] = I18n.t('admin.trends.links.no_link_selected')
   ensure
     redirect_to admin_trends_links_path(filter_params)
   end

+ 5 - 2
app/controllers/admin/trends/statuses_controller.rb

@@ -2,17 +2,20 @@
 
 class Admin::Trends::StatusesController < Admin::BaseController
   def index
-    authorize :status, :index?
+    authorize [:admin, :status], :review?
 
+    @locales  = StatusTrend.pluck('distinct language')
     @statuses = filtered_statuses.page(params[:page])
     @form     = Trends::StatusBatch.new
   end
 
   def batch
+    authorize [:admin, :status], :review?
+
     @form = Trends::StatusBatch.new(trends_status_batch_params.merge(current_account: current_account, action: action_from_button))
     @form.save
   rescue ActionController::ParameterMissing
-    flash[:alert] = I18n.t('admin.accounts.no_account_selected')
+    flash[:alert] = I18n.t('admin.trends.statuses.no_status_selected')
   ensure
     redirect_to admin_trends_statuses_path(filter_params)
   end

+ 4 - 2
app/controllers/admin/trends/tags_controller.rb

@@ -2,17 +2,19 @@
 
 class Admin::Trends::TagsController < Admin::BaseController
   def index
-    authorize :tag, :index?
+    authorize :tag, :review?
 
     @tags = filtered_tags.page(params[:page])
     @form = Trends::TagBatch.new
   end
 
   def batch
+    authorize :tag, :review?
+
     @form = Trends::TagBatch.new(trends_tag_batch_params.merge(current_account: current_account, action: action_from_button))
     @form.save
   rescue ActionController::ParameterMissing
-    flash[:alert] = I18n.t('admin.accounts.no_account_selected')
+    flash[:alert] = I18n.t('admin.trends.tags.no_tag_selected')
   ensure
     redirect_to admin_trends_tags_path(filter_params)
   end

+ 34 - 0
app/controllers/admin/users/roles_controller.rb

@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+module Admin
+  class Users::RolesController < BaseController
+    before_action :set_user
+
+    def show
+      authorize @user, :change_role?
+    end
+
+    def update
+      authorize @user, :change_role?
+
+      @user.current_account = current_account
+
+      if @user.update(resource_params)
+        log_action :change_role, @user
+        redirect_to admin_account_path(@user.account_id), notice: I18n.t('admin.accounts.change_role.changed_msg')
+      else
+        render :show
+      end
+    end
+
+    private
+
+    def set_user
+      @user = User.find(params[:user_id])
+    end
+
+    def resource_params
+      params.require(:user).permit(:role_id)
+    end
+  end
+end

+ 1 - 1
app/controllers/admin/two_factor_authentications_controller.rb → app/controllers/admin/users/two_factor_authentications_controller.rb

@@ -1,7 +1,7 @@
 # frozen_string_literal: true
 
 module Admin
-  class TwoFactorAuthenticationsController < BaseController
+  class Users::TwoFactorAuthenticationsController < BaseController
     before_action :set_target_user
 
     def destroy

+ 19 - 0
app/controllers/admin/webhooks/secrets_controller.rb

@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module Admin
+  class Webhooks::SecretsController < BaseController
+    before_action :set_webhook
+
+    def rotate
+      authorize @webhook, :rotate_secret?
+      @webhook.rotate_secret!
+      redirect_to admin_webhook_path(@webhook)
+    end
+
+    private
+
+    def set_webhook
+      @webhook = Webhook.find(params[:webhook_id])
+    end
+  end
+end

+ 77 - 0
app/controllers/admin/webhooks_controller.rb

@@ -0,0 +1,77 @@
+# frozen_string_literal: true
+
+module Admin
+  class WebhooksController < BaseController
+    before_action :set_webhook, except: [:index, :new, :create]
+
+    def index
+      authorize :webhook, :index?
+
+      @webhooks = Webhook.page(params[:page])
+    end
+
+    def new
+      authorize :webhook, :create?
+
+      @webhook = Webhook.new
+    end
+
+    def create
+      authorize :webhook, :create?
+
+      @webhook = Webhook.new(resource_params)
+
+      if @webhook.save
+        redirect_to admin_webhook_path(@webhook)
+      else
+        render :new
+      end
+    end
+
+    def show
+      authorize @webhook, :show?
+    end
+
+    def edit
+      authorize @webhook, :update?
+    end
+
+    def update
+      authorize @webhook, :update?
+
+      if @webhook.update(resource_params)
+        redirect_to admin_webhook_path(@webhook)
+      else
+        render :show
+      end
+    end
+
+    def enable
+      authorize @webhook, :enable?
+      @webhook.enable!
+      redirect_to admin_webhook_path(@webhook)
+    end
+
+    def disable
+      authorize @webhook, :disable?
+      @webhook.disable!
+      redirect_to admin_webhook_path(@webhook)
+    end
+
+    def destroy
+      authorize @webhook, :destroy?
+      @webhook.destroy!
+      redirect_to admin_webhooks_path
+    end
+
+    private
+
+    def set_webhook
+      @webhook = Webhook.find(params[:id])
+    end
+
+    def resource_params
+      params.require(:webhook).permit(:url, events: [])
+    end
+  end
+end

+ 12 - 2
app/controllers/api/base_controller.rb

@@ -24,6 +24,10 @@ class Api::BaseController < ApplicationController
     render json: { error: 'Duplicate record' }, status: 422
   end
 
+  rescue_from Date::Error do
+    render json: { error: 'Invalid date supplied' }, status: 422
+  end
+
   rescue_from ActiveRecord::RecordNotFound do
     render json: { error: 'Record not found' }, status: 404
   end
@@ -53,7 +57,7 @@ class Api::BaseController < ApplicationController
     render json: { error: I18n.t('errors.429') }, status: 429
   end
 
-  rescue_from ActionController::ParameterMissing do |e|
+  rescue_from ActionController::ParameterMissing, Mastodon::InvalidParameterError do |e|
     render json: { error: e.to_s }, status: 400
   end
 
@@ -129,6 +133,12 @@ class Api::BaseController < ApplicationController
   end
 
   def disallow_unauthenticated_api_access?
-    authorized_fetch_mode?
+    ENV['DISALLOW_UNAUTHENTICATED_API_ACCESS'] == 'true' || Rails.configuration.x.whitelist_mode
+  end
+
+  private
+
+  def respond_with_error(code)
+    render json: { error: Rack::Utils::HTTP_STATUS_CODES[code] }, status: code
   end
 end

+ 1 - 1
app/controllers/api/v1/accounts/follower_accounts_controller.rb

@@ -1,7 +1,7 @@
 # frozen_string_literal: true
 
 class Api::V1::Accounts::FollowerAccountsController < Api::BaseController
-  before_action -> { doorkeeper_authorize! :read, :'read:accounts' }
+  before_action -> { authorize_if_got_token! :read, :'read:accounts' }
   before_action :set_account
   after_action :insert_pagination_headers
 

+ 1 - 1
app/controllers/api/v1/accounts/following_accounts_controller.rb

@@ -1,7 +1,7 @@
 # frozen_string_literal: true
 
 class Api::V1::Accounts::FollowingAccountsController < Api::BaseController
-  before_action -> { doorkeeper_authorize! :read, :'read:accounts' }
+  before_action -> { authorize_if_got_token! :read, :'read:accounts' }
   before_action :set_account
   after_action :insert_pagination_headers
 

+ 1 - 1
app/controllers/api/v1/accounts/pins_controller.rb

@@ -8,7 +8,7 @@ class Api::V1::Accounts::PinsController < Api::BaseController
   before_action :set_account
 
   def create
-    AccountPin.create!(account: current_account, target_account: @account)
+    AccountPin.find_or_create_by!(account: current_account, target_account: @account)
     render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships_presenter
   end
 

+ 3 - 3
app/controllers/api/v1/accounts_controller.rb

@@ -30,12 +30,12 @@ class Api::V1::AccountsController < Api::BaseController
     self.response_body = Oj.dump(response.body)
     self.status        = response.status
   rescue ActiveRecord::RecordInvalid => e
-    render json: ValidationErrorFormatter.new(e, :'account.username' => :username, :'invite_request.text' => :reason).as_json, status: :unprocessable_entity
+    render json: ValidationErrorFormatter.new(e, 'account.username': :username, 'invite_request.text': :reason).as_json, status: :unprocessable_entity
   end
 
   def follow
-    follow  = FollowService.new.call(current_user.account, @account, reblogs: params.key?(:reblogs) ? truthy_param?(:reblogs) : nil, notify: params.key?(:notify) ? truthy_param?(:notify) : nil, with_rate_limit: true)
-    options = @account.locked? || current_user.account.silenced? ? {} : { following_map: { @account.id => { reblogs: follow.show_reblogs?, notify: follow.notify? } }, requested_map: { @account.id => false } }
+    follow  = FollowService.new.call(current_user.account, @account, reblogs: params.key?(:reblogs) ? truthy_param?(:reblogs) : nil, notify: params.key?(:notify) ? truthy_param?(:notify) : nil, languages: params.key?(:languages) ? params[:languages] : nil, with_rate_limit: true)
+    options = @account.locked? || current_user.account.silenced? ? {} : { following_map: { @account.id => { reblogs: follow.show_reblogs?, notify: follow.notify?, languages: follow.languages } }, requested_map: { @account.id => false } }
 
     render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships(**options)
   end

+ 6 - 1
app/controllers/api/v1/admin/account_actions_controller.rb

@@ -1,11 +1,16 @@
 # frozen_string_literal: true
 
 class Api::V1::Admin::AccountActionsController < Api::BaseController
+  include Authorization
+
   before_action -> { authorize_if_got_token! :'admin:write', :'admin:write:accounts' }
-  before_action :require_staff!
   before_action :set_account
 
+  after_action :verify_authorized
+
   def create
+    authorize @account, :show?
+
     account_action                 = Admin::AccountAction.new(resource_params)
     account_action.target_account  = @account
     account_action.current_account = current_account

+ 6 - 5
app/controllers/api/v1/admin/accounts_controller.rb

@@ -8,11 +8,11 @@ class Api::V1::Admin::AccountsController < Api::BaseController
 
   before_action -> { authorize_if_got_token! :'admin:read', :'admin:read:accounts' }, only: [:index, :show]
   before_action -> { authorize_if_got_token! :'admin:write', :'admin:write:accounts' }, except: [:index, :show]
-  before_action :require_staff!
   before_action :set_accounts, only: :index
   before_action :set_account, except: :index
   before_action :require_local_account!, only: [:enable, :approve, :reject]
 
+  after_action :verify_authorized
   after_action :insert_pagination_headers, only: :index
 
   FILTER_PARAMS = %i(
@@ -60,14 +60,13 @@ class Api::V1::Admin::AccountsController < Api::BaseController
   def reject
     authorize @account.user, :reject?
     DeleteAccountService.new.call(@account, reserve_email: false, reserve_username: false)
-    render json: @account, serializer: REST::Admin::AccountSerializer
+    render_empty
   end
 
   def destroy
     authorize @account, :destroy?
-    json = render_to_body json: @account, serializer: REST::Admin::AccountSerializer
     Admin::AccountDeletionWorker.perform_async(@account.id)
-    render json: json
+    render_empty
   end
 
   def unsensitive
@@ -119,7 +118,9 @@ class Api::V1::Admin::AccountsController < Api::BaseController
       translated_params[:status] = status.to_s if params[status].present?
     end
 
-    translated_params[:permissions] = 'staff' if params[:staff].present?
+    if params[:staff].present?
+      translated_params[:role_ids] = UserRole.that_can(:manage_reports).map(&:id)
+    end
 
     translated_params
   end

+ 95 - 0
app/controllers/api/v1/admin/canonical_email_blocks_controller.rb

@@ -0,0 +1,95 @@
+# frozen_string_literal: true
+
+class Api::V1::Admin::CanonicalEmailBlocksController < Api::BaseController
+  include Authorization
+  include AccountableConcern
+
+  LIMIT = 100
+
+  before_action -> { authorize_if_got_token! :'admin:read', :'admin:read:canonical_email_blocks' }, only: [:index, :show, :test]
+  before_action -> { authorize_if_got_token! :'admin:write', :'admin:write:canonical_email_blocks' }, except: [:index, :show, :test]
+
+  before_action :set_canonical_email_blocks, only: :index
+  before_action :set_canonical_email_blocks_from_test, only: [:test]
+  before_action :set_canonical_email_block, only: [:show, :destroy]
+
+  after_action :verify_authorized
+  after_action :insert_pagination_headers, only: :index
+
+  PAGINATION_PARAMS = %i(limit).freeze
+
+  def index
+    authorize :canonical_email_block, :index?
+    render json: @canonical_email_blocks, each_serializer: REST::Admin::CanonicalEmailBlockSerializer
+  end
+
+  def show
+    authorize @canonical_email_block, :show?
+    render json: @canonical_email_block, serializer: REST::Admin::CanonicalEmailBlockSerializer
+  end
+
+  def test
+    authorize :canonical_email_block, :test?
+    render json: @canonical_email_blocks, each_serializer: REST::Admin::CanonicalEmailBlockSerializer
+  end
+
+  def create
+    authorize :canonical_email_block, :create?
+    @canonical_email_block = CanonicalEmailBlock.create!(resource_params)
+    log_action :create, @canonical_email_block
+    render json: @canonical_email_block, serializer: REST::Admin::CanonicalEmailBlockSerializer
+  end
+
+  def destroy
+    authorize @canonical_email_block, :destroy?
+    @canonical_email_block.destroy!
+    log_action :destroy, @canonical_email_block
+    render_empty
+  end
+
+  private
+
+  def resource_params
+    params.permit(:canonical_email_hash, :email)
+  end
+
+  def set_canonical_email_blocks
+    @canonical_email_blocks = CanonicalEmailBlock.order(id: :desc).to_a_paginated_by_id(limit_param(LIMIT), params_slice(:max_id, :since_id, :min_id))
+  end
+
+  def set_canonical_email_blocks_from_test
+    @canonical_email_blocks = CanonicalEmailBlock.matching_email(params[:email])
+  end
+
+  def set_canonical_email_block
+    @canonical_email_block = CanonicalEmailBlock.find(params[:id])
+  end
+
+  def insert_pagination_headers
+    set_pagination_headers(next_path, prev_path)
+  end
+
+  def next_path
+    api_v1_admin_canonical_email_blocks_url(pagination_params(max_id: pagination_max_id)) if records_continue?
+  end
+
+  def prev_path
+    api_v1_admin_canonical_email_blocks_url(pagination_params(min_id: pagination_since_id)) unless @canonical_email_blocks.empty?
+  end
+
+  def pagination_max_id
+    @canonical_email_blocks.last.id
+  end
+
+  def pagination_since_id
+    @canonical_email_blocks.first.id
+  end
+
+  def records_continue?
+    @canonical_email_blocks.size == limit_param(LIMIT)
+  end
+
+  def pagination_params(core_params)
+    params.slice(*PAGINATION_PARAMS).permit(*PAGINATION_PARAMS).merge(core_params)
+  end
+end

+ 5 - 1
app/controllers/api/v1/admin/dimensions_controller.rb

@@ -1,11 +1,15 @@
 # frozen_string_literal: true
 
 class Api::V1::Admin::DimensionsController < Api::BaseController
+  include Authorization
+
   before_action -> { authorize_if_got_token! :'admin:read' }
-  before_action :require_staff!
   before_action :set_dimensions
 
+  after_action :verify_authorized
+
   def create
+    authorize :dashboard, :index?
     render json: @dimensions, each_serializer: REST::Admin::DimensionSerializer
   end
 

+ 95 - 0
app/controllers/api/v1/admin/domain_allows_controller.rb

@@ -0,0 +1,95 @@
+# frozen_string_literal: true
+
+class Api::V1::Admin::DomainAllowsController < Api::BaseController
+  include Authorization
+  include AccountableConcern
+
+  LIMIT = 100
+
+  before_action -> { authorize_if_got_token! :'admin:read', :'admin:read:domain_allows' }, only: [:index, :show]
+  before_action -> { authorize_if_got_token! :'admin:write', :'admin:write:domain_allows' }, except: [:index, :show]
+  before_action :set_domain_allows, only: :index
+  before_action :set_domain_allow, only: [:show, :destroy]
+
+  after_action :verify_authorized
+  after_action :insert_pagination_headers, only: :index
+
+  PAGINATION_PARAMS = %i(limit).freeze
+
+  def create
+    authorize :domain_allow, :create?
+
+    @domain_allow = DomainAllow.find_by(resource_params)
+
+    if @domain_allow.nil?
+      @domain_allow = DomainAllow.create!(resource_params)
+      log_action :create, @domain_allow
+    end
+
+    render json: @domain_allow, serializer: REST::Admin::DomainAllowSerializer
+  end
+
+  def index
+    authorize :domain_allow, :index?
+    render json: @domain_allows, each_serializer: REST::Admin::DomainAllowSerializer
+  end
+
+  def show
+    authorize @domain_allow, :show?
+    render json: @domain_allow, serializer: REST::Admin::DomainAllowSerializer
+  end
+
+  def destroy
+    authorize @domain_allow, :destroy?
+    UnallowDomainService.new.call(@domain_allow)
+    log_action :destroy, @domain_allow
+    render_empty
+  end
+
+  private
+
+  def set_domain_allows
+    @domain_allows = filtered_domain_allows.order(id: :desc).to_a_paginated_by_id(limit_param(LIMIT), params_slice(:max_id, :since_id, :min_id))
+  end
+
+  def set_domain_allow
+    @domain_allow = DomainAllow.find(params[:id])
+  end
+
+  def filtered_domain_allows
+    # TODO: no filtering yet
+    DomainAllow.all
+  end
+
+  def insert_pagination_headers
+    set_pagination_headers(next_path, prev_path)
+  end
+
+  def next_path
+    api_v1_admin_domain_allows_url(pagination_params(max_id: pagination_max_id)) if records_continue?
+  end
+
+  def prev_path
+    api_v1_admin_domain_allows_url(pagination_params(min_id: pagination_since_id)) unless @domain_allows.empty?
+  end
+
+  def pagination_max_id
+    @domain_allows.last.id
+  end
+
+  def pagination_since_id
+    @domain_allows.first.id
+  end
+
+  def records_continue?
+    @domain_allows.size == limit_param(LIMIT)
+  end
+
+  def pagination_params(core_params)
+    params.slice(*PAGINATION_PARAMS).permit(*PAGINATION_PARAMS).merge(core_params)
+  end
+
+  def resource_params
+    params.permit(:domain)
+  end
+end

+ 108 - 0
app/controllers/api/v1/admin/domain_blocks_controller.rb

@@ -0,0 +1,108 @@
+# frozen_string_literal: true
+
+class Api::V1::Admin::DomainBlocksController < Api::BaseController
+  include Authorization
+  include AccountableConcern
+
+  LIMIT = 100
+
+  before_action -> { authorize_if_got_token! :'admin:read', :'admin:read:domain_blocks' }, only: [:index, :show]
+  before_action -> { authorize_if_got_token! :'admin:write', :'admin:write:domain_blocks' }, except: [:index, :show]
+  before_action :set_domain_blocks, only: :index
+  before_action :set_domain_block, only: [:show, :update, :destroy]
+
+  after_action :verify_authorized
+  after_action :insert_pagination_headers, only: :index
+
+  PAGINATION_PARAMS = %i(limit).freeze
+
+  def create
+    authorize :domain_block, :create?
+
+    existing_domain_block = resource_params[:domain].present? ? DomainBlock.rule_for(resource_params[:domain]) : nil
+    return render json: existing_domain_block, serializer: REST::Admin::ExistingDomainBlockErrorSerializer, status: 422 if existing_domain_block.present?
+
+    @domain_block = DomainBlock.create!(resource_params)
+    DomainBlockWorker.perform_async(@domain_block.id)
+    log_action :create, @domain_block
+    render json: @domain_block, serializer: REST::Admin::DomainBlockSerializer
+  end
+
+  def index
+    authorize :domain_block, :index?
+    render json: @domain_blocks, each_serializer: REST::Admin::DomainBlockSerializer
+  end
+
+  def show
+    authorize @domain_block, :show?
+    render json: @domain_block, serializer: REST::Admin::DomainBlockSerializer
+  end
+
+  def update
+    authorize @domain_block, :update?
+    @domain_block.update(domain_block_params)
+    severity_changed = @domain_block.severity_changed?
+    @domain_block.save!
+    DomainBlockWorker.perform_async(@domain_block.id, severity_changed)
+    log_action :update, @domain_block
+    render json: @domain_block, serializer: REST::Admin::DomainBlockSerializer
+  end
+
+  def destroy
+    authorize @domain_block, :destroy?
+    UnblockDomainService.new.call(@domain_block)
+    log_action :destroy, @domain_block
+    render_empty
+  end
+
+  private
+
+  def set_domain_blocks
+    @domain_blocks = filtered_domain_blocks.order(id: :desc).to_a_paginated_by_id(limit_param(LIMIT), params_slice(:max_id, :since_id, :min_id))
+  end
+
+  def set_domain_block
+    @domain_block = DomainBlock.find(params[:id])
+  end
+
+  def filtered_domain_blocks
+    # TODO: no filtering yet
+    DomainBlock.all
+  end
+
+  def domain_block_params
+    params.permit(:severity, :reject_media, :reject_reports, :private_comment, :public_comment, :obfuscate)
+  end
+
+  def insert_pagination_headers
+    set_pagination_headers(next_path, prev_path)
+  end
+
+  def next_path
+    api_v1_admin_domain_blocks_url(pagination_params(max_id: pagination_max_id)) if records_continue?
+  end
+
+  def prev_path
+    api_v1_admin_domain_blocks_url(pagination_params(min_id: pagination_since_id)) unless @domain_blocks.empty?
+  end
+
+  def pagination_max_id
+    @domain_blocks.last.id
+  end
+
+  def pagination_since_id
+    @domain_blocks.first.id
+  end
+
+  def records_continue?
+    @domain_blocks.size == limit_param(LIMIT)
+  end
+
+  def pagination_params(core_params)
+    params.slice(*PAGINATION_PARAMS).permit(*PAGINATION_PARAMS).merge(core_params)
+  end
+
+  def resource_params
+    params.permit(:domain, :severity, :reject_media, :reject_reports, :private_comment, :public_comment, :obfuscate)
+  end
+end

+ 88 - 0
app/controllers/api/v1/admin/email_domain_blocks_controller.rb

@@ -0,0 +1,88 @@
+# frozen_string_literal: true
+
+class Api::V1::Admin::EmailDomainBlocksController < Api::BaseController
+  include Authorization
+  include AccountableConcern
+
+  LIMIT = 100
+
+  before_action -> { authorize_if_got_token! :'admin:read', :'admin:read:email_domain_blocks' }, only: [:index, :show]
+  before_action -> { authorize_if_got_token! :'admin:write', :'admin:write:email_domain_blocks' }, except: [:index, :show]
+  before_action :set_email_domain_blocks, only: :index
+  before_action :set_email_domain_block, only: [:show, :destroy]
+
+  after_action :verify_authorized
+  after_action :insert_pagination_headers, only: :index
+
+  PAGINATION_PARAMS = %i(
+    limit
+  ).freeze
+
+  def create
+    authorize :email_domain_block, :create?
+
+    @email_domain_block = EmailDomainBlock.create!(resource_params)
+    log_action :create, @email_domain_block
+
+    render json: @email_domain_block, serializer: REST::Admin::EmailDomainBlockSerializer
+  end
+
+  def index
+    authorize :email_domain_block, :index?
+    render json: @email_domain_blocks, each_serializer: REST::Admin::EmailDomainBlockSerializer
+  end
+
+  def show
+    authorize @email_domain_block, :show?
+    render json: @email_domain_block, serializer: REST::Admin::EmailDomainBlockSerializer
+  end
+
+  def destroy
+    authorize @email_domain_block, :destroy?
+    @email_domain_block.destroy!
+    log_action :destroy, @email_domain_block
+    render_empty
+  end
+
+  private
+
+  def set_email_domain_blocks
+    @email_domain_blocks = EmailDomainBlock.order(id: :desc).to_a_paginated_by_id(limit_param(LIMIT), params_slice(:max_id, :since_id, :min_id))
+  end
+
+  def set_email_domain_block
+    @email_domain_block = EmailDomainBlock.find(params[:id])
+  end
+
+  def resource_params
+    params.permit(:domain)
+  end
+
+  def insert_pagination_headers
+    set_pagination_headers(next_path, prev_path)
+  end
+
+  def next_path
+    api_v1_admin_email_domain_blocks_url(pagination_params(max_id: pagination_max_id)) if records_continue?
+  end
+
+  def prev_path
+    api_v1_admin_email_domain_blocks_url(pagination_params(min_id: pagination_since_id)) unless @email_domain_blocks.empty?
+  end
+
+  def pagination_max_id
+    @email_domain_blocks.last.id
+  end
+
+  def pagination_since_id
+    @email_domain_blocks.first.id
+  end
+
+  def records_continue?
+    @email_domain_blocks.size == limit_param(LIMIT)
+  end
+
+  def pagination_params(core_params)
+    params.slice(*PAGINATION_PARAMS).permit(*PAGINATION_PARAMS).merge(core_params)
+  end
+end

+ 93 - 0
app/controllers/api/v1/admin/ip_blocks_controller.rb

@@ -0,0 +1,93 @@
+# frozen_string_literal: true
+
+class Api::V1::Admin::IpBlocksController < Api::BaseController
+  include Authorization
+  include AccountableConcern
+
+  LIMIT = 100
+
+  before_action -> { authorize_if_got_token! :'admin:read', :'admin:read:ip_blocks' }, only: [:index, :show]
+  before_action -> { authorize_if_got_token! :'admin:write', :'admin:write:ip_blocks' }, except: [:index, :show]
+  before_action :set_ip_blocks, only: :index
+  before_action :set_ip_block, only: [:show, :update, :destroy]
+
+  after_action :verify_authorized
+  after_action :insert_pagination_headers, only: :index
+
+  PAGINATION_PARAMS = %i(
+    limit
+  ).freeze
+
+  def create
+    authorize :ip_block, :create?
+    @ip_block = IpBlock.create!(resource_params)
+    log_action :create, @ip_block
+    render json: @ip_block, serializer: REST::Admin::IpBlockSerializer
+  end
+
+  def index
+    authorize :ip_block, :index?
+    render json: @ip_blocks, each_serializer: REST::Admin::IpBlockSerializer
+  end
+
+  def show
+    authorize @ip_block, :show?
+    render json: @ip_block, serializer: REST::Admin::IpBlockSerializer
+  end
+
+  def update
+    authorize @ip_block, :update?
+    @ip_block.update(resource_params)
+    log_action :update, @ip_block
+    render json: @ip_block, serializer: REST::Admin::IpBlockSerializer
+  end
+
+  def destroy
+    authorize @ip_block, :destroy?
+    @ip_block.destroy!
+    log_action :destroy, @ip_block
+    render_empty
+  end
+
+  private
+
+  def set_ip_blocks
+    @ip_blocks = IpBlock.order(id: :desc).to_a_paginated_by_id(limit_param(LIMIT), params_slice(:max_id, :since_id, :min_id))
+  end
+
+  def set_ip_block
+    @ip_block = IpBlock.find(params[:id])
+  end
+
+  def resource_params
+    params.permit(:ip, :severity, :comment, :expires_in)
+  end
+
+  def insert_pagination_headers
+    set_pagination_headers(next_path, prev_path)
+  end
+
+  def next_path
+    api_v1_admin_ip_blocks_url(pagination_params(max_id: pagination_max_id)) if records_continue?
+  end
+
+  def prev_path
+    api_v1_admin_ip_blocks_url(pagination_params(min_id: pagination_since_id)) unless @ip_blocks.empty?
+  end
+
+  def pagination_max_id
+    @ip_blocks.last.id
+  end
+
+  def pagination_since_id
+    @ip_blocks.first.id
+  end
+
+  def records_continue?
+    @ip_blocks.size == limit_param(LIMIT)
+  end
+
+  def pagination_params(core_params)
+    params.slice(*PAGINATION_PARAMS).permit(*PAGINATION_PARAMS).merge(core_params)
+  end
+end

+ 5 - 1
app/controllers/api/v1/admin/measures_controller.rb

@@ -1,11 +1,15 @@
 # frozen_string_literal: true
 
 class Api::V1::Admin::MeasuresController < Api::BaseController
+  include Authorization
+
   before_action -> { authorize_if_got_token! :'admin:read' }
-  before_action :require_staff!
   before_action :set_measures
 
+  after_action :verify_authorized
+
   def create
+    authorize :dashboard, :index?
     render json: @measures, each_serializer: REST::Admin::MeasureSerializer
   end
 

+ 1 - 1
app/controllers/api/v1/admin/reports_controller.rb

@@ -8,10 +8,10 @@ class Api::V1::Admin::ReportsController < Api::BaseController
 
   before_action -> { authorize_if_got_token! :'admin:read', :'admin:read:reports' }, only: [:index, :show]
   before_action -> { authorize_if_got_token! :'admin:write', :'admin:write:reports' }, except: [:index, :show]
-  before_action :require_staff!
   before_action :set_reports, only: :index
   before_action :set_report, except: :index
 
+  after_action :verify_authorized
   after_action :insert_pagination_headers, only: :index
 
   FILTER_PARAMS = %i(

+ 5 - 1
app/controllers/api/v1/admin/retention_controller.rb

@@ -1,11 +1,15 @@
 # frozen_string_literal: true
 
 class Api::V1::Admin::RetentionController < Api::BaseController
+  include Authorization
+
   before_action -> { authorize_if_got_token! :'admin:read' }
-  before_action :require_staff!
   before_action :set_cohorts
 
+  after_action :verify_authorized
+
   def create
+    authorize :dashboard, :index?
     render json: @cohorts, each_serializer: REST::Admin::CohortSerializer
   end
 

+ 11 - 9
app/controllers/api/v1/admin/trends/links_controller.rb

@@ -1,17 +1,19 @@
 # frozen_string_literal: true
 
-class Api::V1::Admin::Trends::LinksController < Api::BaseController
+class Api::V1::Admin::Trends::LinksController < Api::V1::Trends::LinksController
   before_action -> { authorize_if_got_token! :'admin:read' }
-  before_action :require_staff!
-  before_action :set_links
-
-  def index
-    render json: @links, each_serializer: REST::Trends::LinkSerializer
-  end
 
   private
 
-  def set_links
-    @links = Trends.links.query.limit(limit_param(10))
+  def enabled?
+    super || current_user&.can?(:manage_taxonomies)
+  end
+
+  def links_from_trends
+    if current_user&.can?(:manage_taxonomies)
+      Trends.links.query
+    else
+      super
+    end
   end
 end

+ 11 - 9
app/controllers/api/v1/admin/trends/statuses_controller.rb

@@ -1,17 +1,19 @@
 # frozen_string_literal: true
 
-class Api::V1::Admin::Trends::StatusesController < Api::BaseController
+class Api::V1::Admin::Trends::StatusesController < Api::V1::Trends::StatusesController
   before_action -> { authorize_if_got_token! :'admin:read' }
-  before_action :require_staff!
-  before_action :set_statuses
-
-  def index
-    render json: @statuses, each_serializer: REST::StatusSerializer
-  end
 
   private
 
-  def set_statuses
-    @statuses = cache_collection(Trends.statuses.query.limit(limit_param(DEFAULT_STATUSES_LIMIT)), Status)
+  def enabled?
+    super || current_user&.can?(:manage_taxonomies)
+  end
+
+  def statuses_from_trends
+    if current_user&.can?(:manage_taxonomies)
+      Trends.statuses.query
+    else
+      super
+    end
   end
 end

+ 11 - 9
app/controllers/api/v1/admin/trends/tags_controller.rb

@@ -1,17 +1,19 @@
 # frozen_string_literal: true
 
-class Api::V1::Admin::Trends::TagsController < Api::BaseController
+class Api::V1::Admin::Trends::TagsController < Api::V1::Trends::TagsController
   before_action -> { authorize_if_got_token! :'admin:read' }
-  before_action :require_staff!
-  before_action :set_tags
-
-  def index
-    render json: @tags, each_serializer: REST::Admin::TagSerializer
-  end
 
   private
 
-  def set_tags
-    @tags = Trends.tags.query.limit(limit_param(10))
+  def enabled?
+    super || current_user&.can?(:manage_taxonomies)
+  end
+
+  def tags_from_trends
+    if current_user&.can?(:manage_taxonomies)
+      Trends.tags.query
+    else
+      super
+    end
   end
 end

+ 1 - 1
app/controllers/api/v1/featured_tags/suggestions_controller.rb

@@ -6,7 +6,7 @@ class Api::V1::FeaturedTags::SuggestionsController < Api::BaseController
   before_action :set_recently_used_tags, only: :index
 
   def index
-    render json: @recently_used_tags, each_serializer: REST::TagSerializer
+    render json: @recently_used_tags, each_serializer: REST::TagSerializer, relationships: TagRelationshipsPresenter.new(@recently_used_tags, current_user&.account_id)
   end
 
   private

+ 3 - 5
app/controllers/api/v1/featured_tags_controller.rb

@@ -13,14 +13,12 @@ class Api::V1::FeaturedTagsController < Api::BaseController
   end
 
   def create
-    @featured_tag = current_account.featured_tags.new(featured_tag_params)
-    @featured_tag.reset_data
-    @featured_tag.save!
-    render json: @featured_tag, serializer: REST::FeaturedTagSerializer
+    featured_tag = CreateFeaturedTagService.new.call(current_account, featured_tag_params[:name])
+    render json: featured_tag, serializer: REST::FeaturedTagSerializer
   end
 
   def destroy
-    @featured_tag.destroy!
+    RemoveFeaturedTagWorker.perform_async(current_account.id, @featured_tag.id)
     render_empty
   end
 

+ 28 - 9
app/controllers/api/v1/filters_controller.rb

@@ -8,21 +8,32 @@ class Api::V1::FiltersController < Api::BaseController
   before_action :set_filter, only: [:show, :update, :destroy]
 
   def index
-    render json: @filters, each_serializer: REST::FilterSerializer
+    render json: @filters, each_serializer: REST::V1::FilterSerializer
   end
 
   def create
-    @filter = current_account.custom_filters.create!(resource_params)
-    render json: @filter, serializer: REST::FilterSerializer
+    ApplicationRecord.transaction do
+      filter_category = current_account.custom_filters.create!(resource_params)
+      @filter = filter_category.keywords.create!(keyword_params)
+    end
+
+    render json: @filter, serializer: REST::V1::FilterSerializer
   end
 
   def show
-    render json: @filter, serializer: REST::FilterSerializer
+    render json: @filter, serializer: REST::V1::FilterSerializer
   end
 
   def update
-    @filter.update!(resource_params)
-    render json: @filter, serializer: REST::FilterSerializer
+    ApplicationRecord.transaction do
+      @filter.update!(keyword_params)
+      @filter.custom_filter.assign_attributes(filter_params)
+      raise Mastodon::ValidationError, I18n.t('filters.errors.deprecated_api_multiple_keywords') if @filter.custom_filter.changed? && @filter.custom_filter.keywords.count > 1
+
+      @filter.custom_filter.save!
+    end
+
+    render json: @filter, serializer: REST::V1::FilterSerializer
   end
 
   def destroy
@@ -33,14 +44,22 @@ class Api::V1::FiltersController < Api::BaseController
   private
 
   def set_filters
-    @filters = current_account.custom_filters
+    @filters = CustomFilterKeyword.includes(:custom_filter).where(custom_filter: { account: current_account })
   end
 
   def set_filter
-    @filter = current_account.custom_filters.find(params[:id])
+    @filter = CustomFilterKeyword.includes(:custom_filter).where(custom_filter: { account: current_account }).find(params[:id])
   end
 
   def resource_params
-    params.permit(:phrase, :expires_in, :irreversible, :whole_word, context: [])
+    params.permit(:phrase, :expires_in, :irreversible, context: [])
+  end
+
+  def filter_params
+    resource_params.slice(:expires_in, :irreversible, :context)
+  end
+
+  def keyword_params
+    resource_params.slice(:phrase, :whole_word)
   end
 end

+ 52 - 0
app/controllers/api/v1/followed_tags_controller.rb

@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+class Api::V1::FollowedTagsController < Api::BaseController
+  TAGS_LIMIT = 100
+
+  before_action -> { doorkeeper_authorize! :follow, :read, :'read:follows' }, except: :show
+  before_action :require_user!
+  before_action :set_results
+
+  after_action :insert_pagination_headers, only: :show
+
+  def index
+    render json: @results.map(&:tag), each_serializer: REST::TagSerializer, relationships: TagRelationshipsPresenter.new(@results.map(&:tag), current_user&.account_id)
+  end
+
+  private
+
+  def set_results
+    @results = TagFollow.where(account: current_account).joins(:tag).eager_load(:tag).to_a_paginated_by_id(
+      limit_param(TAGS_LIMIT),
+      params_slice(:max_id, :since_id, :min_id)
+    )
+  end
+
+  def insert_pagination_headers
+    set_pagination_headers(next_path, prev_path)
+  end
+
+  def next_path
+    api_v1_followed_tags_url pagination_params(max_id: pagination_max_id) if records_continue?
+  end
+
+  def prev_path
+    api_v1_followed_tags_url pagination_params(since_id: pagination_since_id) unless @results.empty?
+  end
+
+  def pagination_max_id
+    @results.last.id
+  end
+
+  def pagination_since_id
+    @results.first.id
+  end
+
+  def records_continue?
+    @results.size == limit_param(TAG_LIMIT)
+  end
+
+  def pagination_params(core_params)
+    params.slice(:limit).permit(:limit).merge(core_params)
+  end
+end

+ 23 - 0
app/controllers/api/v1/instances/domain_blocks_controller.rb

@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+class Api::V1::Instances::DomainBlocksController < Api::BaseController
+  skip_before_action :require_authenticated_user!, unless: :whitelist_mode?
+
+  before_action :require_enabled_api!
+  before_action :set_domain_blocks
+
+  def index
+    expires_in 3.minutes, public: true
+    render json: @domain_blocks, each_serializer: REST::DomainBlockSerializer, with_comment: (Setting.show_domain_blocks_rationale == 'all' || (Setting.show_domain_blocks_rationale == 'users' && user_signed_in?))
+  end
+
+  private
+
+  def require_enabled_api!
+    head 404 unless Setting.show_domain_blocks == 'all' || (Setting.show_domain_blocks == 'users' && user_signed_in?)
+  end
+
+  def set_domain_blocks
+    @domain_blocks = DomainBlock.with_user_facing_limitations.by_severity
+  end
+end

+ 18 - 0
app/controllers/api/v1/instances/extended_descriptions_controller.rb

@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+class Api::V1::Instances::ExtendedDescriptionsController < Api::BaseController
+  skip_before_action :require_authenticated_user!, unless: :whitelist_mode?
+
+  before_action :set_extended_description
+
+  def show
+    expires_in 3.minutes, public: true
+    render json: @extended_description, serializer: REST::ExtendedDescriptionSerializer
+  end
+
+  private
+
+  def set_extended_description
+    @extended_description = ExtendedDescription.current
+  end
+end

+ 18 - 0
app/controllers/api/v1/instances/privacy_policies_controller.rb

@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+class Api::V1::Instances::PrivacyPoliciesController < Api::BaseController
+  skip_before_action :require_authenticated_user!, unless: :whitelist_mode?
+
+  before_action :set_privacy_policy
+
+  def show
+    expires_in 1.day, public: true
+    render json: @privacy_policy, serializer: REST::PrivacyPolicySerializer
+  end
+
+  private
+
+  def set_privacy_policy
+    @privacy_policy = PrivacyPolicy.current
+  end
+end

+ 1 - 1
app/controllers/api/v1/instances_controller.rb

@@ -6,6 +6,6 @@ class Api::V1::InstancesController < Api::BaseController
 
   def show
     expires_in 3.minutes, public: true
-    render_with_cache json: {}, serializer: REST::InstanceSerializer, root: 'instance'
+    render_with_cache json: InstancePresenter.new, serializer: REST::V1::InstanceSerializer, root: 'instance'
   end
 end

+ 4 - 0
app/controllers/api/v1/lists_controller.rb

@@ -7,6 +7,10 @@ class Api::V1::ListsController < Api::BaseController
   before_action :require_user!
   before_action :set_list, except: [:index, :create]
 
+  rescue_from ArgumentError do |e|
+    render json: { error: e.to_s }, status: 422
+  end
+
   def index
     @lists = List.where(account: current_account).all
     render json: @lists, each_serializer: REST::ListSerializer

+ 1 - 1
app/controllers/api/v1/push/subscriptions_controller.rb

@@ -52,6 +52,6 @@ class Api::V1::Push::SubscriptionsController < Api::BaseController
   def data_params
     return {} if params[:data].blank?
 
-    params.require(:data).permit(:policy, alerts: [:follow, :follow_request, :favourite, :reblog, :mention, :poll, :status])
+    params.require(:data).permit(:policy, alerts: Notification::TYPES)
   end
 end

Some files were not shown because too many files changed in this diff